monorepo/cloud/maplefile-backend/internal/repo/filemetadata/get.go

217 lines
6.8 KiB
Go

// monorepo/cloud/maplefile-backend/internal/maplefile/repo/filemetadata/get.go
package filemetadata
import (
"fmt"
"sync"
"time"
"github.com/gocql/gocql"
"go.uber.org/zap"
dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
)
func (impl *fileMetadataRepositoryImpl) Get(id gocql.UUID) (*dom_file.File, error) {
var (
collectionID, ownerID, createdByUserID, modifiedByUserID gocql.UUID
encryptedMetadata, encryptedKeyJSON, encryptionVersion string
encryptedHash, encryptedFileObjectKey string
encryptedThumbnailObjectKey string
encryptedFileSizeInBytes, encryptedThumbnailSizeInBytes int64
tagsJSON string
createdAt, modifiedAt, tombstoneExpiry time.Time
version, tombstoneVersion uint64
state string
)
query := `SELECT id, collection_id, owner_id, encrypted_metadata, encrypted_file_key,
encryption_version, encrypted_hash, encrypted_file_object_key, encrypted_file_size_in_bytes,
encrypted_thumbnail_object_key, encrypted_thumbnail_size_in_bytes, tags,
created_at, created_by_user_id, modified_at, modified_by_user_id, version,
state, tombstone_version, tombstone_expiry
FROM maplefile.files_by_id WHERE id = ?`
err := impl.Session.Query(query, id).Scan(
&id, &collectionID, &ownerID, &encryptedMetadata, &encryptedKeyJSON,
&encryptionVersion, &encryptedHash, &encryptedFileObjectKey, &encryptedFileSizeInBytes,
&encryptedThumbnailObjectKey, &encryptedThumbnailSizeInBytes, &tagsJSON,
&createdAt, &createdByUserID, &modifiedAt, &modifiedByUserID, &version,
&state, &tombstoneVersion, &tombstoneExpiry)
if err != nil {
if err == gocql.ErrNotFound {
return nil, nil
}
return nil, fmt.Errorf("failed to get file: %w", err)
}
// Deserialize encrypted file key
encryptedFileKey, err := impl.deserializeEncryptedFileKey(encryptedKeyJSON)
if err != nil {
return nil, fmt.Errorf("failed to deserialize encrypted file key: %w", err)
}
// Deserialize tags
tags, err := impl.deserializeTags(tagsJSON)
if err != nil {
return nil, fmt.Errorf("failed to deserialize tags: %w", err)
}
file := &dom_file.File{
ID: id,
CollectionID: collectionID,
OwnerID: ownerID,
EncryptedMetadata: encryptedMetadata,
EncryptedFileKey: encryptedFileKey,
EncryptionVersion: encryptionVersion,
EncryptedHash: encryptedHash,
EncryptedFileObjectKey: encryptedFileObjectKey,
EncryptedFileSizeInBytes: encryptedFileSizeInBytes,
EncryptedThumbnailObjectKey: encryptedThumbnailObjectKey,
EncryptedThumbnailSizeInBytes: encryptedThumbnailSizeInBytes,
Tags: tags,
CreatedAt: createdAt,
CreatedByUserID: createdByUserID,
ModifiedAt: modifiedAt,
ModifiedByUserID: modifiedByUserID,
Version: version,
State: state,
TombstoneVersion: tombstoneVersion,
TombstoneExpiry: tombstoneExpiry,
}
return file, nil
}
func (impl *fileMetadataRepositoryImpl) GetByIDs(ids []gocql.UUID) ([]*dom_file.File, error) {
if len(ids) == 0 {
return []*dom_file.File{}, nil
}
// Use a buffered channel to collect results from goroutines
resultsChan := make(chan *dom_file.File, len(ids))
var wg sync.WaitGroup
// Launch a goroutine for each ID lookup
for _, id := range ids {
wg.Add(1)
go func(id gocql.UUID) {
defer wg.Done()
// Call the existing state-aware Get method
file, err := impl.Get(id)
if err != nil {
impl.Logger.Warn("failed to get file by ID",
zap.String("file_id", id.String()),
zap.Error(err))
// Send nil on error to indicate failure/absence for this ID
resultsChan <- nil
return
}
// Get returns nil for ErrNotFound or inactive state when stateAware is true.
// Send the potentially nil file result to the channel.
resultsChan <- file
}(id) // Pass id into the closure
}
// Goroutine to close the channel once all workers are done
go func() {
wg.Wait()
close(resultsChan)
}()
// Collect results from the channel
var files []*dom_file.File
for file := range resultsChan {
// Only append non-nil files (found and active)
if file != nil {
files = append(files, file)
}
}
// The original function logs warnings for errors but doesn't return an error
// from GetByIDs itself. We maintain this behavior.
return files, nil
}
func (impl *fileMetadataRepositoryImpl) GetByCollection(collectionID gocql.UUID) ([]*dom_file.File, error) {
var fileIDs []gocql.UUID
query := `SELECT id FROM maplefile.files_by_collection
WHERE collection_id = ?`
iter := impl.Session.Query(query, collectionID).Iter()
var fileID gocql.UUID
for iter.Scan(&fileID) {
fileIDs = append(fileIDs, fileID)
}
if err := iter.Close(); err != nil {
return nil, fmt.Errorf("failed to get files by collection: %w", err)
}
return impl.loadMultipleFiles(fileIDs)
}
func (impl *fileMetadataRepositoryImpl) loadMultipleFiles(fileIDs []gocql.UUID) ([]*dom_file.File, error) {
if len(fileIDs) == 0 {
return []*dom_file.File{}, nil
}
// Use a buffered channel to collect results from goroutines
// We expect up to len(fileIDs) results, some of which might be nil.
resultsChan := make(chan *dom_file.File, len(fileIDs))
var wg sync.WaitGroup
// Launch a goroutine for each ID lookup
for _, id := range fileIDs {
wg.Add(1)
go func(id gocql.UUID) {
defer wg.Done()
// Call the existing state-aware Get method
// This method returns nil if the file is not found, or if it's
// found but not in the 'active' state.
file, err := impl.Get(id)
if err != nil {
// Log the error but continue processing other IDs.
impl.Logger.Warn("failed to load file",
zap.String("file_id", id.String()),
zap.Error(err))
// Send nil on error, consistent with how Get returns nil for not found/inactive.
resultsChan <- nil
return
}
// Get returns nil for ErrNotFound or inactive state when stateAware is true.
// Send the potentially nil file result to the channel.
resultsChan <- file
}(id) // Pass id into the closure
}
// Goroutine to close the channel once all workers are done
go func() {
wg.Wait()
close(resultsChan)
}()
// Collect results from the channel
var files []*dom_file.File
for file := range resultsChan {
// Only append non-nil files (found and active, or found but error logged)
if file != nil {
files = append(files, file)
}
}
// The original function logged warnings for errors but didn't return an error
// from loadMultipleFiles itself. We maintain this behavior.
return files, nil
}