Įkėlimas nuo pradžios iki galo, su gyva progreso juosta
Aštuoni failai, pateikimas ant ką tik pastatyto sluoksnio: rodinio modelis, jo atvaizdavimas, sąrašo fragmentas, įkėlimo valdiklis ir keturi maži sujungimo pakeitimai, kurie viską pagyvina. Tvarka: documents_web.go → documents_view.go → documents.templ → documents.go → dashboard.templ → auth.go → router.go → layout.templ.
1. internal/web/documents_web.go (naujas) — DocumentView, šablonui skirta forma: paprastos eilutės ir loginė reikšmė, jokio pgtype. Šablonai niekada tiesiogiai neliečia db.Document.
2. internal/handlers/documents_view.go (naujas) — viewDocument atvaizduoja db.Document į DocumentView (suformatuoja laiko žymą, sužmogina baitų skaičių), viewDocuments atvaizduoja pjūvį, o humanizeBytes pateikia 1.4 MB.
3. internal/web/documents.templ (naujas) — DocumentList(docs), fragmentas su id="doc-list", ir documentRow. Būtent šį gabalą HTMX pakeičia: įkėlimo forma taikosi į #doc-list ir pakeičia jį šviežiu sąrašu. (Peržiūros ir trynimo veiksmai atkeliaus kitą pamoką kartu su juos palaikančiais maršrutais.)
4. internal/handlers/documents.go (naujas) — Dashboard (perkeltas čia iš auth.go, dabar rodantis savininko dokumentus) ir UploadDocument:
r.Body = http.MaxBytesReader(w, r.Body, h.Cfg.MaxUploadBytes+1024)
file, header, err := r.FormFile("file")
// ...
key, size, hash, err := h.Store.Save(r.Context(), file) // srautas + maiša, vienas perėjimas
doc, err := h.Queries.CreateDocument(r.Context(), db.CreateDocumentParams{ /* ... */ })
if err != nil {
_ = h.Store.Delete(r.Context(), key) // nepalik našlaičio failo
// ...
}
Jei eilutės įterpimas nepavyksta jau įrašius failą, Store.Delete pašalina našlaitį — saugykla ir duomenų bazė laikomos sinchronizuotos. respondWithDocList grąžina atnaujintą fragmentą HTMX skambintojams ir peradresuoja paprastą formos siuntimą. sanitizeFilename nušluosto kelio skirtukus ir valdymo simbolius iš kliento pateikto vardo.
5. internal/web/dashboard.templ (keitimas) — vietos rezervavimo skydelis išauga į tikrą: herojų, įkėlimo kortelę (hx-post="/documents", hx-encoding="multipart/form-data", hx-target="#doc-list", <progress> juostą) ir @DocumentList(docs). Mažas skriptas sujungia htmx xhr:progress įvykius su progreso elementu. Dashboard dabar priima docs []DocumentView.
6. internal/handlers/auth.go (keitimas) — senas vietos rezervavimo Dashboard čia pašalintas; jis persikėlė į documents.go, kur gali išvardyti dokumentus. Daugiau auth.go niekas nesikeičia.
7. internal/handlers/router.go (keitimas) — vienas maršrutas autentifikuotoje grupėje: pr.Post("/documents", h.UploadDocument).
8. internal/web/layout.templ (keitimas) — du papildymai, leidžiantys HTMX siuntimams praeiti CSRF: <meta name="csrf-token"> ir mažas htmx:configRequest klausytojas, kopijuojantis žetoną į X-CSRF-Token antraštę. Įkėlimo forma neneša paslėpto lauko, tad antraštė yra tai, kaip gorilla/csrf mato žetoną — ir jis nuskaito antraštę prieš apdorodamas kūną, tad multipart srautas nesuvartojamas du kartus.
Kiekviena kita forma įterpia gorilla.csrf.Token kaip paslėptą įvestį. Multipart įkėlimas negali — žetonas atvyktų po failo sraute, o gorilla/csrf jį reikia pirma. X-CSRF-Token antraštė tai išsprendžia: htmx prideda ją prie kiekvienos užklausos iš meta žymos, ir ji nuskaitoma prieš kūną. Be šio sujungimo įkėlimo POST grąžina 403 Forbidden.
Patikra — perseeduok, go run ./cmd/signflow, prisijunk ir atverk /dashboard:
- Pasirink failą ir spausk Upload. Progreso juosta prisipildo, o dokumentų sąrašas pasikeičia vietoje su tavo failu — be viso puslapio perkrovimo.
- Eilutė rodo sužmogintą dydį ir
draftženkliuką. - Patikrink terminalą:
level=INFO msg="document uploaded" id=… owner=you@example.com size=1048576 sha256=4a7d1ed414474e4033ac29ccb8653d9b...
- Įsitikink, kad baitai ir maiša nusėdo:
$ ls uploads/
7f3a9c1e0b2d4f6a8c5e1b3d7f9a2c4e
$ psql "$DATABASE_URL" -c "select filename, size, status, storage_key from documents;"
filename | size | status | storage_key
--------------+---------+--------+----------------------------------
contract.pdf | 1048576 | draft | 7f3a9c1e0b2d4f6a8c5e1b3d7f9a2c4e
storage_key įvardija failą diske; jis nėra eilutės ID. file_hash buvo apskaičiuotas tuo pačiu perėjimu, kuris tuos baitus įrašė.
- Pabandyk failą virš 25 MiB: įkėlimas atmetamas su 413, o į
uploads/ar lentelę nieko neįrašoma.