feat: add OpenAPI specification and routes for serving API documentation
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Shorebird Self-Hosted Server
|
# Shorebird Self-Hosted Server
|
||||||
|
|
||||||
A self-hosted replacement for the Shorebird CodePush API server (`api.shorebird.dev`).
|
A self-hosted replacement for the hosted Shorebird CodePush API server.
|
||||||
See `../shorebird/OPEN_SOURCE_REPLACEMENTS.md` for the current audit of public upstream
|
See `../shorebird/OPEN_SOURCE_REPLACEMENTS.md` for the current audit of public upstream
|
||||||
Shorebird components and the local replacements for hosted Shorebird services.
|
Shorebird components and the local replacements for hosted Shorebird services.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,985 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Open Shorebird Self-Hosted API
|
||||||
|
version: 0.1.0
|
||||||
|
description: |
|
||||||
|
OpenAPI contract for the self-hosted Shorebird-compatible server in this
|
||||||
|
workspace. The API preserves the route shape expected by the open CLI,
|
||||||
|
CodePush client, and updater while using local/self-hosted services.
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:8080
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
tags:
|
||||||
|
- name: auth
|
||||||
|
- name: apps
|
||||||
|
- name: organizations
|
||||||
|
- name: releases
|
||||||
|
- name: patches
|
||||||
|
- name: devices
|
||||||
|
- name: diagnostics
|
||||||
|
- name: admin
|
||||||
|
paths:
|
||||||
|
/health:
|
||||||
|
get:
|
||||||
|
operationId: getHealth
|
||||||
|
security: []
|
||||||
|
tags: [diagnostics]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Server health and backend status.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
/openapi.yaml:
|
||||||
|
get:
|
||||||
|
operationId: getOpenAPISpec
|
||||||
|
security: []
|
||||||
|
tags: [diagnostics]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: This OpenAPI document.
|
||||||
|
content:
|
||||||
|
application/yaml:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/api/v1/openapi.yaml:
|
||||||
|
get:
|
||||||
|
operationId: getOpenAPISpecV1
|
||||||
|
security: []
|
||||||
|
tags: [diagnostics]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: This OpenAPI document served from a versioned path.
|
||||||
|
content:
|
||||||
|
application/yaml:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/auth/token:
|
||||||
|
post:
|
||||||
|
operationId: createAuthToken
|
||||||
|
security: []
|
||||||
|
tags: [auth]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: JWT and user metadata.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthTokenResponse'
|
||||||
|
/auth/register:
|
||||||
|
post:
|
||||||
|
operationId: createAuthUser
|
||||||
|
security: []
|
||||||
|
tags: [auth]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RegisterRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Registered user.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PrivateUser'
|
||||||
|
/auth/refresh:
|
||||||
|
post:
|
||||||
|
operationId: refreshAuthToken
|
||||||
|
tags: [auth]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Refreshed JWT.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthTokenResponse'
|
||||||
|
/auth/public-settings:
|
||||||
|
get:
|
||||||
|
operationId: getAuthPublicSettings
|
||||||
|
security: []
|
||||||
|
tags: [auth]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Public registration and SSO settings.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
/api/v1/patches/check:
|
||||||
|
post:
|
||||||
|
operationId: patchCheck
|
||||||
|
security: []
|
||||||
|
tags: [devices]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PatchCheckRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Device patch check result.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PatchCheckResponse'
|
||||||
|
/api/v1/patches/events:
|
||||||
|
post:
|
||||||
|
operationId: patchEvents
|
||||||
|
security: []
|
||||||
|
tags: [devices]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Event accepted.
|
||||||
|
/api/v1/users/me:
|
||||||
|
get:
|
||||||
|
operationId: getCurrentUser
|
||||||
|
tags: [auth]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Current user.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PrivateUser'
|
||||||
|
patch:
|
||||||
|
operationId: updateCurrentUserPassword
|
||||||
|
tags: [auth]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [current_password, new_password]
|
||||||
|
properties:
|
||||||
|
current_password:
|
||||||
|
type: string
|
||||||
|
new_password:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Password updated.
|
||||||
|
/api/v1/users:
|
||||||
|
post:
|
||||||
|
operationId: createUser
|
||||||
|
tags: [auth]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateUserRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Created user.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PrivateUser'
|
||||||
|
/api/v1/apps:
|
||||||
|
get:
|
||||||
|
operationId: getApps
|
||||||
|
tags: [apps]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Apps visible to the current user.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [apps]
|
||||||
|
properties:
|
||||||
|
apps:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/App'
|
||||||
|
post:
|
||||||
|
operationId: createApp
|
||||||
|
tags: [apps]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateAppRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Created app.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/App'
|
||||||
|
/api/v1/apps/{appId}:
|
||||||
|
delete:
|
||||||
|
operationId: deleteApp
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: App deleted.
|
||||||
|
/api/v1/apps/{appId}/transfer:
|
||||||
|
patch:
|
||||||
|
operationId: transferApp
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [organization_id]
|
||||||
|
properties:
|
||||||
|
organization_id:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Transferred app.
|
||||||
|
/api/v1/apps/{appId}/channels:
|
||||||
|
get:
|
||||||
|
operationId: getChannels
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: App channels.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [channels]
|
||||||
|
properties:
|
||||||
|
channels:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Channel'
|
||||||
|
post:
|
||||||
|
operationId: createChannel
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateChannelRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Created channel.
|
||||||
|
/api/v1/apps/{appId}/releases:
|
||||||
|
get:
|
||||||
|
operationId: getReleases
|
||||||
|
tags: [releases]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: App releases.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [releases]
|
||||||
|
properties:
|
||||||
|
releases:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Release'
|
||||||
|
post:
|
||||||
|
operationId: createRelease
|
||||||
|
tags: [releases]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateReleaseRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Created release.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Release'
|
||||||
|
/api/v1/apps/{appId}/releases/{releaseId}:
|
||||||
|
patch:
|
||||||
|
operationId: updateRelease
|
||||||
|
tags: [releases]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/ReleaseId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UpdateReleaseRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Updated release.
|
||||||
|
delete:
|
||||||
|
operationId: deleteRelease
|
||||||
|
tags: [releases]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/ReleaseId'
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Release deleted.
|
||||||
|
/api/v1/apps/{appId}/releases/{releaseId}/artifacts:
|
||||||
|
get:
|
||||||
|
operationId: getReleaseArtifacts
|
||||||
|
tags: [releases]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/ReleaseId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Release artifacts.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
post:
|
||||||
|
operationId: createReleaseArtifact
|
||||||
|
tags: [releases]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/ReleaseId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateReleaseArtifactRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Uploaded release artifact metadata.
|
||||||
|
/api/v1/apps/{appId}/patches:
|
||||||
|
post:
|
||||||
|
operationId: createPatch
|
||||||
|
tags: [patches]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreatePatchRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Created patch.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Patch'
|
||||||
|
/api/v1/apps/{appId}/patches/{patchId}:
|
||||||
|
patch:
|
||||||
|
operationId: updatePatch
|
||||||
|
tags: [patches]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/PatchId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UpdatePatchRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Updated patch.
|
||||||
|
/api/v1/apps/{appId}/patches/{patchId}/artifacts:
|
||||||
|
post:
|
||||||
|
operationId: createPatchArtifact
|
||||||
|
tags: [patches]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/PatchId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreatePatchArtifactRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Uploaded patch artifact metadata.
|
||||||
|
/api/v1/apps/{appId}/releases/{releaseId}/patches:
|
||||||
|
get:
|
||||||
|
operationId: getReleasePatches
|
||||||
|
tags: [patches]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
- $ref: '#/components/parameters/ReleaseId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Patches for a release.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [patches]
|
||||||
|
properties:
|
||||||
|
patches:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Patch'
|
||||||
|
/api/v1/apps/{appId}/patches/promote:
|
||||||
|
post:
|
||||||
|
operationId: promotePatch
|
||||||
|
tags: [patches]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PromotePatchRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Promoted patch.
|
||||||
|
/api/v1/apps/{appId}/patches/rollback:
|
||||||
|
post:
|
||||||
|
operationId: rollbackPatch
|
||||||
|
tags: [patches]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Rolled back patch.
|
||||||
|
/api/v1/organizations:
|
||||||
|
get:
|
||||||
|
operationId: getOrganizations
|
||||||
|
tags: [organizations]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Organizations.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [organizations]
|
||||||
|
properties:
|
||||||
|
organizations:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Organization'
|
||||||
|
post:
|
||||||
|
operationId: createOrganization
|
||||||
|
tags: [organizations]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [name]
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization_type:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Created organization.
|
||||||
|
/api/v1/organizations/{orgId}/users:
|
||||||
|
get:
|
||||||
|
operationId: getOrganizationUsers
|
||||||
|
tags: [organizations]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/OrgId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Organization users.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [users]
|
||||||
|
properties:
|
||||||
|
users:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/OrganizationUser'
|
||||||
|
post:
|
||||||
|
operationId: updateOrganizationUser
|
||||||
|
tags: [organizations]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/OrgId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [email, role]
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Organization user role updated.
|
||||||
|
/api/v1/apps/{appId}/metrics/active-hours:
|
||||||
|
get:
|
||||||
|
operationId: getActiveHours
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Active hour metrics.
|
||||||
|
/api/v1/apps/{appId}/metrics/unique-users:
|
||||||
|
get:
|
||||||
|
operationId: getUniqueUsers
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Unique user metrics.
|
||||||
|
/api/v1/apps/{appId}/metrics/version-distribution:
|
||||||
|
get:
|
||||||
|
operationId: getVersionDistribution
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Version distribution metrics.
|
||||||
|
/api/v1/apps/{appId}/metrics/patch-adoption:
|
||||||
|
get:
|
||||||
|
operationId: getPatchAdoption
|
||||||
|
tags: [apps]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Patch adoption metrics.
|
||||||
|
/api/v1/diagnostics/gcp_upload:
|
||||||
|
get:
|
||||||
|
operationId: getGCPUploadSpeedTestURL
|
||||||
|
tags: [diagnostics]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Upload speed-test URL.
|
||||||
|
/api/v1/diagnostics/gcp_download:
|
||||||
|
get:
|
||||||
|
operationId: getGCPDownloadSpeedTestURL
|
||||||
|
tags: [diagnostics]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Download speed-test URL.
|
||||||
|
/api/v1/admin/users:
|
||||||
|
get:
|
||||||
|
operationId: listAdminUsers
|
||||||
|
tags: [admin]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Admin user list.
|
||||||
|
/api/v1/admin/users/{userId}/verify-email:
|
||||||
|
post:
|
||||||
|
operationId: verifyAdminUserEmail
|
||||||
|
tags: [admin]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/UserId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Email marked verified.
|
||||||
|
/api/v1/admin/users/{userId}/admin:
|
||||||
|
post:
|
||||||
|
operationId: setAdminUser
|
||||||
|
tags: [admin]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/UserId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Admin flag updated.
|
||||||
|
/api/v1/admin/settings:
|
||||||
|
get:
|
||||||
|
operationId: getAdminSettings
|
||||||
|
tags: [admin]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Registration and SSO settings.
|
||||||
|
put:
|
||||||
|
operationId: updateAdminSettings
|
||||||
|
tags: [admin]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Settings updated.
|
||||||
|
/api/v1/admin/patches/{patchId}/target-devices:
|
||||||
|
get:
|
||||||
|
operationId: getPatchTargetDevices
|
||||||
|
tags: [admin]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PatchId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Targeted device IDs.
|
||||||
|
post:
|
||||||
|
operationId: addPatchTargetDevice
|
||||||
|
tags: [admin]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PatchId'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [client_id]
|
||||||
|
properties:
|
||||||
|
client_id:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Target device added.
|
||||||
|
/api/v1/admin/patches/{patchId}/target-devices/{clientId}:
|
||||||
|
delete:
|
||||||
|
operationId: removePatchTargetDevice
|
||||||
|
tags: [admin]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PatchId'
|
||||||
|
- name: clientId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Target device removed.
|
||||||
|
/api/v1/admin/apps/{appId}/events:
|
||||||
|
get:
|
||||||
|
operationId: getPatchEvents
|
||||||
|
tags: [admin]
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AppId'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Patch event list.
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
parameters:
|
||||||
|
AppId:
|
||||||
|
name: appId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
OrgId:
|
||||||
|
name: orgId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
ReleaseId:
|
||||||
|
name: releaseId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
PatchId:
|
||||||
|
name: patchId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
UserId:
|
||||||
|
name: userId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
schemas:
|
||||||
|
ErrorResponse:
|
||||||
|
type: object
|
||||||
|
required: [message]
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
details:
|
||||||
|
type: [string, 'null']
|
||||||
|
LoginRequest:
|
||||||
|
type: object
|
||||||
|
required: [email, password]
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
RegisterRequest:
|
||||||
|
type: object
|
||||||
|
required: [email, password, name]
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
AuthTokenResponse:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/PrivateUser'
|
||||||
|
PrivateUser:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
PublicUser:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
CreateUserRequest:
|
||||||
|
type: object
|
||||||
|
required: [email, password, name]
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
Organization:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
OrganizationUser:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
App:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
app_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
display_name:
|
||||||
|
type: string
|
||||||
|
CreateAppRequest:
|
||||||
|
type: object
|
||||||
|
required: [display_name]
|
||||||
|
properties:
|
||||||
|
display_name:
|
||||||
|
type: string
|
||||||
|
organization_id:
|
||||||
|
type: integer
|
||||||
|
Channel:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
CreateChannelRequest:
|
||||||
|
type: object
|
||||||
|
required: [name]
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
Release:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
flutter_revision:
|
||||||
|
type: string
|
||||||
|
CreateReleaseRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
required: [version, flutter_revision]
|
||||||
|
properties:
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
flutter_revision:
|
||||||
|
type: string
|
||||||
|
flutter_version:
|
||||||
|
type: string
|
||||||
|
platform_statuses:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
UpdateReleaseRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
CreateReleaseArtifactRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
required: [arch, platform, hash, size, storage_key]
|
||||||
|
properties:
|
||||||
|
arch:
|
||||||
|
type: string
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
hash:
|
||||||
|
type: string
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
storage_key:
|
||||||
|
type: string
|
||||||
|
can_sideload:
|
||||||
|
type: boolean
|
||||||
|
ReleaseArtifact:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
Patch:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
number:
|
||||||
|
type: integer
|
||||||
|
CreatePatchRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
required: [release_id]
|
||||||
|
properties:
|
||||||
|
release_id:
|
||||||
|
type: integer
|
||||||
|
notes:
|
||||||
|
type: string
|
||||||
|
UpdatePatchRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
CreatePatchArtifactRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
required: [arch, platform, hash, size, storage_key]
|
||||||
|
properties:
|
||||||
|
arch:
|
||||||
|
type: string
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
hash:
|
||||||
|
type: string
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
storage_key:
|
||||||
|
type: string
|
||||||
|
hash_signature:
|
||||||
|
type: string
|
||||||
|
podfile_lock_hash:
|
||||||
|
type: string
|
||||||
|
offline_expires_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
PromotePatchRequest:
|
||||||
|
type: object
|
||||||
|
required: [patch_id, channel]
|
||||||
|
properties:
|
||||||
|
patch_id:
|
||||||
|
type: integer
|
||||||
|
channel:
|
||||||
|
type: string
|
||||||
|
PatchCheckRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
required: [app_id, channel, release_version, platform, arch]
|
||||||
|
properties:
|
||||||
|
app_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
channel:
|
||||||
|
type: string
|
||||||
|
release_version:
|
||||||
|
type: string
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
arch:
|
||||||
|
type: string
|
||||||
|
current_patch_number:
|
||||||
|
type: [integer, 'null']
|
||||||
|
client_id:
|
||||||
|
type: [string, 'null']
|
||||||
|
accept_encrypted_patch:
|
||||||
|
type: boolean
|
||||||
|
PatchCheckResponse:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
properties:
|
||||||
|
patch_available:
|
||||||
|
type: boolean
|
||||||
|
patch:
|
||||||
|
type: [object, 'null']
|
||||||
|
remove_patch:
|
||||||
|
type: [object, 'null']
|
||||||
|
rolled_back_patch_numbers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
PatchEncryptionMetadata:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -15,6 +16,9 @@ import (
|
|||||||
"github.com/shorebird-server/internal/storage"
|
"github.com/shorebird-server/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed openapi.yaml
|
||||||
|
var openAPISpec []byte
|
||||||
|
|
||||||
// NewRouter creates the HTTP router with all API routes.
|
// NewRouter creates the HTTP router with all API routes.
|
||||||
func NewRouter(authService *authpkg.Service, database db.Store, store storage.Store, mailer *email.Mailer, baseURL string, patchDelivery PatchDeliveryConfig) *chi.Mux {
|
func NewRouter(authService *authpkg.Service, database db.Store, store storage.Store, mailer *email.Mailer, baseURL string, patchDelivery PatchDeliveryConfig) *chi.Mux {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
@@ -48,6 +52,9 @@ func NewRouter(authService *authpkg.Service, database db.Store, store storage.St
|
|||||||
// Auth middleware
|
// Auth middleware
|
||||||
authMw := middleware.AuthMiddleware(authService, database)
|
authMw := middleware.AuthMiddleware(authService, database)
|
||||||
|
|
||||||
|
r.Get("/openapi.yaml", serveOpenAPISpec)
|
||||||
|
r.Get("/api/v1/openapi.yaml", serveOpenAPISpec)
|
||||||
|
|
||||||
// --- Local storage upload/download (used when STORAGE_DRIVER=local) ---
|
// --- Local storage upload/download (used when STORAGE_DRIVER=local) ---
|
||||||
r.Route("/storage", func(r chi.Router) {
|
r.Route("/storage", func(r chi.Router) {
|
||||||
r.Post("/upload/{scope}/*", storageHandler.Upload)
|
r.Post("/upload/{scope}/*", storageHandler.Upload)
|
||||||
@@ -188,6 +195,12 @@ func NewRouter(authService *authpkg.Service, database db.Store, store storage.St
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveOpenAPISpec(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/yaml; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write(openAPISpec)
|
||||||
|
}
|
||||||
|
|
||||||
// findWebDir locates the web/ directory. It searches relative to the
|
// findWebDir locates the web/ directory. It searches relative to the
|
||||||
// binary and from common workspace paths so that the dashboard works
|
// binary and from common workspace paths so that the dashboard works
|
||||||
// in local development as well as in the Docker image.
|
// in local development as well as in the Docker image.
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOpenAPISpecRoutes(t *testing.T) {
|
||||||
|
router := NewRouter(nil, nil, nil, nil, "", PatchDeliveryConfig{})
|
||||||
|
|
||||||
|
for _, path := range []string{"/openapi.yaml", "/api/v1/openapi.yaml"} {
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
|
request := httptest.NewRequest(http.MethodGet, path, nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
if response.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", response.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
if contentType := response.Header().Get("Content-Type"); !strings.Contains(contentType, "application/yaml") {
|
||||||
|
t.Fatalf("Content-Type = %q, want application/yaml", contentType)
|
||||||
|
}
|
||||||
|
body := response.Body.String()
|
||||||
|
for _, required := range []string{
|
||||||
|
"openapi: 3.1.0",
|
||||||
|
"/api/v1/patches/check:",
|
||||||
|
"/openapi.yaml:",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(body, required) {
|
||||||
|
t.Fatalf("spec body missing %q", required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user