ops design and app

add more comments feature, more tags feature, and other frontend updates
This commit is contained in:
2026-06-09 18:27:59 -04:00
parent 1e939f307d
commit 04a1f2bb6d
33 changed files with 3276 additions and 113 deletions

View File

@@ -31,6 +31,9 @@ type Service struct {
sourceDir string
}
type FileAccessFunc func(FileEntry) bool
type PathAccessFunc func(string) bool
// NewService creates a new sync service.
func NewService(repo *Repository, docService *docs.Service, contentStore *store.ContentStore, sourceDir string, logger *slog.Logger) *Service {
return &Service{
@@ -44,10 +47,15 @@ func NewService(repo *Repository, docService *docs.Service, contentStore *store.
// InitSync creates a new snapshot from the current server state.
func (s *Service) InitSync(ctx context.Context, deviceID, userID string) (*Snapshot, error) {
return s.InitSyncFiltered(ctx, deviceID, userID, nil)
}
func (s *Service) InitSyncFiltered(ctx context.Context, deviceID, userID string, canRead FileAccessFunc) (*Snapshot, error) {
files, err := s.buildSnapshotFromDisk(ctx)
if err != nil {
return nil, fmt.Errorf("build snapshot: %w", err)
}
files = filterFiles(files, canRead)
snap, err := s.repo.CreateSnapshot(ctx, deviceID, userID, files)
if err != nil {
@@ -60,6 +68,10 @@ func (s *Service) InitSync(ctx context.Context, deviceID, userID string) (*Snaps
// ApplyDelta processes client changes and computes server delta + conflicts.
func (s *Service) ApplyDelta(ctx context.Context, snapshotID, userID string, clientDelta Delta) (*DeltaResult, error) {
return s.ApplyDeltaFiltered(ctx, snapshotID, userID, clientDelta, nil, nil)
}
func (s *Service) ApplyDeltaFiltered(ctx context.Context, snapshotID, userID string, clientDelta Delta, canRead FileAccessFunc, canWrite PathAccessFunc) (*DeltaResult, error) {
snap, err := s.repo.GetSnapshot(ctx, snapshotID)
if err != nil {
return nil, fmt.Errorf("get snapshot: %w", err)
@@ -67,6 +79,14 @@ func (s *Service) ApplyDelta(ctx context.Context, snapshotID, userID string, cli
if snap.UserID != userID {
return nil, ErrForbidden
}
for _, clientChange := range clientDelta.Changes {
if canWrite != nil && !canWrite(clientChange.Path) {
return nil, ErrForbidden
}
if clientChange.OldPath != "" && canWrite != nil && !canWrite(clientChange.OldPath) {
return nil, ErrForbidden
}
}
serverFiles := make(map[string]FileEntry)
for _, f := range snap.Files {
@@ -91,6 +111,7 @@ func (s *Service) ApplyDelta(ctx context.Context, snapshotID, userID string, cli
if err != nil {
return nil, fmt.Errorf("build current snapshot: %w", err)
}
currentFiles = filterFiles(currentFiles, canRead)
currentMap := make(map[string]FileEntry)
for _, f := range currentFiles {
@@ -186,6 +207,7 @@ func (s *Service) ApplyDelta(ctx context.Context, snapshotID, userID string, cli
if err != nil {
return nil, fmt.Errorf("build post-delta snapshot: %w", err)
}
latestFiles = filterFiles(latestFiles, canRead)
newSnap, err := s.repo.CreateSnapshot(ctx, snap.DeviceID, snap.UserID, latestFiles)
if err != nil {
return nil, fmt.Errorf("create post-delta snapshot: %w", err)
@@ -200,6 +222,10 @@ func (s *Service) ApplyDelta(ctx context.Context, snapshotID, userID string, cli
// ResolveConflicts applies resolved changes and creates a new snapshot.
func (s *Service) ResolveConflicts(ctx context.Context, snapshotID, userID string, resolutions []Resolution) (*Snapshot, error) {
return s.ResolveConflictsFiltered(ctx, snapshotID, userID, resolutions, nil, nil)
}
func (s *Service) ResolveConflictsFiltered(ctx context.Context, snapshotID, userID string, resolutions []Resolution, canRead FileAccessFunc, canWrite PathAccessFunc) (*Snapshot, error) {
snap, err := s.repo.GetSnapshot(ctx, snapshotID)
if err != nil {
return nil, fmt.Errorf("get snapshot: %w", err)
@@ -207,6 +233,14 @@ func (s *Service) ResolveConflicts(ctx context.Context, snapshotID, userID strin
if snap.UserID != userID {
return nil, ErrForbidden
}
for _, resolution := range resolutions {
if canWrite != nil && !canWrite(resolution.Path) {
return nil, ErrForbidden
}
if resolution.NewPath != "" && canWrite != nil && !canWrite(resolution.NewPath) {
return nil, ErrForbidden
}
}
// Get current server state
if _, err := s.buildSnapshotFromDisk(ctx); err != nil {
@@ -255,6 +289,7 @@ func (s *Service) ResolveConflicts(ctx context.Context, snapshotID, userID strin
if err != nil {
return nil, fmt.Errorf("build resolved snapshot: %w", err)
}
files = filterFiles(files, canRead)
newSnap, err := s.repo.CreateSnapshot(ctx, snap.DeviceID, snap.UserID, files)
if err != nil {
@@ -265,6 +300,19 @@ func (s *Service) ResolveConflicts(ctx context.Context, snapshotID, userID strin
return newSnap, nil
}
func filterFiles(files []FileEntry, canRead FileAccessFunc) []FileEntry {
if canRead == nil {
return files
}
filtered := make([]FileEntry, 0, len(files))
for _, file := range files {
if canRead(file) {
filtered = append(filtered, file)
}
}
return filtered
}
// GetContent returns raw file content by hash.
func (s *Service) GetContent(hash string) ([]byte, error) {
if !isSHA256Hex(hash) {