// monorepo/cloud/maplefile-backend/internal/maplefile/repo/filemetadata/delete.go package filemetadata import ( "context" "fmt" "time" "github.com/gocql/gocql" "go.uber.org/zap" dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file" ) func (impl *fileMetadataRepositoryImpl) SoftDelete(id gocql.UUID) error { file, err := impl.Get(id) if err != nil { return fmt.Errorf("failed to get file for soft delete: %w", err) } if file == nil { return fmt.Errorf("file not found") } // Validate state transition if err := dom_file.IsValidStateTransition(file.State, dom_file.FileStateDeleted); err != nil { return fmt.Errorf("invalid state transition: %w", err) } // Update file state file.State = dom_file.FileStateDeleted file.ModifiedAt = time.Now() file.Version++ file.TombstoneVersion = file.Version file.TombstoneExpiry = time.Now().Add(30 * 24 * time.Hour) // 30 days return impl.Update(file) } func (impl *fileMetadataRepositoryImpl) SoftDeleteMany(ids []gocql.UUID) error { for _, id := range ids { if err := impl.SoftDelete(id); err != nil { impl.Logger.Warn("failed to soft delete file", zap.String("file_id", id.String()), zap.Error(err)) } } return nil } func (impl *fileMetadataRepositoryImpl) HardDelete(id gocql.UUID) error { file, err := impl.Get(id) if err != nil { return fmt.Errorf("failed to get file for hard delete: %w", err) } if file == nil { return fmt.Errorf("file not found") } batch := impl.Session.NewBatch(gocql.LoggedBatch) // 1. Delete from main table batch.Query(`DELETE FROM maplefile.files_by_id WHERE id = ?`, id) // 2. Delete from collection table batch.Query(`DELETE FROM maplefile.files_by_collection WHERE collection_id = ? AND modified_at = ? AND id = ?`, file.CollectionID, file.ModifiedAt, id) // 3. Delete from owner table batch.Query(`DELETE FROM maplefile.files_by_owner WHERE owner_id = ? AND modified_at = ? AND id = ?`, file.OwnerID, file.ModifiedAt, id) // 4. Delete from created_by table batch.Query(`DELETE FROM maplefile.files_by_creator WHERE created_by_user_id = ? AND created_at = ? AND id = ?`, file.CreatedByUserID, file.CreatedAt, id) // 5. Delete from user sync table batch.Query(`DELETE FROM maplefile.files_by_user WHERE user_id = ? AND modified_at = ? AND id = ?`, file.OwnerID, file.ModifiedAt, id) // 6. Delete from denormalized files_by_tag_id table for all tags for _, tag := range file.Tags { batch.Query(`DELETE FROM maplefile.files_by_tag_id WHERE tag_id = ? AND file_id = ?`, tag.ID, id) } // Execute batch if err := impl.Session.ExecuteBatch(batch); err != nil { impl.Logger.Error("failed to hard delete file", zap.String("file_id", id.String()), zap.Error(err)) return fmt.Errorf("failed to hard delete file: %w", err) } // Decrement collection file count if the file was active if file.State == dom_file.FileStateActive { if err := impl.CollectionRepo.DecrementFileCount(context.Background(), file.CollectionID); err != nil { impl.Logger.Error("failed to decrement collection file count", zap.String("file_id", id.String()), zap.String("collection_id", file.CollectionID.String()), zap.Error(err)) // Don't fail the entire operation if count update fails } } impl.Logger.Info("file hard deleted successfully", zap.String("file_id", id.String())) return nil } func (impl *fileMetadataRepositoryImpl) HardDeleteMany(ids []gocql.UUID) error { for _, id := range ids { if err := impl.HardDelete(id); err != nil { impl.Logger.Warn("failed to hard delete file", zap.String("file_id", id.String()), zap.Error(err)) } } return nil }