79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"connectrpc.com/connect"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
homelabv1 "pwa-homelab-mon/gen/homelab/v1"
|
|
"strings"
|
|
)
|
|
|
|
// --- docs ---
|
|
|
|
func docHash(b []byte) string {
|
|
h := sha256.Sum256(b)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
// docPath resolves a doc name safely: plain file name only, no separators, always inside docsDir.
|
|
func (s *server) docPath(name string) (string, error) {
|
|
if filepath.Base(name) != name || filepath.Ext(name) != ".md" {
|
|
return "", fmt.Errorf("bad doc name %q", name)
|
|
}
|
|
p := filepath.Join(s.docsDir, name)
|
|
if filepath.Dir(p) != s.docsDir {
|
|
return "", fmt.Errorf("bad doc name %q", name)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (s *server) ListDocs(context.Context, *connect.Request[homelabv1.ListDocsRequest]) (*connect.Response[homelabv1.ListDocsResponse], error) {
|
|
resp := &homelabv1.ListDocsResponse{}
|
|
entries, err := os.ReadDir(s.docsDir)
|
|
if err != nil {
|
|
return connect.NewResponse(resp), nil // no docs dir = no docs
|
|
}
|
|
for _, e := range entries {
|
|
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
|
|
continue
|
|
}
|
|
b, err := os.ReadFile(filepath.Join(s.docsDir, e.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resp.Docs = append(resp.Docs, &homelabv1.DocMeta{Name: e.Name(), Hash: docHash(b)})
|
|
}
|
|
return connect.NewResponse(resp), nil
|
|
}
|
|
|
|
func (s *server) GetDoc(_ context.Context, req *connect.Request[homelabv1.GetDocRequest]) (*connect.Response[homelabv1.GetDocResponse], error) {
|
|
p, err := s.docPath(req.Msg.GetName())
|
|
if err != nil {
|
|
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
|
}
|
|
b, err := os.ReadFile(p)
|
|
if err != nil {
|
|
return nil, connect.NewError(connect.CodeNotFound, err)
|
|
}
|
|
return connect.NewResponse(&homelabv1.GetDocResponse{Content: string(b)}), nil
|
|
}
|
|
|
|
func (s *server) SaveDoc(_ context.Context, req *connect.Request[homelabv1.SaveDocRequest]) (*connect.Response[homelabv1.SaveDocResponse], error) {
|
|
p, err := s.docPath(req.Msg.GetName())
|
|
if err != nil {
|
|
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
|
}
|
|
content := []byte(req.Msg.GetContent())
|
|
// ponytail: client authority — last write wins, no base-hash check; add optimistic locking if concurrent edits appear
|
|
if err := os.WriteFile(p, content, 0o644); err != nil {
|
|
return nil, connect.NewError(connect.CodeInternal, err)
|
|
}
|
|
log.Printf("doc saved: %s (%d bytes)", filepath.Base(p), len(content))
|
|
return connect.NewResponse(&homelabv1.SaveDocResponse{Hash: docHash(content)}), nil
|
|
}
|