136 lines
3.0 KiB
Go
136 lines
3.0 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.threesix.ai/jordan/persona-community-2/services/persona-api/internal/domain"
|
|
"git.threesix.ai/jordan/persona-community-2/services/persona-api/internal/port"
|
|
)
|
|
|
|
// Compile-time interface check.
|
|
var _ port.MediaRepository = (*MediaRepository)(nil)
|
|
|
|
// MediaRepository is an in-memory media metadata store for standalone development.
|
|
type MediaRepository struct {
|
|
mu sync.RWMutex
|
|
objects map[domain.MediaObjectID]*domain.MediaObject
|
|
byPath map[string]domain.MediaObjectID
|
|
}
|
|
|
|
// NewMediaRepository creates a new in-memory media repository.
|
|
func NewMediaRepository() *MediaRepository {
|
|
return &MediaRepository{
|
|
objects: make(map[domain.MediaObjectID]*domain.MediaObject),
|
|
byPath: make(map[string]domain.MediaObjectID),
|
|
}
|
|
}
|
|
|
|
func (r *MediaRepository) copyObject(obj *domain.MediaObject) *domain.MediaObject {
|
|
cp := *obj
|
|
return &cp
|
|
}
|
|
|
|
func (r *MediaRepository) Create(_ context.Context, obj *domain.MediaObject) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
r.objects[obj.ID] = r.copyObject(obj)
|
|
r.byPath[obj.Path] = obj.ID
|
|
return nil
|
|
}
|
|
|
|
func (r *MediaRepository) Get(_ context.Context, id domain.MediaObjectID) (*domain.MediaObject, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
obj, ok := r.objects[id]
|
|
if !ok || obj.DeletedAt != nil {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return r.copyObject(obj), nil
|
|
}
|
|
|
|
func (r *MediaRepository) ListByUser(_ context.Context, userID domain.UserID, opts port.ListMediaOptions) ([]domain.MediaObject, int, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
var all []domain.MediaObject
|
|
for _, obj := range r.objects {
|
|
if obj.UserID != userID || obj.DeletedAt != nil {
|
|
continue
|
|
}
|
|
if opts.ContentTypePrefix != "" && !strings.HasPrefix(obj.ContentType, opts.ContentTypePrefix) {
|
|
continue
|
|
}
|
|
all = append(all, *r.copyObject(obj))
|
|
}
|
|
|
|
// Sort by created_at DESC
|
|
sort.Slice(all, func(i, j int) bool {
|
|
return all[i].CreatedAt.After(all[j].CreatedAt)
|
|
})
|
|
|
|
total := len(all)
|
|
|
|
// Apply pagination
|
|
limit := opts.Limit
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
offset := opts.Offset
|
|
if offset > len(all) {
|
|
offset = len(all)
|
|
}
|
|
end := offset + limit
|
|
if end > len(all) {
|
|
end = len(all)
|
|
}
|
|
|
|
return all[offset:end], total, nil
|
|
}
|
|
|
|
func (r *MediaRepository) SoftDelete(_ context.Context, id domain.MediaObjectID) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
obj, ok := r.objects[id]
|
|
if !ok {
|
|
return domain.ErrNotFound
|
|
}
|
|
now := time.Now()
|
|
obj.DeletedAt = &now
|
|
return nil
|
|
}
|
|
|
|
func (r *MediaRepository) HardDelete(_ context.Context, id domain.MediaObjectID) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
obj, ok := r.objects[id]
|
|
if !ok {
|
|
return domain.ErrNotFound
|
|
}
|
|
delete(r.byPath, obj.Path)
|
|
delete(r.objects, id)
|
|
return nil
|
|
}
|
|
|
|
func (r *MediaRepository) GetByPath(_ context.Context, path string) (*domain.MediaObject, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
id, ok := r.byPath[path]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
obj, ok := r.objects[id]
|
|
if !ok || obj.DeletedAt != nil {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return r.copyObject(obj), nil
|
|
}
|