feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39
+31
-2
@@ -95,6 +95,9 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var tooBig *http.MaxBytesError
|
var tooBig *http.MaxBytesError
|
||||||
|
if a.abandoned(r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if !errors.As(err, &tooBig) {
|
if !errors.As(err, &tooBig) {
|
||||||
// A malformed multipart body (a header with a control byte, a truncated
|
// A malformed multipart body (a header with a control byte, a truncated
|
||||||
// part) is the sender's to fix: a 400, not a server error, and the error
|
// part) is the sender's to fix: a 400, not a server error, and the error
|
||||||
@@ -130,10 +133,24 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "Choose an audio file to upload.")
|
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "Choose an audio file to upload.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// abandoned reports, and logs at INFO, that the client has already gone: its request
|
||||||
|
// context is cancelled, so there is nobody to answer and rendering a page would run its
|
||||||
|
// database read on a dead context and log a false ERROR. A hang-up mid-upload is routine.
|
||||||
|
func (a *App) abandoned(r *http.Request) bool {
|
||||||
|
if r.Context().Err() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a.logger().Info("upload abandoned", "reason", "client_gone")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// uploadFailure maps an Accept (or multipart) error to a response: a validation
|
// uploadFailure maps an Accept (or multipart) error to a response: a validation
|
||||||
// rejection is the user's to fix (422, or 413 for size) and carries its own
|
// rejection is the user's to fix (422, or 413 for size) and carries its own
|
||||||
// message; anything else is ours.
|
// message; anything else is ours.
|
||||||
func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) {
|
func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) {
|
||||||
|
if a.abandoned(r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
var rej *uploads.RejectError
|
var rej *uploads.RejectError
|
||||||
var tooBig *http.MaxBytesError
|
var tooBig *http.MaxBytesError
|
||||||
switch {
|
switch {
|
||||||
@@ -152,8 +169,9 @@ func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID strin
|
|||||||
a.logger().Info("upload rejected", "reason", string(uploads.ReasonTooLarge))
|
a.logger().Info("upload rejected", "reason", string(uploads.ReasonTooLarge))
|
||||||
a.renderUploadsPage(w, r, userID, http.StatusRequestEntityTooLarge,
|
a.renderUploadsPage(w, r, userID, http.StatusRequestEntityTooLarge,
|
||||||
"This file is larger than the upload limit.")
|
"This file is larger than the upload limit.")
|
||||||
case errors.Is(err, io.ErrUnexpectedEOF):
|
case errors.Is(err, uploads.ErrClientRead):
|
||||||
// The sender hung up mid-file: theirs to retry, not a server fault.
|
// An error reading the CLIENT's body: theirs to retry, not a server fault. Only that
|
||||||
|
// marker counts: io.ErrUnexpectedEOF alone is also what a database drop looks like.
|
||||||
a.logger().Info("upload rejected", "reason", "truncated")
|
a.logger().Info("upload rejected", "reason", "truncated")
|
||||||
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "The upload was cut off before it finished. Please try again.")
|
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "The upload was cut off before it finished. Please try again.")
|
||||||
default:
|
default:
|
||||||
@@ -219,6 +237,17 @@ func titleFromFilename(name string) string {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasNote reports whether any phase carries a note. A note is the only warning for a
|
||||||
|
// recording that is shorter than its file claimed, so a record that has one is shown open.
|
||||||
|
func hasNote(steps []domain.ProgressStep) bool {
|
||||||
|
for _, s := range steps {
|
||||||
|
if s.Note != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// uuidShape is the form of an upload id. Anything else cannot be one, so it is a 404.
|
// uuidShape is the form of an upload id. Anything else cannot be one, so it is a 404.
|
||||||
var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user