diff --git a/backend/internal/domain/repositories.go b/backend/internal/domain/repositories.go index 0af56a2..edca68f 100644 --- a/backend/internal/domain/repositories.go +++ b/backend/internal/domain/repositories.go @@ -11,7 +11,7 @@ type FileRepository interface { GetSlice(ctx context.Context, user_id int, filter, sort string, limit, offset int) (files Slice[FileItem], domainErr *DomainError) Get(ctx context.Context, user_id int, file_id string) (file FileFull, domainErr *DomainError) Add(ctx context.Context, user_id int, name, mime string, datetime time.Time, notes string, metadata json.RawMessage) (file FileCore, domainErr *DomainError) - Update(ctx context.Context, user_id int, file_id string, updates map[string]interface{}) (domainErr *DomainError) - Delete(ctx context.Context, user_id int, file_id string) (domainErr *DomainError) + Update(ctx context.Context, file_id string, updates map[string]interface{}) (domainErr *DomainError) + Delete(ctx context.Context, file_id string) (domainErr *DomainError) GetTags(ctx context.Context, user_id int, file_id string) (tags []TagItem, domainErr *DomainError) } diff --git a/backend/internal/infrastructure/persistence/postgres/file_repository.go b/backend/internal/infrastructure/persistence/postgres/file_repository.go index 0ec5c9d..83a32d3 100644 --- a/backend/internal/infrastructure/persistence/postgres/file_repository.go +++ b/backend/internal/infrastructure/persistence/postgres/file_repository.go @@ -22,7 +22,7 @@ func NewFileRepository(db *pgxpool.Pool) *FileRepository { return &FileRepository{db: db} } -// Check if user can view file +// Get user permissions on file func (s *FileRepository) GetAccess(ctx context.Context, user_id int, file_id string) (canView, canEdit bool, domainErr *domain.DomainError) { row := s.db.QueryRow(ctx, ` SELECT @@ -74,7 +74,7 @@ func (s *FileRepository) GetSlice(ctx context.Context, user_id int, filter, sort FROM data.files f JOIN system.mime m ON m.id=f.mime_id JOIN system.users u ON u.id=f.creator_id - WHERE NOT f.is_deleted AND (f.creator_id=$1 OR (SELECT view FROM acl.files WHERE file_id=f.id AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1)) AND + WHERE f.is_deleted IS FALSE AND (f.creator_id=$1 OR (SELECT view FROM acl.files WHERE file_id=f.id AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1)) AND ` query += filterCond queryCount := query @@ -150,7 +150,7 @@ func (s *FileRepository) Get(ctx context.Context, user_id int, file_id string) ( FROM data.files f JOIN system.mime m ON m.id=f.mime_id JOIN system.users u ON u.id=f.creator_id - WHERE NOT f.is_deleted AND f.id=$2 AND (f.creator_id=$1 OR (SELECT view FROM acl.files WHERE file_id=$2 AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1)) + WHERE f.is_deleted IS FALSE `, user_id, file_id) err := row.Scan(&file.ID, &file.Name, &file.MIME.Name, &file.MIME.Extension, &file.CreatedAt, &file.Creator.Name, &file.Creator.IsAdmin, &file.Notes, &file.Metadata, &file.Viewed) if err != nil { @@ -184,10 +184,10 @@ func (s *FileRepository) Add(ctx context.Context, user_id int, name, mime string return } row = s.db.QueryRow(ctx, ` - INSERT INTO data.files (name, mime_id, datetime, creator_id, notes, metadata) - VALUES (NULLIF($1, ''), $2, $3, $4, NULLIF($5 ,''), $6) - RETURNING id - `, name, mime_id, datetime, user_id, notes, metadata) + INSERT INTO data.files (name, mime_id, datetime, creator_id, notes, metadata) + VALUES (NULLIF($1, ''), $2, $3, $4, NULLIF($5 ,''), $6) + RETURNING id + `, name, mime_id, datetime, user_id, notes, metadata) err = row.Scan(&file.ID) if err != nil { var pgErr *pgconn.PgError @@ -205,39 +205,45 @@ func (s *FileRepository) Add(ctx context.Context, user_id int, name, mime string } // Update file -func (s *FileRepository) Update(ctx context.Context, user_id int, file_id string, updates map[string]interface{}) (domainErr *domain.DomainError) { +func (s *FileRepository) Update(ctx context.Context, file_id string, updates map[string]interface{}) (domainErr *domain.DomainError) { if len(updates) == 0 { - domainErr = domain.NewDomainError(errors.ErrUnsupported, domain.ErrValidation, "request body", "no fields provided for update") + domainErr = domain.NewDomainError(nil, domain.ErrValidation, "request body", "no fields provided for update") return } - writableFields := map[string]bool{ - "name": true, - "datetime": true, - "notes": true, - "metadata": true, - } query := "UPDATE data.files SET" - newValues := []interface{}{user_id} + newValues := []interface{}{file_id} count := 2 for field, value := range updates { - if !writableFields[field] { - domainErr = domain.NewDomainError(errors.ErrUnsupported, domain.ErrValidation, "field", field) + switch field { + case "name", "notes": + query += fmt.Sprintf(" %s=NULLIF($%d, '')", field, count) + case "datetime": + query += fmt.Sprintf(" %s=NULLIF($%d, '')::timestamptz", field, count) + case "metadata": + query += fmt.Sprintf(" %s=NULLIF($%d, '')::jsonb", field, count) + default: + domainErr = domain.NewDomainError(nil, domain.ErrValidation, "field", field) return } - query += fmt.Sprintf(" %s=NULLIF($%d, '')", field, count) newValues = append(newValues, value) count++ } - query += fmt.Sprintf( - " WHERE id=$%d AND (creator_id=$1 OR (SELECT edit FROM acl.files WHERE file_id=$%d AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1))", - count, count) - newValues = append(newValues, file_id) + query += fmt.Sprintf(" WHERE id=$1") commandTag, err := s.db.Exec(ctx, query, newValues...) if err != nil { var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") { - domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message) - return + if errors.As(err, &pgErr) { + switch pgErr.Code { + case "22P02", "22007": + domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message) + return + case "42804": + domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message) + return + case "23502": + domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message) + return + } } domainErr = domain.NewUnexpectedError(err) return @@ -250,10 +256,10 @@ func (s *FileRepository) Update(ctx context.Context, user_id int, file_id string } // Delete file -func (s *FileRepository) Delete(ctx context.Context, user_id int, file_id string) (domainErr *domain.DomainError) { +func (s *FileRepository) Delete(ctx context.Context, file_id string) (domainErr *domain.DomainError) { commandTag, err := s.db.Exec(ctx, - "DELETE FROM data.files WHERE id=$2 AND (creator_id=$1 OR (SELECT edit FROM acl.files WHERE file_id=$2 AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1))", - user_id, file_id) + "UPDATE data.files SET is_deleted=true WHERE id=$1", + file_id) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") { @@ -270,6 +276,7 @@ func (s *FileRepository) Delete(ctx context.Context, user_id int, file_id string return } +// Get list of tags of file func (s *FileRepository) GetTags(ctx context.Context, user_id int, file_id string) (tags []domain.TagItem, domainErr *domain.DomainError) { rows, err := s.db.Query(ctx, ` SELECT