package file import ( "encoding/json" "fmt" "strings" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/file" "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/pkg/storage" ) const ( fileKeyPrefix = "file:" collectionFileIndex = "collection_file_index:" statusFileIndex = "status_file_index:" ) type repository struct { storage storage.Storage } // ProvideRepository creates a new file repository for Wire func ProvideRepository(storage storage.Storage) file.Repository { return &repository{storage: storage} } func (r *repository) Create(f *file.File) error { return r.save(f) } func (r *repository) Get(id string) (*file.File, error) { key := fileKeyPrefix + id data, err := r.storage.Get(key) if err != nil { return nil, fmt.Errorf("failed to get file: %w", err) } if data == nil { return nil, nil } var f file.File if err := json.Unmarshal(data, &f); err != nil { return nil, fmt.Errorf("failed to unmarshal file: %w", err) } return &f, nil } func (r *repository) Update(f *file.File) error { // Get existing file to clean up old indexes if collection changed existing, err := r.Get(f.ID) if err != nil { return fmt.Errorf("failed to get existing file: %w", err) } if existing != nil { // Clean up old collection index if collection changed if existing.CollectionID != f.CollectionID { oldIndexKey := collectionFileIndex + existing.CollectionID + ":" + existing.ID _ = r.storage.Delete(oldIndexKey) } // Clean up old status index if status changed if existing.SyncStatus != f.SyncStatus { oldStatusKey := statusFileIndex + existing.SyncStatus.String() + ":" + existing.ID _ = r.storage.Delete(oldStatusKey) } } return r.save(f) } func (r *repository) Delete(id string) error { // Get file first to remove indexes f, err := r.Get(id) if err != nil { return err } if f == nil { return nil // Nothing to delete } // Delete collection index collIndexKey := collectionFileIndex + f.CollectionID + ":" + id if err := r.storage.Delete(collIndexKey); err != nil { return fmt.Errorf("failed to delete collection index: %w", err) } // Delete status index statusKey := statusFileIndex + f.SyncStatus.String() + ":" + id if err := r.storage.Delete(statusKey); err != nil { return fmt.Errorf("failed to delete status index: %w", err) } // Delete file fileKey := fileKeyPrefix + id if err := r.storage.Delete(fileKey); err != nil { return fmt.Errorf("failed to delete file: %w", err) } return nil } func (r *repository) List() ([]*file.File, error) { var files []*file.File err := r.storage.Iterate(func(key, value []byte) error { keyStr := string(key) if strings.HasPrefix(keyStr, fileKeyPrefix) { var f file.File if err := json.Unmarshal(value, &f); err != nil { return fmt.Errorf("failed to unmarshal file: %w", err) } files = append(files, &f) } return nil }) if err != nil { return nil, fmt.Errorf("failed to list files: %w", err) } return files, nil } func (r *repository) ListByCollection(collectionID string) ([]*file.File, error) { var files []*file.File prefix := collectionFileIndex + collectionID + ":" err := r.storage.Iterate(func(key, value []byte) error { keyStr := string(key) if strings.HasPrefix(keyStr, prefix) { // Extract file ID from index key fileID := strings.TrimPrefix(keyStr, prefix) f, err := r.Get(fileID) if err != nil { return err } if f != nil { files = append(files, f) } } return nil }) if err != nil { return nil, fmt.Errorf("failed to list files by collection: %w", err) } return files, nil } func (r *repository) ListByStatus(status file.SyncStatus) ([]*file.File, error) { var files []*file.File prefix := statusFileIndex + status.String() + ":" err := r.storage.Iterate(func(key, value []byte) error { keyStr := string(key) if strings.HasPrefix(keyStr, prefix) { // Extract file ID from index key fileID := strings.TrimPrefix(keyStr, prefix) f, err := r.Get(fileID) if err != nil { return err } if f != nil { files = append(files, f) } } return nil }) if err != nil { return nil, fmt.Errorf("failed to list files by status: %w", err) } return files, nil } func (r *repository) Exists(id string) (bool, error) { key := fileKeyPrefix + id data, err := r.storage.Get(key) if err != nil { return false, fmt.Errorf("failed to check file existence: %w", err) } return data != nil, nil } // save persists the file and maintains indexes func (r *repository) save(f *file.File) error { data, err := json.Marshal(f) if err != nil { return fmt.Errorf("failed to marshal file: %w", err) } // Save file by ID fileKey := fileKeyPrefix + f.ID if err := r.storage.Set(fileKey, data); err != nil { return fmt.Errorf("failed to save file: %w", err) } // Create collection index (for ListByCollection) collIndexKey := collectionFileIndex + f.CollectionID + ":" + f.ID if err := r.storage.Set(collIndexKey, []byte(f.ID)); err != nil { return fmt.Errorf("failed to create collection index: %w", err) } // Create status index (for ListByStatus) statusKey := statusFileIndex + f.SyncStatus.String() + ":" + f.ID if err := r.storage.Set(statusKey, []byte(f.ID)); err != nil { return fmt.Errorf("failed to create status index: %w", err) } return nil }