Commit 4de4707
feat(proto): myfestival v1alpha API contract, OpenAPI generation, and client codegen (#425)
* docs(api): add proto-first AIP contract + buf/OpenAPI tooling
Define the online "my festival" API as Protocol Buffers following Google's
AIPs, as the source of truth for the ratings/recommendations endpoints. An
OpenAPI v3 doc is generated from it via a buf BSR remote plugin.
- proto/cambeerfestival/myfestival/v1: Rating/RatingSummary,
Recommendation/RecommendationSummary resources and MyFestivalService with
google.api.http annotations.
- Resource-oriented design: nested resource names, Update+allow_missing
upsert (AIP-134), bodyless Delete (AIP-135), paginated List of summaries
(AIP-158), field_behavior + resource annotations.
- buf.yaml (googleapis dep, AIP-aware lint) and buf.gen.yaml (gnostic OpenAPI
remote plugin); buf added to the dev mise env with proto:lint / format /
dep-update / generate tasks. See proto/README.md.
Contract only — buf lint/build and OpenAPI generation, plus reworking the
worker to conform, are pending network access to buf.build.
* fix(proto): add buf.lock and apply buf format
Run buf dep update to pin googleapis BSR dependency (buf.lock was
missing from the branch), then buf format -w to normalise whitespace —
collapsing multi-line option/field blocks onto single lines per buf's
default style. buf lint now passes cleanly.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* refactor(proto): model Rating and Recommendation as singleton resources
The caller is always implicit — device/user identity comes from the auth
context, not the URL. Replacing the ratings/{device} collection with a
singleton per (caller, drink) means:
- Device IDs never leak into resource names or logs
- URLs are unchanged when device tokens are replaced by user tokens
- Clients never need to know their own identity to construct a name
Pattern changes:
festivals/{f}/drinks/{d}/ratings/{device} → festivals/{f}/drinks/{d}/rating
festivals/{f}/drinks/{d}/recommendations/{device} → festivals/{f}/drinks/{d}/recommendation
Also: drop plural from singleton resource annotations; add DECLARATIVE_FRIENDLY
style; clarify allow_missing is always-true for singletons (retained for
AIP-134 generator compat); update all comments from "device" to "caller".
buf lint passes.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* feat(proto): add api-linter (AIP design linter) to dev toolchain
Install googleapis/api-linter via mise (github backend, dev-only while
the API design is in flux; move to base once shapes and signatures
stabilise).
Add proto:api-lint task: builds a buf descriptor set then runs
api-linter with --descriptor-set-in so imported googleapis protos
resolve without a separate proto-path.
Add proto/.api-linter.yaml config that suppresses three intentional
divergences from the default ruleset:
- 0191 java-*: not a Java API
- 0156 forbidden-methods: singletons expose Delete because ratings are
absent-until-rated, not always-present
- 0123 resource-pattern-singular: summary patterns use {drink} not
{rating_summary} for readable URLs
Fix proto issues surfaced by the first run:
- Remove style: DECLARATIVE_FRIENDLY (triggered etag/LRO/reconciling
requirements we don't want; the singleton pattern stands alone)
- Restore plural on Rating/Recommendation (required metadata even for
singletons per AIP-0123)
- Add doc comments to all request/response messages (AIP-0192)
- Remove blank lines between section-separator comments and first RPCs
(AIP-0192 only-leading-comments)
buf lint and api-linter both pass clean.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* chore: lock api-linter 2.3.1 in mise.dev.lock; refresh mise.lock
mise lock regenerated after adding github:googleapis/api-linter to the
dev env. Stale ubi:googleapis/api-linter entry pruned automatically.
mise.lock refreshed with current node patch version.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* feat(proto): redesign myfestival API as v1alpha with unified Review
- Rename package v1 → v1alpha (API still in design; other my-festival
features pending). HTTP paths flip to /v1alpha/. Promotes to v1beta/v1
subsystem-by-subsystem as the design stabilises.
- Merge Rating + Recommendation singletons into a single Review singleton
(festivals/{festival}/drinks/{drink}/review). Eliminates 4 round trips
per drink card and keeps both callers signals in one resource.
- Add ListReviews for bulk pre-load on app open (parent=festivals/{f}).
- Add ReviewSummary aggregate resource with ListReviewSummaries for
whole-festival list/grid views.
- Replace allow_missing (misleading no-op on singletons) with proper
upsert semantics; update_mask now meaningful (star_rating vs
would_recommend independently updatable).
- Fix resource type hostname: data.cambeerfestival.app/* everywhere.
- Add api-linter (googleapis/api-linter) to dev toolchain in mise.dev.toml.
Suppressed: java options, delete-on-singleton (absent-until-written),
resource-pattern-singular for reviewSummaries/{drink} natural key.
- Delete rating.proto and recommendation.proto (superseded by review.proto).
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* feat(proto): extend myfestival v1alpha to full personal state + api hostname
Add Bookmark, Note, and Tasting singleton resources to round out the
personal "my festival" state a caller can store per drink:
Bookmark festivals/{festival}/drinks/{drink}/bookmark
Presence = bookmarked; create_time only field.
Note festivals/{festival}/drinks/{drink}/note
Free-text tasting note (content, update_time).
Tasting festivals/{festival}/drinks/{drink}/tasting
Tried-it log with optional pours counter; create_time +
update_time. Paired with TastingSummary aggregate
(taster_count, total_pours) for social "N people tried this".
Each resource gets Get/Update/Delete/List. All List RPCs are scoped to
a parent festival for bulk pre-load on app open.
Also: switch default_host and all resource type URIs from
data.cambeerfestival.app to api.cambeerfestival.app. The data.* host
is a static CDN; api.* will route to Workers for authenticated,
dynamic endpoints.
buf lint, buf format, and api-linter all pass clean.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* feat(proto): add proto→OpenAPI→clients generation pipeline
Full pipeline: proto → OpenAPI → Worker TS types + Flutter Dart client.
## proto → OpenAPI (buf generate)
- Add `option go_package` to all v1alpha proto files (required by the
gnostic-openapi BSR plugin for import resolution)
- `buf generate` now produces `docs/code/api/openapi/openapi.yaml`
(899 lines, OpenAPI 3.0.3, server: https://api.cambeerfestival.app)
## OpenAPI → Worker TypeScript types
- Add `openapi-typescript` as devDep in `cloudflare-worker/`
- `proto:clients:types` task generates `cloudflare-worker/src/api-types.ts`
(1051 lines of fully-typed path/operation/component interfaces)
## OpenAPI → Flutter Dart client (packages/myfestival_client)
- `proto:clients:dart` task downloads openapi-generator-cli 7.13.0 JAR
(cached to ~/.cache/openapi-generator/) and generates a `dart-dio`
package at `packages/myfestival_client/`
- Runs `dart pub get` + `dart run build_runner build` inside the package
to produce the built_value `.g.dart` serialization files
- Root `.gitignore` updated: `!packages/**/*.g.dart` exception allows
generated package serializers to be committed alongside their source
- `pubspec.yaml`: add `myfestival_client: {path: packages/myfestival_client}`
as a path dependency (resolves cleanly with `flutter pub get`)
## Mise tasks
- `proto:clients` — chains types + dart
- `proto:clients:types` — openapi-typescript → api-types.ts
- `proto:clients:dart` — JAR download + openapi-generator + build_runner
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* chore(proto): use buf managed mode for go_package instead of file options
Move go_package from individual .proto files into buf.gen.yaml managed
mode (go_package_prefix). buf injects the option at generation time only;
the proto files stay language-neutral.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* chore(mise): move buf to dev toolchain, keep proto tasks out of CI
buf was accidentally in base mise.toml, so CI would install it despite
having no proto tasks to run. Moved to mise.dev.toml alongside api-linter.
Both migrate to base when the API design stabilises and proto tasks enter CI.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* chore: gitignore generated packages/, remove premature pubspec dep
packages/myfestival_client/ is a build artifact — regenerated from
openapi.yaml via `proto:clients:dart`. No need to commit ~8k lines of
generated Dart and lock file churn. Also removes the pubspec.yaml path
dep since the app doesn't consume the client yet.
Add packages/ to .gitignore. Wire it in when the client is actually used.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* chore: gitignore cloudflare-worker/src/ (generated TS types)
api-types.ts is generated from openapi.yaml via proto:clients:types.
Same rationale as packages/ — commit the source spec, not the derived artifact.
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
* chore: gitignore docs/code/api/openapi/ (generated from proto)
openapi.yaml is derived from the proto files via buf generate.
No generated artifacts committed — proto files are the source of truth.
Regenerate with: MISE_ENV=dev ./bin/mise run proto:generate
https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d698db7 commit 4de4707
18 files changed
Lines changed: 1242 additions & 1 deletion
File tree
- cloudflare-worker
- docs/code/api
- proto
- cambeerfestival/myfestival/v1alpha
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
49 | 52 | | |
50 | 53 | | |
51 | 54 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
0 commit comments