Skip to content

API platform: lunarphp/api kernel, admin auth and proving surfaces (spec 0077) - #2714

Draft
glennjacobs wants to merge 6 commits into
2.xfrom
feat/0077-api-platform
Draft

API platform: lunarphp/api kernel, admin auth and proving surfaces (spec 0077)#2714
glennjacobs wants to merge 6 commits into
2.xfrom
feat/0077-api-platform

Conversation

@glennjacobs

@glennjacobs glennjacobs commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements spec 0077 (specs/0077-api-platform.md): a new lunarphp/api package with one kernel and two surfaces, plus the core prerequisites the spec calls out.

Kernel (Lunar\Api): Resource, ResourceExtension, Field, Embed, Filter, Sort, SerializationContext; the per-surface registry (Api::storefront('v1')->resource() / extend() / replace(), extensions keyed by the built-in so they survive a replacement); the JSON:API-style grammar (include, fields[type], filter[name][op], sort, page[number|size|cursor]) with every unregistered value rejected as a 422 listing what is allowed; the data / meta / links envelope; a single error renderer producing JSON:API error objects for the grammar, validation, auth and framework exceptions; _schema and lunar:api:schema.

Storefront v1: context headers (X-Lunar-Channel, X-Lunar-Currency, Accept-Language, echoed on the response), the signed stateless X-Lunar-Cart token driving CartSession::use(), the CustomerResolver contract; catalogue reads (products, brands, collections, collection groups, all visibility-scoped to the context), GET /cart, POST /cart/lines, GET /me when a host guard is configured.

Admin v1: Lunar-owned ApiKey (SHA-256 hash, plaintext shown once, abilities, optional staff owner, expiry, revocation), the lunar-api-key guard driver, an abilities gate that reuses the staff permission vocabulary, activity-log attribution to the owner or the key, lunar:api:key create|list|revoke, /api-keys endpoints, and read-only products as the proving resource.

Core: InvalidatesCache::cacheKey() now returns public_id so cache tags correlate with API ids; settings:manage-api-keys, catalog:read, sales:read, settings:read join Auth\Manifest with labels in all 16 locales.

One naming deviation from the spec: the include value class is Embed, because include is a reserved word in PHP. The spec is updated to say so.

OpenAPI (spec 0081, slice 9 of 0077)

_schema and lunar:api:schema are gone. Each surface now serves an OpenAPI 3.1 document at GET /{prefix}/v1/openapi.json, and php artisan lunar:api:openapi {surface} [--api-version=v1] [--out=] [--yaml] writes the same thing from the console. The document is generated from the registry, so extension fields, filters, sorts and routes show up automatically, and it describes the whole surface with ability gates as x-lunar-requires.

  • Types. Lunar\Api\OpenApi\Schema value class; Field::type() / Field::nullable() / Filter::type() / Embed::many(); attribute fields infer from model casts and dates, translatable fields serialise per surface (string on storefront, TranslationMap on admin), includes reference the target resource with cardinality read from the Eloquent relation. Untyped members degrade to x-lunar-untyped; OpenApiCoverageTest asserts no built-in is.
  • Descriptions. Resource::label() / description(), describe() on fields, filters, includes and sorts, #[Operation] / #[Responds] on controller methods, DescribesSchema for bespoke bodies, descriptions() on form requests. index() / show() are described by convention. Every built-in field, include, filter, sort and endpoint is described (also asserted).
  • Generator. RulesToSchema, SurfaceProfile, ComponentBuilder, OperationBuilder, Generator, Document with SurfaceRegistry::tapDocument(). Operations are tagged by resource type with the label as x-group for Mintlify; deepObject fields / filter parameters; per-surface headers, security schemes and envelope meta; shared Money, TranslationMap, Error, ErrorResponse, PaginationMeta, Links components; unused shared components are pruned. Memoised per registry until it changes.
  • CI. New openapi job boots the packages through testbench.yaml (no host app, no database), generates both documents, lints them with npx @redocly/cli@2 lint, uploads them as the openapi artefact. Both documents lint clean locally; the only warning is the unused Customer schema when no storefront guard is configured.
  • Orval smoke run (orval 8.28.1, fetch client) against the storefront document: 165 exports, functions named from operationId (productsIndex, cartsLinesStore), JSDoc from descriptions, and filter typed as an object per filter. Caveat recorded in the spec: Orval's built-in fetch URL builder serialises deepObject one level deep, so the operator form (filter[price][gte]) needs the axios client with a qs serialiser or a mutator; the eq shorthand works out of the box.

Not in this repo: the docs-site wiring (docs.json tabs and overlays) lives in the docs repo.

Left for the child specs

Per the spec's own plan, the remaining storefront writes and checkout (0078), admin catalogue/sales/inventory/settings writes and idempotency keys (0079), webhooks (0080), and the search filter and Stripe intent route (slice 8) are not in this PR. The slice checklist in the spec reflects what shipped.

Test plan

  • vendor/bin/pest --testsuite api (61 tests, new suite added to phpunit.xml and the CI matrix)
  • vendor/bin/pest --testsuite core --parallel (cache tag assertions moved to public_id)
  • vendor/bin/pest --testsuite panel --parallel, admin, filament
  • vendor/bin/phpstan analyse
  • vendor/bin/pint --dirty

🤖 Generated with Claude Code

glennjacobs and others added 2 commits September 4, 2026 17:31
…0077)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
New lunarphp/api package:

- Kernel: Resource, ResourceExtension, Field, Embed, Filter, Sort,
  SerializationContext; per-surface registry with resource(), extend()
  and replace(); JSON:API-style query grammar rejecting anything
  unregistered with a 422 that lists the allowed values; data/meta/links
  envelope; one error renderer for the JSON:API error object; _schema
  endpoint and lunar:api:schema.
- Storefront v1: X-Lunar-Channel / X-Lunar-Currency / Accept-Language
  context middleware, signed stateless X-Lunar-Cart token, CustomerResolver
  contract; products, brands, collections, collection groups, GET /cart,
  POST /cart/lines, GET /me behind the host guard.
- Admin v1: Lunar-owned ApiKey model and migration, lunar-api-key guard
  driver and lunar-api guard, abilities gate reusing the staff permission
  vocabulary, activity-log attribution, lunar:api:key, /api-keys CRUD,
  read-only products as the proving resource.
- 16-locale error and validation strings; api testsuite in phpunit.xml
  and the CI matrix.

Core prerequisites: InvalidatesCache::cacheKey() returns public_id so
cache tags correlate with API ids; settings:manage-api-keys, catalog:read,
sales:read and settings:read join the permission manifest with labels in
all 16 locales.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
glennjacobs and others added 4 commits September 6, 2026 18:26
Follow-on to 0077: typed fields and filters, in-code descriptions, per-resource
tags, openapi.json and lunar:api:openapi replacing _schema, CI generation and
lint. English-only descriptions and @redocly/cli lint agreed in review.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replaces the _schema endpoint and lunar:api:schema with an OpenAPI 3.1
document per surface, served at openapi.json and written by
lunar:api:openapi. Adds the Schema value class, type() / nullable() /
describe() on the kernel, cast and relation inference, the Operation and
Responds attributes, DescribesSchema, rule-to-schema conversion, the
generator with tapDocument(), and an openapi CI job that generates both
documents through testbench.yaml and lints them with Redocly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant