feat(proto): myfestival v1alpha API contract, OpenAPI generation, and client codegen - #425
Merged
Conversation
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.
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
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a proto-first contract for the “my festival” online API (ratings + recommendations), using Google AIP resource patterns as the source of truth and enabling OpenAPI v3 generation via buf for the Cloudflare Worker and HTTP clients.
Changes:
- Introduces v1 protobuf resources (
Rating/Recommendationplus aggregate*Summary) and aMyFestivalServicewithgoogle.api.httpREST mappings. - Adds buf module/lint configuration and a remote OpenAPI generator plugin configuration (plus a pinned
buf.lockdependency). - Adds developer mise tasks to lint/format/update deps/generate OpenAPI and documents the workflow in
proto/README.md.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| proto/README.md | Documents the proto-first contract, resource model, and generation commands. |
| proto/cambeerfestival/myfestival/v1/rating.proto | Defines Rating and RatingSummary resource schemas. |
| proto/cambeerfestival/myfestival/v1/recommendation.proto | Defines Recommendation and RecommendationSummary resource schemas. |
| proto/cambeerfestival/myfestival/v1/my_festival_service.proto | Defines service RPC surface + HTTP annotations + request/response messages. |
| proto/buf.yaml | Configures buf module + STANDARD lint with documented AIP-motivated exceptions. |
| proto/buf.lock | Pins googleapis dependency commit/digest for reproducibility. |
| proto/buf.gen.yaml | Configures OpenAPI generation via BSR remote plugin. |
| mise.dev.toml | Adds proto:* developer tasks for lint/format/dep-update/generate. |
| dir = "proto" | ||
| run = "buf generate" | ||
|
|
||
| # All tasks moved to mise-tasks/ for better maintainability and shellcheck/shfmt support: |
Comment on lines
+109
to
+111
| // If true (the default for this API), create the rating when absent (upsert). | ||
| bool allow_missing = 3 [(google.api.field_behavior) = OPTIONAL]; | ||
| } |
Comment on lines
+172
to
+174
| // If true (the default for this API), create the answer when absent (upsert). | ||
| bool allow_missing = 3 [(google.api.field_behavior) = OPTIONAL]; | ||
| } |
Comment on lines
+1
to
+7
| # API contract (proto-first) | ||
|
|
||
| The online "my festival" API (ratings + recommendations) is defined here as | ||
| Protocol Buffers following [Google's API Improvement Proposals](https://google.aip.dev) | ||
| (AIP). The proto is the source of truth; an OpenAPI v3 document is generated | ||
| from it for the (hand-written) Cloudflare Worker implementation and any HTTP | ||
| clients. |
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
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
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
- 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
…ostname
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
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
…ions 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
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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
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
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
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
Contributor
🚀 Cloudflare Pages PreviewYour preview deployment is ready! Preview URL: https://feat-proto-api-contract.staging-cambeerfestival.pages.dev This preview will be automatically updated when you push new commits to this PR. |
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Defines the online "my festival" API as Protocol Buffers under
v1alpha, following Google's AIPs. Establishes the proto→OpenAPI generation pipeline and local client codegen tooling. Split out from #423.Why v1alpha
The API is in active design — other "my festival" subsystems are not yet designed.
v1alphasignals no stability guarantees (AIP-181); each subsystem graduates tov1beta→v1independently. HTTP paths are/v1alpha/…, host isapi.cambeerfestival.app(distinct from the staticdata.*CDN).Proto contract (
proto/cambeerfestival/myfestival/v1alpha/)Five language-neutral files (
go_packageinjected at generation time via buf managed mode — not written to source).Resources — caller-scoped singletons per drink, absent-until-written
Review.../drinks/{drink}/reviewoptional star_rating,optional would_recommendBookmark.../drinks/{drink}/bookmarkcreate_timeonly — presence = bookmarkedNote.../drinks/{drink}/notecontent(free text),update_timeTasting.../drinks/{drink}/tastingoptional pours,create_time,update_timeCaller identity is implicit in auth context — never in resource names (device IDs private, sign-in upgrade transparent).
Aggregates — public, read-only
ReviewSummary.../reviewSummaries/{drink}rating_count,average_rating,response_count,recommend_count,recommend_rateTastingSummary.../tastingSummaries/{drink}taster_count,total_poursService —
MyFestivalServiceEach resource gets Get/Update (upsert)/Delete/List. List RPCs scoped to a parent festival for bulk pre-load on app open.
update_maskon Review letsstar_ratingandwould_recommendbe updated independently.Deleted
rating.protoandrecommendation.proto(superseded byreview.proto).Tooling (dev-only — not in CI yet)
All proto tooling in
mise.dev.toml. Bothbufandapi-lintermove to basemise.tomlwhen the API stabilises and proto tasks enter CI.proto/buf.yaml— googleapis BSR dep, AIP-aware STANDARD lint rulesetproto/buf.gen.yaml— gnostic OpenAPI remote plugin + buf managed mode (go_package_prefix)proto/.api-linter.yaml— suppresses java options, delete-on-singleton (AIP-156), resource-pattern-singular for{drink}natural keysbuf lint,buf format, andapi-linterall pass cleanMise tasks
proto:generateproto:clients:typesproto:clients:dartproto:clientsproto:lint/proto:format/proto:api-lintNothing generated is committed
Proto files are the source of truth. All derived artifacts are gitignored and regenerated locally:
Companion PR
Worker implementation: #426 (
feat/worker-ratings-api) — needs updating to conform to/v1alpha/paths and the unified resource model.Generated by Claude Code