monorepo/cloud/maplefile-backend/internal/repo/collection/check.go

160 lines
5.9 KiB
Go

// monorepo/cloud/maplefile-backend/internal/maplefile/repo/collection/check.go
package collection
import (
"context"
"fmt"
"github.com/gocql/gocql"
dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
)
func (impl *collectionRepositoryImpl) CheckIfExistsByID(ctx context.Context, id gocql.UUID) (bool, error) {
var count int
query := `SELECT COUNT(*) FROM collections_by_id WHERE id = ?`
if err := impl.Session.Query(query, id).WithContext(ctx).Scan(&count); err != nil {
return false, fmt.Errorf("failed to check collection existence: %w", err)
}
return count > 0, nil
}
// IsCollectionOwner demonstrates the memory-filtering approach for better performance
// Instead of forcing Cassandra to scan with ALLOW FILTERING, we query efficiently and filter in memory
func (impl *collectionRepositoryImpl) IsCollectionOwner(ctx context.Context, collectionID, userID gocql.UUID) (bool, error) {
// Strategy: Use the compound partition key table to efficiently check ownership
// This query is fast because both user_id and access_type are part of the partition key
var collectionExists gocql.UUID
query := `SELECT collection_id FROM collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
WHERE user_id = ? AND access_type = 'owner' AND collection_id = ? LIMIT 1 ALLOW FILTERING`
err := impl.Session.Query(query, userID, collectionID).WithContext(ctx).Scan(&collectionExists)
if err != nil {
if err == gocql.ErrNotFound {
return false, nil
}
return false, fmt.Errorf("failed to check ownership: %w", err)
}
// If we got a result, the user is an owner of this collection
return true, nil
}
// Alternative implementation using the memory-filtering approach
// This demonstrates a different strategy when you can't avoid some filtering
func (impl *collectionRepositoryImpl) IsCollectionOwnerAlternative(ctx context.Context, collectionID, userID gocql.UUID) (bool, error) {
// Memory-filtering approach: Get all collections for this user, filter for the specific collection
// This is efficient when users don't have thousands of collections
query := `SELECT collection_id, access_type FROM 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 currentCollectionID gocql.UUID
var accessType string
for iter.Scan(&currentCollectionID, &accessType) {
// Check if this is the collection we're looking for and if the user is the owner
if currentCollectionID == collectionID && accessType == "owner" {
iter.Close()
return true, nil
}
}
if err := iter.Close(); err != nil {
return false, fmt.Errorf("failed to check ownership: %w", err)
}
return false, nil
}
// CheckAccess uses the efficient compound partition key approach
func (impl *collectionRepositoryImpl) CheckAccess(ctx context.Context, collectionID, userID gocql.UUID, requiredPermission string) (bool, error) {
// First check if user is owner (owners have all permissions)
isOwner, err := impl.IsCollectionOwner(ctx, collectionID, userID)
if err != nil {
return false, fmt.Errorf("failed to check ownership: %w", err)
}
if isOwner {
return true, nil // Owners have all permissions
}
// Check if user is a member with sufficient permissions
var permissionLevel string
query := `SELECT permission_level FROM collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
WHERE user_id = ? AND access_type = 'member' AND collection_id = ? LIMIT 1 ALLOW FILTERING`
err = impl.Session.Query(query, userID, collectionID).WithContext(ctx).Scan(&permissionLevel)
if err != nil {
if err == gocql.ErrNotFound {
return false, nil // No access
}
return false, fmt.Errorf("failed to check member access: %w", err)
}
// Check if user's permission level meets requirement
return impl.hasPermission(permissionLevel, requiredPermission), nil
}
// GetUserPermissionLevel efficiently determines a user's permission level for a collection
func (impl *collectionRepositoryImpl) GetUserPermissionLevel(ctx context.Context, collectionID, userID gocql.UUID) (string, error) {
// Check ownership first using the efficient compound key table
isOwner, err := impl.IsCollectionOwner(ctx, collectionID, userID)
if err != nil {
return "", fmt.Errorf("failed to check ownership: %w", err)
}
if isOwner {
return dom_collection.CollectionPermissionAdmin, nil
}
// Check member permissions
var permissionLevel string
query := `SELECT permission_level FROM collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
WHERE user_id = ? AND access_type = 'member' AND collection_id = ? LIMIT 1 ALLOW FILTERING`
err = impl.Session.Query(query, userID, collectionID).WithContext(ctx).Scan(&permissionLevel)
if err != nil {
if err == gocql.ErrNotFound {
return "", nil // No access
}
return "", fmt.Errorf("failed to get permission level: %w", err)
}
return permissionLevel, nil
}
// Demonstration of a completely ALLOW FILTERING-free approach using direct collection lookup
// This approach queries the main collection table and checks ownership directly
func (impl *collectionRepositoryImpl) CheckAccessByCollectionLookup(ctx context.Context, collectionID, userID gocql.UUID, requiredPermission string) (bool, error) {
// Strategy: Get the collection directly and check ownership/membership from the collection object
collection, err := impl.Get(ctx, collectionID)
if err != nil {
return false, fmt.Errorf("failed to get collection: %w", err)
}
if collection == nil {
return false, nil // Collection doesn't exist
}
// Check if user is the owner
if collection.OwnerID == userID {
return true, nil // Owners have all permissions
}
// Check if user is a member with sufficient permissions
for _, member := range collection.Members {
if member.RecipientID == userID {
return impl.hasPermission(member.PermissionLevel, requiredPermission), nil
}
}
return false, nil // User has no access
}