summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorKagami Sascha Rosylight <saschanaz@outlook.com>2023-03-09 18:37:44 +0100
committerGitHub <noreply@github.com>2023-03-10 02:37:44 +0900
commite0b7633a7adb6f2744e1142637bbbd6ac6624031 (patch)
treedab693ed58c315cc0d4ea3ab2348512b72ccba67 /packages/backend/src
parentfix(client): Solve the problem of not automatically jumping to /admin/overvie... (diff)
downloadsharkey-e0b7633a7adb6f2744e1142637bbbd6ac6624031.tar.gz
sharkey-e0b7633a7adb6f2744e1142637bbbd6ac6624031.tar.bz2
sharkey-e0b7633a7adb6f2744e1142637bbbd6ac6624031.zip
enhance(backend): restore OpenAPI endpoints (#10281)
* enhance(backend): restore OpenAPI endpoints * Update CHANGELOG.md * version * set max-age * update redoc * follow redoc documentation --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/server/FileServerService.ts16
-rw-r--r--packages/backend/src/server/ServerModule.ts2
-rw-r--r--packages/backend/src/server/ServerService.ts14
-rw-r--r--packages/backend/src/server/api/ApiServerService.ts2
-rw-r--r--packages/backend/src/server/api/openapi/OpenApiServerService.ts31
-rw-r--r--packages/backend/src/server/api/openapi/gen-spec.ts193
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts5
7 files changed, 246 insertions, 17 deletions
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index 835657b625..6db9a9672c 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -2,7 +2,6 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
-import fastifyStatic from '@fastify/static';
import rename from 'rename';
import type { Config } from '@/config.js';
import type { DriveFile, DriveFilesRepository } from '@/models/index.js';
@@ -60,11 +59,6 @@ export class FileServerService {
done();
});
- fastify.register(fastifyStatic, {
- root: _dirname,
- serve: false,
- });
-
fastify.get('/files/app-default.jpg', (request, reply) => {
const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
reply.header('Content-Type', 'image/jpeg');
@@ -311,20 +305,20 @@ export class FileServerService {
.linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast
.flatten({ background: '#000' })
.toColorspace('b-w');
-
+
const stats = await mask.clone().stats();
-
+
if (stats.entropy < 0.1) {
// エントロピーがあまりない場合は404にする
throw new StatusError('Skip to provide badge', 404);
}
-
+
const data = sharp({
create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
})
.pipelineColorspace('b-w')
.boolean(await mask.png().toBuffer(), 'eor');
-
+
image = {
data: await data.png().toBuffer(),
ext: 'png',
@@ -396,7 +390,7 @@ export class FileServerService {
const { filename } = await this.downloadService.downloadUrl(url, path);
const { mime, ext } = await this.fileInfoService.detectType(path);
-
+
return {
state: 'remote',
mime, ext,
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index a5a5f9e7f9..6bae0bafda 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -33,6 +33,7 @@ import { LocalTimelineChannelService } from './api/stream/channels/local-timelin
import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
import { UserListChannelService } from './api/stream/channels/user-list.js';
+import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
@Module({
imports: [
@@ -72,6 +73,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
QueueStatsChannelService,
ServerStatsChannelService,
UserListChannelService,
+ OpenApiServerService,
],
exports: [
ServerService,
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index e61383468c..3f116845cb 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -1,7 +1,9 @@
import cluster from 'node:cluster';
import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import Fastify, { FastifyInstance } from 'fastify';
+import fastifyStatic from '@fastify/static';
import { IsNull } from 'typeorm';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Config } from '@/config.js';
@@ -21,6 +23,9 @@ import { StreamingApiServerService } from './api/StreamingApiServerService.js';
import { WellKnownServerService } from './WellKnownServerService.js';
import { FileServerService } from './FileServerService.js';
import { ClientServerService } from './web/ClientServerService.js';
+import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
+
+const _dirname = fileURLToPath(new URL('.', import.meta.url));
@Injectable()
export class ServerService implements OnApplicationShutdown {
@@ -42,6 +47,7 @@ export class ServerService implements OnApplicationShutdown {
private userEntityService: UserEntityService,
private apiServerService: ApiServerService,
+ private openApiServerService: OpenApiServerService,
private streamingApiServerService: StreamingApiServerService,
private activityPubServerService: ActivityPubServerService,
private wellKnownServerService: WellKnownServerService,
@@ -71,7 +77,15 @@ export class ServerService implements OnApplicationShutdown {
});
}
+ // Register non-serving static server so that the child services can use reply.sendFile.
+ // `root` here is just a placeholder and each call must use its own `rootPath`.
+ fastify.register(fastifyStatic, {
+ root: _dirname,
+ serve: false,
+ });
+
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
+ fastify.register(this.openApiServerService.createServer);
fastify.register(this.fileServerService.createServer);
fastify.register(this.activityPubServerService.createServer);
fastify.register(this.nodeinfoServerService.createServer);
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 115d60986c..b806ad5ca3 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -167,7 +167,7 @@ export class ApiServerService {
// Make sure any unknown path under /api returns HTTP 404 Not Found,
// because otherwise ClientServerService will return the base client HTML
// page with HTTP 200.
- fastify.get('*', (request, reply) => {
+ fastify.get('/*', (request, reply) => {
reply.code(404);
// Mock ApiCallService.send's error handling
reply.send({
diff --git a/packages/backend/src/server/api/openapi/OpenApiServerService.ts b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
new file mode 100644
index 0000000000..e804ba276c
--- /dev/null
+++ b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
@@ -0,0 +1,31 @@
+import { fileURLToPath } from 'node:url';
+import { Inject, Injectable } from '@nestjs/common';
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { genOpenapiSpec } from './gen-spec.js';
+import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
+
+const staticAssets = fileURLToPath(new URL('../../../../assets/', import.meta.url));
+
+@Injectable()
+export class OpenApiServerService {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+ ) {
+ }
+
+ @bindThis
+ public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
+ fastify.get('/api-doc', async (_request, reply) => {
+ reply.header('Cache-Control', 'public, max-age=86400');
+ return await reply.sendFile('/redoc.html', staticAssets);
+ });
+ fastify.get('/api.json', (_request, reply) => {
+ reply.header('Cache-Control', 'public, max-age=600');
+ reply.send(genOpenapiSpec(this.config));
+ });
+ done();
+ }
+}
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
new file mode 100644
index 0000000000..fa62480c02
--- /dev/null
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -0,0 +1,193 @@
+import type { Config } from '@/config.js';
+import endpoints from '../endpoints.js';
+import { errors as basicErrors } from './errors.js';
+import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
+
+export function genOpenapiSpec(config: Config) {
+ const spec = {
+ openapi: '3.0.0',
+
+ info: {
+ version: config.version,
+ title: 'Misskey API',
+ 'x-logo': { url: '/static-assets/api-doc.png' },
+ },
+
+ externalDocs: {
+ description: 'Repository',
+ url: 'https://github.com/misskey-dev/misskey',
+ },
+
+ servers: [{
+ url: config.apiUrl,
+ }],
+
+ paths: {} as any,
+
+ components: {
+ schemas: schemas,
+
+ securitySchemes: {
+ ApiKeyAuth: {
+ type: 'apiKey',
+ in: 'body',
+ name: 'i',
+ },
+ },
+ },
+ };
+
+ for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
+ const errors = {} as any;
+
+ if (endpoint.meta.errors) {
+ for (const e of Object.values(endpoint.meta.errors)) {
+ errors[e.code] = {
+ value: {
+ error: e,
+ },
+ };
+ }
+ }
+
+ const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
+
+ let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
+ desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
+ if (endpoint.meta.kind) {
+ const kind = endpoint.meta.kind;
+ desc += ` / **Permission**: *${kind}*`;
+ }
+
+ const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
+ const schema = { ...endpoint.params };
+
+ if (endpoint.meta.requireFile) {
+ schema.properties = {
+ ...schema.properties,
+ file: {
+ type: 'string',
+ format: 'binary',
+ description: 'The file contents.',
+ },
+ };
+ schema.required = [...schema.required ?? [], 'file'];
+ }
+
+ const info = {
+ operationId: endpoint.name,
+ summary: endpoint.name,
+ description: desc,
+ externalDocs: {
+ description: 'Source code',
+ url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
+ },
+ ...(endpoint.meta.tags ? {
+ tags: [endpoint.meta.tags[0]],
+ } : {}),
+ ...(endpoint.meta.requireCredential ? {
+ security: [{
+ ApiKeyAuth: [],
+ }],
+ } : {}),
+ requestBody: {
+ required: true,
+ content: {
+ [requestType]: {
+ schema,
+ },
+ },
+ },
+ responses: {
+ ...(endpoint.meta.res ? {
+ '200': {
+ description: 'OK (with results)',
+ content: {
+ 'application/json': {
+ schema: resSchema,
+ },
+ },
+ },
+ } : {
+ '204': {
+ description: 'OK (without any results)',
+ },
+ }),
+ '400': {
+ description: 'Client error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: { ...errors, ...basicErrors['400'] },
+ },
+ },
+ },
+ '401': {
+ description: 'Authentication error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['401'],
+ },
+ },
+ },
+ '403': {
+ description: 'Forbidden error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['403'],
+ },
+ },
+ },
+ '418': {
+ description: 'I\'m Ai',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['418'],
+ },
+ },
+ },
+ ...(endpoint.meta.limit ? {
+ '429': {
+ description: 'To many requests',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['429'],
+ },
+ },
+ },
+ } : {}),
+ '500': {
+ description: 'Internal server error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['500'],
+ },
+ },
+ },
+ },
+ };
+
+ spec.paths['/' + endpoint.name] = {
+ post: info,
+ };
+ }
+
+ return spec;
+}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 98cdd31206..fb76f07e48 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -195,11 +195,6 @@ export class ClientServerService {
//#region static assets
fastify.register(fastifyStatic, {
- root: _dirname,
- serve: false,
- });
-
- fastify.register(fastifyStatic, {
root: staticAssets,
prefix: '/static-assets/',
maxAge: ms('7 days'),