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 }