Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
334
cloud/maplefile-backend/internal/repo/collection/count.go
Normal file
334
cloud/maplefile-backend/internal/repo/collection/count.go
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
// monorepo/cloud/maplefile-backend/internal/maplefile/repo/collection/count.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
|
||||
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
|
||||
)
|
||||
|
||||
// CountOwnedCollections counts all collections (folders + albums) owned by the user
|
||||
func (impl *collectionRepositoryImpl) CountOwnedCollections(ctx context.Context, userID gocql.UUID) (int, error) {
|
||||
return impl.countCollectionsByUserAndType(ctx, userID, "owner", "")
|
||||
}
|
||||
|
||||
// CountSharedCollections counts all collections (folders + albums) shared with the user
|
||||
func (impl *collectionRepositoryImpl) CountSharedCollections(ctx context.Context, userID gocql.UUID) (int, error) {
|
||||
return impl.countCollectionsByUserAndType(ctx, userID, "member", "")
|
||||
}
|
||||
|
||||
// CountOwnedFolders counts only folders owned by the user
|
||||
func (impl *collectionRepositoryImpl) CountOwnedFolders(ctx context.Context, userID gocql.UUID) (int, error) {
|
||||
return impl.countCollectionsByUserAndType(ctx, userID, "owner", dom_collection.CollectionTypeFolder)
|
||||
}
|
||||
|
||||
// CountSharedFolders counts only folders shared with the user
|
||||
func (impl *collectionRepositoryImpl) CountSharedFolders(ctx context.Context, userID gocql.UUID) (int, error) {
|
||||
return impl.countCollectionsByUserAndType(ctx, userID, "member", dom_collection.CollectionTypeFolder)
|
||||
}
|
||||
|
||||
// countCollectionsByUserAndType is a helper method that efficiently counts collections
|
||||
// filterType: empty string for all types, or specific type like "folder"
|
||||
func (impl *collectionRepositoryImpl) countCollectionsByUserAndType(ctx context.Context, userID gocql.UUID, accessType, filterType string) (int, error) {
|
||||
// Use the access-type-specific table for efficient querying
|
||||
query := `SELECT collection_id FROM maplefile.collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
|
||||
WHERE user_id = ? AND access_type = ?`
|
||||
|
||||
impl.Logger.Debug("Starting collection count query",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.String("filter_type", filterType))
|
||||
|
||||
iter := impl.Session.Query(query, userID, accessType).WithContext(ctx).Iter()
|
||||
|
||||
count := 0
|
||||
totalScanned := 0
|
||||
var collectionID gocql.UUID
|
||||
var debugCollectionIDs []string
|
||||
|
||||
// Iterate through results and count based on criteria
|
||||
for iter.Scan(&collectionID) {
|
||||
totalScanned++
|
||||
debugCollectionIDs = append(debugCollectionIDs, collectionID.String())
|
||||
|
||||
impl.Logger.Debug("Processing collection for count",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.Int("total_scanned", totalScanned),
|
||||
zap.String("access_type", accessType))
|
||||
|
||||
// Get the collection to check state and type
|
||||
collection, err := impl.getBaseCollection(ctx, collectionID)
|
||||
if err != nil {
|
||||
impl.Logger.Warn("failed to get collection for counting",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if collection == nil {
|
||||
impl.Logger.Warn("collection not found for counting",
|
||||
zap.String("collection_id", collectionID.String()))
|
||||
continue
|
||||
}
|
||||
|
||||
impl.Logger.Debug("Collection details for counting",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("state", collection.State),
|
||||
zap.String("collection_type", collection.CollectionType),
|
||||
zap.String("owner_id", collection.OwnerID.String()),
|
||||
zap.String("querying_user_id", userID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.String("required_filter_type", filterType))
|
||||
|
||||
// Only count active collections
|
||||
if collection.State != dom_collection.CollectionStateActive {
|
||||
impl.Logger.Debug("Skipping collection due to non-active state",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("state", collection.State))
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by type if specified
|
||||
if filterType != "" && collection.CollectionType != filterType {
|
||||
impl.Logger.Debug("Skipping collection due to type filter",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("collection_type", collection.CollectionType),
|
||||
zap.String("required_type", filterType))
|
||||
continue
|
||||
}
|
||||
|
||||
count++
|
||||
impl.Logger.Info("Collection counted",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.String("owner_id", collection.OwnerID.String()),
|
||||
zap.String("querying_user_id", userID.String()),
|
||||
zap.Bool("is_owner", collection.OwnerID == userID),
|
||||
zap.Int("current_count", count))
|
||||
}
|
||||
|
||||
if err := iter.Close(); err != nil {
|
||||
impl.Logger.Error("failed to count collections",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.String("filter_type", filterType),
|
||||
zap.Error(err))
|
||||
return 0, fmt.Errorf("failed to count collections: %w", err)
|
||||
}
|
||||
|
||||
impl.Logger.Info("Collection count completed",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.String("filter_type", filterType),
|
||||
zap.Int("final_count", count),
|
||||
zap.Int("total_scanned", totalScanned),
|
||||
zap.Strings("scanned_collection_ids", debugCollectionIDs))
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// FIXED DEBUG: Query both access types separately to avoid ALLOW FILTERING
|
||||
func (impl *collectionRepositoryImpl) DebugCollectionRecords(ctx context.Context, userID gocql.UUID) error {
|
||||
impl.Logger.Info("=== DEBUG: Checking OWNER records ===")
|
||||
|
||||
// Check owner records
|
||||
ownerQuery := `SELECT user_id, access_type, modified_at, collection_id, permission_level, state
|
||||
FROM maplefile.collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
|
||||
WHERE user_id = ? AND access_type = ?`
|
||||
|
||||
ownerIter := impl.Session.Query(ownerQuery, userID, "owner").WithContext(ctx).Iter()
|
||||
|
||||
var (
|
||||
resultUserID gocql.UUID
|
||||
accessType string
|
||||
modifiedAt time.Time
|
||||
collectionID gocql.UUID
|
||||
permissionLevel string
|
||||
state string
|
||||
)
|
||||
|
||||
ownerCount := 0
|
||||
for ownerIter.Scan(&resultUserID, &accessType, &modifiedAt, &collectionID, &permissionLevel, &state) {
|
||||
ownerCount++
|
||||
impl.Logger.Info("DEBUG: Found OWNER record",
|
||||
zap.Int("record_number", ownerCount),
|
||||
zap.String("user_id", resultUserID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.Time("modified_at", modifiedAt),
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("permission_level", permissionLevel),
|
||||
zap.String("state", state))
|
||||
}
|
||||
ownerIter.Close()
|
||||
|
||||
impl.Logger.Info("=== DEBUG: Checking MEMBER records ===")
|
||||
|
||||
// Check member records
|
||||
memberIter := impl.Session.Query(ownerQuery, userID, "member").WithContext(ctx).Iter()
|
||||
|
||||
memberCount := 0
|
||||
for memberIter.Scan(&resultUserID, &accessType, &modifiedAt, &collectionID, &permissionLevel, &state) {
|
||||
memberCount++
|
||||
impl.Logger.Info("DEBUG: Found MEMBER record",
|
||||
zap.Int("record_number", memberCount),
|
||||
zap.String("user_id", resultUserID.String()),
|
||||
zap.String("access_type", accessType),
|
||||
zap.Time("modified_at", modifiedAt),
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("permission_level", permissionLevel),
|
||||
zap.String("state", state))
|
||||
}
|
||||
memberIter.Close()
|
||||
|
||||
impl.Logger.Info("DEBUG: Total records summary",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.Int("owner_records", ownerCount),
|
||||
zap.Int("member_records", memberCount),
|
||||
zap.Int("total_records", ownerCount+memberCount))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alternative optimized implementation for when you need both owned and shared counts
|
||||
// This reduces database round trips by querying once and separating in memory
|
||||
func (impl *collectionRepositoryImpl) countCollectionsSummary(ctx context.Context, userID gocql.UUID, filterType string) (ownedCount, sharedCount int, err error) {
|
||||
// Query all collections for the user using the general table
|
||||
query := `SELECT collection_id, access_type FROM maplefile.collections_by_user_id_with_desc_modified_at_and_asc_collection_id
|
||||
WHERE user_id = ?`
|
||||
|
||||
iter := impl.Session.Query(query, userID).WithContext(ctx).Iter()
|
||||
|
||||
var collectionID gocql.UUID
|
||||
var accessType string
|
||||
|
||||
for iter.Scan(&collectionID, &accessType) {
|
||||
// Get the collection to check state and type
|
||||
collection, getErr := impl.getBaseCollection(ctx, collectionID)
|
||||
if getErr != nil {
|
||||
impl.Logger.Warn("failed to get collection for counting summary",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.Error(getErr))
|
||||
continue
|
||||
}
|
||||
|
||||
if collection == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only count active collections
|
||||
if collection.State != dom_collection.CollectionStateActive {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by type if specified
|
||||
if filterType != "" && collection.CollectionType != filterType {
|
||||
continue
|
||||
}
|
||||
|
||||
// Count based on access type
|
||||
switch accessType {
|
||||
case "owner":
|
||||
ownedCount++
|
||||
case "member":
|
||||
sharedCount++
|
||||
}
|
||||
}
|
||||
|
||||
if err = iter.Close(); err != nil {
|
||||
impl.Logger.Error("failed to count collections summary",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.String("filter_type", filterType),
|
||||
zap.Error(err))
|
||||
return 0, 0, fmt.Errorf("failed to count collections summary: %w", err)
|
||||
}
|
||||
|
||||
impl.Logger.Debug("counted collections summary successfully",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.String("filter_type", filterType),
|
||||
zap.Int("owned_count", ownedCount),
|
||||
zap.Int("shared_count", sharedCount))
|
||||
|
||||
return ownedCount, sharedCount, nil
|
||||
}
|
||||
|
||||
// CountTotalUniqueFolders counts unique folders accessible to the user (deduplicates owned+shared)
|
||||
func (impl *collectionRepositoryImpl) CountTotalUniqueFolders(ctx context.Context, userID gocql.UUID) (int, error) {
|
||||
// Use a set to track unique collection IDs to avoid double-counting
|
||||
uniqueCollectionIDs := make(map[gocql.UUID]bool)
|
||||
|
||||
// Query all collections for the user using the general table
|
||||
query := `SELECT collection_id FROM maplefile.collections_by_user_id_with_desc_modified_at_and_asc_collection_id
|
||||
WHERE user_id = ?`
|
||||
|
||||
iter := impl.Session.Query(query, userID).WithContext(ctx).Iter()
|
||||
|
||||
var collectionID gocql.UUID
|
||||
totalScanned := 0
|
||||
|
||||
for iter.Scan(&collectionID) {
|
||||
totalScanned++
|
||||
|
||||
// Get the collection to check state and type
|
||||
collection, err := impl.getBaseCollection(ctx, collectionID)
|
||||
if err != nil {
|
||||
impl.Logger.Warn("failed to get collection for unique counting",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if collection == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
impl.Logger.Debug("Processing collection for unique count",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("state", collection.State),
|
||||
zap.String("collection_type", collection.CollectionType),
|
||||
zap.Int("total_scanned", totalScanned))
|
||||
|
||||
// Only count active folders
|
||||
if collection.State != dom_collection.CollectionStateActive {
|
||||
impl.Logger.Debug("Skipping collection due to non-active state",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("state", collection.State))
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by folder type
|
||||
if collection.CollectionType != dom_collection.CollectionTypeFolder {
|
||||
impl.Logger.Debug("Skipping collection due to type filter",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("collection_type", collection.CollectionType))
|
||||
continue
|
||||
}
|
||||
|
||||
// Add to unique set (automatically deduplicates)
|
||||
uniqueCollectionIDs[collectionID] = true
|
||||
|
||||
impl.Logger.Debug("Added unique folder to count",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.Int("current_unique_count", len(uniqueCollectionIDs)))
|
||||
}
|
||||
|
||||
if err := iter.Close(); err != nil {
|
||||
impl.Logger.Error("failed to count unique folders",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.Error(err))
|
||||
return 0, fmt.Errorf("failed to count unique folders: %w", err)
|
||||
}
|
||||
|
||||
uniqueCount := len(uniqueCollectionIDs)
|
||||
|
||||
impl.Logger.Info("Unique folder count completed",
|
||||
zap.String("user_id", userID.String()),
|
||||
zap.Int("total_scanned", totalScanned),
|
||||
zap.Int("unique_folders", uniqueCount))
|
||||
|
||||
return uniqueCount, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue