Add /api/block/ API

This commit is contained in:
Peter Stuifzand 2021-01-17 17:36:17 +01:00
parent 8f65d459ff
commit 35b8c45169
5 changed files with 150 additions and 30 deletions

View File

@ -1,5 +1,9 @@
# TODO # TODO
## Remove old/normal page editor
- The normal page editor is still available. It should be removed.
## API for blocks ## API for blocks
### POST /api/block/append ### POST /api/block/append

29
file.go
View File

@ -145,16 +145,26 @@ func (fp *FilePages) Get(name string) Page {
t, err := time.Parse("2006-01-02", name) t, err := time.Parse("2006-01-02", name)
if err == nil { if err == nil {
date = true date = true
names = append(names, formatDateTitle(t), name) names = append(names, formatDatePageName(t), name)
} else {
names = append(names, name)
} }
for _, name := range names { for _, name := range names {
log.Printf("Trying %q", name)
blocks, err := loadBlocks(fp.dirname, name) blocks, err := loadBlocks(fp.dirname, name)
if err != nil && errors.Is(err, BlockNotFound) { if err != nil && errors.Is(err, BlockNotFound) {
continue continue
} }
return fp.blocksBackendGet(name, blocks, titleOption{date, t}) return fp.blocksBackendGet(name, blocks, titleOption{date, t})
} }
return fp.oldPagesBackend(name)
page, err := fp.oldPagesBackend(name)
if err != nil {
return fp.blocksBackendGet(name, BlockResponse{ParentID: "root", PageID: name}, titleOption{date, t})
}
return page
} }
func (fp *FilePages) blocksBackendGet(name string, blocks BlockResponse, option titleOption) Page { func (fp *FilePages) blocksBackendGet(name string, blocks BlockResponse, option titleOption) Page {
@ -176,6 +186,9 @@ func (fp *FilePages) blocksBackendGet(name string, blocks BlockResponse, option
} }
title := formatTitle(blocks.Texts[name], option) title := formatTitle(blocks.Texts[name], option)
if title == "" {
title = cleanTitle(name)
}
return Page{ return Page{
Name: name, Name: name,
@ -194,7 +207,7 @@ func formatTitle(title string, option titleOption) string {
return title return title
} }
func (fp *FilePages) oldPagesBackend(title string) Page { func (fp *FilePages) oldPagesBackend(title string) (Page, error) {
name := strings.Replace(title, " ", "_", -1) name := strings.Replace(title, " ", "_", -1)
title = strings.Replace(title, "_", " ", -1) title = strings.Replace(title, "_", " ", -1)
@ -211,7 +224,7 @@ func (fp *FilePages) oldPagesBackend(title string) Page {
Name: name, Name: name,
Content: "", Content: "",
Refs: refs, Refs: refs,
} }, err
} }
defer f.Close() defer f.Close()
@ -223,7 +236,7 @@ func (fp *FilePages) oldPagesBackend(title string) Page {
Title: title, Title: title,
Content: "", Content: "",
Refs: refs, Refs: refs,
} }, err
} }
return Page{ return Page{
@ -231,7 +244,7 @@ func (fp *FilePages) oldPagesBackend(title string) Page {
Title: title, Title: title,
Content: string(body), Content: string(body),
Refs: refs, Refs: refs,
} }, nil
} }
func (fp *FilePages) Save(p string, page Page, summary, author string) error { func (fp *FilePages) Save(p string, page Page, summary, author string) error {
@ -506,13 +519,13 @@ func loadBlocks(dirname, rootBlockID string) (BlockResponse, error) {
func loadBlock(dirname, blockID string) (Block, error) { func loadBlock(dirname, blockID string) (Block, error) {
f, err := os.Open(filepath.Join(dirname, BlocksDirectory, blockID)) f, err := os.Open(filepath.Join(dirname, BlocksDirectory, blockID))
if err != nil { if err != nil {
return Block{}, fmt.Errorf("%q: %w", BlockNotFound) return Block{}, fmt.Errorf("%q: %w", blockID, BlockNotFound)
} }
defer f.Close() defer f.Close()
var block Block var block Block
err = json.NewDecoder(f).Decode(&block) err = json.NewDecoder(f).Decode(&block)
if err != nil { if err != nil {
return Block{}, fmt.Errorf("%q: %v", err) return Block{}, fmt.Errorf("%q: %v", blockID, err)
} }
return block, nil return block, nil
} }

98
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
@ -26,12 +27,19 @@ func init() {
log.SetFlags(log.Lshortfile) log.SetFlags(log.Lshortfile)
} }
type authorizedKey string
var (
authKey = authorizedKey("authorizedKey")
)
var ( var (
mp PagesRepository mp PagesRepository
port = flag.Int("port", 8080, "listen port") port = flag.Int("port", 8080, "listen port")
baseurl = flag.String("baseurl", "", "baseurl") baseurl = flag.String("baseurl", "", "baseurl")
redirectURI string = "" redirectURI = ""
authToken = "XVlBzgbaiCMRAjWw"
) )
type Backref struct { type Backref struct {
@ -620,6 +628,10 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
page := r.URL.Path[1:] page := r.URL.Path[1:]
if page == "favicon.ico" {
http.Error(w, "Not Found", 404)
return
}
page = resolvePageName(page) page = resolvePageName(page)
mpPage := mp.Get(page) mpPage := mp.Get(page)
@ -636,7 +648,7 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var rawMsg json.RawMessage var rawMsg json.RawMessage
err = json.NewDecoder(strings.NewReader(pageText)).Decode(&rawMsg) err = json.NewDecoder(strings.NewReader(pageText)).Decode(&rawMsg)
title := cleanTitle(page) title := cleanTitle(mpPage.Title)
jsonPage := pageText != "" && err == nil jsonPage := pageText != "" && err == nil
if jsonPage { if jsonPage {
@ -889,7 +901,65 @@ func main() {
mp = NewFilePages(dataDir, searchIndex) mp = NewFilePages(dataDir, searchIndex)
http.Handle("/auth/", &authHandler{}) http.Handle("/auth/", &authHandler{})
http.HandleFunc("/api/document/append", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/block/", wrapAuth(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if !r.Context().Value(authKey).(bool) {
http.Error(w, "Unauthorized", 401)
return
}
if r.Method != "GET" {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
format := r.URL.Query().Get("format")
if format == "" {
format = "json"
}
if !(format == "json" || format == "metakv") {
http.Error(w, "unknown format", http.StatusBadRequest)
}
page := r.URL.Path
page = strings.TrimPrefix(page, "/api/block/")
page = resolvePageName(page)
mpPage := mp.Get(page)
pageText := mpPage.Content
if pageText == "" {
http.NotFound(w, r)
return
}
if format == "json" {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
// Shortcut for json output
_, err := io.WriteString(w, pageText)
if err != nil {
http.Error(w, err.Error(), 500)
}
} else if format == "metakv" {
so, err := createStructuredFormat(mpPage)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
err = enc.Encode(so)
if err != nil {
http.Error(w, err.Error(), 500)
}
}
}))
http.HandleFunc("/api/block/append", wrapAuth(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
http.Error(w, err.Error(), 400) http.Error(w, err.Error(), 400)
@ -904,6 +974,7 @@ func main() {
page := mp.Get(id) page := mp.Get(id)
log.Println(page.Content) log.Println(page.Content)
var listItems []ListItem var listItems []ListItem
id = page.Name // Use the name that was actually loaded
err = json.NewDecoder(strings.NewReader(page.Content)).Decode(&listItems) err = json.NewDecoder(strings.NewReader(page.Content)).Decode(&listItems)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -912,8 +983,9 @@ func main() {
} }
newId := &ID{"1", true} newId := &ID{"1", true}
generatedID := newId.NewID()
listItems = append(listItems, ListItem{ listItems = append(listItems, ListItem{
ID: newId.NewID(), ID: generatedID,
Indented: 0, Indented: 0,
Text: r.Form.Get("text"), Text: r.Form.Get("text"),
Fleeting: false, Fleeting: false,
@ -936,8 +1008,10 @@ func main() {
http.Error(w, fmt.Sprintf("while saving: %s", err.Error()), 500) http.Error(w, fmt.Sprintf("while saving: %s", err.Error()), 500)
return return
} }
fmt.Println(generatedID)
return return
}) }))
http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
http.ServeFile(w, r, filepath.Join(dataDir, LinksFile)) http.ServeFile(w, r, filepath.Join(dataDir, LinksFile))
@ -997,6 +1071,20 @@ func main() {
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
} }
func wrapAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" || auth == "Token "+authToken {
r = r.WithContext(context.WithValue(r.Context(), authKey, auth != ""))
// auth == "", require cookie in handler
handler.ServeHTTP(w, r)
return
}
http.Error(w, "Authorization Required", 401)
}
}
func createSearchIndex(dataDir, indexName string) (bleve.Index, error) { func createSearchIndex(dataDir, indexName string) (bleve.Index, error) {
indexDir := filepath.Join(dataDir, indexName) indexDir := filepath.Join(dataDir, indexName)
if _, err := os.Stat(indexDir); os.IsNotExist(err) { if _, err := os.Stat(indexDir); os.IsNotExist(err) {

View File

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@ -71,24 +72,20 @@ func getSessionCookie(w http.ResponseWriter, r *http.Request) (string, error) {
c, err := r.Cookie("session") c, err := r.Cookie("session")
var sessionVar string var sessionVar string
if err != nil { if err != nil && errors.Is(err, http.ErrNoCookie) {
if err == http.ErrNoCookie {
sessionVar = RandStringBytes(16) sessionVar = RandStringBytes(16)
newCookie := &http.Cookie{ c = &http.Cookie{
Name: "session", Name: "session",
Value: sessionVar, Value: sessionVar,
Expires: time.Now().Add(24 * time.Hour), Expires: time.Now().Add(24 * time.Hour),
Path: "/", Path: "/",
} }
http.SetCookie(w, newCookie)
return sessionVar, nil
}
return "", err
} else { } else {
sessionVar = c.Value sessionVar = c.Value
c.Expires = time.Now().Add(24 * time.Hour)
} }
http.SetCookie(w, c)
return sessionVar, nil return sessionVar, nil
} }

20
util.go
View File

@ -99,10 +99,28 @@ func (sw *stopwatch) Stop() {
func todayPage() string { func todayPage() string {
now := time.Now() now := time.Now()
return formatDateTitle(now) return formatDatePageName(now)
} }
func formatDateTitle(date time.Time) string { func formatDateTitle(date time.Time) string {
months := []string{
"",
"januari",
"februari",
"maart",
"april",
"mei",
"juni",
"juli",
"augustus",
"september",
"oktober",
"november",
"december",
}
return fmt.Sprintf("%d %s %d", date.Day(), months[date.Month()], date.Year())
}
func formatDatePageName(date time.Time) string {
months := []string{ months := []string{
"", "",
"januari", "januari",