Skip to content

feat!: require project id and move client API under /projects/{projectID}#2

Closed
jeroenrinzema wants to merge 2 commits into
mainfrom
feat/project-in-url
Closed

feat!: require project id and move client API under /projects/{projectID}#2
jeroenrinzema wants to merge 2 commits into
mainfrom
feat/project-in-url

Conversation

@jeroenrinzema

@jeroenrinzema jeroenrinzema commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Breaking change

The Lunogram Client API now requires the project UUID as a path segment.
Every authenticated endpoint moves from /api/client/... to
/api/client/projects/{projectID}/.... Auth is unchanged (same API key/token);
only the URL gains the project. This is a hard cut with no backwards
compatibility
.

To match, the SDK now takes the project ID as a required construction
parameter
. Callers supply it once when building the client; the SDK injects
/projects/{projectID} into every Client API path automatically. You never pass
the project per call.

Before / after

// Before
client := lunogram.NewClient("api-key")
user, err := client.UpsertUser(ctx, req)

// After — projectID (a UUID) is required; NewClient now returns an error
client, err := lunogram.NewClient("api-key", "11111111-1111-1111-1111-111111111111")
if err != nil {
    log.Fatal(err) // empty or non-UUID projectID
}
user, err := client.UpsertUser(ctx, req)

NewClient(apiKey, projectID string, opts ...Option) (*Client, error) validates
that projectID is a non-empty UUID and returns an error otherwise. Functional
options (WithBaseURL, WithHTTPClient, WithTimeout) are unchanged.

Path changes (old → new)

All Client API endpoints implemented by this SDK:

Old New
/api/client/users /api/client/projects/{projectID}/users
/api/client/users/events /api/client/projects/{projectID}/users/events
/api/client/users/scheduled /api/client/projects/{projectID}/users/scheduled
/api/client/organizations /api/client/projects/{projectID}/organizations
/api/client/organizations/users /api/client/projects/{projectID}/organizations/users
/api/client/organizations/events /api/client/projects/{projectID}/organizations/events
/api/client/organizations/scheduled /api/client/projects/{projectID}/organizations/scheduled

The prefix is injected in one place (Client.clientPath in client.go), so
resource methods now pass project-relative paths (e.g. /users). The broader
platform contract also moves inbox/devices/push/auth-methods endpoints under
/projects/{projectID}; those are not currently implemented in this SDK, so no
SDK code references them.

How it's wired

  • Client gains a projectID field, set and validated in NewClient.
  • clientPath(path) returns "/api/client/projects/" + projectID + path; the
    transport (do) builds the URL via c.baseURL + c.clientPath(path).
  • Resource files (users.go, organizations.go, scheduled.go) pass
    project-relative paths — no per-call project handling.

Files touched

  • client.go — required projectID, UUID validation, clientPath, transport.
  • users.go, organizations.go, scheduled.go — project-relative paths.
  • integration_test.go — construct via new signature; read/skip on LUNOGRAM_PROJECT_ID.
  • README.md — project-scoping docs, updated quick start and option snippets.

Validation

go build ./..., go vet ./..., gofmt -l . (clean), and go test ./... all
pass. Integration tests skip without live-API env vars, as before.

Dependency

This depends on the corresponding platform change that requires the project in
the Client API URL. It should merge once that ships.

…tID}

The Lunogram Client API now scopes every endpoint to a project via the
URL: /api/client/... becomes /api/client/projects/{projectID}/....

NewClient now takes a required projectID (a UUID) and returns an error if
it is empty or malformed. The project segment is injected centrally in the
transport (clientPath), so resource methods pass project-relative paths.

This is a hard, intentional breaking change with no backwards compatibility.
It depends on the corresponding platform change that requires the project
in the URL, and should merge once that ships.
@jeroenrinzema

Copy link
Copy Markdown
Contributor Author

Superseded by #3. The spec-driven SDK in #3 retargets to main and carries this project-in-URL facade (NewClient(apiKey, projectID), the /api/client/projects/{projectID} clientPath injection, and the UUID validation) in its history. Closing in favor of generating the client from the OpenAPI spec, which already encodes the project-scoped paths.

jeroenrinzema added a commit that referenced this pull request Jun 25, 2026
Migrate the SDK to spec-driven code generation, mirroring the js-sdk
template. The low-level type layer is now generated from the platform's
vendored OpenAPI spec; the hand-written facade (NewClient, functional
options, the /api/client/projects/{projectID} prefix, google/uuid
validation, Bearer auth) stays single-point and unchanged.

Layout:
- spec/client.yaml: vendored, pinned spec (see spec/SOURCE.md for the
  source repo, spec path, and pinned ref). No platform release needed —
  any ref is fetchable via the raw URL.
- gen/: spec-generated types (client.gen.go, DO-NOT-EDIT) via
  oapi-codegen v2.7.0, wired through go:generate and a go.mod tool
  dependency so `go generate` is deterministic with no separate install.
  gen/types.go is a hand-written companion for the spec's x-go-type
  references (pagination params), mirroring the platform's own pattern.
- conformance_test.go: asserts every facade request/response type shares
  the exact JSON wire shape of its generated spec counterpart, so a spec
  change surfaces as a failing test rather than a silent runtime mismatch.

CI:
- ci.yml: regenerate from the spec and fail on drift (git diff gen), then
  build, vet, gofmt, and test.
- spec-sync.yml: weekly cron + manual; re-fetch the spec at the pin in
  spec/SOURCE.md, regenerate, and open a PR on change.

The public API is unchanged from #2; the ! marks the build-system and
dependency shift (go.mod tool directive, generated low-level layer).
@jeroenrinzema jeroenrinzema deleted the feat/project-in-url branch June 25, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant