160 lines
5.9 KiB
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(¤tCollectionID, &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
|
|
}
|