feat(web): POST /uploads/{id}/delete, owner-scoped and refused while the recording is active (#37)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,9 @@ type Store interface {
|
|||||||
// non-owner's id is ErrUploadNotFound.
|
// non-owner's id is ErrUploadNotFound.
|
||||||
GetUpload(ctx context.Context, userID, id string) (*domain.UploadedTranscript, error)
|
GetUpload(ctx context.Context, userID, id string) (*domain.UploadedTranscript, error)
|
||||||
ListUploads(ctx context.Context, userID string) ([]domain.UploadedTranscript, error)
|
ListUploads(ctx context.Context, userID string) ([]domain.UploadedTranscript, error)
|
||||||
|
// DeleteUpload removes a finished upload; ErrUploadInProgress while it is still being
|
||||||
|
// processed, ErrUploadNotFound for a non-owner.
|
||||||
|
DeleteUpload(ctx context.Context, userID, id string) error
|
||||||
|
|
||||||
// SetTranscriptStatus clears or updates a video's transcript backoff state.
|
// SetTranscriptStatus clears or updates a video's transcript backoff state.
|
||||||
// Used by handleRetryNow to clear rate_limited_at before immediate processing.
|
// Used by handleRetryNow to clear rate_limited_at before immediate processing.
|
||||||
@@ -200,6 +203,7 @@ func (a *App) Router() http.Handler {
|
|||||||
app.HandleFunc("POST /uploads", a.handleUploadPost)
|
app.HandleFunc("POST /uploads", a.handleUploadPost)
|
||||||
app.HandleFunc("GET /uploads/{id}", a.handleUploadDetail)
|
app.HandleFunc("GET /uploads/{id}", a.handleUploadDetail)
|
||||||
app.HandleFunc("GET /uploads/{id}/status", a.handleUploadStatus)
|
app.HandleFunc("GET /uploads/{id}/status", a.handleUploadStatus)
|
||||||
|
app.HandleFunc("POST /uploads/{id}/delete", a.handleUploadDelete)
|
||||||
// Chat over the uploaded transcript reuses the ADR-027 Chatter; mounted only
|
// Chat over the uploaded transcript reuses the ADR-027 Chatter; mounted only
|
||||||
// when one is wired.
|
// when one is wired.
|
||||||
if a.Chat != nil {
|
if a.Chat != nil {
|
||||||
|
|||||||
@@ -210,6 +210,32 @@ func (a *App) handleUploadStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.render(w, r, uploadStatus(*u, a.Chat != nil, a.now()))
|
a.render(w, r, uploadStatus(*u, a.Chat != nil, a.now()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleUploadDelete removes one of the caller's finished recordings and returns to the
|
||||||
|
// list. Another user's id is a 404 like every other upload route; a recording still being
|
||||||
|
// transcribed is refused (409) rather than deleted under the worker (#37).
|
||||||
|
func (a *App) handleUploadDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, ok := a.currentUserID(w, r)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := r.PathValue("id")
|
||||||
|
if !uuidShape.MatchString(id) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch err := a.Store.DeleteUpload(r.Context(), userID, id); {
|
||||||
|
case err == nil:
|
||||||
|
http.Redirect(w, r, "/uploads", http.StatusSeeOther)
|
||||||
|
case errors.Is(err, store.ErrUploadNotFound):
|
||||||
|
http.NotFound(w, r)
|
||||||
|
case errors.Is(err, store.ErrUploadInProgress):
|
||||||
|
a.renderUploadsPage(w, r, userID, http.StatusConflict,
|
||||||
|
"This recording is still being transcribed. You can delete it once it has finished.")
|
||||||
|
default:
|
||||||
|
a.serverError(w, r, "delete upload", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) loadOwnedUpload(w http.ResponseWriter, r *http.Request) (*domain.UploadedTranscript, bool) {
|
func (a *App) loadOwnedUpload(w http.ResponseWriter, r *http.Request) (*domain.UploadedTranscript, bool) {
|
||||||
userID, ok := a.currentUserID(w, r)
|
userID, ok := a.currentUserID(w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user