feat: allow to write to notes

This commit is contained in:
Max Richter
2025-10-31 14:16:32 +01:00
parent dc2cd1108e
commit 814be43be8
7 changed files with 104 additions and 3 deletions

View File

@@ -29,11 +29,14 @@
value: 5 value: 5
codec: const codec: const
hidden: true hidden: true
- path: keywords
- path: reviewRating.worstRating - path: reviewRating.worstRating
value: 1 value: 1
codec: const codec: const
hidden: true hidden: true
} }
--- ---
# { headline } # { headline }
{ articleBody } { articleBody }

View File

@@ -34,7 +34,9 @@
- path: suitableForDiet - path: suitableForDiet
} }
--- ---
# { name | text } # { name | text }
{ description | text } { description | text }
## Ingredients ## Ingredients

View File

@@ -35,5 +35,7 @@
- path: reviewBody - path: reviewBody
} }
--- ---
# { itemReviewed.name } # { itemReviewed.name }
{ reviewBody } { reviewBody }

View File

@@ -37,7 +37,7 @@ func RenderFile(rawJSON []byte) ([]byte, error) {
} }
// 5) validate JSON against schema // 5) validate JSON against schema
if schemaName, ok := data["@schema"].(string); ok { if schemaName, ok := data["_schema"].(string); ok {
if validationErr := validator.ValidateSchema(data, schemaName); validationErr != nil { if validationErr := validator.ValidateSchema(data, schemaName); validationErr != nil {
return nil, fmt.Errorf("failed to validate schema: %w", validationErr) return nil, fmt.Errorf("failed to validate schema: %w", validationErr)
} }

View File

@@ -257,6 +257,66 @@ func (l *LocalFsAdapter) Read(path string) (*Entry, error) {
} }
func (l *LocalFsAdapter) Write(path string, content []byte) error { func (l *LocalFsAdapter) Write(path string, content []byte) error {
pathParts := strings.Split(strings.Trim(path, "/"), "/")
if len(pathParts) == 0 || pathParts[0] == "" {
return errors.New("invalid path")
}
rootIdentifier := pathParts[0]
var targetRoot string
for _, r := range l.roots {
if filepath.Base(r) == rootIdentifier {
targetRoot = r
break
}
}
if targetRoot == "" {
return ErrNotFound
}
subPath := filepath.Join(pathParts[1:]...)
target := filepath.Join(targetRoot, subPath)
absTarget, err := filepath.Abs(target)
if err != nil {
return err
}
absRoot, err := filepath.Abs(targetRoot)
if err != nil {
return err
}
if !strings.HasPrefix(absTarget, absRoot) {
return errors.New("path escapes root")
}
dir := filepath.Dir(absTarget)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
err = os.WriteFile(absTarget, content, 0o644)
if err != nil {
return err
}
// Invalidate cache
l.mu.Lock()
defer l.mu.Unlock()
currentPath := path
for {
delete(l.cache, currentPath)
if currentPath == "/" {
break
}
lastSlash := strings.LastIndex(currentPath, "/")
if lastSlash <= 0 {
currentPath = "/"
} else {
currentPath = currentPath[:lastSlash]
}
}
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package handler
import ( import (
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
) )
@@ -10,6 +11,7 @@ type ErrorResponse struct {
} }
func writeError(w http.ResponseWriter, code int, err error) { func writeError(w http.ResponseWriter, code int, err error) {
log.Printf("error: %s", err)
writeJSON(w, code, ErrorResponse{Error: err.Error()}) writeJSON(w, code, ErrorResponse{Error: err.Error()})
} }

View File

@@ -3,8 +3,12 @@ package handler
import ( import (
"errors" "errors"
"fmt"
"io"
"net/http" "net/http"
"strings"
"git.max-richter.dev/max/marka/renderer"
"git.max-richter.dev/max/marka/server/internal/adapters" "git.max-richter.dev/max/marka/server/internal/adapters"
) )
@@ -44,7 +48,35 @@ func (h *Handler) get(w http.ResponseWriter, target string) {
writeError(w, http.StatusInternalServerError, errors.New("unknown entry type")) writeError(w, http.StatusInternalServerError, errors.New("unknown entry type"))
} }
func (h *Handler) post(w http.ResponseWriter, target string) { func (h *Handler) post(w http.ResponseWriter, r *http.Request, target string) {
body, err := io.ReadAll(r.Body)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
defer r.Body.Close()
contentType := r.Header.Get("Content-Type")
isJSON := strings.HasPrefix(contentType, "application/json")
var contentToWrite []byte
if strings.HasSuffix(target, ".md") && isJSON {
renderedContent, err := renderer.RenderFile(body)
if err != nil {
writeError(w, http.StatusInternalServerError, fmt.Errorf("failed to render file: %w", err))
return
}
contentToWrite = renderedContent
} else {
contentToWrite = body
}
if err := h.adapter.Write(target, contentToWrite); err != nil {
writeError(w, http.StatusInternalServerError, fmt.Errorf("failed to write file: %w", err))
return
}
w.WriteHeader(http.StatusNoContent)
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -59,7 +91,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case http.MethodGet: case http.MethodGet:
h.get(w, target) h.get(w, target)
case http.MethodPost: case http.MethodPost:
h.post(w, target) h.post(w, r, target)
default: default:
writeError(w, http.StatusMethodNotAllowed, errors.New("method not allowed")) writeError(w, http.StatusMethodNotAllowed, errors.New("method not allowed"))
} }