talas-group/talas-wiki/internal/wiki/comments.go
senke 66471934af Initial commit: Talas Group project management & documentation
Knowledge base of ~80+ markdown files across 14 domains (00-13),
Logseq graph, hardware design files (KiCAD), infrastructure configs,
and talas-wiki static site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:10:41 +02:00

190 lines
4.2 KiB
Go

package wiki
import (
"encoding/json"
"os"
"path/filepath"
"sort"
"sync"
"time"
)
type Comment struct {
ID string `json:"id"`
Author string `json:"author"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
type PageComments struct {
PagePath string `json:"page_path"`
Comments []Comment `json:"comments"`
}
type CommentStore struct {
mu sync.RWMutex
dataDir string
}
func NewCommentStore(docsRoot string) *CommentStore {
dir := filepath.Join(docsRoot, ".wiki-data", "comments")
os.MkdirAll(dir, 0755)
return &CommentStore{dataDir: dir}
}
func (cs *CommentStore) filePath(pagePath string) string {
safe := filepath.Clean(pagePath)
safe = filepath.Join(cs.dataDir, safe+".json")
return safe
}
func (cs *CommentStore) Get(pagePath string) []Comment {
cs.mu.RLock()
defer cs.mu.RUnlock()
data, err := os.ReadFile(cs.filePath(pagePath))
if err != nil {
return nil
}
var comments []Comment
json.Unmarshal(data, &comments)
return comments
}
func (cs *CommentStore) Add(pagePath, author, content string) Comment {
cs.mu.Lock()
defer cs.mu.Unlock()
comments := cs.readUnsafe(pagePath)
c := Comment{
ID: time.Now().Format("20060102150405"),
Author: author,
Content: content,
CreatedAt: time.Now(),
}
comments = append(comments, c)
cs.writeUnsafe(pagePath, comments)
return c
}
func (cs *CommentStore) Delete(pagePath, commentID string) {
cs.mu.Lock()
defer cs.mu.Unlock()
comments := cs.readUnsafe(pagePath)
var filtered []Comment
for _, c := range comments {
if c.ID != commentID {
filtered = append(filtered, c)
}
}
cs.writeUnsafe(pagePath, filtered)
}
func (cs *CommentStore) readUnsafe(pagePath string) []Comment {
data, err := os.ReadFile(cs.filePath(pagePath))
if err != nil {
return nil
}
var comments []Comment
json.Unmarshal(data, &comments)
return comments
}
func (cs *CommentStore) writeUnsafe(pagePath string, comments []Comment) {
sort.Slice(comments, func(i, j int) bool {
return comments[i].CreatedAt.Before(comments[j].CreatedAt)
})
data, _ := json.MarshalIndent(comments, "", " ")
fp := cs.filePath(pagePath)
os.MkdirAll(filepath.Dir(fp), 0755)
os.WriteFile(fp, data, 0644)
}
// Review proposals: stored as JSON with proposed content
type ReviewProposal struct {
ID string `json:"id"`
PagePath string `json:"page_path"`
Author string `json:"author"`
Message string `json:"message"`
NewContent string `json:"new_content"`
CreatedAt time.Time `json:"created_at"`
}
type ReviewStore struct {
mu sync.RWMutex
dataDir string
}
func NewReviewStore(docsRoot string) *ReviewStore {
dir := filepath.Join(docsRoot, ".wiki-data", "reviews")
os.MkdirAll(dir, 0755)
return &ReviewStore{dataDir: dir}
}
func (rs *ReviewStore) List() []ReviewProposal {
rs.mu.RLock()
defer rs.mu.RUnlock()
entries, err := os.ReadDir(rs.dataDir)
if err != nil {
return nil
}
var proposals []ReviewProposal
for _, e := range entries {
if filepath.Ext(e.Name()) != ".json" {
continue
}
data, err := os.ReadFile(filepath.Join(rs.dataDir, e.Name()))
if err != nil {
continue
}
var p ReviewProposal
if json.Unmarshal(data, &p) == nil {
proposals = append(proposals, p)
}
}
sort.Slice(proposals, func(i, j int) bool {
return proposals[i].CreatedAt.After(proposals[j].CreatedAt)
})
return proposals
}
func (rs *ReviewStore) Get(id string) *ReviewProposal {
rs.mu.RLock()
defer rs.mu.RUnlock()
data, err := os.ReadFile(filepath.Join(rs.dataDir, id+".json"))
if err != nil {
return nil
}
var p ReviewProposal
if json.Unmarshal(data, &p) != nil {
return nil
}
return &p
}
func (rs *ReviewStore) Create(pagePath, author, message, content string) ReviewProposal {
rs.mu.Lock()
defer rs.mu.Unlock()
p := ReviewProposal{
ID: time.Now().Format("20060102150405"),
PagePath: pagePath,
Author: author,
Message: message,
NewContent: content,
CreatedAt: time.Now(),
}
data, _ := json.MarshalIndent(p, "", " ")
os.WriteFile(filepath.Join(rs.dataDir, p.ID+".json"), data, 0644)
return p
}
func (rs *ReviewStore) Delete(id string) {
rs.mu.Lock()
defer rs.mu.Unlock()
os.Remove(filepath.Join(rs.dataDir, id+".json"))
}