monorepo/cloud/maplefile-backend/internal/service/collection/utils.go

158 lines
7 KiB
Go

// monorepo/cloud/backend/internal/maplefile/service/collection/utils.go
package collection
import (
"time"
"github.com/gocql/gocql"
"go.uber.org/zap"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)
// Helper function to get owner email from members list
// The owner is always a member with their email, so we can look them up
func getOwnerEmailFromMembers(collection *dom_collection.Collection) string {
if collection == nil {
return ""
}
for _, member := range collection.Members {
if member.RecipientID == collection.OwnerID {
return member.RecipientEmail
}
}
return ""
}
// Helper function to map a CollectionMembershipDTO to a CollectionMembership domain model
// This assumes a direct field-by-field copy is intended by the DTO structure.
func mapMembershipDTOToDomain(dto *CollectionMembershipDTO) dom_collection.CollectionMembership {
return dom_collection.CollectionMembership{
ID: dto.ID, // Copy DTO ID
CollectionID: dto.CollectionID, // Copy DTO CollectionID
RecipientID: dto.RecipientID, // Copy DTO RecipientID
RecipientEmail: dto.RecipientEmail, // Copy DTO RecipientEmail
GrantedByID: dto.GrantedByID, // Copy DTO GrantedByID
EncryptedCollectionKey: dto.EncryptedCollectionKey, // Copy DTO EncryptedCollectionKey
PermissionLevel: dto.PermissionLevel, // Copy DTO PermissionLevel
CreatedAt: dto.CreatedAt, // Copy DTO CreatedAt
IsInherited: dto.IsInherited, // Copy DTO IsInherited
InheritedFromID: dto.InheritedFromID, // Copy DTO InheritedFromID
// Note: ModifiedAt/By, Version are not in Membership DTO/Domain
}
}
// Helper function to map a CreateCollectionRequestDTO to a Collection domain model.
// This function recursively maps all fields, including nested members and children,
// copying values directly from the DTO. Server-side overrides for fields like
// ID, OwnerID, timestamps, and version are applied *after* this mapping in the Execute method.
// userID and now are passed for potential use in recursive calls if needed for consistency,
// though the primary goal here is to copy DTO values.
func mapCollectionDTOToDomain(dto *CreateCollectionRequestDTO, userID gocql.UUID, now time.Time) *dom_collection.Collection {
if dto == nil {
return nil
}
collection := &dom_collection.Collection{
// Copy all scalar/pointer fields directly from the DTO as requested by the prompt.
// Fields like ID, OwnerID, timestamps, and version from the DTO
// represent the client's proposed state and will be potentially
// overridden by server-managed values later in the Execute method.
ID: dto.ID,
OwnerID: dto.OwnerID,
EncryptedName: dto.EncryptedName,
EncryptedCustomIcon: dto.EncryptedCustomIcon,
CollectionType: dto.CollectionType,
EncryptedCollectionKey: dto.EncryptedCollectionKey,
ParentID: dto.ParentID,
AncestorIDs: dto.AncestorIDs,
CreatedAt: dto.CreatedAt,
CreatedByUserID: dto.CreatedByUserID,
ModifiedAt: dto.ModifiedAt,
ModifiedByUserID: dto.ModifiedByUserID,
}
// Map members slice from DTO to domain model slice
if len(dto.Members) > 0 {
collection.Members = make([]dom_collection.CollectionMembership, len(dto.Members))
for i, memberDTO := range dto.Members {
collection.Members[i] = mapMembershipDTOToDomain(memberDTO)
}
}
return collection
}
// Helper function to map a Collection domain model to a CollectionResponseDTO
// This function should ideally exclude sensitive data (like recipient-specific keys)
// that should not be part of a general response.
// fileCount is the number of active files in this collection (pass 0 if not known)
// ownerEmail is the email address of the collection owner (pass "" if not known)
func mapCollectionToDTO(collection *dom_collection.Collection, fileCount int, ownerEmail string) *CollectionResponseDTO {
if collection == nil {
return nil
}
responseDTO := &CollectionResponseDTO{
ID: collection.ID,
OwnerID: collection.OwnerID,
OwnerEmail: ownerEmail,
EncryptedName: collection.EncryptedName,
EncryptedCustomIcon: collection.EncryptedCustomIcon,
CollectionType: collection.CollectionType,
ParentID: collection.ParentID,
AncestorIDs: collection.AncestorIDs,
Tags: collection.Tags,
// Note: EncryptedCollectionKey from the domain model is the owner's key.
// Including it in the general response DTO might be acceptable if the response
// is only sent to the owner and contains *their* key. Otherwise, this field
// might need conditional inclusion or exclusion. The prompt does not require
// changing this, so we keep the original mapping which copies the owner's key.
EncryptedCollectionKey: collection.EncryptedCollectionKey,
CreatedAt: collection.CreatedAt,
ModifiedAt: collection.ModifiedAt,
FileCount: fileCount,
Version: collection.Version,
// Members slice needs mapping to MembershipResponseDTO
Members: make([]MembershipResponseDTO, len(collection.Members)),
}
// Map members
for i, member := range collection.Members {
responseDTO.Members[i] = MembershipResponseDTO{
ID: member.ID,
RecipientID: member.RecipientID,
RecipientEmail: member.RecipientEmail, // Email for display
PermissionLevel: member.PermissionLevel,
GrantedByID: member.GrantedByID,
CollectionID: member.CollectionID, // Redundant but useful
IsInherited: member.IsInherited,
InheritedFromID: member.InheritedFromID,
CreatedAt: member.CreatedAt,
// Note: EncryptedCollectionKey for this member is recipient-specific
// and should NOT be included in a general response DTO unless
// filtered for the specific recipient receiving the response.
// The MembershipResponseDTO does not have a field for this, which is correct.
EncryptedCollectionKey: member.EncryptedCollectionKey,
}
}
// Debug: Log what we're sending in the DTO
logger, _ := zap.NewDevelopment()
logger.Info("🔍 mapCollectionToDTO: Mapping collection to DTO",
zap.String("collection_id", collection.ID.String()),
zap.Int("domain_members_count", len(collection.Members)),
zap.Int("dto_members_count", len(responseDTO.Members)),
zap.Int("domain_tags_count", len(collection.Tags)),
zap.Int("dto_tags_count", len(responseDTO.Tags)))
for i, member := range responseDTO.Members {
logger.Info("🔍 mapCollectionToDTO: DTO member",
zap.Int("index", i),
zap.String("recipient_email", validation.MaskEmail(member.RecipientEmail)),
zap.String("recipient_id", member.RecipientID.String()),
zap.Int("encrypted_key_length", len(member.EncryptedCollectionKey)))
}
return responseDTO
}