feat: simplify data and add cache to LocalFsAdapter
This commit is contained in:
15
server/README.md
Normal file
15
server/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
## Marka Server
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Recipe",
|
||||||
|
"modTime": 123123123123,
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"name": "Baguette",
|
||||||
|
"modTime": 123123123123,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@@ -8,8 +8,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.max-richter.dev/max/marka/server-new/internal/adapters"
|
"git.max-richter.dev/max/marka/server/internal/adapters"
|
||||||
"git.max-richter.dev/max/marka/server-new/internal/handler"
|
"git.max-richter.dev/max/marka/server/internal/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
type multi []string
|
type multi []string
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
module git.max-richter.dev/max/marka/server-new
|
module git.max-richter.dev/max/marka/server
|
||||||
|
|
||||||
go 1.24.7
|
go 1.24.7
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.max-richter.dev/max/marka/parser"
|
"git.max-richter.dev/max/marka/parser"
|
||||||
@@ -12,52 +13,70 @@ import (
|
|||||||
|
|
||||||
type LocalFsAdapter struct {
|
type LocalFsAdapter struct {
|
||||||
roots []string
|
roots []string
|
||||||
|
cache map[string]*Entry
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LocalFsAdapter) readDir(path string, root string) (FsResponse, error) {
|
func (l *LocalFsAdapter) readDir(path string, root string) (*Entry, error) {
|
||||||
dirInfo, err := os.Stat(path)
|
dirInfo, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := os.ReadDir(path)
|
entries, err := os.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]FsDirEntry, 0, len(entries))
|
out := make([]*Entry, 0, len(entries))
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
info, _ := e.Info()
|
info, _ := e.Info()
|
||||||
|
|
||||||
entryType := "dir"
|
entryType := "dir"
|
||||||
if !e.IsDir() {
|
if !e.IsDir() {
|
||||||
entryType = contentTypeFor(e.Name())
|
entryType = "file"
|
||||||
}
|
}
|
||||||
|
|
||||||
var content any
|
var content any
|
||||||
if !e.IsDir() && entryType == "application/markdown" {
|
var mime string
|
||||||
entryPath := filepath.Join(path, e.Name())
|
var size int64
|
||||||
fileContent, err := os.ReadFile(entryPath)
|
if !e.IsDir() {
|
||||||
if err == nil {
|
mime = contentTypeFor(e.Name())
|
||||||
parsedContent, err := parser.ParseFile(string(fileContent))
|
if info != nil {
|
||||||
|
size = info.Size()
|
||||||
|
}
|
||||||
|
if mime == "application/markdown" {
|
||||||
|
entryPath := filepath.Join(path, e.Name())
|
||||||
|
fileContent, err := os.ReadFile(entryPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content = parsedContent
|
parsedContent, err := parser.ParseFile(string(fileContent))
|
||||||
|
if err == nil {
|
||||||
|
content = parsedContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out = append(out, FsDirEntry{
|
childPath, err := filepath.Rel(root, filepath.Join(path, e.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responseChildPath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), childPath))
|
||||||
|
|
||||||
|
out = append(out, &Entry{
|
||||||
Name: e.Name(),
|
Name: e.Name(),
|
||||||
|
Path: responseChildPath,
|
||||||
Type: entryType,
|
Type: entryType,
|
||||||
IsDir: e.IsDir(),
|
|
||||||
ModTime: info.ModTime(),
|
ModTime: info.ModTime(),
|
||||||
|
MIME: mime,
|
||||||
|
Size: size,
|
||||||
Content: content,
|
Content: content,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath, err := filepath.Rel(root, path)
|
relPath, err := filepath.Rel(root, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if relPath == "." {
|
if relPath == "." {
|
||||||
relPath = ""
|
relPath = ""
|
||||||
@@ -65,75 +84,111 @@ func (l LocalFsAdapter) readDir(path string, root string) (FsResponse, error) {
|
|||||||
|
|
||||||
responsePath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), relPath))
|
responsePath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), relPath))
|
||||||
|
|
||||||
return FsResponse{
|
return &Entry{
|
||||||
Dir: &FsDir{
|
Type: "dir",
|
||||||
Files: out,
|
Name: filepath.Base(responsePath),
|
||||||
Name: responsePath,
|
Path: responsePath,
|
||||||
ModTime: dirInfo.ModTime(),
|
ModTime: dirInfo.ModTime(),
|
||||||
},
|
Content: out,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LocalFsAdapter) readFile(path string, root string) (FsResponse, error) {
|
func (l *LocalFsAdapter) readFile(path string, root string) (*Entry, error) {
|
||||||
fi, err := os.Stat(path)
|
fi, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return FsResponse{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath, err := filepath.Rel(root, path)
|
relPath, err := filepath.Rel(root, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
responsePath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), relPath))
|
responsePath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), relPath))
|
||||||
|
mime := contentTypeFor(path)
|
||||||
|
var content any
|
||||||
|
|
||||||
return FsResponse{
|
fileContent, err := os.ReadFile(path)
|
||||||
File: &FsFile{
|
if err != nil {
|
||||||
Name: responsePath,
|
return nil, err
|
||||||
Type: contentTypeFor(path),
|
}
|
||||||
ModTime: fi.ModTime(),
|
|
||||||
Content: data,
|
if mime == "application/markdown" {
|
||||||
},
|
parsedContent, err := parser.ParseFile(string(fileContent))
|
||||||
|
if err == nil {
|
||||||
|
content = parsedContent
|
||||||
|
} else {
|
||||||
|
// Fallback to raw content on parsing error
|
||||||
|
content = fileContent
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = fileContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Entry{
|
||||||
|
Type: "file",
|
||||||
|
Name: fi.Name(),
|
||||||
|
Path: responsePath,
|
||||||
|
ModTime: fi.ModTime(),
|
||||||
|
MIME: mime,
|
||||||
|
Size: fi.Size(),
|
||||||
|
Content: content,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LocalFsAdapter) Read(path string) (FsResponse, error) {
|
func (l *LocalFsAdapter) Read(path string) (*Entry, error) {
|
||||||
if path == "/" {
|
if path == "/" {
|
||||||
entries := make([]FsDirEntry, 0, len(l.roots))
|
|
||||||
var latestModTime time.Time
|
var latestModTime time.Time
|
||||||
for _, r := range l.roots {
|
for _, r := range l.roots {
|
||||||
info, err := os.Stat(r)
|
info, err := os.Stat(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
entries = append(entries, FsDirEntry{
|
|
||||||
Name: filepath.Base(r),
|
|
||||||
Type: "dir",
|
|
||||||
IsDir: true,
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
})
|
|
||||||
if info.ModTime().After(latestModTime) {
|
if info.ModTime().After(latestModTime) {
|
||||||
latestModTime = info.ModTime()
|
latestModTime = info.ModTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return FsResponse{
|
l.mu.RLock()
|
||||||
Dir: &FsDir{
|
cached, found := l.cache[path]
|
||||||
Files: entries,
|
l.mu.RUnlock()
|
||||||
Name: "/",
|
|
||||||
ModTime: latestModTime,
|
if found && !latestModTime.After(cached.ModTime) {
|
||||||
},
|
return cached, nil
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
entries := make([]*Entry, 0, len(l.roots))
|
||||||
|
for _, r := range l.roots {
|
||||||
|
info, err := os.Stat(r)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := filepath.Base(r)
|
||||||
|
entries = append(entries, &Entry{
|
||||||
|
Name: name,
|
||||||
|
Path: "/" + name,
|
||||||
|
Type: "dir",
|
||||||
|
ModTime: info.ModTime(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := &Entry{
|
||||||
|
Type: "dir",
|
||||||
|
Name: "/",
|
||||||
|
Path: "/",
|
||||||
|
ModTime: latestModTime,
|
||||||
|
Content: entries,
|
||||||
|
}
|
||||||
|
|
||||||
|
l.mu.Lock()
|
||||||
|
l.cache[path] = entry
|
||||||
|
l.mu.Unlock()
|
||||||
|
return entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pathParts := strings.Split(strings.Trim(path, "/"), "/")
|
pathParts := strings.Split(strings.Trim(path, "/"), "/")
|
||||||
if len(pathParts) == 0 || pathParts[0] == "" {
|
if len(pathParts) == 0 || pathParts[0] == "" {
|
||||||
return FsResponse{}, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
rootIdentifier := pathParts[0]
|
rootIdentifier := pathParts[0]
|
||||||
@@ -146,7 +201,7 @@ func (l LocalFsAdapter) Read(path string) (FsResponse, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if targetRoot == "" {
|
if targetRoot == "" {
|
||||||
return FsResponse{}, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
subPath := filepath.Join(pathParts[1:]...)
|
subPath := filepath.Join(pathParts[1:]...)
|
||||||
@@ -154,38 +209,58 @@ func (l LocalFsAdapter) Read(path string) (FsResponse, error) {
|
|||||||
|
|
||||||
absTarget, err := filepath.Abs(target)
|
absTarget, err := filepath.Abs(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
absRoot, err := filepath.Abs(targetRoot)
|
absRoot, err := filepath.Abs(targetRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(absTarget, absRoot) {
|
if !strings.HasPrefix(absTarget, absRoot) {
|
||||||
return FsResponse{}, errors.New("path escapes root")
|
return nil, errors.New("path escapes root")
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(target)
|
fi, err := os.Stat(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return FsResponse{}, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return FsResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.mu.RLock()
|
||||||
|
cached, found := l.cache[path]
|
||||||
|
l.mu.RUnlock()
|
||||||
|
|
||||||
|
if found && !fi.ModTime().After(cached.ModTime) {
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEntry *Entry
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return l.readDir(target, targetRoot)
|
newEntry, err = l.readDir(target, targetRoot)
|
||||||
|
} else {
|
||||||
|
newEntry, err = l.readFile(target, targetRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.readFile(target, targetRoot)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.mu.Lock()
|
||||||
|
l.cache[path] = newEntry
|
||||||
|
l.mu.Unlock()
|
||||||
|
|
||||||
|
return newEntry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (LocalFsAdapter) Write(path string, content []byte) error {
|
func (l *LocalFsAdapter) Write(path string, content []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalFsAdapter(roots []string) (FileAdapter, error) {
|
func NewLocalFsAdapter(roots []string) (FileAdapter, error) {
|
||||||
return LocalFsAdapter{
|
return &LocalFsAdapter{
|
||||||
roots: roots,
|
roots: roots,
|
||||||
|
cache: make(map[string]*Entry),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,8 @@ package adapters
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"mime"
|
"mime"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SafeRel(root, requested string) (string, error) {
|
func SafeRel(root, requested string) (string, error) {
|
||||||
@@ -37,20 +35,6 @@ func ResponsePath(root, full string) string {
|
|||||||
return "/" + filepath.ToSlash(rel)
|
return "/" + filepath.ToSlash(rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sizeOrZero(fi os.FileInfo) int64 {
|
|
||||||
if fi == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return fi.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func modTimeOrZero(fi os.FileInfo) time.Time {
|
|
||||||
if fi == nil {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
return fi.ModTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
var textPlainExtensions = map[string]bool{
|
var textPlainExtensions = map[string]bool{
|
||||||
".txt": true,
|
".txt": true,
|
||||||
".log": true,
|
".log": true,
|
||||||
|
@@ -3,33 +3,26 @@ package adapters
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// Entry represents a file or directory in the filesystem.
|
||||||
|
type Entry struct {
|
||||||
|
Type string `json:"type"` // "file" | "dir"
|
||||||
|
Name string `json:"name"` // base name
|
||||||
|
Path string `json:"path"` // full path or virtual path
|
||||||
|
ModTime time.Time `json:"modTime"` // last modified
|
||||||
|
|
||||||
|
// File-only (optional)
|
||||||
|
MIME string `json:"mime,omitempty"` // e.g. "text/markdown"
|
||||||
|
Size int64 `json:"size,omitempty"` // bytes
|
||||||
|
|
||||||
|
// Content:
|
||||||
|
// - markdown file: any (parsed AST / arbitrary JSON)
|
||||||
|
// - directory: []*Entry (children)
|
||||||
|
// - other files: omitted
|
||||||
|
Content any `json:"content,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileAdapter is the interface for accessing the file system.
|
||||||
type FileAdapter interface {
|
type FileAdapter interface {
|
||||||
Read(path string) (FsResponse, error)
|
Read(path string) (*Entry, error)
|
||||||
Write(path string, content []byte) error
|
Write(path string, content []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FsFile struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Content []byte `json:"content"`
|
|
||||||
ModTime time.Time `json:"modTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FsDirEntry struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
IsDir bool `json:"isDir,omitempty"`
|
|
||||||
ModTime time.Time `json:"modTime"`
|
|
||||||
Content any `json:"content,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FsDir struct {
|
|
||||||
Files []FsDirEntry `json:"files"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
ModTime time.Time `json:"modTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FsResponse struct {
|
|
||||||
Dir *FsDir `json:"dir,omitempty"`
|
|
||||||
File *FsFile `json:"file,omitempty"`
|
|
||||||
}
|
|
||||||
|
@@ -3,8 +3,6 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.max-richter.dev/max/marka/server-new/internal/adapters"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrorResponse struct {
|
type ErrorResponse struct {
|
||||||
@@ -20,8 +18,3 @@ func writeJSON(w http.ResponseWriter, code int, v any) {
|
|||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
_ = json.NewEncoder(w).Encode(v)
|
_ = json.NewEncoder(w).Encode(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFile(w http.ResponseWriter, file *adapters.FsFile) {
|
|
||||||
w.Header().Set("Content-Type", file.Type)
|
|
||||||
w.Write(file.Content)
|
|
||||||
}
|
|
||||||
|
@@ -4,68 +4,44 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.max-richter.dev/max/marka/parser"
|
"git.max-richter.dev/max/marka/server/internal/adapters"
|
||||||
"git.max-richter.dev/max/marka/server-new/internal/adapters"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResponseItem struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Content any `json:"content,omitempty"`
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
IsDir bool `json:"isDir"`
|
|
||||||
Size int64 `json:"size,omitempty"`
|
|
||||||
ModTime time.Time `json:"modTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
adapter adapters.FileAdapter
|
adapter adapters.FileAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) get(w http.ResponseWriter, target string) {
|
func (h *Handler) get(w http.ResponseWriter, target string) {
|
||||||
fsEntry, err := h.adapter.Read(target)
|
entry, err := h.adapter.Read(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, 500, err)
|
if errors.Is(err, adapters.ErrNotFound) {
|
||||||
return
|
writeError(w, http.StatusNotFound, err)
|
||||||
}
|
|
||||||
|
|
||||||
if fsEntry.File != nil {
|
|
||||||
|
|
||||||
if fsEntry.File.Content != nil && fsEntry.File.Type == "application/markdown" {
|
|
||||||
data, err := parser.ParseFile(string(fsEntry.File.Content))
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, 500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := ResponseItem{
|
|
||||||
Name: fsEntry.File.Name,
|
|
||||||
Type: fsEntry.File.Type,
|
|
||||||
Content: data,
|
|
||||||
IsDir: false,
|
|
||||||
Size: int64(len(fsEntry.File.Content)),
|
|
||||||
ModTime: fsEntry.File.ModTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, 200, res)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
writeError(w, http.StatusInternalServerError, err)
|
||||||
writeFile(w, fsEntry.File)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if fsEntry.Dir != nil {
|
if entry.Type == "file" {
|
||||||
res := ResponseItem{
|
// For non-markdown files, content is []byte, write it directly
|
||||||
Name: fsEntry.Dir.Name,
|
if contentBytes, ok := entry.Content.([]byte); ok {
|
||||||
Content: fsEntry.Dir.Files,
|
w.Header().Set("Content-Type", entry.MIME)
|
||||||
IsDir: true,
|
w.Write(contentBytes)
|
||||||
ModTime: fsEntry.Dir.ModTime,
|
return
|
||||||
}
|
}
|
||||||
writeJSON(w, 200, res)
|
// For markdown files, content is parsed, return the whole Entry as JSON
|
||||||
|
writeJSON(w, http.StatusOK, entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For directories, return the whole Entry as JSON
|
||||||
|
if entry.Type == "dir" {
|
||||||
|
writeJSON(w, http.StatusOK, entry)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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, target string) {
|
||||||
|
Reference in New Issue
Block a user