fix(17-06): singleton route retries a lost first-or-create race

Two first requests to GET .../singleton both missed the record; the
loser's insert hit the unique index and the route answered 500. A
unique violation from SingletonRecord now reruns the transaction once,
which finds the winner's record. TestSingleton fires six concurrent
first requests and requires 200 with the same id from each.
This commit is contained in:
2026-10-12 19:31:01 +02:00
parent 91593da1f7
commit 6312929148
4 changed files with 60 additions and 7 deletions

View File

@@ -448,7 +448,7 @@ Bulk actions have their own namespace next to the toolbar and widget actions: a
## Singleton controllers
A controller that edits exactly one record, such as a site-wide definition, implements `pact.AdminSingleton`. Its `SingletonRecord(ctx, db)` returns that record and creates it first when it does not exist (first or create), with `db` the request's transaction. Make the creation safe against two first requests at once, for example with a unique key column and GORM's `FirstOrCreate`.
A controller that edits exactly one record, such as a site-wide definition, implements `pact.AdminSingleton`. Its `SingletonRecord(ctx, db)` returns that record and creates it first when it does not exist (first or create), with `db` the request's transaction. Make the creation safe against two first requests at once, for example with a unique key column and GORM's `FirstOrCreate`: when the losing request's insert hits that unique index (a Postgres unique violation), the singleton route runs its transaction once more, and the second `SingletonRecord` call finds the winner's record.
- `GET <prefix>/api/v1/{vendor}/{plugin}/{controller}/singleton` answers the record's id (`cabana.AdminSingletonResult`), after checking that the form scope (`pact.FormExtendQuery`) can load it. The admin then opens the record's form.
- The form schema carries `singleton: true`. Show and update work as on any controller.