mizzkey/packages/backend/src/server/api/endpoint-base.ts

97 lines
2.7 KiB
TypeScript
Raw Normal View History

2022-09-18 03:27:08 +09:00
import * as fs from 'node:fs';
import Ajv from 'ajv';
2023-02-13 15:50:22 +09:00
import type { LocalUser } from '@/models/entities/User.js';
2022-09-18 03:27:08 +09:00
import type { AccessToken } from '@/models/entities/AccessToken.js';
import { ApiError } from './error.js';
2023-05-22 04:50:18 +00:00
import { endpoints, getEndpointSchema } from 'misskey-js/built/endpoints.js';
2023-05-21 18:49:44 +00:00
import type { IEndpointMeta, ResponseOf, SchemaOrUndefined } from 'misskey-js/built/endpoints.types.js';
import type { Endpoints } from 'misskey-js';
2023-05-22 01:43:00 +00:00
import { WeakSerialized } from 'schema-type';
2022-09-18 03:27:08 +09:00
const ajv = new Ajv({
useDefaults: true,
});
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
export type Response = Record<string, any> | void;
type File = {
name: string | null;
path: string;
};
2023-05-25 16:53:29 +00:00
export type Executor<T extends IEndpointMeta, P = SchemaOrUndefined<T['defines'][number]['req'], true>> =
2023-05-21 18:49:44 +00:00
(
params: P,
user: LocalUser | (T['requireCredential'] extends true ? never : null),
token: AccessToken | null,
file?: File,
cleanup?: () => any,
ip?: string | null,
headers?: Record<string, string> | null
2023-05-25 16:53:29 +00:00
) => Promise<WeakSerialized<ResponseOf<T, P, true>>>;
2022-09-18 03:27:08 +09:00
2023-05-21 18:49:44 +00:00
// ExecutorWrapperの型はあえて緩くしておく
export type ExecutorWrapper =
(
params: any,
user: LocalUser | null,
token: AccessToken | null,
file?: File,
ip?: string | null,
headers?: Record<string, string> | null
) => Promise<any>;
2022-09-18 03:27:08 +09:00
2023-05-21 18:49:44 +00:00
export abstract class Endpoint<E extends keyof Endpoints, T extends IEndpointMeta = Endpoints[E]> {
public readonly name: E;
public readonly meta: Endpoints[E];
public exec: ExecutorWrapper;
2022-09-18 03:27:08 +09:00
2023-05-21 18:49:44 +00:00
constructor(cb: Executor<T>) {
this.meta = endpoints[this.name];
2023-05-22 04:50:18 +00:00
const req = getEndpointSchema('req', this.name);
const validate = req ? ajv.compile(req) : null;
2023-05-21 18:49:44 +00:00
this.exec = (params, user, token, file, ip, headers) => {
2022-09-18 03:27:08 +09:00
let cleanup: undefined | (() => void) = undefined;
2023-05-21 18:49:44 +00:00
if (this.meta.requireFile) {
2022-09-18 03:27:08 +09:00
cleanup = () => {
if (file) fs.unlink(file.path, () => {});
2022-09-18 03:27:08 +09:00
};
if (file == null) return Promise.reject(new ApiError({
message: 'File required.',
code: 'FILE_REQUIRED',
id: '4267801e-70d1-416a-b011-4ee502885d8b',
}));
}
2023-05-22 04:50:18 +00:00
if (validate) {
const valid = validate(params);
if (!valid) {
if (file) cleanup!();
const errors = validate.errors!;
const err = new ApiError({
message: 'Invalid param.',
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}, {
param: errors[0].schemaPath,
reason: errors[0].message,
});
return Promise.reject(err);
}
} else {
// validateがnullである場合、paramsがnullや空オブジェクトであるべきではあるが、
// 特にチェックはしない
2022-09-18 03:27:08 +09:00
}
2023-05-21 18:49:44 +00:00
return cb(params as any, user as any, token, file, cleanup, ip, headers);
2022-09-18 03:27:08 +09:00
};
}
}