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,24 @@
// monorepo/cloud/backend/internal/maplefile/domain/collection/constants.go
package collection
const (
CollectionTypeFolder = "folder"
CollectionTypeAlbum = "album"
)
const ( // Permission levels
CollectionPermissionReadOnly = "read_only"
CollectionPermissionReadWrite = "read_write"
CollectionPermissionAdmin = "admin"
)
const (
CollectionStateActive = "active"
CollectionStateDeleted = "deleted"
CollectionStateArchived = "archived"
)
const (
CollectionAccessTypeOwner = "owner"
CollectionAccessTypeMember = "member"
)

View file

@ -0,0 +1,43 @@
// monorepo/cloud/backend/internal/maplefile/domain/collection/filter.go
package collection
import "github.com/gocql/gocql"
// CollectionFilterOptions defines the filtering options for retrieving collections
type CollectionFilterOptions struct {
// IncludeOwned includes collections where the user is the owner
IncludeOwned bool `json:"include_owned"`
// IncludeShared includes collections where the user is a member (shared with them)
IncludeShared bool `json:"include_shared"`
// UserID is the user for whom we're filtering collections
UserID gocql.UUID `json:"user_id"`
}
// CollectionFilterResult represents the result of a filtered collection query
type CollectionFilterResult struct {
// OwnedCollections are collections where the user is the owner
OwnedCollections []*Collection `json:"owned_collections"`
// SharedCollections are collections shared with the user
SharedCollections []*Collection `json:"shared_collections"`
// TotalCount is the total number of collections returned
TotalCount int `json:"total_count"`
}
// GetAllCollections returns all collections (owned + shared) in a single slice
func (r *CollectionFilterResult) GetAllCollections() []*Collection {
allCollections := make([]*Collection, 0, len(r.OwnedCollections)+len(r.SharedCollections))
allCollections = append(allCollections, r.OwnedCollections...)
allCollections = append(allCollections, r.SharedCollections...)
return allCollections
}
// IsValid checks if the filter options are valid
func (options *CollectionFilterOptions) IsValid() bool {
// At least one filter option must be enabled
return options.IncludeOwned || options.IncludeShared
}
// ShouldIncludeAll returns true if both owned and shared collections should be included
func (options *CollectionFilterOptions) ShouldIncludeAll() bool {
return options.IncludeOwned && options.IncludeShared
}

View file

@ -0,0 +1,89 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/domain/collection/interface.go
package collection
import (
"context"
"time"
"github.com/gocql/gocql"
)
// CollectionRepository defines the interface for collection persistence operations
type CollectionRepository interface {
// Collection CRUD operations
Create(ctx context.Context, collection *Collection) error
Get(ctx context.Context, id gocql.UUID) (*Collection, error)
Update(ctx context.Context, collection *Collection) error
SoftDelete(ctx context.Context, id gocql.UUID) error // Now soft delete
HardDelete(ctx context.Context, id gocql.UUID) error
// State management operations
Archive(ctx context.Context, id gocql.UUID) error
Restore(ctx context.Context, id gocql.UUID) error
// Hierarchical queries (now state-aware)
FindByParent(ctx context.Context, parentID gocql.UUID) ([]*Collection, error)
FindRootCollections(ctx context.Context, ownerID gocql.UUID) ([]*Collection, error)
FindDescendants(ctx context.Context, collectionID gocql.UUID) ([]*Collection, error)
// GetFullHierarchy(ctx context.Context, rootID gocql.UUID) (*Collection, error) // DEPRECATED AND WILL BE REMOVED
// Move collection to a new parent
MoveCollection(ctx context.Context, collectionID, newParentID gocql.UUID, updatedAncestors []gocql.UUID, updatedPathSegments []string) error
// Collection ownership and access queries (now state-aware)
CheckIfExistsByID(ctx context.Context, id gocql.UUID) (bool, error)
GetAllByUserID(ctx context.Context, ownerID gocql.UUID) ([]*Collection, error)
GetCollectionsSharedWithUser(ctx context.Context, userID gocql.UUID) ([]*Collection, error)
IsCollectionOwner(ctx context.Context, collectionID, userID gocql.UUID) (bool, error)
CheckAccess(ctx context.Context, collectionID, userID gocql.UUID, requiredPermission string) (bool, error)
GetUserPermissionLevel(ctx context.Context, collectionID, userID gocql.UUID) (string, error)
// Filtered collection queries (now state-aware)
GetCollectionsWithFilter(ctx context.Context, options CollectionFilterOptions) (*CollectionFilterResult, error)
// Collection membership operations
AddMember(ctx context.Context, collectionID gocql.UUID, membership *CollectionMembership) error
RemoveMember(ctx context.Context, collectionID, recipientID gocql.UUID) error
RemoveUserFromAllCollections(ctx context.Context, userID gocql.UUID, userEmail string) ([]gocql.UUID, error)
UpdateMemberPermission(ctx context.Context, collectionID, recipientID gocql.UUID, newPermission string) error
GetCollectionMembership(ctx context.Context, collectionID, recipientID gocql.UUID) (*CollectionMembership, error)
// Hierarchical sharing
AddMemberToHierarchy(ctx context.Context, rootID gocql.UUID, membership *CollectionMembership) error
RemoveMemberFromHierarchy(ctx context.Context, rootID, recipientID gocql.UUID) error
// GetCollectionSyncData retrieves collection sync data with pagination for the specified user
GetCollectionSyncData(ctx context.Context, userID gocql.UUID, cursor *CollectionSyncCursor, limit int64) (*CollectionSyncResponse, error)
GetCollectionSyncDataByAccessType(ctx context.Context, userID gocql.UUID, cursor *CollectionSyncCursor, limit int64, accessType string) (*CollectionSyncResponse, error)
// Count operations for all collection types (folders + albums)
CountOwnedCollections(ctx context.Context, userID gocql.UUID) (int, error)
CountSharedCollections(ctx context.Context, userID gocql.UUID) (int, error)
CountOwnedFolders(ctx context.Context, userID gocql.UUID) (int, error)
CountSharedFolders(ctx context.Context, userID gocql.UUID) (int, error)
CountTotalUniqueFolders(ctx context.Context, userID gocql.UUID) (int, error)
// IP Anonymization for GDPR compliance
AnonymizeOldIPs(ctx context.Context, cutoffDate time.Time) (int, error)
AnonymizeCollectionIPsByOwner(ctx context.Context, ownerID gocql.UUID) (int, error) // For GDPR right-to-be-forgotten
// File count maintenance operations
IncrementFileCount(ctx context.Context, collectionID gocql.UUID) error
DecrementFileCount(ctx context.Context, collectionID gocql.UUID) error
// RecalculateAllFileCounts recalculates file_count for all collections
// by counting active files. Used for data migration/repair.
RecalculateAllFileCounts(ctx context.Context) (*RecalculateAllFileCountsResult, error)
// Tag-related operations
// ListByTagID retrieves all collections that have the specified tag assigned
// Used for tag update propagation (updating embedded tag data across all collections)
ListByTagID(ctx context.Context, tagID gocql.UUID) ([]*Collection, error)
}
// RecalculateAllFileCountsResult holds the results of the recalculation operation
type RecalculateAllFileCountsResult struct {
TotalCollections int
UpdatedCount int
ErrorCount int
}

View file

@ -0,0 +1,124 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/domain/collection/model.go
package collection
import (
"time"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/crypto"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/tag"
"github.com/gocql/gocql"
)
// Collection represents a folder or album.
// Can be used for both root collections and embedded subcollections
type Collection struct {
// Identifiers
// ID is the unique identifier for the collection in the cloud backend.
ID gocql.UUID `bson:"_id" json:"id"`
// OwnerID is the ID of the user who originally created and owns this collection.
// The owner has administrative privileges by default.
OwnerID gocql.UUID `bson:"owner_id" json:"owner_id"`
// Encryption and Content Details
// EncryptedName is the name of the collection, encrypted using the collection's unique key.
// Stored and transferred in encrypted form.
EncryptedName string `bson:"encrypted_name" json:"encrypted_name"`
// CollectionType indicates the nature of the collection, either "folder" or "album".
// Defined by CollectionTypeFolder and CollectionTypeAlbum constants.
CollectionType string `bson:"collection_type" json:"collection_type"` // "folder" or "album"
// EncryptedCollectionKey is the unique symmetric key used to encrypt the collection's data (like name and file metadata).
// This key is encrypted with the owner's master key for storage and transmission,
// allowing the owner's device to decrypt it using their master key.
EncryptedCollectionKey *crypto.EncryptedCollectionKey `bson:"encrypted_collection_key" json:"encrypted_collection_key"`
// EncryptedCustomIcon stores the custom icon for this collection, encrypted with the collection key.
// Empty string means use default folder/album icon.
// Contains either an emoji character (e.g., "📷") or "icon:<identifier>" for predefined icons.
EncryptedCustomIcon string `bson:"encrypted_custom_icon" json:"encrypted_custom_icon"`
// Sharing
// Collection members (users with access)
Members []CollectionMembership `bson:"members" json:"members"`
// Hierarchical structure fields
// ParentID is the ID of the parent collection if this is a subcollection.
// It is omitted (nil) for root collections. Used to reconstruct the hierarchy.
ParentID gocql.UUID `bson:"parent_id,omitempty" json:"parent_id,omitempty"` // Parent collection ID, not stored for root collections
// AncestorIDs is an array containing the IDs of all parent collections up to the root.
// This field is used for efficient querying and traversal of the collection hierarchy without joins.
AncestorIDs []gocql.UUID `bson:"ancestor_ids,omitempty" json:"ancestor_ids,omitempty"` // Array of ancestor IDs for efficient querying
// File count for performance optimization
// FileCount stores the number of active files in this collection.
// This denormalized field eliminates N+1 queries when listing collections.
FileCount int64 `bson:"file_count" json:"file_count"`
// DEPRECATED: Replaced by Tags field below
// TagIDs []gocql.UUID `bson:"tag_ids,omitempty" json:"tag_ids,omitempty"`
// Tags stores full embedded tag data (eliminates frontend API lookups)
// Stored as JSON text in database, marshaled/unmarshaled automatically
Tags []tag.EmbeddedTag `bson:"tags,omitempty" json:"tags,omitempty"`
// Ownership, timestamps and conflict resolution
// CreatedAt is the timestamp when the collection was initially created.
// Recorded on the local device and synced.
CreatedAt time.Time `bson:"created_at" json:"created_at"`
// CreatedByUserID is the ID of the user who created this file.
CreatedByUserID gocql.UUID `bson:"created_by_user_id" json:"created_by_user_id"`
// ModifiedAt is the timestamp of the last modification to the collection's metadata or content.
// Updated on the local device and synced.
ModifiedAt time.Time `bson:"modified_at" json:"modified_at"`
ModifiedByUserID gocql.UUID `bson:"modified_by_user_id" json:"modified_by_user_id"`
// The current version of the file.
Version uint64 `bson:"version" json:"version"` // Every mutation (create, update, delete, etc) is a versioned operation, keep track of the version number with this variable
// State management
State string `bson:"state" json:"state"` // active, deleted, archived
TombstoneVersion uint64 `bson:"tombstone_version" json:"tombstone_version"` // The `version` number that this collection was deleted at.
TombstoneExpiry time.Time `bson:"tombstone_expiry" json:"tombstone_expiry"`
}
// CollectionMembership represents a user's access to a collection
type CollectionMembership struct {
ID gocql.UUID `bson:"_id" json:"id"`
CollectionID gocql.UUID `bson:"collection_id" json:"collection_id"` // ID of the collection (redundant but helpful for queries)
RecipientID gocql.UUID `bson:"recipient_id" json:"recipient_id"` // User receiving access
RecipientEmail string `bson:"recipient_email" json:"recipient_email"` // Email for display purposes
GrantedByID gocql.UUID `bson:"granted_by_id" json:"granted_by_id"` // User who shared the collection
// Collection key encrypted with recipient's public key using box_seal. This matches the box_seal format which doesn't need a separate nonce.
EncryptedCollectionKey []byte `bson:"encrypted_collection_key" json:"encrypted_collection_key"`
// Access details
PermissionLevel string `bson:"permission_level" json:"permission_level"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
// Sharing origin tracking
IsInherited bool `bson:"is_inherited" json:"is_inherited"` // Tracks whether access was granted directly or inherited from a parent
InheritedFromID gocql.UUID `bson:"inherited_from_id,omitempty" json:"inherited_from_id,omitempty"` // InheritedFromID identifies which parent collection granted this access
}
// CollectionSyncCursor represents cursor-based pagination for sync operations
type CollectionSyncCursor struct {
LastModified time.Time `json:"last_modified" bson:"last_modified"`
LastID gocql.UUID `json:"last_id" bson:"last_id"`
}
// CollectionSyncItem represents minimal collection data for sync operations
type CollectionSyncItem struct {
ID gocql.UUID `json:"id" bson:"_id"`
Version uint64 `json:"version" bson:"version"`
ModifiedAt time.Time `json:"modified_at" bson:"modified_at"`
State string `json:"state" bson:"state"`
ParentID *gocql.UUID `json:"parent_id,omitempty" bson:"parent_id,omitempty"`
TombstoneVersion uint64 `bson:"tombstone_version" json:"tombstone_version"`
TombstoneExpiry time.Time `bson:"tombstone_expiry" json:"tombstone_expiry"`
EncryptedCustomIcon string `json:"encrypted_custom_icon,omitempty" bson:"encrypted_custom_icon,omitempty"`
}
// CollectionSyncResponse represents the response for collection sync data
type CollectionSyncResponse struct {
Collections []CollectionSyncItem `json:"collections"`
NextCursor *CollectionSyncCursor `json:"next_cursor,omitempty"`
HasMore bool `json:"has_more"`
}

View file

@ -0,0 +1,37 @@
// monorepo/cloud/backend/internal/maplefile/domain/collection/state_validator.go
package collection
import "errors"
// StateTransition validates collection state transitions
type StateTransition struct {
From string
To string
}
// IsValidStateTransition checks if a state transition is allowed
func IsValidStateTransition(from, to string) error {
validTransitions := map[StateTransition]bool{
// From active
{CollectionStateActive, CollectionStateDeleted}: true,
{CollectionStateActive, CollectionStateArchived}: true,
// From deleted (cannot be restored nor archived)
{CollectionStateDeleted, CollectionStateActive}: false,
{CollectionStateDeleted, CollectionStateArchived}: false,
// From archived (can only be restored to active)
{CollectionStateArchived, CollectionStateActive}: true,
// Same state transitions (no-op)
{CollectionStateActive, CollectionStateActive}: true,
{CollectionStateDeleted, CollectionStateDeleted}: true,
{CollectionStateArchived, CollectionStateArchived}: true,
}
if !validTransitions[StateTransition{from, to}] {
return errors.New("invalid state transition from " + from + " to " + to)
}
return nil
}