Tema
/
Interneto svetainių serverio dalies kūrimas GO

Kvietimas nuo pradžios iki galo, vienoje transakcijoje

Šeši failai kortelėse, iš apačios į viršų: rodinio modelis, jo atvaizdavimai, kvietimo valdiklis, tada savininko detalės puslapis, rodantis formą ir sąrašą. signing.gosigning_view.goweb/signing.godocuments.godocuments.templrouter.go.

1. internal/handlers/signing.go (naujas) — InviteSigners ir parseEmails. Valdiklis — transakcija gyvai:

tx, err := h.Pool.Begin(r.Context())
// ...
defer tx.Rollback(context.Background()) // nieko nedaro po sėkmingo Commit
q := h.Queries.WithTx(tx)               // tie patys užklausų metodai, susieti su tx

for _, addr := range emails {
	raw, hash, err := auth.GenerateToken()          // raw → nuoroda, hash → eilutė
	q.CreateSigner(r.Context(), db.CreateSignerParams{ /* ...TokenHash: hash... */ })
	invites = append(invites, invite{email: addr, link: h.Cfg.BaseURL + "/sign/" + raw})
}
q.MarkDocumentSent(r.Context(), /* ... */)
tx.Commit(r.Context())

defer tx.Rollback — apsaugos tinklas: jei bet kuris žingsnis grįžta anksti, atšaukimas suveikia ir niekas neįrašoma; po sėkmingo Commit atšaukimas nieko nedaro. El. laiškai apdorojami ir siunčiami po commit, tad konsolės siuntėjas spausdina nuorodas tik tikrai išsiųstam dokumentui. parseEmails suskaido teksto lauką (nauja eilutė/kablelis/tarpas), normalizuoja, patikrina ir pašalina dublikatus; tuščias ar visai neteisingas sąrašas iš naujo atvaizduoja detalės puslapį su klaida, o ne siunčia.

2. internal/handlers/signing_view.go (naujas) — viewSigner atvaizduoja db.Signer į web.SignerView, o viewSigners sudaro SignerRoster (viso + pasirašiusiųjų skaičius). Pasirašymo būsenos laukai sujungti dabar; jie lieka tušti, kol kas nors pasirašys kitą pamoką.

3. internal/web/signing.go (naujas) — SignerView ir SignerRoster rodinio struktūros. Paprastos eilutės, jokio pgtype.

4. internal/handlers/documents.go (keitimas) — ViewDocument dabar deleguoja naujam renderDocumentDetail, kuris įkelia pasirašytojų sąrašą ir atvaizduoja detalės puslapį. Ir peržiūros maršrutas, ir nepavykęs kvietimas atvaizduoja per šį vieną kelią.

5. internal/web/documents.templ (keitimas) — DocumentDetail įgyja du parametrus (roster, errMsg) ir du skyrius: kvietimo formą, kol dokumentas juodraštis, ir pasirašytojų sąrašą, kai jis išsiųstas.

6. internal/handlers/router.go (keitimas) — vienas maršrutas autentifikuotoje grupėje: pr.Post("/documents/{id}/invite", h.InviteSigners).

Patikra — perseeduok, paleisk, prisijunk, atverk juodraščio detalės puslapį:

  • Invite signers forma jau ten. Įvesk du adresus (po vieną eilutėje) ir Send for signature.
  • Puslapis persikrauna rodydamas dokumentą kaip sent, su Signers (0 of 2 signed) sąrašu — abu pending.
  • Dev pašto siuntėjas atspausdino kiekvieną pasirašymo nuorodą į terminalą:
────────────────────────────────────────────────────────
To:      alice@example.com
Subject: Please sign "contract.pdf"

You've been invited to sign "contract.pdf" on SignFlow.

Open your signing link (valid 30 days):
http://localhost:8080/sign/9f3b2c...e1
────────────────────────────────────────────────────────
  • Patvirtink, kad DB saugo tik maišas ir kad kvietimas buvo atominis (dokumentas sent ir abi pasirašytojų eilutės yra):
$ psql "$DATABASE_URL" -c "select email, status, left(token_hash,16) as token_hash from signers;"
      email        | status  |    token_hash
-------------------+---------+------------------
 alice@example.com | pending | 3a7f9c2b1e0d4f6a
 bob@example.com   | pending | c1d2e3f4a5b6c7d8

Neapdorotas žetonas gyvena tik išsiųstoje nuorodoje; eilutė laiko jo SHA-256. Tos nuorodos atvėrimas — kita pamoka.