Add /api/block/ API
This commit is contained in:
parent
8f65d459ff
commit
35b8c45169
4
TODO.md
4
TODO.md
|
@ -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
29
file.go
|
@ -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
102
main.go
|
@ -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) {
|
||||
|
|
25
session.go
25
session.go
|
@ -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
20
util.go
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue
Block a user