feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39

Merged
mathias merged 299 commits from feat/33-audio-upload-transcript into main 2026-10-03 14:13:37 +00:00
Showing only changes of commit 22c0317c71 - Show all commits
+14 -1
View File
@@ -152,6 +152,10 @@ 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):
// The sender hung up mid-file: theirs to retry, not a server fault.
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.")
default: default:
// Not err: it can carry the file's name, a spool path or a database value. // Not err: it can carry the file's name, a spool path or a database value.
a.logger().Error("upload failed", "kind", errkind.Of(err)) a.logger().Error("upload failed", "kind", errkind.Of(err))
@@ -218,6 +222,12 @@ func titleFromFilename(name string) string {
// 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}$`)
// isBidiControl reports the invisible characters that change text direction. Emoji joiners
// (ZWJ, ZWNJ) and variation selectors are not among them and are kept.
func isBidiControl(r rune) bool {
return r >= 0x202A && r <= 0x202E || r >= 0x2066 && r <= 0x2069 || r == 0x200E || r == 0x200F || r == 0x061C
}
// cleanTitle makes a title safe to store and show: invalid UTF-8 dropped, control // cleanTitle makes a title safe to store and show: invalid UTF-8 dropped, control
// characters (NUL, newlines, escapes) turned into spaces, runs of space collapsed, trimmed // characters (NUL, newlines, escapes) turned into spaces, runs of space collapsed, trimmed
// and clipped. A title is cosmetic; bad bytes in it must never fail an upload, and Postgres // and clipped. A title is cosmetic; bad bytes in it must never fail an upload, and Postgres
@@ -225,7 +235,10 @@ var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4
func cleanTitle(s string) string { func cleanTitle(s string) string {
s = strings.ToValidUTF8(s, "") s = strings.ToValidUTF8(s, "")
s = strings.Map(func(r rune) rune { s = strings.Map(func(r rune) rune {
if unicode.IsControl(r) { switch {
case isBidiControl(r):
return -1 // dropped: U+202E would reverse the rest of the title on its owner's page
case unicode.IsControl(r):
return ' ' return ' '
} }
return r return r