From 564b82674fec20f158c2aedba310b7c76ed13bb8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 01:38:37 +0000 Subject: [PATCH 1/3] feat: rebrand to magicians.vr4deaf.org and add platform API sections --- README.md | 294 +++++------------- .../components/headers/Rate-Limit-Limit.yaml | 5 + .../headers/Rate-Limit-Remaining.yaml | 5 + .../components/headers/Rate-Limit-Reset.yaml | 5 + .../components/schemas/DatabaseSchema.yaml | 46 +++ openapi/components/schemas/Email.yaml | 2 +- openapi/components/schemas/IngressRoute.yaml | 48 +++ openapi/components/schemas/NetworkNode.yaml | 38 +++ openapi/components/schemas/NpxJob.yaml | 65 ++++ openapi/components/schemas/RatePolicy.yaml | 43 +++ .../components/schemas/RegistryPackage.yaml | 42 +++ openapi/components/schemas/Rule.yaml | 56 ++++ openapi/components/schemas/RuleEvent.yaml | 26 ++ openapi/components/schemas/User.yaml | 2 +- openapi/components/schemas/Vendor.yaml | 43 +++ openapi/openapi.yaml | 200 ++++++++---- openapi/paths/db.yaml | 27 ++ openapi/paths/db_{schema}.yaml | 61 ++++ openapi/paths/ingress.yaml | 58 ++++ openapi/paths/ingress_{routeId}.yaml | 78 +++++ openapi/paths/network.yaml | 30 ++ openapi/paths/network_status.yaml | 50 +++ openapi/paths/npx.yaml | 97 ++++++ openapi/paths/npx_{jobId}.yaml | 55 ++++ openapi/paths/rate.yaml | 54 ++++ openapi/paths/rate_{resource}.yaml | 76 +++++ openapi/paths/registry.yaml | 85 +++++ openapi/paths/registry_{package}.yaml | 52 ++++ openapi/paths/rules.yaml | 57 ++++ openapi/paths/rules_{ruleId}.yaml | 76 +++++ openapi/paths/rules_{ruleId}_evaluate.yaml | 60 ++++ openapi/paths/users_{username}.yaml | 2 +- openapi/paths/vendors.yaml | 54 ++++ openapi/paths/vendors_{vendorId}.yaml | 76 +++++ package-lock.json | 29 ++ 35 files changed, 1712 insertions(+), 285 deletions(-) create mode 100644 openapi/components/headers/Rate-Limit-Limit.yaml create mode 100644 openapi/components/headers/Rate-Limit-Remaining.yaml create mode 100644 openapi/components/headers/Rate-Limit-Reset.yaml create mode 100644 openapi/components/schemas/DatabaseSchema.yaml create mode 100644 openapi/components/schemas/IngressRoute.yaml create mode 100644 openapi/components/schemas/NetworkNode.yaml create mode 100644 openapi/components/schemas/NpxJob.yaml create mode 100644 openapi/components/schemas/RatePolicy.yaml create mode 100644 openapi/components/schemas/RegistryPackage.yaml create mode 100644 openapi/components/schemas/Rule.yaml create mode 100644 openapi/components/schemas/RuleEvent.yaml create mode 100644 openapi/components/schemas/Vendor.yaml create mode 100644 openapi/paths/db.yaml create mode 100644 openapi/paths/db_{schema}.yaml create mode 100644 openapi/paths/ingress.yaml create mode 100644 openapi/paths/ingress_{routeId}.yaml create mode 100644 openapi/paths/network.yaml create mode 100644 openapi/paths/network_status.yaml create mode 100644 openapi/paths/npx.yaml create mode 100644 openapi/paths/npx_{jobId}.yaml create mode 100644 openapi/paths/rate.yaml create mode 100644 openapi/paths/rate_{resource}.yaml create mode 100644 openapi/paths/registry.yaml create mode 100644 openapi/paths/registry_{package}.yaml create mode 100644 openapi/paths/rules.yaml create mode 100644 openapi/paths/rules_{ruleId}.yaml create mode 100644 openapi/paths/rules_{ruleId}_evaluate.yaml create mode 100644 openapi/paths/vendors.yaml create mode 100644 openapi/paths/vendors_{vendorId}.yaml create mode 100644 package-lock.json diff --git a/README.md b/README.md index 960a698..f2ce54c 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,105 @@ -# OpenAPI Definition Starter +# Magicians VR4Deaf Platform — API Documentation -## How to use this starter +OpenAPI 3.1 specification for the **Magicians VR4Deaf Platform** internal and +public API, rendered with [Redocly](https://redocly.com/). -![Click use template button](https://user-images.githubusercontent.com/3975738/92927304-12e35d80-f446-11ea-9bd3-a0f8a69792d0.png) +## Base URLs -## Working on your OpenAPI Definition +| Environment | URL | +|-------------|-----| +| Public API gateway | `https://magicians.vr4deaf.org/api/v1` | +| Internal (service-to-service, cluster only) | `https://internal.magicians.vr4deaf.org/api/v1` | -### Install - -1. Install [Node JS](https://nodejs.org/). -2. Clone this repo and run `npm install` in the repo root. - -### Usage - -#### `npm start` -Starts the reference docs preview server. +## API Sections -#### `npm test` -Validates the definition. +| Tag | Description | +|-----|-------------| +| **User** | User account management | +| **Admin** | Administrator-only operations | +| **Info** | Platform information and health | +| **Network** | Internal network topology and node status | +| **Registry** | Internal-only npm/Docker/Helm package registry | +| **Vendors** | Third-party vendor integrations | +| **Rate** | Rate limit policy management | +| **Rules** | Business rules engine (JSON Logic DSL) | +| **NPX** | Ephemeral Node package execution jobs | +| **Ingress** | Ingress routing table and path rewrites | +| **Database** | Database proxy layer — schema introspection | -## Contribution Guide +## Authentication -Below is a sample contribution guide. The tools -in the repository don't restrict you to any -specific structure. Adjust the contribution guide -to match your own structure. However, if you -don't have a structure in mind, this is a -good place to start. +Services use short-lived `X-Service-Token` JWTs (service-to-service). +Human administrators use OAuth2 implicit flow against +`https://magicians.vr4deaf.org/api/oauth/dialog`. -Update this contribution guide if you -adjust the file/folder organization. +Long-lived `api_key` header tokens are also supported for automated +integrations. -The `.redocly.yaml` controls settings for various -tools including the lint tool and the reference -docs engine. Open it to find examples and -[read the docs](https://redocly.com/docs/cli/configuration/) -for more information. +## Development +### Prerequisites -### Schemas +- [Node.js](https://nodejs.org/) ≥ 18 -#### Adding Schemas - -1. Navigate to the `openapi/components/schemas` folder. -2. Add a file named as you wish to name the schema. -3. Define the schema. -4. Refer to the schema using the `$ref` (see example below). - -##### Example Schema -This is a very simple schema example: -```yaml -type: string -description: The resource ID. Defaults to UUID v4 -maxLength: 50 -example: 4f6cf35x-2c4y-483z-a0a9-158621f77a21 -``` -This is a more complex schema example: -```yaml -type: object -properties: - id: - description: The customer identifier string - readOnly: true - allOf: - - $ref: ./ResourceId.yaml - websiteId: - description: The website's ID - allOf: - - $ref: ./ResourceId.yaml - paymentToken: - type: string - writeOnly: true - description: | - A write-only payment token; if supplied, it will be converted into a - payment instrument and be set as the `defaultPaymentInstrument`. The - value of this property will override the `defaultPaymentInstrument` - in the case that both are supplied. The token may only be used once - before it is expired. - defaultPaymentInstrument: - $ref: ./PaymentInstrument.yaml - createdTime: - description: The customer created time - allOf: - - $ref: ./ServerTimestamp.yaml - updatedTime: - description: The customer updated time - allOf: - - $ref: ./ServerTimestamp.yaml - tags: - description: A list of customer's tags - readOnly: true - type: array - items: - $ref: ./Tags/Tag.yaml - revision: - description: > - The number of times the customer data has been modified. - - The revision is useful when analyzing webhook data to determine if the - change takes precedence over the current representation. - type: integer - readOnly: true - _links: - type: array - description: The links related to resource - readOnly: true - minItems: 3 - items: - anyOf: - - $ref: ./Links/SelfLink.yaml - - $ref: ./Links/NotesLink.yaml - - $ref: ./Links/DefaultPaymentInstrumentLink.yaml - - $ref: ./Links/LeadSourceLink.yaml - - $ref: ./Links/WebsiteLink.yaml - _embedded: - type: array - description: >- - Any embedded objects available that are requested by the `expand` - querystring parameter. - readOnly: true - minItems: 1 - items: - anyOf: - - $ref: ./Embeds/LeadSourceEmbed.yaml +### Install +```bash +npm install ``` -If you have an JSON example, you can convert it to JSON schema using Redocly's [JSON to JSON schema tool](https://redocly.com/tools/json-to-json-schema/). +### Preview docs locally -##### Using the `$ref` - -Notice in the complex example above the schema definition itself has `$ref` links to other schemas defined. - -Here is a small excerpt with an example: - -```yaml -defaultPaymentInstrument: - $ref: ./PaymentInstrument.yaml +```bash +npm start ``` -The value of the `$ref` is the path to the other schema definition. - -You may use `$ref`s to compose schema from other existing schema to avoid duplication. +Opens a live-reloading Redoc preview at `http://localhost:8080`. -You will use `$ref`s to reference schema from your path definitions. +### Validate the spec -#### Editing Schemas - -1. Navigate to the `openapi/components/schemas` folder. -2. Open the file you wish to edit. -3. Edit. +```bash +npm test +``` -### Paths +Runs `redocly lint` against all API definitions. -#### Adding a Path +## Contributing -1. Navigate to the `openapi/paths` folder. -2. Add a new YAML file named like your URL endpoint except replacing `/` with `_` (or whichever character you prefer) and putting path parameters into curly braces like `{example}`. -3. Add the path and a ref to it inside of your `openapi.yaml` file inside of the `openapi` folder. +### File layout -Example addition to the `openapi.yaml` file: -```yaml -'/customers/{id}': - $ref: './paths/customers_{id}.yaml' ``` - -Here is an example of a YAML file named `customers_{id}.yaml` in the `paths` folder: - -```yaml -get: - tags: - - Customers - summary: Retrieve a list of customers - operationId: GetCustomerCollection - description: | - You can have a markdown description here. - parameters: - - $ref: ../components/parameters/collectionLimit.yaml - - $ref: ../components/parameters/collectionOffset.yaml - - $ref: ../components/parameters/collectionFilter.yaml - - $ref: ../components/parameters/collectionQuery.yaml - - $ref: ../components/parameters/collectionExpand.yaml - - $ref: ../components/parameters/collectionFields.yaml - responses: - '200': - description: A list of Customers was retrieved successfully - headers: - Rate-Limit-Limit: - $ref: ../components/headers/Rate-Limit-Limit.yaml - Rate-Limit-Remaining: - $ref: ../components/headers/Rate-Limit-Remaining.yaml - Rate-Limit-Reset: - $ref: ../components/headers/Rate-Limit-Reset.yaml - Pagination-Total: - $ref: ../components/headers/Pagination-Total.yaml - Pagination-Limit: - $ref: ../components/headers/Pagination-Limit.yaml - Pagination-Offset: - $ref: ../components/headers/Pagination-Offset.yaml - content: - application/json: - schema: - type: array - items: - $ref: ../components/schemas/Customer.yaml - text/csv: - schema: - type: array - items: - $ref: ../components/schemas/Customer.yaml - '401': - $ref: ../components/responses/AccessForbidden.yaml - x-code-samples: - - lang: PHP - source: - $ref: ../code_samples/PHP/customers/get.php -post: - tags: - - Customers - summary: Create a customer (without an ID) - operationId: PostCustomer - description: Another markdown description here. - requestBody: - $ref: ../components/requestBodies/Customer.yaml - responses: - '201': - $ref: ../components/responses/Customer.yaml - '401': - $ref: ../components/responses/AccessForbidden.yaml - '409': - $ref: ../components/responses/Conflict.yaml - '422': - $ref: ../components/responses/InvalidDataError.yaml - x-code-samples: - - lang: PHP - source: - $ref: ../code_samples/PHP/customers/post.php +openapi/ +├── openapi.yaml # Root spec (info, servers, paths index, webhooks) +├── paths/ # One YAML file per route +│ ├── network.yaml +│ ├── registry.yaml +│ ├── vendors.yaml +│ ├── rate.yaml +│ ├── rules.yaml +│ ├── npx.yaml +│ ├── ingress.yaml +│ ├── db.yaml +│ └── ... +└── components/ + ├── schemas/ # Reusable schema objects + ├── headers/ # Reusable response headers + └── responses/ # Reusable response objects ``` -You'll see extensive usage of `$ref`s in this example to different types of components including schemas. +### Adding a new path -You'll also notice `$ref`s to code samples. +1. Create `openapi/paths/.yaml` (replace `/` with `_`). +2. Add a `$ref` entry in `openapi/openapi.yaml` under `paths:`. +3. Run `npm test` to validate. -### Code samples +### Adding a new schema -Automated code sample generations is enabled in the Redocly configuration file. Add manual code samples by the following process: +1. Create `openapi/components/schemas/.yaml`. +2. Reference it in your path file with `$ref: '../components/schemas/.yaml'`. -1. Navigate to the `openapi/code_samples` folder. -2. Navigate to the `` (e.g. PHP) sub-folder. -3. Navigate to the `path` folder, and add ref to the code sample. +### Redocly configuration -You can add languages by adding new folders at the appropriate path level. +`redocly.yaml` controls linting rules and code-sample generation. +See the [Redocly CLI docs](https://redocly.com/docs/cli/configuration/) for +all available options. -More details inside the `code_samples` folder README. diff --git a/openapi/components/headers/Rate-Limit-Limit.yaml b/openapi/components/headers/Rate-Limit-Limit.yaml new file mode 100644 index 0000000..6fc73b8 --- /dev/null +++ b/openapi/components/headers/Rate-Limit-Limit.yaml @@ -0,0 +1,5 @@ +description: "The maximum number of requests allowed in the current window." +schema: + type: integer + format: int32 + example: 1000 diff --git a/openapi/components/headers/Rate-Limit-Remaining.yaml b/openapi/components/headers/Rate-Limit-Remaining.yaml new file mode 100644 index 0000000..3e3a1bc --- /dev/null +++ b/openapi/components/headers/Rate-Limit-Remaining.yaml @@ -0,0 +1,5 @@ +description: "The number of requests remaining in the current window." +schema: + type: integer + format: int32 + example: 847 diff --git a/openapi/components/headers/Rate-Limit-Reset.yaml b/openapi/components/headers/Rate-Limit-Reset.yaml new file mode 100644 index 0000000..292548a --- /dev/null +++ b/openapi/components/headers/Rate-Limit-Reset.yaml @@ -0,0 +1,5 @@ +description: "ISO 8601 timestamp when the current rate limit window resets." +schema: + type: string + format: date-time + example: "2024-01-15T10:00:00Z" diff --git a/openapi/components/schemas/DatabaseSchema.yaml b/openapi/components/schemas/DatabaseSchema.yaml new file mode 100644 index 0000000..6cd3a94 --- /dev/null +++ b/openapi/components/schemas/DatabaseSchema.yaml @@ -0,0 +1,46 @@ +type: object +required: + - schemaName + - engine +properties: + schemaName: + type: string + description: "Database schema / namespace name." + example: "users_db" + engine: + type: string + enum: + - postgres + - mysql + - sqlite + - mongo + description: "Database engine." + host: + type: string + description: "Database host (internal cluster DNS). Read-only from API; set via platform config." + readOnly: true + example: "postgres.default.svc.cluster.local" + port: + type: integer + readOnly: true + example: 5432 + tables: + type: array + readOnly: true + description: "Table names available in this schema (populated by introspection)." + items: + type: string + example: + - users + - sessions + - audit_log + readOnly: + type: boolean + default: false + description: "When true, only SELECT queries are permitted through the proxy." + connectionPoolSize: + type: integer + minimum: 1 + maximum: 100 + default: 10 + description: "Connection pool size." diff --git a/openapi/components/schemas/Email.yaml b/openapi/components/schemas/Email.yaml index 49268de..d4ca552 100644 --- a/openapi/components/schemas/Email.yaml +++ b/openapi/components/schemas/Email.yaml @@ -1,4 +1,4 @@ description: "User's email address." type: string format: email -example: bunny.rabbit@example.com +example: bunny.rabbit@magicians.vr4deaf.org diff --git a/openapi/components/schemas/IngressRoute.yaml b/openapi/components/schemas/IngressRoute.yaml new file mode 100644 index 0000000..66b3e4c --- /dev/null +++ b/openapi/components/schemas/IngressRoute.yaml @@ -0,0 +1,48 @@ +type: object +required: + - routeId + - host + - path + - backend +properties: + routeId: + type: string + description: "Unique route identifier." + example: "route-auth-001" + host: + type: string + description: "Virtual host to match (supports wildcard prefix, e.g. `*.magicians.vr4deaf.org`)." + example: "api.magicians.vr4deaf.org" + path: + type: string + description: "URL path prefix to match." + example: "/api/v1/users" + backend: + type: string + description: "Upstream service URL or cluster-internal service name." + example: "http://user-service.default.svc.cluster.local:3000" + rewritePath: + type: string + description: "If set, the matched path prefix is rewritten to this value before forwarding." + example: "/v1/users" + stripPrefix: + type: boolean + default: false + description: "Strip the matched path prefix before forwarding (ignored when `rewritePath` is set)." + priority: + type: integer + default: 100 + description: "Lower number = higher match priority." + tls: + type: boolean + default: true + description: "Whether to enforce TLS for this route." + enabled: + type: boolean + default: true + description: "Whether this route is active." + annotations: + type: object + additionalProperties: + type: string + description: "Key-value annotations forwarded to the underlying ingress controller." diff --git a/openapi/components/schemas/NetworkNode.yaml b/openapi/components/schemas/NetworkNode.yaml new file mode 100644 index 0000000..7ccbaf5 --- /dev/null +++ b/openapi/components/schemas/NetworkNode.yaml @@ -0,0 +1,38 @@ +type: object +required: + - nodeId + - host + - status +properties: + nodeId: + type: string + description: "Unique identifier for the network node." + example: "node-us-east-1a" + host: + type: string + description: "Hostname or IP of the node." + example: "10.0.1.5" + status: + type: string + enum: + - healthy + - degraded + - unreachable + description: "Current health status of the node." + zone: + type: string + description: "Availability zone the node resides in." + example: "us-east-1a" + services: + type: array + description: "Services running on this node." + items: + type: string + example: + - auth-service + - registry-service + lastSeen: + type: string + format: date-time + description: "Last heartbeat timestamp." + example: "2024-01-15T09:00:00Z" diff --git a/openapi/components/schemas/NpxJob.yaml b/openapi/components/schemas/NpxJob.yaml new file mode 100644 index 0000000..49f7ee9 --- /dev/null +++ b/openapi/components/schemas/NpxJob.yaml @@ -0,0 +1,65 @@ +type: object +required: + - package +properties: + jobId: + type: string + description: "Unique job identifier (UUID v4)." + example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + package: + type: string + description: "NPM package name (optionally scoped) to execute." + example: "@magicians/data-migrator" + version: + type: string + description: "Semver version constraint. Defaults to `latest`." + example: "^1.2.0" + default: "latest" + args: + type: array + description: "Command-line arguments passed to the package binary." + items: + type: string + example: + - "--env" + - "production" + env: + type: object + additionalProperties: + type: string + description: "Environment variables injected into the job container." + status: + type: string + enum: + - pending + - running + - succeeded + - failed + - cancelled + description: "Current job status." + readOnly: true + exitCode: + type: integer + description: "Process exit code (set when status is `succeeded` or `failed`)." + readOnly: true + logs: + type: string + description: "Captured stdout+stderr (available after completion)." + readOnly: true + startedAt: + type: string + format: date-time + readOnly: true + description: "When the job started running." + example: "2024-01-15T09:00:00Z" + completedAt: + type: string + format: date-time + readOnly: true + description: "When the job finished." + example: "2024-01-15T09:01:30Z" + requestedBy: + type: string + description: "Service or user that submitted the job." + example: "deploy-service" + readOnly: true diff --git a/openapi/components/schemas/RatePolicy.yaml b/openapi/components/schemas/RatePolicy.yaml new file mode 100644 index 0000000..3b4dc8b --- /dev/null +++ b/openapi/components/schemas/RatePolicy.yaml @@ -0,0 +1,43 @@ +type: object +required: + - resource + - limit + - windowSeconds +properties: + resource: + type: string + description: "Resource identifier (path prefix or service name) this policy applies to." + example: "/api/v1/users" + limit: + type: integer + minimum: 1 + description: "Maximum number of requests allowed per window." + example: 1000 + windowSeconds: + type: integer + minimum: 1 + description: "Rolling window duration in seconds." + example: 3600 + burstLimit: + type: integer + description: "Optional instantaneous burst allowance above the rolling limit." + example: 50 + scope: + type: string + enum: + - global + - per_user + - per_service + - per_ip + description: "Whether the limit is shared globally or applied per-entity." + default: per_user + currentCount: + type: integer + readOnly: true + description: "Requests consumed in the current window (read-only)." + resetsAt: + type: string + format: date-time + readOnly: true + description: "When the current window resets (read-only)." + example: "2024-01-15T10:00:00Z" diff --git a/openapi/components/schemas/RegistryPackage.yaml b/openapi/components/schemas/RegistryPackage.yaml new file mode 100644 index 0000000..d1130ba --- /dev/null +++ b/openapi/components/schemas/RegistryPackage.yaml @@ -0,0 +1,42 @@ +type: object +required: + - name + - version + - type +properties: + name: + type: string + description: "Package name (scoped or unscoped npm-style)." + example: "@magicians/auth-client" + version: + type: string + description: "Semantic version string." + example: "2.3.1" + type: + type: string + enum: + - npm + - docker + - helm + description: "Package type." + description: + type: string + description: "Short description of the package." + publishedAt: + type: string + format: date-time + description: "When this version was published." + example: "2024-01-10T14:30:00Z" + publishedBy: + type: string + description: "Service account or user that published the package." + example: "ci-service" + internal: + type: boolean + default: true + description: "Always `true` — packages in this registry are internal only." + tarballUrl: + type: string + format: uri + description: "URL to download the package tarball (requires `read:registry` scope)." + example: "https://internal.magicians.vr4deaf.org/registry/@magicians/auth-client/-/auth-client-2.3.1.tgz" diff --git a/openapi/components/schemas/Rule.yaml b/openapi/components/schemas/Rule.yaml new file mode 100644 index 0000000..24af8ad --- /dev/null +++ b/openapi/components/schemas/Rule.yaml @@ -0,0 +1,56 @@ +type: object +required: + - ruleId + - name + - expression +properties: + ruleId: + type: string + description: "Unique rule identifier." + example: "rule-auth-001" + name: + type: string + description: "Human-readable rule name." + example: "Block banned users" + description: + type: string + description: "What the rule does." + expression: + type: string + description: "Rule expression in the platform DSL (JSON Logic compatible)." + example: '{"in": [{"var": "user.status"}, ["banned"]]}' + enabled: + type: boolean + default: true + description: "Whether the rule is active." + priority: + type: integer + minimum: 0 + description: "Lower number = higher priority when multiple rules match." + default: 100 + actions: + type: array + description: "Actions to take when the rule evaluates to true." + items: + type: object + properties: + type: + type: string + enum: + - block + - redirect + - notify + - tag + payload: + type: object + additionalProperties: true + createdAt: + type: string + format: date-time + readOnly: true + example: "2024-01-01T00:00:00Z" + updatedAt: + type: string + format: date-time + readOnly: true + example: "2024-01-15T12:00:00Z" diff --git a/openapi/components/schemas/RuleEvent.yaml b/openapi/components/schemas/RuleEvent.yaml new file mode 100644 index 0000000..3255cfc --- /dev/null +++ b/openapi/components/schemas/RuleEvent.yaml @@ -0,0 +1,26 @@ +type: object +required: + - ruleId + - result + - triggeredAt +properties: + ruleId: + type: string + description: "The ID of the rule that was evaluated." + example: "rule-auth-001" + result: + type: boolean + description: "Whether the rule evaluated to true." + context: + type: object + additionalProperties: true + description: "The context object passed to the rule engine at evaluation time." + triggeredAt: + type: string + format: date-time + description: "ISO 8601 timestamp of when the rule was triggered." + example: "2024-01-15T09:00:00Z" + serviceId: + type: string + description: "The originating service identifier." + example: "auth-service" diff --git a/openapi/components/schemas/User.yaml b/openapi/components/schemas/User.yaml index 7c817f8..015910c 100644 --- a/openapi/components/schemas/User.yaml +++ b/openapi/components/schemas/User.yaml @@ -63,7 +63,7 @@ properties: description: "Unique ID of the user." externalDocs: description: Example of external documentation link - url: 'https://example.com' + url: 'https://magicians.vr4deaf.org/docs/user-id' allOf: - $ref: './UserID.yaml' xml: diff --git a/openapi/components/schemas/Vendor.yaml b/openapi/components/schemas/Vendor.yaml new file mode 100644 index 0000000..185a346 --- /dev/null +++ b/openapi/components/schemas/Vendor.yaml @@ -0,0 +1,43 @@ +type: object +required: + - vendorId + - name + - status +properties: + vendorId: + type: string + description: "Unique vendor identifier." + example: "vendor-stripe-001" + name: + type: string + description: "Human-readable vendor name." + example: "Stripe" + status: + type: string + enum: + - active + - suspended + - pending + description: "Integration status." + baseUrl: + type: string + format: uri + description: "The vendor's API base URL as configured." + example: "https://api.stripe.com/v1" + scopes: + type: array + items: + type: string + description: "Platform scopes granted to this vendor." + example: + - "read:users" + webhookUrl: + type: string + format: uri + description: "Endpoint where the platform pushes events to this vendor." + example: "https://vendor-endpoint.example.com/webhooks/magicians" + createdAt: + type: string + format: date-time + description: "When this vendor integration was created." + example: "2024-01-01T00:00:00Z" diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 7e231f6..725c217 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,75 +1,100 @@ openapi: 3.1.0 info: version: 1.0.0 - title: Example API - termsOfService: https://example.com/terms/ + title: Magicians VR4Deaf Platform API + termsOfService: https://magicians.vr4deaf.org/terms/ contact: - name: Contact our support - email: contact@example.com - url: http://example.com/contact + name: Platform Support + email: contact@magicians.vr4deaf.org + url: https://magicians.vr4deaf.org/contact license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html x-logo: - url: 'https://redocly.github.io/openapi-template/logo.png' - altText: OpenAPI example logo + url: 'https://magicians.vr4deaf.org/logo.png' + altText: Magicians VR4Deaf Platform logo description: > - This is an **example** API to demonstrate features of the OpenAPI - specification. + This is the **Magicians VR4Deaf Platform** internal API. # Introduction - This API definition is intended to to be a good starting point for - describing your API in [OpenAPI/Swagger - format](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md). + The Magicians VR4Deaf Platform exposes REST endpoints for internal services, + vendor integrations, network management, registry access, ingress routing, + rate limiting, business rules, NPX task execution, service-to-service + communication, and database operations. - It also demonstrates features of the - [create-openapi-repo](https://github.com/Redocly/create-openapi-repo) tool - and the [Redoc](https://github.com/Redocly/Redoc) documentation engine. Beyond - the standard OpenAPI syntax, we use a few - [vendor extensions](https://github.com/Redocly/Redoc/blob/main/docs/redoc-vendor-extensions.md). + All endpoints are **internal only** unless explicitly marked public. - # OpenAPI Specification + # Authentication - The goal of The OpenAPI Specification is to define a standard, language-agnostic interface to REST APIs which - allows both humans and computers to discover and understand the capabilities - of the service without access to source - code, documentation, or through network traffic inspection. When properly - defined via OpenAPI, a consumer can - understand and interact with the remote service with a minimal amount of - implementation logic. Similar to what - interfaces have done for lower-level programming, OpenAPI removes the - guesswork in calling the service. + Services authenticate using short-lived JWT bearer tokens issued by the + platform OAuth2 server at + `https://magicians.vr4deaf.org/api/oauth/dialog`. Service accounts use the + `service_account` grant type. Human administrators use the implicit flow. + + # Rate Limiting + + Every endpoint enforces rate limits. Limits are returned in the + `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining`, and `X-Rate-Limit-Reset` + response headers. When a limit is exceeded the API responds with + `429 Too Many Requests`. + + # Service-to-Service Communication + + Internal services call each other through the cluster ingress at + `https://internal.magicians.vr4deaf.org`. Requests must carry a valid + `X-Service-Token` header issued by the `/auth/service-token` endpoint. externalDocs: - description: "Find out how to create a GitHub repo for your OpenAPI definition." - url: 'https://github.com/Redocly/create-openapi-repo' + description: "Platform internal wiki and runbooks." + url: 'https://magicians.vr4deaf.org/docs' tags: - - name: Echo - description: "Example echo operations." - name: User - description: "Example actions on user accounts." + description: "User account management." - name: Admin - description: "Example operations reserved for administrators." + description: "Operations reserved for administrators." - name: Info - description: "Example operations for retrieving information." - - name: Tag - description: "This is a tag description." + description: "Platform information and health." + - name: Network + description: "Internal network topology and status." + - name: Registry + description: "Internal-only package/image registry. Requires registry scope." + - name: Vendors + description: "Third-party vendor integrations." + - name: Rate + description: "Rate limiting configuration and current state." + - name: Rules + description: "Business rules engine — create, read, update, and evaluate rules." + - name: NPX + description: "NPX task execution — run Node packages as ephemeral service jobs." + - name: Ingress + description: "Ingress routing table — manage virtual hosts and path rewrites." + - name: Database + description: "Database proxy layer — schema introspection and query management." x-tagGroups: - - name: General + - name: Users tags: - User + - Admin + - name: Platform + tags: - Info - - Echo - - name: Administration + - Network + - Ingress + - name: Data & Rules tags: - - Admin + - Database + - Rules + - Rate + - name: Integrations + tags: + - Vendors + - Registry + - NPX servers: - - url: https://{tenant}/api/v1 - variables: - tenant: - default: www - description: Your tenant id - - url: https://example.com/api/v1 + - url: https://magicians.vr4deaf.org/api/v1 + description: Public API gateway + - url: https://internal.magicians.vr4deaf.org/api/v1 + description: Internal service-to-service endpoint (cluster network only) paths: '/users/{username}': $ref: 'paths/users_{username}.yaml' @@ -77,36 +102,74 @@ paths: $ref: 'paths/user.yaml' '/user/list': $ref: 'paths/user-status.yaml' - /pathItem: - $ref: paths/pathItem.yaml - /pathItemWithExamples: - $ref: paths/pathItemWithExamples.yaml - '/echo': - $ref: 'paths/echo.yaml' + '/network': + $ref: 'paths/network.yaml' + '/network/status': + $ref: 'paths/network_status.yaml' + '/registry': + $ref: 'paths/registry.yaml' + '/registry/{package}': + $ref: 'paths/registry_{package}.yaml' + '/vendors': + $ref: 'paths/vendors.yaml' + '/vendors/{vendorId}': + $ref: 'paths/vendors_{vendorId}.yaml' + '/rate': + $ref: 'paths/rate.yaml' + '/rate/{resource}': + $ref: 'paths/rate_{resource}.yaml' + '/rules': + $ref: 'paths/rules.yaml' + '/rules/{ruleId}': + $ref: 'paths/rules_{ruleId}.yaml' + '/rules/{ruleId}/evaluate': + $ref: 'paths/rules_{ruleId}_evaluate.yaml' + '/npx': + $ref: 'paths/npx.yaml' + '/npx/{jobId}': + $ref: 'paths/npx_{jobId}.yaml' + '/ingress': + $ref: 'paths/ingress.yaml' + '/ingress/{routeId}': + $ref: 'paths/ingress_{routeId}.yaml' + '/db': + $ref: 'paths/db.yaml' + '/db/{schema}': + $ref: 'paths/db_{schema}.yaml' components: securitySchemes: main_auth: - description: "Example description text of the OAuth2 scheme." + description: "OAuth2 implicit flow for human administrators." type: oauth2 flows: implicit: - authorizationUrl: http://example.com/api/oauth/dialog + authorizationUrl: https://magicians.vr4deaf.org/api/oauth/dialog scopes: 'read:users': read user info 'write:users': modify or remove users + 'read:registry': read internal registry + 'write:registry': publish to internal registry + 'manage:ingress': manage ingress routes + 'manage:rules': manage business rules + 'manage:rate': manage rate limit policies + 'manage:db': database proxy access + 'manage:vendors': vendor integration management + 'run:npx': execute NPX jobs + service_token: + description: "Short-lived service token for service-to-service calls. Pass in the `X-Service-Token` header." + type: apiKey + in: header + name: X-Service-Token api_key: - description: "Example description text of the API key scheme." + description: "Long-lived API key for automated integrations." type: apiKey in: header name: api_key - basic_auth: - type: http - scheme: basic webhooks: userInfo: post: summary: New user webhook - description: "Information about a new user in the system." + description: "Fired when a new user is created in the platform." operationId: userInfo tags: - Info @@ -117,6 +180,23 @@ webhooks: $ref: 'components/schemas/User.yaml' responses: '200': - description: "Successfully retrieved information about a new user." + description: "Successfully processed user creation event." security: - api_key: [] + ruleTriggered: + post: + summary: Rule triggered webhook + description: "Fired when a business rule evaluation returns `true`." + operationId: ruleTriggered + tags: + - Rules + requestBody: + content: + application/json: + schema: + $ref: 'components/schemas/RuleEvent.yaml' + responses: + '200': + description: "Event acknowledged." + security: + - service_token: [] diff --git a/openapi/paths/db.yaml b/openapi/paths/db.yaml new file mode 100644 index 0000000..8f9ff48 --- /dev/null +++ b/openapi/paths/db.yaml @@ -0,0 +1,27 @@ +get: + tags: + - Database + summary: List database schemas + description: | + Returns all database schemas accessible through the platform database proxy. + + **Internal only** — requires `manage:db` scope. This endpoint is only + reachable via the internal service-to-service endpoint. + operationId: listDatabaseSchemas + security: + - service_token: [] + - main_auth: + - 'manage:db' + responses: + '200': + description: "List of database schemas." + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/DatabaseSchema.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/db_{schema}.yaml b/openapi/paths/db_{schema}.yaml new file mode 100644 index 0000000..16e532b --- /dev/null +++ b/openapi/paths/db_{schema}.yaml @@ -0,0 +1,61 @@ +parameters: + - name: schema + in: path + required: true + description: "Database schema / namespace name." + schema: + type: string + example: "users_db" +get: + tags: + - Database + summary: Get database schema details + description: "Returns metadata for a single database schema, including the table list populated by live introspection." + operationId: getDatabaseSchema + security: + - service_token: [] + - main_auth: + - 'manage:db' + responses: + '200': + description: "Database schema details." + content: + application/json: + schema: + $ref: '../components/schemas/DatabaseSchema.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +put: + tags: + - Database + summary: Update database schema configuration + description: "Updates proxy-level configuration for a schema (pool size, read-only flag). Does **not** modify the underlying database DDL." + operationId: updateDatabaseSchema + security: + - main_auth: + - 'manage:db' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/DatabaseSchema.yaml' + responses: + '200': + description: "Schema configuration updated." + content: + application/json: + schema: + $ref: '../components/schemas/DatabaseSchema.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/ingress.yaml b/openapi/paths/ingress.yaml new file mode 100644 index 0000000..f14ce3e --- /dev/null +++ b/openapi/paths/ingress.yaml @@ -0,0 +1,58 @@ +get: + tags: + - Ingress + summary: List ingress routes + description: "Returns all configured ingress routes in priority order." + operationId: listIngressRoutes + security: + - main_auth: + - 'manage:ingress' + - service_token: [] + responses: + '200': + description: "List of ingress routes." + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/IngressRoute.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml +post: + tags: + - Ingress + summary: Create an ingress route + description: | + Adds a new route to the ingress table. Changes propagate to the cluster + ingress controller within ~30 seconds. + + Requires `manage:ingress` scope. + operationId: createIngressRoute + security: + - main_auth: + - 'manage:ingress' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/IngressRoute.yaml' + responses: + '201': + description: "Ingress route created." + content: + application/json: + schema: + $ref: '../components/schemas/IngressRoute.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '409': + description: "A route with this host+path already exists." + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/ingress_{routeId}.yaml b/openapi/paths/ingress_{routeId}.yaml new file mode 100644 index 0000000..df90c56 --- /dev/null +++ b/openapi/paths/ingress_{routeId}.yaml @@ -0,0 +1,78 @@ +parameters: + - name: routeId + in: path + required: true + description: "Unique route identifier." + schema: + type: string + example: "route-auth-001" +get: + tags: + - Ingress + summary: Get an ingress route + operationId: getIngressRoute + security: + - main_auth: + - 'manage:ingress' + - service_token: [] + responses: + '200': + description: "Ingress route details." + content: + application/json: + schema: + $ref: '../components/schemas/IngressRoute.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +put: + tags: + - Ingress + summary: Update an ingress route + description: "Updates an existing route. Path rewrites and backend changes propagate to the ingress controller within ~30 seconds." + operationId: updateIngressRoute + security: + - main_auth: + - 'manage:ingress' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/IngressRoute.yaml' + responses: + '200': + description: "Ingress route updated." + content: + application/json: + schema: + $ref: '../components/schemas/IngressRoute.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +delete: + tags: + - Ingress + summary: Delete an ingress route + description: "Removes an ingress route. Traffic previously handled by this route will return 404 once propagation completes (~30 seconds)." + operationId: deleteIngressRoute + security: + - main_auth: + - 'manage:ingress' + responses: + '204': + description: "Ingress route deleted." + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/network.yaml b/openapi/paths/network.yaml new file mode 100644 index 0000000..95274fd --- /dev/null +++ b/openapi/paths/network.yaml @@ -0,0 +1,30 @@ +get: + tags: + - Network + summary: List network nodes + description: "Returns all nodes registered in the internal network topology." + operationId: listNetworkNodes + security: + - service_token: [] + - main_auth: + - 'read:users' + responses: + '200': + description: "List of network nodes." + headers: + X-Rate-Limit-Limit: + $ref: ../components/headers/Rate-Limit-Limit.yaml + X-Rate-Limit-Remaining: + $ref: ../components/headers/Rate-Limit-Remaining.yaml + X-Rate-Limit-Reset: + $ref: ../components/headers/Rate-Limit-Reset.yaml + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/NetworkNode.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/network_status.yaml b/openapi/paths/network_status.yaml new file mode 100644 index 0000000..3f2821f --- /dev/null +++ b/openapi/paths/network_status.yaml @@ -0,0 +1,50 @@ +get: + tags: + - Network + summary: Get overall network status + description: "Returns aggregated health of the internal network — node counts by status, latency p50/p99, and any active incidents." + operationId: getNetworkStatus + security: + - service_token: [] + - main_auth: + - 'read:users' + responses: + '200': + description: "Network status summary." + content: + application/json: + schema: + type: object + properties: + healthy: + type: integer + description: "Number of healthy nodes." + example: 12 + degraded: + type: integer + description: "Number of degraded nodes." + example: 1 + unreachable: + type: integer + description: "Number of unreachable nodes." + example: 0 + latencyP50Ms: + type: number + description: "p50 inter-node latency in milliseconds." + example: 2.4 + latencyP99Ms: + type: number + description: "p99 inter-node latency in milliseconds." + example: 18.7 + activeIncidents: + type: integer + description: "Number of active network incidents." + example: 0 + checkedAt: + type: string + format: date-time + example: "2024-01-15T09:00:00Z" + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/npx.yaml b/openapi/paths/npx.yaml new file mode 100644 index 0000000..3e2c77e --- /dev/null +++ b/openapi/paths/npx.yaml @@ -0,0 +1,97 @@ +get: + tags: + - NPX + summary: List NPX jobs + description: "Returns recent NPX job executions. Jobs older than 7 days are automatically purged." + operationId: listNpxJobs + security: + - service_token: [] + - main_auth: + - 'run:npx' + parameters: + - name: status + in: query + description: "Filter by job status." + schema: + type: string + enum: + - pending + - running + - succeeded + - failed + - cancelled + - name: limit + in: query + description: "Maximum number of jobs to return." + schema: + type: integer + default: 50 + maximum: 200 + responses: + '200': + description: "List of NPX jobs." + headers: + X-Rate-Limit-Limit: + $ref: ../components/headers/Rate-Limit-Limit.yaml + X-Rate-Limit-Remaining: + $ref: ../components/headers/Rate-Limit-Remaining.yaml + X-Rate-Limit-Reset: + $ref: ../components/headers/Rate-Limit-Reset.yaml + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/NpxJob.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml +post: + tags: + - NPX + summary: Submit an NPX job + description: | + Submits a Node package for execution as an ephemeral job container. + + The package is pulled from the internal registry (requires `read:registry` + on the service account) or from the public npm registry if not found + internally. + + Jobs are queued and run asynchronously. Poll `GET /npx/{jobId}` for status, + or subscribe to the `npxJobCompleted` webhook. + + **Service-to-service use**: internal services should include + `X-Service-Token` and set `requestedBy` to their service name. + operationId: submitNpxJob + security: + - service_token: [] + - main_auth: + - 'run:npx' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/NpxJob.yaml' + example: + package: "@magicians/data-migrator" + version: "^1.2.0" + args: + - "--env" + - "production" + env: + LOG_LEVEL: "info" + responses: + '202': + description: "Job accepted and queued." + content: + application/json: + schema: + $ref: '../components/schemas/NpxJob.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/npx_{jobId}.yaml b/openapi/paths/npx_{jobId}.yaml new file mode 100644 index 0000000..227d9a2 --- /dev/null +++ b/openapi/paths/npx_{jobId}.yaml @@ -0,0 +1,55 @@ +parameters: + - name: jobId + in: path + required: true + description: "NPX job UUID." + schema: + type: string + format: uuid + example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +get: + tags: + - NPX + summary: Get NPX job status + description: "Returns the current status and, once completed, logs and exit code for an NPX job." + operationId: getNpxJob + security: + - service_token: [] + - main_auth: + - 'run:npx' + responses: + '200': + description: "NPX job details." + content: + application/json: + schema: + $ref: '../components/schemas/NpxJob.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +delete: + tags: + - NPX + summary: Cancel an NPX job + description: "Attempts to cancel a `pending` or `running` job. Has no effect on completed jobs." + operationId: cancelNpxJob + security: + - service_token: [] + - main_auth: + - 'run:npx' + responses: + '200': + description: "Cancellation requested." + content: + application/json: + schema: + $ref: '../components/schemas/NpxJob.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/rate.yaml b/openapi/paths/rate.yaml new file mode 100644 index 0000000..88f7567 --- /dev/null +++ b/openapi/paths/rate.yaml @@ -0,0 +1,54 @@ +get: + tags: + - Rate + summary: List rate limit policies + description: "Returns all configured rate limit policies." + operationId: listRatePolicies + security: + - main_auth: + - 'manage:rate' + - service_token: [] + responses: + '200': + description: "List of rate policies." + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/RatePolicy.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml +post: + tags: + - Rate + summary: Create a rate limit policy + description: "Creates a new rate limit policy for a resource. Requires `manage:rate` scope." + operationId: createRatePolicy + security: + - main_auth: + - 'manage:rate' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/RatePolicy.yaml' + responses: + '201': + description: "Rate policy created." + content: + application/json: + schema: + $ref: '../components/schemas/RatePolicy.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '409': + description: "A policy for this resource already exists." + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/rate_{resource}.yaml b/openapi/paths/rate_{resource}.yaml new file mode 100644 index 0000000..84513b1 --- /dev/null +++ b/openapi/paths/rate_{resource}.yaml @@ -0,0 +1,76 @@ +parameters: + - name: resource + in: path + required: true + description: "URL-encoded resource identifier the policy targets." + schema: + type: string + example: "%2Fapi%2Fv1%2Fusers" +get: + tags: + - Rate + summary: Get a rate limit policy + operationId: getRatePolicy + security: + - main_auth: + - 'manage:rate' + - service_token: [] + responses: + '200': + description: "Rate policy for the resource, including current window state." + content: + application/json: + schema: + $ref: '../components/schemas/RatePolicy.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +put: + tags: + - Rate + summary: Update a rate limit policy + operationId: updateRatePolicy + security: + - main_auth: + - 'manage:rate' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/RatePolicy.yaml' + responses: + '200': + description: "Rate policy updated." + content: + application/json: + schema: + $ref: '../components/schemas/RatePolicy.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +delete: + tags: + - Rate + summary: Delete a rate limit policy + operationId: deleteRatePolicy + security: + - main_auth: + - 'manage:rate' + responses: + '204': + description: "Rate policy deleted." + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/registry.yaml b/openapi/paths/registry.yaml new file mode 100644 index 0000000..07f3440 --- /dev/null +++ b/openapi/paths/registry.yaml @@ -0,0 +1,85 @@ +get: + tags: + - Registry + summary: List registry packages + description: | + Lists all packages in the internal registry. + + **Internal only** — requires `read:registry` scope. This endpoint is not + reachable from the public API gateway. + operationId: listRegistryPackages + security: + - service_token: [] + - main_auth: + - 'read:registry' + parameters: + - name: type + in: query + description: "Filter by package type." + schema: + type: string + enum: + - npm + - docker + - helm + - name: q + in: query + description: "Search packages by name." + schema: + type: string + example: "@magicians/" + responses: + '200': + description: "List of registry packages." + headers: + X-Rate-Limit-Limit: + $ref: ../components/headers/Rate-Limit-Limit.yaml + X-Rate-Limit-Remaining: + $ref: ../components/headers/Rate-Limit-Remaining.yaml + X-Rate-Limit-Reset: + $ref: ../components/headers/Rate-Limit-Reset.yaml + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/RegistryPackage.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml +post: + tags: + - Registry + summary: Publish a package + description: | + Publishes a new package version to the internal registry. + + **Internal only** — requires `write:registry` scope. + operationId: publishRegistryPackage + security: + - service_token: [] + - main_auth: + - 'write:registry' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/RegistryPackage.yaml' + responses: + '201': + description: "Package published successfully." + content: + application/json: + schema: + $ref: '../components/schemas/RegistryPackage.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '409': + description: "Package version already exists." + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/registry_{package}.yaml b/openapi/paths/registry_{package}.yaml new file mode 100644 index 0000000..1d3d850 --- /dev/null +++ b/openapi/paths/registry_{package}.yaml @@ -0,0 +1,52 @@ +parameters: + - name: package + in: path + description: "URL-encoded package name, e.g. `%40magicians%2Fauth-client`." + required: true + schema: + type: string + example: "%40magicians%2Fauth-client" +get: + tags: + - Registry + summary: Get package details + description: "Returns metadata for the latest version of a package. Append `@` to the name to retrieve a specific version." + operationId: getRegistryPackage + security: + - service_token: [] + - main_auth: + - 'read:registry' + responses: + '200': + description: "Package metadata." + content: + application/json: + schema: + $ref: '../components/schemas/RegistryPackage.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +delete: + tags: + - Registry + summary: Unpublish a package version + description: | + Removes a specific package version from the registry. + + Requires `write:registry` scope. Deletion is permanent and cannot be undone. + operationId: deleteRegistryPackage + security: + - main_auth: + - 'write:registry' + responses: + '204': + description: "Package version deleted." + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/rules.yaml b/openapi/paths/rules.yaml new file mode 100644 index 0000000..1fe6c95 --- /dev/null +++ b/openapi/paths/rules.yaml @@ -0,0 +1,57 @@ +get: + tags: + - Rules + summary: List business rules + description: "Returns all rules defined in the platform rules engine." + operationId: listRules + security: + - main_auth: + - 'manage:rules' + - service_token: [] + parameters: + - name: enabled + in: query + description: "Filter by enabled state." + schema: + type: boolean + responses: + '200': + description: "List of rules." + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/Rule.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml +post: + tags: + - Rules + summary: Create a rule + description: "Adds a new rule to the rules engine. Requires `manage:rules` scope." + operationId: createRule + security: + - main_auth: + - 'manage:rules' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/Rule.yaml' + responses: + '201': + description: "Rule created." + content: + application/json: + schema: + $ref: '../components/schemas/Rule.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/rules_{ruleId}.yaml b/openapi/paths/rules_{ruleId}.yaml new file mode 100644 index 0000000..d9bc92b --- /dev/null +++ b/openapi/paths/rules_{ruleId}.yaml @@ -0,0 +1,76 @@ +parameters: + - name: ruleId + in: path + required: true + description: "Unique rule identifier." + schema: + type: string + example: "rule-auth-001" +get: + tags: + - Rules + summary: Get a rule + operationId: getRule + security: + - main_auth: + - 'manage:rules' + - service_token: [] + responses: + '200': + description: "Rule details." + content: + application/json: + schema: + $ref: '../components/schemas/Rule.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +put: + tags: + - Rules + summary: Update a rule + operationId: updateRule + security: + - main_auth: + - 'manage:rules' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/Rule.yaml' + responses: + '200': + description: "Rule updated." + content: + application/json: + schema: + $ref: '../components/schemas/Rule.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +delete: + tags: + - Rules + summary: Delete a rule + operationId: deleteRule + security: + - main_auth: + - 'manage:rules' + responses: + '204': + description: "Rule deleted." + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/rules_{ruleId}_evaluate.yaml b/openapi/paths/rules_{ruleId}_evaluate.yaml new file mode 100644 index 0000000..853d3e9 --- /dev/null +++ b/openapi/paths/rules_{ruleId}_evaluate.yaml @@ -0,0 +1,60 @@ +parameters: + - name: ruleId + in: path + required: true + description: "Unique rule identifier." + schema: + type: string + example: "rule-auth-001" +post: + tags: + - Rules + summary: Evaluate a rule + description: | + Evaluates a rule against a provided context object and returns whether it + triggered. Useful for testing rules before enabling them in production and + for ad-hoc service-to-service rule checks. + operationId: evaluateRule + security: + - service_token: [] + - main_auth: + - 'manage:rules' + requestBody: + required: true + description: "Context object to pass to the rule expression." + content: + application/json: + schema: + type: object + additionalProperties: true + example: + user: + status: "banned" + id: "usr-123" + responses: + '200': + description: "Rule evaluation result." + content: + application/json: + schema: + type: object + properties: + ruleId: + type: string + example: "rule-auth-001" + result: + type: boolean + description: "Whether the rule expression evaluated to true." + actions: + type: array + description: "Actions that would be taken (if rule result is true)." + items: + type: object + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/users_{username}.yaml b/openapi/paths/users_{username}.yaml index a615ba9..224d546 100644 --- a/openapi/paths/users_{username}.yaml +++ b/openapi/paths/users_{username}.yaml @@ -48,7 +48,7 @@ get: $ref: '../components/schemas/User.yaml' example: username: 'user1' - email: 'user@example.com' + email: 'user@magicians.vr4deaf.org' '403': description: Forbidden $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/vendors.yaml b/openapi/paths/vendors.yaml new file mode 100644 index 0000000..0784d67 --- /dev/null +++ b/openapi/paths/vendors.yaml @@ -0,0 +1,54 @@ +get: + tags: + - Vendors + summary: List vendor integrations + description: "Returns all configured third-party vendor integrations." + operationId: listVendors + security: + - main_auth: + - 'manage:vendors' + - service_token: [] + responses: + '200': + description: "List of vendor integrations." + content: + application/json: + schema: + type: array + items: + $ref: '../components/schemas/Vendor.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml +post: + tags: + - Vendors + summary: Register a vendor integration + description: "Registers a new third-party vendor integration. Requires `manage:vendors` scope." + operationId: createVendor + security: + - main_auth: + - 'manage:vendors' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/Vendor.yaml' + responses: + '201': + description: "Vendor integration registered." + content: + application/json: + schema: + $ref: '../components/schemas/Vendor.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '409': + description: "A vendor with this ID already exists." + $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/vendors_{vendorId}.yaml b/openapi/paths/vendors_{vendorId}.yaml new file mode 100644 index 0000000..4591175 --- /dev/null +++ b/openapi/paths/vendors_{vendorId}.yaml @@ -0,0 +1,76 @@ +parameters: + - name: vendorId + in: path + required: true + description: "Unique vendor identifier." + schema: + type: string + example: "vendor-stripe-001" +get: + tags: + - Vendors + summary: Get a vendor integration + operationId: getVendor + security: + - main_auth: + - 'manage:vendors' + - service_token: [] + responses: + '200': + description: "Vendor integration details." + content: + application/json: + schema: + $ref: '../components/schemas/Vendor.yaml' + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +put: + tags: + - Vendors + summary: Update a vendor integration + operationId: updateVendor + security: + - main_auth: + - 'manage:vendors' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/Vendor.yaml' + responses: + '200': + description: "Vendor integration updated." + content: + application/json: + schema: + $ref: '../components/schemas/Vendor.yaml' + '400': + $ref: ../components/responses/Problem.yaml + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml +delete: + tags: + - Vendors + summary: Remove a vendor integration + operationId: deleteVendor + security: + - main_auth: + - 'manage:vendors' + responses: + '204': + description: "Vendor integration removed." + '401': + $ref: ../components/responses/Problem.yaml + '403': + $ref: ../components/responses/Problem.yaml + '404': + $ref: ../components/responses/Problem.yaml diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2f76892 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "acme-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "acme-api", + "version": "1.0.0", + "dependencies": { + "@redocly/cli": "2.36.0" + } + }, + "node_modules/@redocly/cli": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.36.0.tgz", + "integrity": "sha512-0ky8u/Zzx4zT35rJZY27KOuzXMI29JLt5FMbfzFV7AtVlK3DLjDmJjqprnhqUMJlt04R+FKSXahBZo1dUF6R5A==", + "license": "MIT", + "bin": { + "openapi": "bin/cli.js", + "redocly": "bin/cli.js" + }, + "engines": { + "node": ">=22.12.0 || >=20.19.0 <21.0.0", + "npm": ">=10" + } + } + } +} From 9ad12a5698e2e6d04f7c6dae57f8d5ee4d47d7a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 01:39:26 +0000 Subject: [PATCH 2/3] fix: rename package to magicians-platform-api, fix vendor webhook example domain --- openapi/components/schemas/Vendor.yaml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi/components/schemas/Vendor.yaml b/openapi/components/schemas/Vendor.yaml index 185a346..59ab744 100644 --- a/openapi/components/schemas/Vendor.yaml +++ b/openapi/components/schemas/Vendor.yaml @@ -35,7 +35,7 @@ properties: type: string format: uri description: "Endpoint where the platform pushes events to this vendor." - example: "https://vendor-endpoint.example.com/webhooks/magicians" + example: "https://external-vendor.stripe.com/webhooks/magicians" createdAt: type: string format: date-time diff --git a/package.json b/package.json index 8e08d98..9ed46d5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "acme-api", + "name": "magicians-platform-api", "version": "1.0.0", "dependencies": { "@redocly/cli": "2.36.0" From 0e6ad26ad7e365972d13db1a959532f228e53142 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:53:00 +0000 Subject: [PATCH 3/3] fix: address review feedback on OpenAPI docs --- README.md | 2 +- openapi/openapi.yaml | 1 + openapi/paths/db.yaml | 3 +++ openapi/paths/ingress.yaml | 1 - openapi/paths/network.yaml | 2 +- openapi/paths/network_status.yaml | 2 +- openapi/paths/rate.yaml | 1 - openapi/paths/registry.yaml | 4 +++- openapi/paths/vendors.yaml | 1 - 9 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4a0f7e7..a6307e3 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ integrations. ### Prerequisites -- [Node.js](https://nodejs.org/) ≥ 18 +- [Node.js](https://nodejs.org/) ≥ 20.19.0 or ≥ 22.12.0 ### Install diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 725c217..9b1dfa3 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -147,6 +147,7 @@ components: scopes: 'read:users': read user info 'write:users': modify or remove users + 'read:network': read internal network status 'read:registry': read internal registry 'write:registry': publish to internal registry 'manage:ingress': manage ingress routes diff --git a/openapi/paths/db.yaml b/openapi/paths/db.yaml index 8f9ff48..a2a61f7 100644 --- a/openapi/paths/db.yaml +++ b/openapi/paths/db.yaml @@ -1,3 +1,6 @@ +servers: + - url: https://internal.magicians.vr4deaf.org/api/v1 + description: Internal service-to-service endpoint (cluster network only) get: tags: - Database diff --git a/openapi/paths/ingress.yaml b/openapi/paths/ingress.yaml index f14ce3e..eaed12d 100644 --- a/openapi/paths/ingress.yaml +++ b/openapi/paths/ingress.yaml @@ -54,5 +54,4 @@ post: '403': $ref: ../components/responses/Problem.yaml '409': - description: "A route with this host+path already exists." $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/network.yaml b/openapi/paths/network.yaml index 95274fd..e3065bd 100644 --- a/openapi/paths/network.yaml +++ b/openapi/paths/network.yaml @@ -7,7 +7,7 @@ get: security: - service_token: [] - main_auth: - - 'read:users' + - 'read:network' responses: '200': description: "List of network nodes." diff --git a/openapi/paths/network_status.yaml b/openapi/paths/network_status.yaml index 3f2821f..2a88351 100644 --- a/openapi/paths/network_status.yaml +++ b/openapi/paths/network_status.yaml @@ -7,7 +7,7 @@ get: security: - service_token: [] - main_auth: - - 'read:users' + - 'read:network' responses: '200': description: "Network status summary." diff --git a/openapi/paths/rate.yaml b/openapi/paths/rate.yaml index 88f7567..666277b 100644 --- a/openapi/paths/rate.yaml +++ b/openapi/paths/rate.yaml @@ -50,5 +50,4 @@ post: '403': $ref: ../components/responses/Problem.yaml '409': - description: "A policy for this resource already exists." $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/registry.yaml b/openapi/paths/registry.yaml index 07f3440..7fcfd5e 100644 --- a/openapi/paths/registry.yaml +++ b/openapi/paths/registry.yaml @@ -1,3 +1,6 @@ +servers: + - url: https://internal.magicians.vr4deaf.org/api/v1 + description: Internal service-to-service endpoint (cluster network only) get: tags: - Registry @@ -81,5 +84,4 @@ post: '403': $ref: ../components/responses/Problem.yaml '409': - description: "Package version already exists." $ref: ../components/responses/Problem.yaml diff --git a/openapi/paths/vendors.yaml b/openapi/paths/vendors.yaml index 0784d67..5962d1b 100644 --- a/openapi/paths/vendors.yaml +++ b/openapi/paths/vendors.yaml @@ -50,5 +50,4 @@ post: '403': $ref: ../components/responses/Problem.yaml '409': - description: "A vendor with this ID already exists." $ref: ../components/responses/Problem.yaml