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
## Remove old/normal page editor
- The normal page editor is still available. It should be removed.
## API for blocks
### 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)
if err == nil {
date = true
names = append(names, formatDateTitle(t), name)
names = append(names, formatDatePageName(t), name)
} else {
names = append(names, name)
}
for _, name := range names {
log.Printf("Trying %q", name)
blocks, err := loadBlocks(fp.dirname, name)
if err != nil && errors.Is(err, BlockNotFound) {
continue
}
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 {
@ -176,6 +186,9 @@ func (fp *FilePages) blocksBackendGet(name string, blocks BlockResponse, option
}
title := formatTitle(blocks.Texts[name], option)
if title == "" {
title = cleanTitle(name)
}
return Page{
Name: name,
@ -194,7 +207,7 @@ func formatTitle(title string, option titleOption) string {
return title
}
func (fp *FilePages) oldPagesBackend(title string) Page {
func (fp *FilePages) oldPagesBackend(title string) (Page, error) {
name := strings.Replace(title, " ", "_", -1)
title = strings.Replace(title, "_", " ", -1)
@ -211,7 +224,7 @@ func (fp *FilePages) oldPagesBackend(title string) Page {
Name: name,
Content: "",
Refs: refs,
}
}, err
}
defer f.Close()
@ -223,7 +236,7 @@ func (fp *FilePages) oldPagesBackend(title string) Page {
Title: title,
Content: "",
Refs: refs,
}
}, err
}
return Page{
@ -231,7 +244,7 @@ func (fp *FilePages) oldPagesBackend(title string) Page {
Title: title,
Content: string(body),
Refs: refs,
}
}, nil
}
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) {
f, err := os.Open(filepath.Join(dirname, BlocksDirectory, blockID))
if err != nil {
return Block{}, fmt.Errorf("%q: %w", BlockNotFound)
return Block{}, fmt.Errorf("%q: %w", blockID, BlockNotFound)
}
defer f.Close()
var block Block
err = json.NewDecoder(f).Decode(&block)
if err != nil {
return Block{}, fmt.Errorf("%q: %v", err)
return Block{}, fmt.Errorf("%q: %v", blockID, err)
}
return block, nil
}

102
main.go
View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
@ -26,12 +27,19 @@ func init() {
log.SetFlags(log.Lshortfile)
}
type authorizedKey string
var (
authKey = authorizedKey("authorizedKey")
)
var (
mp PagesRepository
port = flag.Int("port", 8080, "listen port")
baseurl = flag.String("baseurl", "", "baseurl")
redirectURI string = ""
port = flag.Int("port", 8080, "listen port")
baseurl = flag.String("baseurl", "", "baseurl")
redirectURI = ""
authToken = "XVlBzgbaiCMRAjWw"
)
type Backref struct {
@ -620,6 +628,10 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
page := r.URL.Path[1:]
if page == "favicon.ico" {
http.Error(w, "Not Found", 404)
return
}
page = resolvePageName(page)
mpPage := mp.Get(page)
@ -636,7 +648,7 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var rawMsg json.RawMessage
err = json.NewDecoder(strings.NewReader(pageText)).Decode(&rawMsg)
title := cleanTitle(page)
title := cleanTitle(mpPage.Title)
jsonPage := pageText != "" && err == nil
if jsonPage {
@ -889,7 +901,65 @@ func main() {
mp = NewFilePages(dataDir, searchIndex)
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()
if err != nil {
http.Error(w, err.Error(), 400)
@ -904,6 +974,7 @@ func main() {
page := mp.Get(id)
log.Println(page.Content)
var listItems []ListItem
id = page.Name // Use the name that was actually loaded
err = json.NewDecoder(strings.NewReader(page.Content)).Decode(&listItems)
if err != nil && err != io.EOF {
@ -912,8 +983,9 @@ func main() {
}
newId := &ID{"1", true}
generatedID := newId.NewID()
listItems = append(listItems, ListItem{
ID: newId.NewID(),
ID: generatedID,
Indented: 0,
Text: r.Form.Get("text"),
Fleeting: false,
@ -936,8 +1008,10 @@ func main() {
http.Error(w, fmt.Sprintf("while saving: %s", err.Error()), 500)
return
}
fmt.Println(generatedID)
return
})
}))
http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
http.ServeFile(w, r, filepath.Join(dataDir, LinksFile))
@ -997,6 +1071,20 @@ func main() {
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) {
indexDir := filepath.Join(dataDir, indexName)
if _, err := os.Stat(indexDir); os.IsNotExist(err) {

View File

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

20
util.go
View File

@ -99,10 +99,28 @@ func (sw *stopwatch) Stop() {
func todayPage() string {
now := time.Now()
return formatDateTitle(now)
return formatDatePageName(now)
}
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{
"",
"januari",