events-backend: add OpenAPI schema

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-05-22 20:19:00 +02:00
parent ea4b912f93
commit acdefea2a1
6 changed files with 515 additions and 14 deletions
+3
View File
@@ -44,6 +44,7 @@
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"generate": "backstage-repo-tools package schema openapi generate --server",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
@@ -52,6 +53,7 @@
},
"dependencies": {
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-openapi-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
@@ -70,6 +72,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-events-backend-test-utils": "workspace:^",
"@backstage/repo-tools": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -0,0 +1,289 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { createValidatedOpenApiRouter } from '@backstage/backend-openapi-utils';
export const spec = {
openapi: '3.0.3',
info: {
title: 'events',
version: '1',
description: 'The Backstage backend plugin that powers the events system.',
license: {
name: 'Apache-2.0',
url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
},
contact: {},
},
servers: [
{
url: '/',
},
],
components: {
examples: {},
headers: {},
parameters: {
subscriptionId: {
name: 'subscriptionId',
in: 'path',
required: true,
allowReserved: true,
schema: {
type: 'string',
},
},
},
requestBodies: {},
responses: {
ErrorResponse: {
description: 'An error response from the backend.',
content: {
'application/json; charset=utf-8': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
},
schemas: {
Event: {
type: 'object',
required: ['topic', 'payload'],
properties: {
topic: {
type: 'string',
description: 'The topic that the event is published on',
},
payload: {
$ref: '#/components/schemas/JsonObject',
description: 'The event payload',
},
},
},
Error: {
type: 'object',
properties: {
error: {
type: 'object',
properties: {
name: {
type: 'string',
},
message: {
type: 'string',
},
},
required: ['name', 'message'],
},
request: {
type: 'object',
properties: {
method: {
type: 'string',
},
url: {
type: 'string',
},
},
required: ['method', 'url'],
},
response: {
type: 'object',
properties: {
statusCode: {
type: 'number',
},
},
required: ['statusCode'],
},
},
required: ['error', 'request', 'response'],
},
JsonObject: {
type: 'object',
properties: {},
additionalProperties: {},
},
},
securitySchemes: {
JWT: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
paths: {
'/hub/events': {
post: {
operationId: 'PostEvent',
description: 'Publish a new event',
responses: {
default: {
$ref: '#/components/responses/ErrorResponse',
},
},
security: [
{},
{
JWT: [],
},
],
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['event'],
properties: {
event: {
$ref: '#/components/schemas/Event',
},
subscriptionIds: {
type: 'array',
description:
'The IDs of subscriptions that have already received this event',
items: {
type: 'string',
},
},
},
},
examples: {
'Publishing a simple Event': {
value: {
event: {
topic: 'test-topic',
payload: {
myData: 'foo',
},
},
},
},
},
},
},
},
},
},
'/hub/subscriptions/{subscriptionId}': {
put: {
operationId: 'PutSubscription',
description:
'Ensures that the subscription exists with the provided configuration',
responses: {
'201': {
description: 'The subscription exists or was created successfully',
},
default: {
$ref: '#/components/responses/ErrorResponse',
},
},
security: [
{},
{
JWT: [],
},
],
parameters: [
{
$ref: '#/components/parameters/subscriptionId',
},
],
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['topics'],
properties: {
topics: {
type: 'array',
description: 'The topics to subscribe to',
items: {
type: 'string',
},
},
},
},
examples: {
'Subscribing to a single topic': {
value: {
topics: ['test-topic'],
},
},
},
},
},
},
},
},
'/hub/subscriptions/{subscriptionId}/events': {
get: {
operationId: 'GetSubscriptionEvents',
description: 'Get new events for the provided subscription',
responses: {
'200': {
description: 'New events',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
events: {
type: 'array',
items: {
$ref: '#/components/schemas/Event',
},
},
},
required: ['results'],
},
},
},
},
'201': {
description: 'Block poll response, new events are available',
},
default: {
$ref: '#/components/responses/ErrorResponse',
},
},
security: [
{},
{
JWT: [],
},
],
parameters: [
{
$ref: '#/components/parameters/subscriptionId',
},
],
},
},
},
} as const;
export const createOpenApiRouter = async (
options?: Parameters<typeof createValidatedOpenApiRouter>['1'],
) => createValidatedOpenApiRouter<typeof spec>(spec, options);
@@ -0,0 +1,185 @@
openapi: 3.0.3
info:
title: events
version: '1'
description: The Backstage backend plugin that powers the events system.
license:
name: Apache-2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
contact: {}
servers:
- url: /
components:
examples: {}
headers: {}
parameters:
subscriptionId:
name: subscriptionId
in: path
required: true
allowReserved: true
schema:
type: string
requestBodies: {}
responses:
ErrorResponse:
description: An error response from the backend.
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
schemas:
Event:
type: object
required:
- topic
- payload
properties:
topic:
type: string
description: The topic that the event is published on
payload:
$ref: '#/components/schemas/JsonObject'
description: The event payload
Error:
type: object
properties:
error:
type: object
properties:
name:
type: string
message:
type: string
required:
- name
- message
request:
type: object
properties:
method:
type: string
url:
type: string
required:
- method
- url
response:
type: object
properties:
statusCode:
type: number
required:
- statusCode
required:
- error
- request
- response
JsonObject:
type: object
properties: {}
# Free form object.
additionalProperties: {}
securitySchemes:
JWT:
type: http
scheme: bearer
bearerFormat: JWT
paths:
/hub/events:
post:
operationId: PostEvent
description: Publish a new event
responses:
default:
$ref: '#/components/responses/ErrorResponse'
security:
- {}
- JWT: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- event
properties:
event:
$ref: '#/components/schemas/Event'
subscriptionIds:
type: array
description: The IDs of subscriptions that have already received this event
items:
type: string
examples:
Publishing a simple Event:
value:
event:
topic: test-topic
payload:
myData: foo
/hub/subscriptions/{subscriptionId}:
put:
operationId: PutSubscription
description: Ensures that the subscription exists with the provided configuration
responses:
'201':
description: The subscription exists or was created successfully
default:
$ref: '#/components/responses/ErrorResponse'
security:
- {}
- JWT: []
parameters:
- $ref: '#/components/parameters/subscriptionId'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- topics
properties:
topics:
type: array
description: The topics to subscribe to
items:
type: string
examples:
Subscribing to a single topic:
value:
topics:
- test-topic
/hub/subscriptions/{subscriptionId}/events:
get:
operationId: GetSubscriptionEvents
description: Get new events for the provided subscription
responses:
'200':
description: New events
content:
application/json:
schema:
type: object
properties:
events:
type: array
items:
$ref: '#/components/schemas/Event'
required:
- results
'201':
description: Block poll response, new events are available
default:
$ref: '#/components/responses/ErrorResponse'
security:
- {}
- JWT: []
parameters:
- $ref: '#/components/parameters/subscriptionId'
@@ -97,7 +97,7 @@ export const eventsPlugin = createBackendPlugin({
http.bind(eventsRouter);
const hub = await EventHub.create({ logger, httpAuth });
eventsRouter.use('/hub', hub.handler());
router.use(hub.handler());
router.use(eventsRouter);
router.addAuthPolicy({
@@ -20,7 +20,7 @@ import {
HttpAuthService,
LoggerService,
} from '@backstage/backend-plugin-api';
import express, { Handler } from 'express';
import { Handler } from 'express';
import Router from 'express-promise-router';
import { Socket } from 'net';
import { STATUS_CODES } from 'http';
@@ -30,6 +30,8 @@ import { fromZodError } from 'zod-validation-error';
import { JsonObject, JsonValue } from '@backstage/types';
import { serializeError } from '@backstage/errors';
import { EventParams } from '@backstage/plugin-events-node';
import { spec, createOpenApiRouter } from '../../schema/openapi.generated';
import { internal } from '@backstage/backend-openapi-utils';
/*
@@ -395,15 +397,23 @@ export class EventHub {
const hub = new EventHub(server, router, logger, httpAuth);
// WS
router.get('/connect', hub.#handleGetConnect);
router.get('/hub/connect', hub.#handleGetConnect);
router.use(express.json());
const apiRouter = await createOpenApiRouter();
router.post('/events', hub.#handlePostEvents);
router.use(apiRouter);
apiRouter.post('/hub/events', hub.#handlePostEvents);
// Long-polling
router.get('/subscriptions/:id/events', hub.#handleGetSubscription);
router.put('/subscriptions/:id', hub.#handlePutSubscription);
apiRouter.get(
'/hub/subscriptions/:subscriptionId/events',
hub.#handleGetSubscription,
);
apiRouter.put(
'/hub/subscriptions/:subscriptionId',
hub.#handlePutSubscription,
);
return hub;
}
@@ -528,14 +538,18 @@ export class EventHub {
}
};
#handlePostEvents: Handler = async (req, res) => {
#handlePostEvents: internal.DocRequestHandler<
typeof spec,
'/hub/events',
'post'
> = async (req, res) => {
const credentials = await this.#httpAuth.credentials(req, {
allow: ['service'],
});
await this.#store.publish({
params: {
topic: req.body.topic,
eventPayload: req.body.payload,
topic: req.body.event.topic,
eventPayload: req.body.event.payload,
},
subscriberIds: [],
});
@@ -545,11 +559,15 @@ export class EventHub {
res.status(201).end();
};
#handleGetSubscription: Handler = async (req, res) => {
#handleGetSubscription: internal.DocRequestHandler<
typeof spec,
'/hub/subscriptions/{subscriptionId}/events',
'get'
> = async (req, res) => {
const credentials = await this.#httpAuth.credentials(req, {
allow: ['service'],
});
const id = req.params.id;
const id = req.params.subscriptionId;
const { events } = await this.#store.readSubscription(id);
@@ -568,11 +586,15 @@ export class EventHub {
});
};
#handlePutSubscription: Handler = async (req, res) => {
#handlePutSubscription: internal.DocRequestHandler<
typeof spec,
'/hub/subscriptions/{subscriptionId}',
'put'
> = async (req, res) => {
const credentials = await this.#httpAuth.credentials(req, {
allow: ['service'],
});
const id = req.params.id;
const id = req.params.subscriptionId;
await this.#store.upsertSubscription(id, req.body.topics);
+2
View File
@@ -6139,6 +6139,7 @@ __metadata:
dependencies:
"@backstage/backend-common": ^0.25.0
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-openapi-utils": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
@@ -6146,6 +6147,7 @@ __metadata:
"@backstage/errors": "workspace:^"
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
"@backstage/repo-tools": "workspace:^"
"@backstage/types": "workspace:^"
"@types/express": ^4.17.6
express: ^4.17.1