feat(store): DeleteUpload removes a finished upload of the owner (#37)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,10 @@ import (
|
|||||||
// nothing about whether someone else's upload exists (ADR-033).
|
// nothing about whether someone else's upload exists (ADR-033).
|
||||||
var ErrUploadNotFound = errors.New("store: uploaded transcript not found")
|
var ErrUploadNotFound = errors.New("store: uploaded transcript not found")
|
||||||
|
|
||||||
|
// ErrUploadInProgress is returned by DeleteUpload for an upload that is still queued or
|
||||||
|
// transcribing: the worker would write its result to a row that no longer exists.
|
||||||
|
var ErrUploadInProgress = errors.New("store: uploaded transcript is still being processed")
|
||||||
|
|
||||||
// Uploaded transcripts are PRIVATE per-user content, stored in their own
|
// Uploaded transcripts are PRIVATE per-user content, stored in their own
|
||||||
// RLS-scoped table (migration 017) — never in the shared `transcripts` (ADR-021).
|
// RLS-scoped table (migration 017) — never in the shared `transcripts` (ADR-021).
|
||||||
// Every method goes through withUser, so row-level security confines it to the
|
// Every method goes through withUser, so row-level security confines it to the
|
||||||
@@ -103,6 +107,33 @@ func (s *Store) FailUpload(ctx context.Context, userID, id string, kind domain.U
|
|||||||
string(kind))
|
string(kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteUpload removes one of the caller's finished (done or failed) uploads. An upload
|
||||||
|
// still queued or transcribing is refused with ErrUploadInProgress and left untouched; one
|
||||||
|
// that is not the caller's is ErrUploadNotFound, as for every other write (#37).
|
||||||
|
func (s *Store) DeleteUpload(ctx context.Context, userID, id string) error {
|
||||||
|
return s.withUser(ctx, userID, func(tx pgx.Tx) error {
|
||||||
|
ct, err := tx.Exec(ctx,
|
||||||
|
`DELETE FROM uploaded_transcripts
|
||||||
|
WHERE id = $1 AND user_id = $2 AND status IN ('done', 'failed')`, id, userID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("store: delete upload: %w", err)
|
||||||
|
}
|
||||||
|
if ct.RowsAffected() > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var exists bool
|
||||||
|
if err := tx.QueryRow(ctx,
|
||||||
|
`SELECT EXISTS (SELECT 1 FROM uploaded_transcripts WHERE id = $1 AND user_id = $2)`,
|
||||||
|
id, userID).Scan(&exists); err != nil {
|
||||||
|
return fmt.Errorf("store: delete upload: %w", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return ErrUploadInProgress
|
||||||
|
}
|
||||||
|
return ErrUploadNotFound
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// updateUpload runs one UPDATE keyed on ($1 = id, $2 = userID) plus extra args,
|
// updateUpload runs one UPDATE keyed on ($1 = id, $2 = userID) plus extra args,
|
||||||
// and maps "no row touched" to ErrUploadNotFound — which is what a write to another
|
// and maps "no row touched" to ErrUploadNotFound — which is what a write to another
|
||||||
// user's upload looks like under RLS (the row is invisible, not forbidden).
|
// user's upload looks like under RLS (the row is invisible, not forbidden).
|
||||||
|
|||||||
Reference in New Issue
Block a user