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,82 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/add_member.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type AddCollectionMemberUseCase interface {
Execute(ctx context.Context, collectionID gocql.UUID, membership *dom_collection.CollectionMembership) error
}
type addCollectionMemberUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewAddCollectionMemberUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AddCollectionMemberUseCase {
logger = logger.Named("AddCollectionMemberUseCase")
return &addCollectionMemberUseCaseImpl{config, logger, repo}
}
func (uc *addCollectionMemberUseCaseImpl) Execute(ctx context.Context, collectionID gocql.UUID, membership *dom_collection.CollectionMembership) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if membership == nil {
e["membership"] = "Membership details are required"
} else {
// Generate member ID if not provided
if membership.ID.String() == "" || membership.ID.String() == "00000000-0000-0000-0000-000000000000" {
membership.ID = gocql.TimeUUID()
}
if membership.RecipientID.String() == "" {
e["recipient_id"] = "Recipient ID is required"
}
if membership.RecipientEmail == "" {
e["recipient_email"] = "Recipient email is required"
}
if membership.GrantedByID.String() == "" {
e["granted_by_id"] = "Granted by ID is required"
}
if len(membership.EncryptedCollectionKey) == 0 {
e["encrypted_collection_key"] = "Encrypted collection key is required"
}
if membership.PermissionLevel == "" {
// Default permission level will be set in the repository
} else if membership.PermissionLevel != dom_collection.CollectionPermissionReadOnly &&
membership.PermissionLevel != dom_collection.CollectionPermissionReadWrite &&
membership.PermissionLevel != dom_collection.CollectionPermissionAdmin {
e["permission_level"] = "Invalid permission level"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating add collection member",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Add member to collection.
//
return uc.repo.AddMember(ctx, collectionID, membership)
}

View file

@ -0,0 +1,82 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/add_member_to_hierarchy.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type AddMemberToHierarchyUseCase interface {
Execute(ctx context.Context, rootID gocql.UUID, membership *dom_collection.CollectionMembership) error
}
type addMemberToHierarchyUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewAddMemberToHierarchyUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AddMemberToHierarchyUseCase {
logger = logger.Named("AddMemberToHierarchyUseCase")
return &addMemberToHierarchyUseCaseImpl{config, logger, repo}
}
func (uc *addMemberToHierarchyUseCaseImpl) Execute(ctx context.Context, rootID gocql.UUID, membership *dom_collection.CollectionMembership) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if rootID.String() == "" {
e["root_id"] = "Root collection ID is required"
}
if membership == nil {
e["membership"] = "Membership details are required"
} else {
// Generate member ID if not provided
if membership.ID.String() == "" || membership.ID.String() == "00000000-0000-0000-0000-000000000000" {
membership.ID = gocql.TimeUUID()
}
if membership.RecipientID.String() == "" {
e["recipient_id"] = "Recipient ID is required"
}
if membership.RecipientEmail == "" {
e["recipient_email"] = "Recipient email is required"
}
if membership.GrantedByID.String() == "" {
e["granted_by_id"] = "Granted by ID is required"
}
if len(membership.EncryptedCollectionKey) == 0 {
e["encrypted_collection_key"] = "Encrypted collection key is required"
}
if membership.PermissionLevel == "" {
// Default permission level will be set in the repository
} else if membership.PermissionLevel != dom_collection.CollectionPermissionReadOnly &&
membership.PermissionLevel != dom_collection.CollectionPermissionReadWrite &&
membership.PermissionLevel != dom_collection.CollectionPermissionAdmin {
e["permission_level"] = "Invalid permission level"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating add member to hierarchy",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Add member to collection hierarchy.
//
return uc.repo.AddMemberToHierarchy(ctx, rootID, membership)
}

View file

@ -0,0 +1,50 @@
// monorepo/cloud/backend/internal/usecase/collection/anonymize_old_ips.go
package collection
import (
"context"
"time"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
)
type AnonymizeOldIPsUseCase interface {
Execute(ctx context.Context, cutoffDate time.Time) (int, error)
}
type anonymizeOldIPsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewAnonymizeOldIPsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AnonymizeOldIPsUseCase {
logger = logger.Named("CollectionAnonymizeOldIPsUseCase")
return &anonymizeOldIPsUseCaseImpl{config, logger, repo}
}
func (uc *anonymizeOldIPsUseCaseImpl) Execute(ctx context.Context, cutoffDate time.Time) (int, error) {
uc.logger.Debug("Anonymizing old IPs in collection 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 collection tables",
zap.Error(err),
zap.Time("cutoff_date", cutoffDate))
return 0, err
}
uc.logger.Info("Successfully anonymized old IPs in collection tables",
zap.Int("count", count),
zap.Time("cutoff_date", cutoffDate))
return count, nil
}

View file

@ -0,0 +1,97 @@
// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/collection/anonymize_user_references.go
package collection
import (
"context"
"fmt"
"github.com/gocql/gocql"
"go.uber.org/zap"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
)
// 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_collection.CollectionRepository
}
// NewAnonymizeUserReferencesUseCase creates a new use case for anonymizing user references in collections
func NewAnonymizeUserReferencesUseCase(
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) 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 collection metadata",
zap.String("user_id", userID.String()))
// Get all collections owned by this user
collections, err := uc.repo.GetAllByUserID(ctx, userID)
if err != nil {
uc.logger.Error("Failed to get collections by owner",
zap.String("user_id", userID.String()),
zap.Error(err))
return 0, fmt.Errorf("failed to get collections by owner: %w", err)
}
updatedCount := 0
// Update each collection to replace user references with deleted user UUID
for _, collection := range collections {
needsUpdate := false
// Check if this collection has references to the deleted user
if collection.CreatedByUserID == userID {
collection.CreatedByUserID = DeletedUserUUID
needsUpdate = true
}
if collection.ModifiedByUserID == userID {
collection.ModifiedByUserID = DeletedUserUUID
needsUpdate = true
}
// Also anonymize GrantedByID in collection memberships
for i := range collection.Members {
if collection.Members[i].GrantedByID == userID {
collection.Members[i].GrantedByID = DeletedUserUUID
needsUpdate = true
}
}
if needsUpdate {
// Update the collection with anonymized references
if err := uc.repo.Update(ctx, collection); err != nil {
uc.logger.Error("Failed to anonymize user references in collection",
zap.String("collection_id", collection.ID.String()),
zap.String("user_id", userID.String()),
zap.Error(err))
// Continue with other collections even if one fails
continue
}
updatedCount++
}
}
uc.logger.Info("✅ Anonymized user references in collection metadata",
zap.String("user_id", userID.String()),
zap.Int("collections_updated", updatedCount))
return updatedCount, nil
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/collection/archive.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type ArchiveCollectionUseCase interface {
Execute(ctx context.Context, id gocql.UUID) error
}
type archiveCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewArchiveCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) ArchiveCollectionUseCase {
logger = logger.Named("ArchiveCollectionUseCase")
return &archiveCollectionUseCaseImpl{config, logger, repo}
}
func (uc *archiveCollectionUseCaseImpl) Execute(ctx context.Context, id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection archival",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Archive collection using repository method.
//
return uc.repo.Archive(ctx, id)
}

View file

@ -0,0 +1,65 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/check_access.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type CheckCollectionAccessUseCase interface {
Execute(ctx context.Context, collectionID, userID gocql.UUID, requiredPermission string) (bool, error)
}
type checkCollectionAccessUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewCheckCollectionAccessUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CheckCollectionAccessUseCase {
logger = logger.Named("CheckCollectionAccessUseCase")
return &checkCollectionAccessUseCaseImpl{config, logger, repo}
}
func (uc *checkCollectionAccessUseCaseImpl) Execute(ctx context.Context, collectionID, userID gocql.UUID, requiredPermission string) (bool, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if userID.String() == "" {
e["user_id"] = "User ID is required"
}
if requiredPermission == "" {
// Default to read-only if not specified
requiredPermission = dom_collection.CollectionPermissionReadOnly
} else if requiredPermission != dom_collection.CollectionPermissionReadOnly &&
requiredPermission != dom_collection.CollectionPermissionReadWrite &&
requiredPermission != dom_collection.CollectionPermissionAdmin {
e["required_permission"] = "Invalid permission level"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating check collection access",
zap.Any("error", e))
return false, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Check access.
//
return uc.repo.CheckAccess(ctx, collectionID, userID, requiredPermission)
}

View file

@ -0,0 +1,198 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/collection/count_collections.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// CountCollectionsResponse contains the collection counts for a user
type CountCollectionsResponse struct {
OwnedCollections int `json:"owned_collections"`
SharedCollections int `json:"shared_collections"`
TotalCollections int `json:"total_collections"`
}
// CountFoldersResponse contains the folder counts for a user (folders only, not albums)
type CountFoldersResponse struct {
OwnedFolders int `json:"owned_folders"`
SharedFolders int `json:"shared_folders"`
TotalFolders int `json:"total_folders"`
}
type CountUserCollectionsUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) (*CountCollectionsResponse, error)
}
// NEW: Use case specifically for counting folders only
type CountUserFoldersUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) (*CountFoldersResponse, error)
}
type countUserCollectionsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
type countUserFoldersUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewCountUserCollectionsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CountUserCollectionsUseCase {
logger = logger.Named("CountUserCollectionsUseCase")
return &countUserCollectionsUseCaseImpl{config, logger, repo}
}
func NewCountUserFoldersUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CountUserFoldersUseCase {
logger = logger.Named("CountUserFoldersUseCase")
return &countUserFoldersUseCaseImpl{config, logger, repo}
}
func (uc *countUserCollectionsUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) (*CountCollectionsResponse, 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 collections",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Count collections.
//
ownedCollections, err := uc.repo.CountOwnedCollections(ctx, userID)
if err != nil {
uc.logger.Error("Failed to count owned collections",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
sharedCollections, err := uc.repo.CountSharedCollections(ctx, userID)
if err != nil {
uc.logger.Error("Failed to count shared collections",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
response := &CountCollectionsResponse{
OwnedCollections: ownedCollections,
SharedCollections: sharedCollections,
TotalCollections: ownedCollections + sharedCollections,
}
uc.logger.Debug("Successfully counted user collections",
zap.String("user_id", userID.String()),
zap.Int("owned_collections", ownedCollections),
zap.Int("shared_collections", sharedCollections),
zap.Int("total_collections", response.TotalCollections))
return response, nil
}
func (uc *countUserFoldersUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) (*CountFoldersResponse, 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 folders",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: DEBUG - Check what's actually in the database
//
// ADD DEBUG LOGGING - Cast to concrete type to access debug method
if debugRepo, ok := uc.repo.(interface {
DebugCollectionRecords(context.Context, gocql.UUID) error
}); ok {
if debugErr := debugRepo.DebugCollectionRecords(ctx, userID); debugErr != nil {
uc.logger.Warn("Failed to debug collection records", zap.Error(debugErr))
}
}
//
// STEP 3: Count folders with separate owned/shared counts AND total unique count
//
ownedFolders, err := uc.repo.CountOwnedFolders(ctx, userID)
if err != nil {
uc.logger.Error("Failed to count owned folders",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
sharedFolders, err := uc.repo.CountSharedFolders(ctx, userID)
if err != nil {
uc.logger.Error("Failed to count shared folders",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
// NEW: Get the deduplicated total count
var totalUniqueFolders int
if uniqueRepo, ok := uc.repo.(interface {
CountTotalUniqueFolders(context.Context, gocql.UUID) (int, error)
}); ok {
totalUniqueFolders, err = uniqueRepo.CountTotalUniqueFolders(ctx, userID)
if err != nil {
uc.logger.Error("Failed to count unique total folders",
zap.String("user_id", userID.String()),
zap.Error(err))
return nil, err
}
} else {
// Fallback to simple addition if the method is not available
uc.logger.Warn("CountTotalUniqueFolders method not available, using simple addition")
totalUniqueFolders = ownedFolders + sharedFolders
}
response := &CountFoldersResponse{
OwnedFolders: ownedFolders,
SharedFolders: sharedFolders,
TotalFolders: totalUniqueFolders, // Use deduplicated count
}
uc.logger.Info("Successfully counted user folders with deduplication",
zap.String("user_id", userID.String()),
zap.Int("owned_folders", ownedFolders),
zap.Int("shared_folders", sharedFolders),
zap.Int("total_unique_folders", totalUniqueFolders),
zap.Int("would_be_simple_sum", ownedFolders+sharedFolders))
return response, nil
}

View file

@ -0,0 +1,78 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/create.go
package collection
import (
"context"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type CreateCollectionUseCase interface {
Execute(ctx context.Context, collection *dom_collection.Collection) error
}
type createCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewCreateCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CreateCollectionUseCase {
logger = logger.Named("CreateCollectionUseCase")
return &createCollectionUseCaseImpl{config, logger, repo}
}
func (uc *createCollectionUseCaseImpl) Execute(ctx context.Context, collection *dom_collection.Collection) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collection == nil {
e["collection"] = "Collection is required"
} else {
if collection.OwnerID.String() == "" {
e["owner_id"] = "Owner ID is required"
}
if collection.EncryptedName == "" {
e["encrypted_name"] = "Collection name is required"
}
if collection.CollectionType == "" {
e["collection_type"] = "Collection type is required"
} else if collection.CollectionType != dom_collection.CollectionTypeFolder && collection.CollectionType != dom_collection.CollectionTypeAlbum {
e["collection_type"] = "Collection type must be either 'folder' or 'album'"
}
if collection.EncryptedCollectionKey.Ciphertext == nil || len(collection.EncryptedCollectionKey.Ciphertext) == 0 {
e["encrypted_collection_key"] = "Encrypted collection key is required"
}
if collection.State == "" {
e["state"] = "File state is required"
} else if collection.State != dom_collection.CollectionStateActive &&
collection.State != dom_collection.CollectionStateDeleted &&
collection.State != dom_collection.CollectionStateArchived {
e["state"] = "Invalid collection state"
}
if err := dom_collection.IsValidStateTransition(dom_collection.CollectionStateActive, collection.State); err != nil {
e["state"] = err.Error()
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection creation",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Insert into database.
//
return uc.repo.Create(ctx, collection)
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/find_by_parent.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type FindCollectionsByParentUseCase interface {
Execute(ctx context.Context, parentID gocql.UUID) ([]*dom_collection.Collection, error)
}
type findCollectionsByParentUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewFindCollectionsByParentUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) FindCollectionsByParentUseCase {
logger = logger.Named("FindCollectionsByParentUseCase")
return &findCollectionsByParentUseCaseImpl{config, logger, repo}
}
func (uc *findCollectionsByParentUseCaseImpl) Execute(ctx context.Context, parentID gocql.UUID) ([]*dom_collection.Collection, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if parentID.String() == "" {
e["parent_id"] = "Parent ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating find collections by parent",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Find collections by parent.
//
return uc.repo.FindByParent(ctx, parentID)
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/find_descendants.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type FindDescendantsUseCase interface {
Execute(ctx context.Context, collectionID gocql.UUID) ([]*dom_collection.Collection, error)
}
type findDescendantsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewFindDescendantsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) FindDescendantsUseCase {
logger = logger.Named("FindDescendantsUseCase")
return &findDescendantsUseCaseImpl{config, logger, repo}
}
func (uc *findDescendantsUseCaseImpl) Execute(ctx context.Context, collectionID gocql.UUID) ([]*dom_collection.Collection, 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 find descendants",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Find descendants.
//
return uc.repo.FindDescendants(ctx, collectionID)
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/find_root_collections.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type FindRootCollectionsUseCase interface {
Execute(ctx context.Context, ownerID gocql.UUID) ([]*dom_collection.Collection, error)
}
type findRootCollectionsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewFindRootCollectionsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) FindRootCollectionsUseCase {
logger = logger.Named("FindRootCollectionsUseCase")
return &findRootCollectionsUseCaseImpl{config, logger, repo}
}
func (uc *findRootCollectionsUseCaseImpl) Execute(ctx context.Context, ownerID gocql.UUID) ([]*dom_collection.Collection, 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 find root collections",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Find root collections.
//
return uc.repo.FindRootCollections(ctx, ownerID)
}

View file

@ -0,0 +1,65 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/get.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetCollectionUseCase interface {
Execute(ctx context.Context, id gocql.UUID) (*dom_collection.Collection, error)
}
type getCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewGetCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) GetCollectionUseCase {
logger = logger.Named("GetCollectionUseCase")
return &getCollectionUseCaseImpl{config, logger, repo}
}
func (uc *getCollectionUseCaseImpl) Execute(ctx context.Context, id gocql.UUID) (*dom_collection.Collection, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection retrieval",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
collection, err := uc.repo.Get(ctx, id)
if err != nil {
return nil, err
}
if collection == nil {
uc.logger.Debug("Collection not found",
zap.Any("id", id))
return nil, httperror.NewForNotFoundWithSingleField("message", "Collection not found")
}
return collection, nil
}

View file

@ -0,0 +1,70 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/get_filtered.go
package collection
import (
"context"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type GetFilteredCollectionsUseCase interface {
Execute(ctx context.Context, options dom_collection.CollectionFilterOptions) (*dom_collection.CollectionFilterResult, error)
}
type getFilteredCollectionsUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewGetFilteredCollectionsUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) GetFilteredCollectionsUseCase {
logger = logger.Named("GetFilteredCollectionsUseCase")
return &getFilteredCollectionsUseCaseImpl{config, logger, repo}
}
func (uc *getFilteredCollectionsUseCaseImpl) Execute(ctx context.Context, options dom_collection.CollectionFilterOptions) (*dom_collection.CollectionFilterResult, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if options.UserID.String() == "" {
e["user_id"] = "User ID is required"
}
if !options.IsValid() {
e["filter_options"] = "At least one filter option (include_owned or include_shared) must be enabled"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get filtered collections",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get filtered collections from repository.
//
result, err := uc.repo.GetCollectionsWithFilter(ctx, options)
if err != nil {
uc.logger.Error("Failed to get filtered collections from repository",
zap.Any("error", err),
zap.Any("options", options))
return nil, err
}
uc.logger.Debug("Successfully retrieved filtered collections",
zap.Int("owned_count", len(result.OwnedCollections)),
zap.Int("shared_count", len(result.SharedCollections)),
zap.Int("total_count", result.TotalCount),
zap.Any("user_id", options.UserID))
return result, nil
}

View file

@ -0,0 +1,69 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/get_sync_data.go
package collection
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"
)
type GetCollectionSyncDataUseCase interface {
Execute(ctx context.Context, userID gocql.UUID, cursor *dom_collection.CollectionSyncCursor, limit int64, accessType string) (*dom_collection.CollectionSyncResponse, error)
}
type getCollectionSyncDataUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewGetCollectionSyncDataUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) GetCollectionSyncDataUseCase {
logger = logger.Named("GetCollectionSyncDataUseCase")
return &getCollectionSyncDataUseCaseImpl{config, logger, repo}
}
func (uc *getCollectionSyncDataUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID, cursor *dom_collection.CollectionSyncCursor, limit int64, accessType string) (*dom_collection.CollectionSyncResponse, error) {
//
// STEP 1: Validation.
//
// (Skip)
//
// STEP 2: Get filtered collections from repository.
//
if accessType != dom_collection.CollectionAccessTypeMember && accessType != dom_collection.CollectionAccessTypeOwner {
result, err := uc.repo.GetCollectionSyncData(ctx, userID, cursor, limit)
if err != nil {
uc.logger.Error("Failed to get filtered collections from repository",
zap.Any("error", err),
zap.Any("userID", userID),
zap.Any("cursor", cursor),
zap.Int64("limit", limit))
return nil, err
}
return result, nil
}
result, err := uc.repo.GetCollectionSyncDataByAccessType(ctx, userID, cursor, limit, accessType)
if err != nil {
uc.logger.Error("Failed to get filtered collections from repository",
zap.Any("error", err),
zap.Any("userID", userID),
zap.Any("cursor", cursor),
zap.Int64("limit", limit),
zap.String("access_type", accessType))
return nil, err
}
return result, nil
}

View file

@ -0,0 +1,70 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/harddelete.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// HardDeleteCollectionUseCase permanently deletes a collection
// Used for GDPR right-to-be-forgotten implementation
type HardDeleteCollectionUseCase interface {
Execute(ctx context.Context, id gocql.UUID) error
}
type hardDeleteCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewHardDeleteCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) HardDeleteCollectionUseCase {
logger = logger.Named("HardDeleteCollectionUseCase")
return &hardDeleteCollectionUseCaseImpl{config, logger, repo}
}
func (uc *hardDeleteCollectionUseCaseImpl) Execute(ctx context.Context, id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection hard deletion",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Hard delete from database (no tombstone).
//
uc.logger.Info("Hard deleting collection (GDPR mode)",
zap.String("collection_id", id.String()))
err := uc.repo.HardDelete(ctx, id)
if err != nil {
uc.logger.Error("Failed to hard delete collection",
zap.String("collection_id", id.String()),
zap.Error(err))
return err
}
uc.logger.Info("✅ Collection hard deleted successfully",
zap.String("collection_id", id.String()))
return nil
}

View file

@ -0,0 +1,25 @@
package collection
import (
"testing"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
)
// NOTE: Unit tests for HardDeleteCollectionUseCase 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 TestHardDeleteCollectionUseCase_Constructor(t *testing.T) {
// Test that constructor creates use case successfully
cfg := &config.Configuration{}
logger := zap.NewNop()
useCase := NewHardDeleteCollectionUseCase(cfg, logger, nil)
if useCase == nil {
t.Error("Expected use case to be created, got nil")
}
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/list_by_user.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type ListCollectionsByUserUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) ([]*dom_collection.Collection, error)
}
type listCollectionsByUserUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewListCollectionsByUserUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) ListCollectionsByUserUseCase {
logger = logger.Named("ListCollectionsByUserUseCase")
return &listCollectionsByUserUseCaseImpl{config, logger, repo}
}
func (uc *listCollectionsByUserUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) ([]*dom_collection.Collection, 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 list collections by user",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
return uc.repo.GetAllByUserID(ctx, userID)
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/list_shared_with_user.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type ListCollectionsSharedWithUserUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) ([]*dom_collection.Collection, error)
}
type listCollectionsSharedWithUserUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewListCollectionsSharedWithUserUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) ListCollectionsSharedWithUserUseCase {
logger = logger.Named("ListCollectionsSharedWithUserUseCase")
return &listCollectionsSharedWithUserUseCaseImpl{config, logger, repo}
}
func (uc *listCollectionsSharedWithUserUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) ([]*dom_collection.Collection, 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 list shared collections",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get from database.
//
return uc.repo.GetCollectionsSharedWithUser(ctx, userID)
}

View file

@ -0,0 +1,77 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/move_collection.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// MoveCollectionRequest contains data needed to move a collection
type MoveCollectionRequest struct {
CollectionID gocql.UUID `json:"collection_id"`
NewParentID gocql.UUID `json:"new_parent_id"`
UpdatedAncestors []gocql.UUID `json:"updated_ancestors"`
UpdatedPathSegments []string `json:"updated_path_segments"`
}
type MoveCollectionUseCase interface {
Execute(ctx context.Context, request MoveCollectionRequest) error
}
type moveCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewMoveCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) MoveCollectionUseCase {
logger = logger.Named("MoveCollectionUseCase")
return &moveCollectionUseCaseImpl{config, logger, repo}
}
func (uc *moveCollectionUseCaseImpl) Execute(ctx context.Context, request MoveCollectionRequest) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if request.CollectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if request.NewParentID.String() == "" {
e["new_parent_id"] = "New parent ID is required"
}
if len(request.UpdatedAncestors) == 0 {
e["updated_ancestors"] = "Updated ancestors are required"
}
if len(request.UpdatedPathSegments) == 0 {
e["updated_path_segments"] = "Updated path segments are required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating move collection",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Move collection.
//
return uc.repo.MoveCollection(
ctx,
request.CollectionID,
request.NewParentID,
request.UpdatedAncestors,
request.UpdatedPathSegments,
)
}

View file

@ -0,0 +1,216 @@
package collection
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"
)
// Wire providers for collection use cases
func ProvideCreateCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CreateCollectionUseCase {
return NewCreateCollectionUseCase(cfg, logger, repo)
}
func ProvideUpdateCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) UpdateCollectionUseCase {
return NewUpdateCollectionUseCase(cfg, logger, repo)
}
func ProvideGetCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) GetCollectionUseCase {
return NewGetCollectionUseCase(cfg, logger, repo)
}
func ProvideSoftDeleteCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) SoftDeleteCollectionUseCase {
return NewSoftDeleteCollectionUseCase(cfg, logger, repo)
}
func ProvideArchiveCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) ArchiveCollectionUseCase {
return NewArchiveCollectionUseCase(cfg, logger, repo)
}
func ProvideRestoreCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RestoreCollectionUseCase {
return NewRestoreCollectionUseCase(cfg, logger, repo)
}
func ProvideListCollectionsByUserUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) ListCollectionsByUserUseCase {
return NewListCollectionsByUserUseCase(cfg, logger, repo)
}
func ProvideListCollectionsSharedWithUserUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) ListCollectionsSharedWithUserUseCase {
return NewListCollectionsSharedWithUserUseCase(cfg, logger, repo)
}
func ProvideFindRootCollectionsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) FindRootCollectionsUseCase {
return NewFindRootCollectionsUseCase(cfg, logger, repo)
}
func ProvideFindCollectionsByParentUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) FindCollectionsByParentUseCase {
return NewFindCollectionsByParentUseCase(cfg, logger, repo)
}
func ProvideGetCollectionSyncDataUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) GetCollectionSyncDataUseCase {
return NewGetCollectionSyncDataUseCase(cfg, logger, repo)
}
func ProvideCheckCollectionAccessUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CheckCollectionAccessUseCase {
return NewCheckCollectionAccessUseCase(cfg, logger, repo)
}
func ProvideCountUserCollectionsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CountUserCollectionsUseCase {
return NewCountUserCollectionsUseCase(cfg, logger, repo)
}
func ProvideCountUserFoldersUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) CountUserFoldersUseCase {
return NewCountUserFoldersUseCase(cfg, logger, repo)
}
func ProvideUpdateMemberPermissionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) UpdateMemberPermissionUseCase {
return NewUpdateMemberPermissionUseCase(cfg, logger, repo)
}
func ProvideMoveCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) MoveCollectionUseCase {
return NewMoveCollectionUseCase(cfg, logger, repo)
}
func ProvideGetFilteredCollectionsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) GetFilteredCollectionsUseCase {
return NewGetFilteredCollectionsUseCase(cfg, logger, repo)
}
func ProvideAddMemberToHierarchyUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AddMemberToHierarchyUseCase {
return NewAddMemberToHierarchyUseCase(cfg, logger, repo)
}
func ProvideRemoveMemberFromHierarchyUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RemoveMemberFromHierarchyUseCase {
return NewRemoveMemberFromHierarchyUseCase(cfg, logger, repo)
}
func ProvideFindDescendantsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) FindDescendantsUseCase {
return NewFindDescendantsUseCase(cfg, logger, repo)
}
func ProvideAddCollectionMemberUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AddCollectionMemberUseCase {
return NewAddCollectionMemberUseCase(cfg, logger, repo)
}
func ProvideRemoveCollectionMemberUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RemoveCollectionMemberUseCase {
return NewRemoveCollectionMemberUseCase(cfg, logger, repo)
}
func ProvideAnonymizeOldIPsUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AnonymizeOldIPsUseCase {
return NewAnonymizeOldIPsUseCase(cfg, logger, repo)
}
func ProvideRemoveUserFromAllCollectionsUseCase(
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RemoveUserFromAllCollectionsUseCase {
return NewRemoveUserFromAllCollectionsUseCase(logger, repo)
}
func ProvideHardDeleteCollectionUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) HardDeleteCollectionUseCase {
return NewHardDeleteCollectionUseCase(cfg, logger, repo)
}
func ProvideAnonymizeUserReferencesUseCase(
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) AnonymizeUserReferencesUseCase {
return NewAnonymizeUserReferencesUseCase(logger, repo)
}

View file

@ -0,0 +1,57 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/remove_member.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type RemoveCollectionMemberUseCase interface {
Execute(ctx context.Context, collectionID, recipientID gocql.UUID) error
}
type removeCollectionMemberUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewRemoveCollectionMemberUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RemoveCollectionMemberUseCase {
logger = logger.Named("RemoveCollectionMemberUseCase")
return &removeCollectionMemberUseCaseImpl{config, logger, repo}
}
func (uc *removeCollectionMemberUseCaseImpl) Execute(ctx context.Context, collectionID, recipientID gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if recipientID.String() == "" {
e["recipient_id"] = "Recipient ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating remove collection member",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Remove member from collection.
//
return uc.repo.RemoveMember(ctx, collectionID, recipientID)
}

View file

@ -0,0 +1,57 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/remove_member_from_hierarchy.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type RemoveMemberFromHierarchyUseCase interface {
Execute(ctx context.Context, rootID, recipientID gocql.UUID) error
}
type removeMemberFromHierarchyUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewRemoveMemberFromHierarchyUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RemoveMemberFromHierarchyUseCase {
logger = logger.Named("RemoveMemberFromHierarchyUseCase")
return &removeMemberFromHierarchyUseCaseImpl{config, logger, repo}
}
func (uc *removeMemberFromHierarchyUseCaseImpl) Execute(ctx context.Context, rootID, recipientID gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if rootID.String() == "" {
e["root_id"] = "Root collection ID is required"
}
if recipientID.String() == "" {
e["recipient_id"] = "Recipient ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating remove member from hierarchy",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Remove member from collection hierarchy.
//
return uc.repo.RemoveMemberFromHierarchy(ctx, rootID, recipientID)
}

View file

@ -0,0 +1,53 @@
package collection
import (
"context"
"github.com/gocql/gocql"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
)
// RemoveUserFromAllCollectionsUseCase removes a user from all collections they are a member of
// Used for GDPR right-to-be-forgotten implementation
type RemoveUserFromAllCollectionsUseCase interface {
Execute(ctx context.Context, userID gocql.UUID, userEmail string) (int, error)
}
type removeUserFromAllCollectionsUseCaseImpl struct {
logger *zap.Logger
repo collection.CollectionRepository
}
// NewRemoveUserFromAllCollectionsUseCase creates a new use case for removing user from all collections
func NewRemoveUserFromAllCollectionsUseCase(
logger *zap.Logger,
repo collection.CollectionRepository,
) RemoveUserFromAllCollectionsUseCase {
return &removeUserFromAllCollectionsUseCaseImpl{
logger: logger.Named("RemoveUserFromAllCollectionsUseCase"),
repo: repo,
}
}
func (uc *removeUserFromAllCollectionsUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID, userEmail string) (int, error) {
uc.logger.Info("Removing user from all shared collections",
zap.String("user_id", userID.String()))
modifiedCollections, err := uc.repo.RemoveUserFromAllCollections(ctx, userID, userEmail)
if err != nil {
uc.logger.Error("Failed to remove user from all collections",
zap.String("user_id", userID.String()),
zap.Error(err))
return 0, err
}
count := len(modifiedCollections)
uc.logger.Info("✅ Successfully removed user from all shared collections",
zap.String("user_id", userID.String()),
zap.Int("collections_modified", count))
return count, nil
}

View file

@ -0,0 +1,22 @@
package collection
import (
"testing"
"go.uber.org/zap"
)
// NOTE: Unit tests for RemoveUserFromAllCollectionsUseCase 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 TestRemoveUserFromAllCollectionsUseCase_Constructor(t *testing.T) {
// Test that constructor creates use case successfully
logger := zap.NewNop()
useCase := NewRemoveUserFromAllCollectionsUseCase(logger, nil)
if useCase == nil {
t.Error("Expected use case to be created, got nil")
}
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/collection/restore.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type RestoreCollectionUseCase interface {
Execute(ctx context.Context, id gocql.UUID) error
}
type restoreCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewRestoreCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) RestoreCollectionUseCase {
logger = logger.Named("RestoreCollectionUseCase")
return &restoreCollectionUseCaseImpl{config, logger, repo}
}
func (uc *restoreCollectionUseCaseImpl) Execute(ctx context.Context, id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection restoration",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Restore collection using repository method.
//
return uc.repo.Restore(ctx, id)
}

View file

@ -0,0 +1,54 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/softdelete.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type SoftDeleteCollectionUseCase interface {
Execute(ctx context.Context, id gocql.UUID) error
}
type softDeleteCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewSoftDeleteCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) SoftDeleteCollectionUseCase {
logger = logger.Named("SoftDeleteCollectionUseCase")
return &softDeleteCollectionUseCaseImpl{config, logger, repo}
}
func (uc *softDeleteCollectionUseCaseImpl) Execute(ctx context.Context, id gocql.UUID) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if id.String() == "" {
e["id"] = "Collection ID is required"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection deletion",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Delete from database.
//
return uc.repo.SoftDelete(ctx, id)
}

View file

@ -0,0 +1,57 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/update.go
package collection
import (
"context"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type UpdateCollectionUseCase interface {
Execute(ctx context.Context, collection *dom_collection.Collection) error
}
type updateCollectionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewUpdateCollectionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) UpdateCollectionUseCase {
logger = logger.Named("UpdateCollectionUseCase")
return &updateCollectionUseCaseImpl{config, logger, repo}
}
func (uc *updateCollectionUseCaseImpl) Execute(ctx context.Context, collection *dom_collection.Collection) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collection == nil {
e["collection"] = "Collection is required"
} else {
if collection.ID.String() == "" {
e["id"] = "Collection ID is required"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating collection update",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Update in database.
//
return uc.repo.Update(ctx, collection)
}

View file

@ -0,0 +1,64 @@
// monorepo/cloud/backend/internal/maplefile/usecase/collection/update_member_permission.go
package collection
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"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
type UpdateMemberPermissionUseCase interface {
Execute(ctx context.Context, collectionID, recipientID gocql.UUID, newPermission string) error
}
type updateMemberPermissionUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo dom_collection.CollectionRepository
}
func NewUpdateMemberPermissionUseCase(
config *config.Configuration,
logger *zap.Logger,
repo dom_collection.CollectionRepository,
) UpdateMemberPermissionUseCase {
logger = logger.Named("UpdateMemberPermissionUseCase")
return &updateMemberPermissionUseCaseImpl{config, logger, repo}
}
func (uc *updateMemberPermissionUseCaseImpl) Execute(ctx context.Context, collectionID, recipientID gocql.UUID, newPermission string) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if collectionID.String() == "" {
e["collection_id"] = "Collection ID is required"
}
if recipientID.String() == "" {
e["recipient_id"] = "Recipient ID is required"
}
if newPermission == "" {
// Default permission level will be set in the repository
} else if newPermission != dom_collection.CollectionPermissionReadOnly &&
newPermission != dom_collection.CollectionPermissionReadWrite &&
newPermission != dom_collection.CollectionPermissionAdmin {
e["permission_level"] = "Invalid permission level"
}
if len(e) != 0 {
uc.logger.Warn("Failed validating update member permission",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Update member permission.
//
return uc.repo.UpdateMemberPermission(ctx, collectionID, recipientID, newPermission)
}