Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,50 @@
// monorepo/cloud/backend/internal/usecase/filemetadata/anonymize_old_ips.go
package filemetadata
import (
"context"
"time"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
)
type AnonymizeOldIPsUseCase interface {
Execute(ctx context.Context, cutoffDate time.Time) (int, error)
}
type anonymizeOldIPsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewAnonymizeOldIPsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) AnonymizeOldIPsUseCase {
logger = logger.Named("FileMetadataAnonymizeOldIPsUseCase")
return &anonymizeOldIPsUseCaseImpl{config, logger, repo}
}
func (uc *anonymizeOldIPsUseCaseImpl) Execute(ctx context.Context, cutoffDate time.Time) (int, error) {
uc.logger.Debug("Anonymizing old IPs in file metadata tables",
zap.Time("cutoff_date", cutoffDate))
count, err := uc.repo.AnonymizeOldIPs(ctx, cutoffDate)
if err != nil {
uc.logger.Error("Failed to anonymize old IPs in file metadata tables",
zap.Error(err),
zap.Time("cutoff_date", cutoffDate))
return 0, err
}
uc.logger.Info("Successfully anonymized old IPs in file metadata tables",
zap.Int("count", count),
zap.Time("cutoff_date", cutoffDate))
return count, nil
}

View file

@ -0,0 +1,89 @@
// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/filemetadata/anonymize_user_references.go
package filemetadata
import (
"context"
"fmt"
"github.com/gocql/gocql"
"go.uber.org/zap"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
)
// AnonymizeUserReferencesUseCase handles anonymizing CreatedByUserID and ModifiedByUserID
// references when a user is deleted, replacing them with a special "deleted user" UUID.
type AnonymizeUserReferencesUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) (int, error)
}
type anonymizeUserReferencesUseCaseImpl struct {
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
// NewAnonymizeUserReferencesUseCase creates a new use case for anonymizing user references in files
func NewAnonymizeUserReferencesUseCase(
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) AnonymizeUserReferencesUseCase {
return &anonymizeUserReferencesUseCaseImpl{
logger: logger,
repo: repo,
}
}
// DeletedUserUUID is a well-known UUID representing a deleted user
// UUID: 00000000-0000-0000-0000-000000000001 (DELETED_USER)
var DeletedUserUUID = gocql.UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
func (uc *anonymizeUserReferencesUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) (int, error) {
uc.logger.Info("Anonymizing user references in file metadata",
zap.String("user_id", userID.String()))
// Get all files created or modified by this user
files, err := uc.repo.GetByCreatedByUserID(userID)
if err != nil {
uc.logger.Error("Failed to get files by created_by_user_id",
zap.String("user_id", userID.String()),
zap.Error(err))
return 0, fmt.Errorf("failed to get files by creator: %w", err)
}
updatedCount := 0
// Update each file to replace user references with deleted user UUID
for _, file := range files {
needsUpdate := false
// Check if this file has references to the deleted user
if file.CreatedByUserID == userID {
file.CreatedByUserID = DeletedUserUUID
needsUpdate = true
}
if file.ModifiedByUserID == userID {
file.ModifiedByUserID = DeletedUserUUID
needsUpdate = true
}
if needsUpdate {
// Update the file with anonymized references
if err := uc.repo.Update(file); err != nil {
uc.logger.Error("Failed to anonymize user references in file",
zap.String("file_id", file.ID.String()),
zap.String("user_id", userID.String()),
zap.Error(err))
// Continue with other files even if one fails
continue
}
updatedCount++
}
}
uc.logger.Info("✅ Anonymized user references in file metadata",
zap.String("user_id", userID.String()),
zap.Int("files_updated", updatedCount))
return updatedCount, nil
}

View file

@ -0,0 +1,55 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/check_access.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type CheckFileAccessUseCase interface {
Execute(fileID, userID gocql.UUID) (bool, error)
}
type checkFileAccessUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewCheckFileAccessUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CheckFileAccessUseCase {
logger = logger.Named("CheckFileAccessUseCase")
return &checkFileAccessUseCaseImpl{config, logger, repo}
}
func (uc *checkFileAccessUseCaseImpl) Execute(fileID, userID gocql.UUID) (bool, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if fileID.String() == "" {
e["file_id"] = "File ID is required"
}
if userID.String() == "" {
e["user_id"] = "User ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file access check",
zap.Any("error", e))
return false, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Check access in database.
//
return uc.repo.CheckIfUserHasAccess(fileID, userID)
}

View file

@ -0,0 +1,52 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/check_exists.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type CheckFileExistsUseCase interface {
Execute(id gocql.UUID) (bool, error)
}
type checkFileExistsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewCheckFileExistsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CheckFileExistsUseCase {
logger = logger.Named("CheckFileExistsUseCase")
return &checkFileExistsUseCaseImpl{config, logger, repo}
}
func (uc *checkFileExistsUseCaseImpl) Execute(id gocql.UUID) (bool, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "File ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file existence check",
zap.Any("error", e))
return false, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Check existence in database.
//
return uc.repo.CheckIfExistsByID(id)
}

View file

@ -0,0 +1,112 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/filemetadata/count_files.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// CountFilesResponse contains the file count for a user
type CountFilesResponse struct {
TotalFiles int `json:"total_files"`
}
type CountUserFilesUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) (*CountFilesResponse, error)
}
type countUserFilesUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
fileRepo dom_file.FileMetadataRepository
collectionRepo dom_collection.CollectionRepository
}
func NewCountUserFilesUseCase(
config *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
collectionRepo dom_collection.CollectionRepository,
) CountUserFilesUseCase {
logger = logger.Named("CountUserFilesUseCase")
return &countUserFilesUseCaseImpl{config, logger, fileRepo, collectionRepo}
}
func (uc *countUserFilesUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) (*CountFilesResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if userID.String() == "" {
e["user_id"] = "User ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating count user files",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get accessible collections for the user.
//
// Get collections using the efficient filtered query
filterOptions := dom_collection.CollectionFilterOptions{
UserID: userID,
IncludeOwned: true,
IncludeShared: true,
}
collectionResult, err := uc.collectionRepo.GetCollectionsWithFilter(ctx, filterOptions)
if err != nil {
uc.logger.Error("Failed to get accessible collections for file count",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
// Extract collection IDs
allCollections := collectionResult.GetAllCollections()
accessibleCollectionIDs := make([]gocql.UUID, 0, len(allCollections))
for _, collection := range allCollections {
accessibleCollectionIDs = append(accessibleCollectionIDs, collection.ID)
}
uc.logger.Debug("Found accessible collections for file counting",
zap.String("user_id", userID.String()),
zap.Int("owned_collections", len(collectionResult.OwnedCollections)),
zap.Int("shared_collections", len(collectionResult.SharedCollections)),
zap.Int("total_accessible", len(accessibleCollectionIDs)))
//
// STEP 3: Count files in accessible collections.
//
fileCount, err := uc.fileRepo.CountFilesByUser(ctx, userID, accessibleCollectionIDs)
if err != nil {
uc.logger.Error("Failed to count files for user",
zap.String("user_id", userID.String()),
zap.Int("accessible_collections", len(accessibleCollectionIDs)),
zap.Error(err))
return nil, err
}
response := &CountFilesResponse{
TotalFiles: fileCount,
}
uc.logger.Debug("Successfully counted user files",
zap.String("user_id", userID.String()),
zap.Int("accessible_collections", len(accessibleCollectionIDs)),
zap.Int("total_files", fileCount))
return response, nil
}

View file

@ -0,0 +1,84 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/create.go
package filemetadata
import (
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type CreateFileMetadataUseCase interface {
Execute(file *dom_file.File) error
}
type createFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewCreateFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CreateFileMetadataUseCase {
logger = logger.Named("CreateFileMetadataUseCase")
return &createFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *createFileMetadataUseCaseImpl) Execute(file *dom_file.File) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if file == nil {
e["file"] = "File is required"
} else {
if file.CollectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if file.OwnerID.String() == "" {
e["owner_id"] = "Owner ID is required"
}
if file.EncryptedMetadata == "" {
e["encrypted_metadata"] = "Encrypted metadata is required"
}
if file.EncryptedFileKey.Ciphertext == nil || len(file.EncryptedFileKey.Ciphertext) == 0 {
e["encrypted_file_key"] = "Encrypted file key is required"
}
if file.EncryptionVersion == "" {
e["encryption_version"] = "Encryption version is required"
}
if file.EncryptedHash == "" {
e["encrypted_hash"] = "Encrypted hash is required"
}
if file.EncryptedFileObjectKey == "" {
e["encrypted_file_object_key"] = "Encrypted file object key is required"
}
if file.EncryptedFileSizeInBytes <= 0 {
e["encrypted_file_size_in_bytes"] = "Encrypted file size must be greater than 0"
}
if file.State == "" {
e["state"] = "File state is required"
} else if file.State != dom_file.FileStatePending &&
file.State != dom_file.FileStateActive &&
file.State != dom_file.FileStateDeleted &&
file.State != dom_file.FileStateArchived {
e["state"] = "Invalid file state"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata creation",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Insert into database.
//
return uc.repo.Create(file)
}

View file

@ -0,0 +1,84 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/create_many.go
package filemetadata
import (
"fmt"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type CreateManyFileMetadataUseCase interface {
Execute(files []*dom_file.File) error
}
type createManyFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewCreateManyFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CreateManyFileMetadataUseCase {
logger = logger.Named("CreateManyFileMetadataUseCase")
return &createManyFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *createManyFileMetadataUseCaseImpl) Execute(files []*dom_file.File) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if files == nil || len(files) == 0 {
e["files"] = "Files are required"
} else {
for i, file := range files {
if file == nil {
e[fmt.Sprintf("files[%d]", i)] = "File is required"
continue
}
if file.CollectionID.String() == "" {
e[fmt.Sprintf("files[%d].collection_id", i)] = "Collection ID is required"
}
if file.OwnerID.String() == "" {
e[fmt.Sprintf("files[%d].owner_id", i)] = "Owner ID is required"
}
if file.EncryptedMetadata == "" {
e[fmt.Sprintf("files[%d].encrypted_metadata", i)] = "Encrypted metadata is required"
}
if file.EncryptedFileKey.Ciphertext == nil || len(file.EncryptedFileKey.Ciphertext) == 0 {
e[fmt.Sprintf("files[%d].encrypted_file_key", i)] = "Encrypted file key is required"
}
if file.EncryptionVersion == "" {
e[fmt.Sprintf("files[%d].encryption_version", i)] = "Encryption version is required"
}
if file.EncryptedHash == "" {
e[fmt.Sprintf("files[%d].encrypted_hash", i)] = "Encrypted hash is required"
}
if file.EncryptedFileObjectKey == "" {
e[fmt.Sprintf("files[%d].encrypted_file_object_key", i)] = "Encrypted file object key is required"
}
if file.EncryptedFileSizeInBytes <= 0 {
e[fmt.Sprintf("files[%d].encrypted_file_size_in_bytes", i)] = "Encrypted file size must be greater than 0"
}
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata batch creation",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Insert into database.
//
return uc.repo.CreateMany(files)
}

View file

@ -0,0 +1,60 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/delete_many.go
package filemetadata
import (
"fmt"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type DeleteManyFileMetadataUseCase interface {
Execute(ids []gocql.UUID) error
}
type deleteManyFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewDeleteManyFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) DeleteManyFileMetadataUseCase {
logger = logger.Named("DeleteManyFileMetadataUseCase")
return &deleteManyFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *deleteManyFileMetadataUseCaseImpl) Execute(ids []gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if ids == nil || len(ids) == 0 {
e["ids"] = "File IDs are required"
} else {
for i, id := range ids {
if id.String() == "" {
e[fmt.Sprintf("ids[%d]", i)] = "File ID is required"
}
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata batch deletion",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Delete from database.
//
return uc.repo.SoftDeleteMany(ids)
}

View file

@ -0,0 +1,63 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/get.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetFileMetadataUseCase interface {
Execute(id gocql.UUID) (*dom_file.File, error)
}
type getFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewGetFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataUseCase {
logger = logger.Named("GetFileMetadataUseCase")
return &getFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *getFileMetadataUseCaseImpl) Execute(id gocql.UUID) (*dom_file.File, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "File ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata retrieval",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
file, err := uc.repo.Get(id)
if err != nil {
return nil, err
}
if file == nil {
uc.logger.Debug("File metadata not found",
zap.Any("id", id))
return nil, httperror.NewForNotFoundWithSingleField("message", "File not found")
}
return file, nil
}

View file

@ -0,0 +1,52 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/get_by_collection.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetFileMetadataByCollectionUseCase interface {
Execute(collectionID gocql.UUID) ([]*dom_file.File, error)
}
type getFileMetadataByCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewGetFileMetadataByCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByCollectionUseCase {
logger = logger.Named("GetFileMetadataByCollectionUseCase")
return &getFileMetadataByCollectionUseCaseImpl{config, logger, repo}
}
func (uc *getFileMetadataByCollectionUseCaseImpl) Execute(collectionID gocql.UUID) ([]*dom_file.File, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata retrieval by collection",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
return uc.repo.GetByCollection(collectionID)
}

View file

@ -0,0 +1,52 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/get_by_created_by_user_id.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetFileMetadataByCreatedByUserIDUseCase interface {
Execute(createdByUserID gocql.UUID) ([]*dom_file.File, error)
}
type getFileMetadataByCreatedByUserIDUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewGetFileMetadataByCreatedByUserIDUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByCreatedByUserIDUseCase {
logger = logger.Named("GetFileMetadataByCreatedByUserIDUseCase")
return &getFileMetadataByCreatedByUserIDUseCaseImpl{config, logger, repo}
}
func (uc *getFileMetadataByCreatedByUserIDUseCaseImpl) Execute(createdByUserID gocql.UUID) ([]*dom_file.File, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if createdByUserID.String() == "" {
e["created_by_user_id"] = "Created by user ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata retrieval by created_by_user_id",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
return uc.repo.GetByCreatedByUserID(createdByUserID)
}

View file

@ -0,0 +1,60 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/get_by_ids.go
package filemetadata
import (
"fmt"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetFileMetadataByIDsUseCase interface {
Execute(ids []gocql.UUID) ([]*dom_file.File, error)
}
type getFileMetadataByIDsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewGetFileMetadataByIDsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByIDsUseCase {
logger = logger.Named("GetFileMetadataByIDsUseCase")
return &getFileMetadataByIDsUseCaseImpl{config, logger, repo}
}
func (uc *getFileMetadataByIDsUseCaseImpl) Execute(ids []gocql.UUID) ([]*dom_file.File, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if ids == nil || len(ids) == 0 {
e["ids"] = "File IDs are required"
} else {
for i, id := range ids {
if id.String() == "" {
e[fmt.Sprintf("ids[%d]", i)] = "File ID is required"
}
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata retrieval by IDs",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
return uc.repo.GetByIDs(ids)
}

View file

@ -0,0 +1,52 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/get_by_owner_id.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetFileMetadataByOwnerIDUseCase interface {
Execute(ownerID gocql.UUID) ([]*dom_file.File, error)
}
type getFileMetadataByOwnerIDUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewGetFileMetadataByOwnerIDUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByOwnerIDUseCase {
logger = logger.Named("GetFileMetadataByOwnerIDUseCase")
return &getFileMetadataByOwnerIDUseCaseImpl{config, logger, repo}
}
func (uc *getFileMetadataByOwnerIDUseCaseImpl) Execute(ownerID gocql.UUID) ([]*dom_file.File, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if ownerID.String() == "" {
e["owner_id"] = "Created by user ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata retrieval by owner_id",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
return uc.repo.GetByOwnerID(ownerID)
}

View file

@ -0,0 +1,68 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/harddelete.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// HardDeleteFileMetadataUseCase permanently deletes file metadata
// Used for GDPR right-to-be-forgotten implementation
type HardDeleteFileMetadataUseCase interface {
Execute(id gocql.UUID) error
}
type hardDeleteFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewHardDeleteFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) HardDeleteFileMetadataUseCase {
logger = logger.Named("HardDeleteFileMetadataUseCase")
return &hardDeleteFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *hardDeleteFileMetadataUseCaseImpl) Execute(id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "File ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata hard deletion",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Hard delete from database (no tombstone).
//
uc.logger.Info("Hard deleting file metadata (GDPR mode)",
zap.String("file_id", id.String()))
err := uc.repo.HardDelete(id)
if err != nil {
uc.logger.Error("Failed to hard delete file metadata",
zap.String("file_id", id.String()),
zap.Error(err))
return err
}
uc.logger.Info("✅ File metadata hard deleted successfully",
zap.String("file_id", id.String()))
return nil
}

View file

@ -0,0 +1,25 @@
package filemetadata
import (
"testing"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
)
// NOTE: Unit tests for HardDeleteFileMetadataUseCase would require mocks.
// For now, this use case will be tested via integration tests.
// See Task 1.10 in RIGHT_TO_BE_FORGOTTEN_IMPLEMENTATION.md
func TestHardDeleteFileMetadataUseCase_Constructor(t *testing.T) {
// Test that constructor creates use case successfully
cfg := &config.Configuration{}
logger := zap.NewNop()
useCase := NewHardDeleteFileMetadataUseCase(cfg, logger, nil)
if useCase == nil {
t.Error("Expected use case to be created, got nil")
}
}

View file

@ -0,0 +1,66 @@
// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/filemetadata/list_by_owner.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type ListFilesByOwnerIDUseCase interface {
Execute(ctx context.Context, ownerID gocql.UUID) ([]*dom_file.File, error)
}
type listFilesByOwnerIDUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewListFilesByOwnerIDUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) ListFilesByOwnerIDUseCase {
logger = logger.Named("ListFilesByOwnerIDUseCase")
return &listFilesByOwnerIDUseCaseImpl{config, logger, repo}
}
func (uc *listFilesByOwnerIDUseCaseImpl) Execute(ctx context.Context, ownerID gocql.UUID) ([]*dom_file.File, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if ownerID.String() == "" {
e["owner_id"] = "Owner ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating list files by owner",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
files, err := uc.repo.GetByOwnerID(ownerID)
if err != nil {
uc.logger.Error("Failed to get files by owner ID",
zap.String("owner_id", ownerID.String()),
zap.Error(err))
return nil, err
}
uc.logger.Debug("Files successfully retrieved by owner ID",
zap.String("owner_id", ownerID.String()),
zap.Int("count", len(files)))
return files, nil
}

View file

@ -0,0 +1,131 @@
// cloud/maplefile-backend/internal/maplefile/usecase/filemetadata/list_recent_files.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type ListRecentFilesUseCase interface {
Execute(ctx context.Context, userID gocql.UUID, cursor *dom_file.RecentFilesCursor, limit int64) (*dom_file.RecentFilesResponse, error)
}
type listRecentFilesUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
fileRepo dom_file.FileMetadataRepository
collectionRepo dom_collection.CollectionRepository
}
func NewListRecentFilesUseCase(
config *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
collectionRepo dom_collection.CollectionRepository,
) ListRecentFilesUseCase {
logger = logger.Named("ListRecentFilesUseCase")
return &listRecentFilesUseCaseImpl{config, logger, fileRepo, collectionRepo}
}
func (uc *listRecentFilesUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID, cursor *dom_file.RecentFilesCursor, limit int64) (*dom_file.RecentFilesResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if userID.String() == "" {
e["user_id"] = "User ID is required"
}
if limit <= 0 {
e["limit"] = "Limit must be greater than 0"
}
if limit > 100 {
e["limit"] = "Limit cannot exceed 100"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating list recent files",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get accessible collections for the user.
//
uc.logger.Debug("Getting accessible collections for recent files",
zap.String("user_id", userID.String()))
// Get collections using the efficient filtered query
filterOptions := dom_collection.CollectionFilterOptions{
UserID: userID,
IncludeOwned: true,
IncludeShared: true,
}
collectionResult, err := uc.collectionRepo.GetCollectionsWithFilter(ctx, filterOptions)
if err != nil {
uc.logger.Error("Failed to get accessible collections for recent files",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
// Extract collection IDs
allCollections := collectionResult.GetAllCollections()
accessibleCollectionIDs := make([]gocql.UUID, 0, len(allCollections))
for _, collection := range allCollections {
// Only include active collections
if collection.State == "active" {
accessibleCollectionIDs = append(accessibleCollectionIDs, collection.ID)
}
}
uc.logger.Debug("Found accessible collections for recent files",
zap.String("user_id", userID.String()),
zap.Int("owned_collections", len(collectionResult.OwnedCollections)),
zap.Int("shared_collections", len(collectionResult.SharedCollections)),
zap.Int("total_accessible", len(accessibleCollectionIDs)))
// If no accessible collections, return empty response
if len(accessibleCollectionIDs) == 0 {
uc.logger.Info("User has no accessible collections for recent files",
zap.String("user_id", userID.String()))
return &dom_file.RecentFilesResponse{
Files: []dom_file.RecentFilesItem{},
NextCursor: nil,
HasMore: false,
}, nil
}
//
// STEP 3: List recent files for accessible collections.
//
recentFiles, err := uc.fileRepo.ListRecentFiles(ctx, userID, cursor, limit, accessibleCollectionIDs)
if err != nil {
uc.logger.Error("Failed to list recent files",
zap.Any("error", err),
zap.String("user_id", userID.String()))
return nil, err
}
if recentFiles == nil {
uc.logger.Debug("Recent files not found",
zap.String("user_id", userID.String()))
return nil, httperror.NewForNotFoundWithSingleField("message", "Recent files not found")
}
uc.logger.Debug("Recent files successfully retrieved",
zap.String("user_id", userID.String()),
zap.Any("next_cursor", recentFiles.NextCursor),
zap.Int("files_count", len(recentFiles.Files)))
return recentFiles, nil
}

View file

@ -0,0 +1,91 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/filemetadata/list_sync_data.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type ListFileMetadataSyncDataUseCase interface {
Execute(ctx context.Context, userID gocql.UUID, cursor *dom_file.FileSyncCursor, limit int64, accessibleCollectionIDs []gocql.UUID) (*dom_file.FileSyncResponse, error)
}
type listFileMetadataSyncDataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewListFileMetadataSyncDataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) ListFileMetadataSyncDataUseCase {
logger = logger.Named("ListFileMetadataSyncDataUseCase")
return &listFileMetadataSyncDataUseCaseImpl{config, logger, repo}
}
func (uc *listFileMetadataSyncDataUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID, cursor *dom_file.FileSyncCursor, limit int64, accessibleCollectionIDs []gocql.UUID) (*dom_file.FileSyncResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if userID.String() == "" {
e["user_id"] = "User ID is required"
}
if len(accessibleCollectionIDs) == 0 {
e["accessible_collections"] = "At least one accessible collection is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating list file sync data",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
uc.logger.Debug("Listing file sync data",
zap.String("user_id", userID.String()),
zap.Int("accessible_collections_count", len(accessibleCollectionIDs)),
zap.Any("cursor", cursor),
zap.Int64("limit", limit))
//
// STEP 2: List file sync data from repository for accessible collections.
//
result, err := uc.repo.ListSyncData(ctx, userID, cursor, limit, accessibleCollectionIDs)
if err != nil {
uc.logger.Error("Failed to list file sync data from repository",
zap.Any("error", err),
zap.String("user_id", userID.String()))
return nil, err
}
// Log the sync items for debugging
uc.logger.Debug("File sync data retrieved from repository",
zap.String("user_id", userID.String()),
zap.Int("files_count", len(result.Files)),
zap.Bool("has_more", result.HasMore))
// Log each sync item to verify all fields are populated
for i, item := range result.Files {
uc.logger.Debug("File sync item",
zap.Int("index", i),
zap.String("file_id", item.ID.String()),
zap.String("collection_id", item.CollectionID.String()),
zap.Uint64("version", item.Version),
zap.Time("modified_at", item.ModifiedAt),
zap.String("state", item.State),
zap.Uint64("tombstone_version", item.TombstoneVersion),
zap.Time("tombstone_expiry", item.TombstoneExpiry),
zap.Int64("encrypted_file_size_in_bytes", item.EncryptedFileSizeInBytes))
}
return result, nil
}

View file

@ -0,0 +1,197 @@
package filemetadata
import (
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
)
// Wire providers for file metadata use cases
func ProvideCreateFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CreateFileMetadataUseCase {
return NewCreateFileMetadataUseCase(cfg, logger, repo)
}
func ProvideUpdateFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) UpdateFileMetadataUseCase {
return NewUpdateFileMetadataUseCase(cfg, logger, repo)
}
func ProvideGetFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataUseCase {
return NewGetFileMetadataUseCase(cfg, logger, repo)
}
func ProvideSoftDeleteFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) SoftDeleteFileMetadataUseCase {
return NewSoftDeleteFileMetadataUseCase(cfg, logger, repo)
}
func ProvideGetFileMetadataByCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByCollectionUseCase {
return NewGetFileMetadataByCollectionUseCase(cfg, logger, repo)
}
func ProvideGetFileMetadataByCreatedByUserIDUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByCreatedByUserIDUseCase {
return NewGetFileMetadataByCreatedByUserIDUseCase(cfg, logger, repo)
}
func ProvideDeleteManyFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) DeleteManyFileMetadataUseCase {
return NewDeleteManyFileMetadataUseCase(cfg, logger, repo)
}
func ProvideCountUserFilesUseCase(
cfg *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
collectionRepo dom_collection.CollectionRepository,
) CountUserFilesUseCase {
return NewCountUserFilesUseCase(cfg, logger, fileRepo, collectionRepo)
}
func ProvideCheckFileAccessUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CheckFileAccessUseCase {
return NewCheckFileAccessUseCase(cfg, logger, repo)
}
func ProvideGetStorageSizeByCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetStorageSizeByCollectionUseCase {
return NewGetStorageSizeByCollectionUseCase(cfg, logger, repo)
}
func ProvideGetFileMetadataByOwnerIDUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByOwnerIDUseCase {
return NewGetFileMetadataByOwnerIDUseCase(cfg, logger, repo)
}
func ProvideCheckFileExistsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CheckFileExistsUseCase {
return NewCheckFileExistsUseCase(cfg, logger, repo)
}
func ProvideGetStorageSizeByUserUseCase(
cfg *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
collectionRepo dom_collection.CollectionRepository,
) GetStorageSizeByUserUseCase {
return NewGetStorageSizeByUserUseCase(cfg, logger, fileRepo, collectionRepo)
}
func ProvideListRecentFilesUseCase(
cfg *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
collectionRepo dom_collection.CollectionRepository,
) ListRecentFilesUseCase {
return NewListRecentFilesUseCase(cfg, logger, fileRepo, collectionRepo)
}
func ProvideGetFileMetadataByIDsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetFileMetadataByIDsUseCase {
return NewGetFileMetadataByIDsUseCase(cfg, logger, repo)
}
func ProvideCreateManyFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) CreateManyFileMetadataUseCase {
return NewCreateManyFileMetadataUseCase(cfg, logger, repo)
}
func ProvideListFileMetadataSyncDataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) ListFileMetadataSyncDataUseCase {
return NewListFileMetadataSyncDataUseCase(cfg, logger, repo)
}
func ProvideGetStorageSizeByOwnerUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) GetStorageSizeByOwnerUseCase {
return NewGetStorageSizeByOwnerUseCase(cfg, logger, repo)
}
func ProvideAnonymizeOldIPsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) AnonymizeOldIPsUseCase {
return NewAnonymizeOldIPsUseCase(cfg, logger, repo)
}
func ProvideListFilesByOwnerIDUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) ListFilesByOwnerIDUseCase {
return NewListFilesByOwnerIDUseCase(cfg, logger, repo)
}
func ProvideRestoreFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) RestoreFileMetadataUseCase {
return NewRestoreFileMetadataUseCase(cfg, logger, repo)
}
func ProvideHardDeleteFileMetadataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) HardDeleteFileMetadataUseCase {
return NewHardDeleteFileMetadataUseCase(cfg, logger, repo)
}
func ProvideAnonymizeUserReferencesUseCase(
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) AnonymizeUserReferencesUseCase {
return NewAnonymizeUserReferencesUseCase(logger, repo)
}

View file

@ -0,0 +1,65 @@
// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/filemetadata/restore.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type RestoreFileMetadataUseCase interface {
Execute(ctx context.Context, id gocql.UUID) error
}
type restoreFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewRestoreFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) RestoreFileMetadataUseCase {
logger = logger.Named("RestoreFileMetadataUseCase")
return &restoreFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *restoreFileMetadataUseCaseImpl) Execute(ctx context.Context, id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "File ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata restoration",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Restore file metadata using repository method.
//
err := uc.repo.Restore(id)
if err != nil {
uc.logger.Error("Failed to restore file metadata",
zap.String("file_id", id.String()),
zap.Error(err))
return err
}
uc.logger.Info("File metadata successfully restored",
zap.String("file_id", id.String()))
return nil
}

View file

@ -0,0 +1,52 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/delete.go
package filemetadata
import (
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type SoftDeleteFileMetadataUseCase interface {
Execute(id gocql.UUID) error
}
type softDeleteFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewSoftDeleteFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) SoftDeleteFileMetadataUseCase {
logger = logger.Named("SoftDeleteFileMetadataUseCase")
return &softDeleteFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *softDeleteFileMetadataUseCaseImpl) Execute(id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "File ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata deletion",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Soft-delete from database.
//
return uc.repo.SoftDelete(id)
}

View file

@ -0,0 +1,88 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/filemetadata/storage_size.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// StorageSizeBreakdownResponse contains detailed storage breakdown
type StorageSizeBreakdownResponse struct {
OwnedSizeBytes int64 `json:"owned_size_bytes"`
SharedSizeBytes int64 `json:"shared_size_bytes"`
TotalSizeBytes int64 `json:"total_size_bytes"`
CollectionBreakdownBytes map[string]int64 `json:"collection_breakdown_bytes"`
OwnedCollectionsCount int `json:"owned_collections_count"`
SharedCollectionsCount int `json:"shared_collections_count"`
}
// Use case interfaces
type GetStorageSizeByCollectionUseCase interface {
Execute(ctx context.Context, collectionID gocql.UUID) (*StorageSizeResponse, error)
}
// Use case implementations
type getStorageSizeByCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
fileRepo dom_file.FileMetadataRepository
}
// Constructors
func NewGetStorageSizeByCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
) GetStorageSizeByCollectionUseCase {
logger = logger.Named("GetStorageSizeByCollectionUseCase")
return &getStorageSizeByCollectionUseCaseImpl{config, logger, fileRepo}
}
// Use case implementations
func (uc *getStorageSizeByCollectionUseCaseImpl) Execute(ctx context.Context, collectionID gocql.UUID) (*StorageSizeResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get storage size by collection",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Calculate storage size.
//
totalSize, err := uc.fileRepo.GetTotalStorageSizeByCollection(ctx, collectionID)
if err != nil {
uc.logger.Error("Failed to get storage size by collection",
zap.String("collection_id", collectionID.String()),
zap.Error(err))
return nil, err
}
response := &StorageSizeResponse{
TotalSizeBytes: totalSize,
}
uc.logger.Debug("Successfully calculated storage size by collection",
zap.String("collection_id", collectionID.String()),
zap.Int64("total_size_bytes", totalSize))
return response, nil
}

View file

@ -0,0 +1,80 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/filemetadata/storage_size.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// StorageSizeResponse contains storage size information
type StorageSizeResponse struct {
TotalSizeBytes int64 `json:"total_size_bytes"`
}
// Use case interfaces
type GetStorageSizeByOwnerUseCase interface {
Execute(ctx context.Context, ownerID gocql.UUID) (*StorageSizeResponse, error)
}
// Use case implementations
type getStorageSizeByOwnerUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
fileRepo dom_file.FileMetadataRepository
}
// Constructors
func NewGetStorageSizeByOwnerUseCase(
config *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
) GetStorageSizeByOwnerUseCase {
logger = logger.Named("GetStorageSizeByOwnerUseCase")
return &getStorageSizeByOwnerUseCaseImpl{config, logger, fileRepo}
}
// Use case implementations
func (uc *getStorageSizeByOwnerUseCaseImpl) Execute(ctx context.Context, ownerID gocql.UUID) (*StorageSizeResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if ownerID.String() == "" {
e["owner_id"] = "Owner ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get storage size by owner",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Calculate storage size.
//
totalSize, err := uc.fileRepo.GetTotalStorageSizeByOwner(ctx, ownerID)
if err != nil {
uc.logger.Error("Failed to get storage size by owner",
zap.String("owner_id", ownerID.String()),
zap.Error(err))
return nil, err
}
response := &StorageSizeResponse{
TotalSizeBytes: totalSize,
}
uc.logger.Debug("Successfully calculated storage size by owner",
zap.String("owner_id", ownerID.String()),
zap.Int64("total_size_bytes", totalSize))
return response, nil
}

View file

@ -0,0 +1,108 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/filemetadata/storage_size.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// Use case interfaces
type GetStorageSizeByUserUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) (*StorageSizeResponse, error)
}
// Use case implementations
type getStorageSizeByUserUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
fileRepo dom_file.FileMetadataRepository
collectionRepo dom_collection.CollectionRepository
}
// Constructors
func NewGetStorageSizeByUserUseCase(
config *config.Configuration,
logger *zap.Logger,
fileRepo dom_file.FileMetadataRepository,
collectionRepo dom_collection.CollectionRepository,
) GetStorageSizeByUserUseCase {
logger = logger.Named("GetStorageSizeByUserUseCase")
return &getStorageSizeByUserUseCaseImpl{config, logger, fileRepo, collectionRepo}
}
// Use case implementations
func (uc *getStorageSizeByUserUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) (*StorageSizeResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if userID.String() == "" {
e["user_id"] = "User ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get storage size by user",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get accessible collections for the user.
//
filterOptions := dom_collection.CollectionFilterOptions{
UserID: userID,
IncludeOwned: true,
IncludeShared: true,
}
collectionResult, err := uc.collectionRepo.GetCollectionsWithFilter(ctx, filterOptions)
if err != nil {
uc.logger.Error("Failed to get accessible collections for storage size calculation",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
// Extract collection IDs
allCollections := collectionResult.GetAllCollections()
accessibleCollectionIDs := make([]gocql.UUID, 0, len(allCollections))
for _, collection := range allCollections {
accessibleCollectionIDs = append(accessibleCollectionIDs, collection.ID)
}
//
// STEP 3: Calculate storage size.
//
totalSize, err := uc.fileRepo.GetTotalStorageSizeByUser(ctx, userID, accessibleCollectionIDs)
if err != nil {
uc.logger.Error("Failed to get storage size by user",
zap.String("user_id", userID.String()),
zap.Int("accessible_collections", len(accessibleCollectionIDs)),
zap.Error(err))
return nil, err
}
response := &StorageSizeResponse{
TotalSizeBytes: totalSize,
}
uc.logger.Debug("Successfully calculated storage size by user",
zap.String("user_id", userID.String()),
zap.Int("accessible_collections", len(accessibleCollectionIDs)),
zap.Int64("total_size_bytes", totalSize))
return response, nil
}

View file

@ -0,0 +1,81 @@
// monorepo/cloud/backend/internal/maplefile/usecase/filemetadata/update.go
package filemetadata
import (
"context"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type UpdateFileMetadataUseCase interface {
Execute(ctx context.Context, file *dom_file.File) error
}
type updateFileMetadataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_file.FileMetadataRepository
}
func NewUpdateFileMetadataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_file.FileMetadataRepository,
) UpdateFileMetadataUseCase {
logger = logger.Named("UpdateFileMetadataUseCase")
return &updateFileMetadataUseCaseImpl{config, logger, repo}
}
func (uc *updateFileMetadataUseCaseImpl) Execute(ctx context.Context, file *dom_file.File) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if file == nil {
e["file"] = "File is required"
} else {
if file.ID.String() == "" {
e["id"] = "File ID is required"
}
if file.CollectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if file.OwnerID.String() == "" {
e["owner_id"] = "Owner ID is required"
}
if file.EncryptedMetadata == "" {
e["encrypted_metadata"] = "Encrypted metadata is required"
}
if file.EncryptedFileKey.Ciphertext == nil || len(file.EncryptedFileKey.Ciphertext) == 0 {
e["encrypted_file_key"] = "Encrypted file key is required"
}
if file.EncryptionVersion == "" {
e["encryption_version"] = "Encryption version is required"
}
if file.EncryptedHash == "" {
e["encrypted_hash"] = "Encrypted hash is required"
}
if file.EncryptedFileObjectKey == "" {
e["encrypted_file_object_key"] = "Encrypted file object key is required"
}
if file.EncryptedFileSizeInBytes <= 0 {
e["encrypted_file_size_in_bytes"] = "Encrypted file size must be greater than 0"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating file metadata update",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Update in database.
//
return uc.repo.Update(file)
}