Compare commits
2 Commits
dc2cd1108e
...
6c47c8c3e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c47c8c3e9
|
||
|
|
814be43be8
|
@@ -29,11 +29,14 @@
|
||||
value: 5
|
||||
codec: const
|
||||
hidden: true
|
||||
- path: keywords
|
||||
- path: reviewRating.worstRating
|
||||
value: 1
|
||||
codec: const
|
||||
hidden: true
|
||||
}
|
||||
---
|
||||
|
||||
# { headline }
|
||||
|
||||
{ articleBody }
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
- path: suitableForDiet
|
||||
}
|
||||
---
|
||||
|
||||
# { name | text }
|
||||
|
||||
{ description | text }
|
||||
|
||||
## Ingredients
|
||||
|
||||
@@ -35,5 +35,7 @@
|
||||
- path: reviewBody
|
||||
}
|
||||
---
|
||||
|
||||
# { itemReviewed.name }
|
||||
|
||||
{ reviewBody }
|
||||
|
||||
@@ -37,7 +37,7 @@ func RenderFile(rawJSON []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("failed to validate schema: %w", validationErr)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ func main() {
|
||||
fsAdapter, err := adapters.NewLocalFsAdapter(absRoots)
|
||||
must(err)
|
||||
|
||||
http.Handle("/", handler.NewHandler(fsAdapter))
|
||||
apiKey := os.Getenv("MARKA_API_KEY")
|
||||
http.Handle("/", handler.NewHandler(fsAdapter, apiKey))
|
||||
|
||||
log.Printf("listening on %s, roots=%s", *addr, strings.Join(absRoots, ", "))
|
||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||
|
||||
@@ -257,6 +257,66 @@ func (l *LocalFsAdapter) Read(path string) (*Entry, 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
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -10,6 +11,7 @@ type ErrorResponse struct {
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, code int, err error) {
|
||||
log.Printf("error: %s", err)
|
||||
writeJSON(w, code, ErrorResponse{Error: err.Error()})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,18 @@ package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.max-richter.dev/max/marka/renderer"
|
||||
"git.max-richter.dev/max/marka/server/internal/adapters"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
adapter adapters.FileAdapter
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func (h *Handler) get(w http.ResponseWriter, target string) {
|
||||
@@ -44,7 +49,45 @@ func (h *Handler) get(w http.ResponseWriter, target string) {
|
||||
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) {
|
||||
if h.apiKey != "" {
|
||||
if r.Header.Get("Authentication") != h.apiKey {
|
||||
writeError(w, http.StatusUnauthorized, errors.New("invalid api key"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
writeError(w, http.StatusUnauthorized, errors.New("invalid api key"))
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -59,14 +102,15 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
case http.MethodGet:
|
||||
h.get(w, target)
|
||||
case http.MethodPost:
|
||||
h.post(w, target)
|
||||
h.post(w, r, target)
|
||||
default:
|
||||
writeError(w, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandler(adapter adapters.FileAdapter) http.Handler {
|
||||
func NewHandler(adapter adapters.FileAdapter, apiKey string) http.Handler {
|
||||
return &Handler{
|
||||
adapter: adapter,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user