ops design and app
add more comments feature, more tags feature, and other frontend updates
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user