Fix data-corruption on PK-less table; scrub real emails from examples
Critical: NocoDB v2 PATCH /records with no primary key updates EVERY row, so a table missing its Id column made each redeem rewrite all tickets' counts. - nocodb update() now refuses to PATCH a record with no Id (fail-safe). - Document that the tickets/audit tables must have an Id PK; note how to add it. - Replace the real donor email/name used in the /test persona and the FluentForms guide with fake placeholders (donor@example.test / <a-real-donor-email>). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ba5cb8a9fb
commit
65aa878192
4 changed files with 20 additions and 7 deletions
|
|
@ -51,7 +51,9 @@ The app expects the **2026 Campground Tickets** table to be a clone of the 2025
|
|||
| `Ice Total` | Number (prepaid ice bags) |
|
||||
| `Ice Redeemed` | Number (default 0) |
|
||||
|
||||
Total redeemable tickets = sum of the age-bracket columns **excluding `Ages 0-3`** (free). Ice bags remaining = `Ice Total − Ice Redeemed`. Column names are mapped in [`backend/src/fields.ts`](./backend/src/fields.ts) — change them there if the real titles differ. Put the table's ID (right-click table → *Copy Table ID*) in `NOCODB_TABLE_ID`.
|
||||
Total redeemable tickets = sum of the age-bracket columns **excluding `Ages 0-3`** (free). Ice bags remaining = `Ice Total − Ice Redeemed`.
|
||||
|
||||
> **The table MUST have an `Id` primary key.** NocoDB's v2 `PATCH /records` with no primary key updates *every row in the table*, so a PK-less table would make each scan rewrite all tickets. The backend now refuses to update a record with no `Id` (fail-safe), but the table itself must have one. Tables cloned from the existing 2025 table already have `Id`; if you build one by hand via the API, include an `{"title":"Id","uidt":"ID"}` column. Column names are mapped in [`backend/src/fields.ts`](./backend/src/fields.ts) — change them there if the real titles differ. Put the table's ID (right-click table → *Copy Table ID*) in `NOCODB_TABLE_ID`.
|
||||
|
||||
### Audit log table — "2026 Ticket Audit Logs"
|
||||
|
||||
|
|
|
|||
|
|
@ -36,13 +36,14 @@ const PERSONAS: Persona[] = [
|
|||
"5 tickets (2 under-4 free), car parking, 3 ice bags. Check-in a few at a time to test QR reuse; then Ice mode.",
|
||||
},
|
||||
{
|
||||
key: "donor",
|
||||
name: "Adam Stevens (real donor)",
|
||||
email: "adam21stevens@gmail.com",
|
||||
key: "donor2",
|
||||
name: "Donor Dan",
|
||||
email: "donor@example.test",
|
||||
ages: { "Ages 26-45": 2 },
|
||||
rvParking: true,
|
||||
isDonor: true,
|
||||
blurb: "2 tickets, RV parking. Banquet mode → shows real donation total ($801).",
|
||||
blurb:
|
||||
"2 tickets, RV parking, donor-flagged. Banquet mode: this test email has no real donations, so use Banquet's manual email lookup with a real donor's address to see totals.",
|
||||
},
|
||||
{
|
||||
key: "ice",
|
||||
|
|
|
|||
|
|
@ -86,6 +86,15 @@ export class NocoDBClient {
|
|||
|
||||
/** Patch fields on a record identified by its NocoDB Id. */
|
||||
async update(id: number, fields: Record<string, unknown>): Promise<NocoRecord> {
|
||||
// Fail safe: without a valid primary key, a v2 PATCH /records applies to
|
||||
// EVERY row in the table. Refuse rather than mass-corrupt ticket counts.
|
||||
// (A table missing its Id column will trip this — use a table with a PK.)
|
||||
if (id === undefined || id === null || (typeof id === "number" && !Number.isFinite(id))) {
|
||||
throw new NocoDBError(
|
||||
"record has no Id — refusing to update (the table is missing its primary key)",
|
||||
500,
|
||||
);
|
||||
}
|
||||
const body = await this.request(this.recordsUrl, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ Id: id, ...fields }),
|
||||
|
|
|
|||
|
|
@ -116,8 +116,9 @@ by FluentForms conditional logic based on its value.
|
|||
## Test
|
||||
|
||||
```
|
||||
curl "https://scan.beartariacampgrounds.com/api/public/donor-eligibility?key=<SECRET>&email=adam21stevens@gmail.com"
|
||||
# -> {"eligible":true,"tier":"member"}
|
||||
curl "https://scan.beartariacampgrounds.com/api/public/donor-eligibility?key=<SECRET>&email=<a-real-donor-email>"
|
||||
# donor/member -> {"eligible":true,"tier":"member"}
|
||||
# anyone else -> {"eligible":false,"tier":null}
|
||||
```
|
||||
|
||||
If member and donor get the **same** discounted price, simplify: set the donor
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue