Compare commits
8 commits
main
...
feature/ap
Author | SHA1 | Date | |
---|---|---|---|
af80492c16 | |||
94182876c6 | |||
0d15b74193 | |||
7fd6ea563f | |||
91b97e4980 | |||
b5b5dd51af | |||
5c0cf99b59 | |||
096f2129ab |
18 changed files with 120 additions and 5 deletions
|
@ -5,12 +5,23 @@ import authenticate, { AuthenticationError } from './authenticate.js';
|
||||||
import call from './call.js';
|
import call from './call.js';
|
||||||
import { ApiError } from './error.js';
|
import { ApiError } from './error.js';
|
||||||
|
|
||||||
|
function getRequestArguments(ctx: Koa.Context): Record<string, any> {
|
||||||
|
const args = {
|
||||||
|
...(ctx.params || {}),
|
||||||
|
...ctx.query,
|
||||||
|
...(ctx.request.body || {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// For security reasons, we drop the i parameter if it's a GET request
|
||||||
|
if (ctx.method === 'GET') {
|
||||||
|
delete args['i'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<void> {
|
export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<void> {
|
||||||
const body = ctx.is('multipart/form-data')
|
const body = getRequestArguments(ctx);
|
||||||
? (ctx.request as any).body
|
|
||||||
: ctx.method === 'GET'
|
|
||||||
? ctx.query
|
|
||||||
: ctx.request.body;
|
|
||||||
|
|
||||||
const error = (e: ApiError): void => {
|
const error = (e: ApiError): void => {
|
||||||
ctx.status = e.httpStatusCode;
|
ctx.status = e.httpStatusCode;
|
||||||
|
|
|
@ -702,6 +702,24 @@ export interface IEndpointMeta {
|
||||||
* 正常応答をキャッシュ (Cache-Control: public) する秒数
|
* 正常応答をキャッシュ (Cache-Control: public) する秒数
|
||||||
*/
|
*/
|
||||||
readonly cacheSec?: number;
|
readonly cacheSec?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API v2 options
|
||||||
|
*/
|
||||||
|
readonly v2?: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP verb this endpoint supports
|
||||||
|
*/
|
||||||
|
readonly method: 'get' | 'put' | 'post' | 'patch' | 'delete';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path alias for v2 endpoint
|
||||||
|
*
|
||||||
|
* @example (v0) /api/notes/create -> /api/v2/notes
|
||||||
|
*/
|
||||||
|
readonly alias?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEndpoint {
|
export interface IEndpoint {
|
||||||
|
|
|
@ -233,6 +233,10 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get'
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
|
|
@ -21,6 +21,11 @@ export const meta = {
|
||||||
ref: 'Note',
|
ref: 'Note',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/children',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
|
|
@ -19,6 +19,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/clips',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/conversation',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'post',
|
||||||
|
alias: 'notes',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE', 'PURE_RENOTE', 'EXPIRED_POLL', 'NO_SUCH_CHANNEL', 'BLOCKED', 'LESS_RESTRICTIVE_VISIBILITY'],
|
errors: ['NO_SUCH_NOTE', 'PURE_RENOTE', 'EXPIRED_POLL', 'NO_SUCH_CHANNEL', 'BLOCKED', 'LESS_RESTRICTIVE_VISIBILITY'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,11 @@ export const meta = {
|
||||||
minInterval: SECOND,
|
minInterval: SECOND,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'delete',
|
||||||
|
alias: 'notes/:noteId',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['ACCESS_DENIED', 'NO_SUCH_NOTE'],
|
errors: ['ACCESS_DENIED', 'NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ export const meta = {
|
||||||
ref: 'Note',
|
ref: 'Note',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
|
|
@ -23,6 +23,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/reactions/:type?',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/renotes',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/replies',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,11 @@ export const meta = {
|
||||||
ref: 'Note',
|
ref: 'Note',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/status',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,11 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'get',
|
||||||
|
alias: 'notes/:noteId/translate/:targetLang/:sourceLang?',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,11 @@ export const meta = {
|
||||||
minInterval: SECOND,
|
minInterval: SECOND,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
v2: {
|
||||||
|
method: 'delete',
|
||||||
|
alias: 'notes/:noteId/renotes',
|
||||||
|
},
|
||||||
|
|
||||||
errors: ['NO_SUCH_NOTE'],
|
errors: ['NO_SUCH_NOTE'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,11 @@ for (const endpoint of endpoints) {
|
||||||
} else {
|
} else {
|
||||||
router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; });
|
router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (endpoint.meta.v2) {
|
||||||
|
const path = endpoint.meta.v2.alias ?? endpoint.name.replace(/-/g, '_');
|
||||||
|
router[endpoint.meta.v2.method](`/v2/${path}`, handler.bind(null, endpoint));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,19 @@ export function genOpenapiSpec() {
|
||||||
}
|
}
|
||||||
|
|
||||||
spec.paths['/' + endpoint.name] = path;
|
spec.paths['/' + endpoint.name] = path;
|
||||||
|
|
||||||
|
if (endpoint.meta.v2) {
|
||||||
|
// we need a clone of the API endpoint info because otherwise we change it by reference
|
||||||
|
const infoClone = structuredClone(info);
|
||||||
|
const route = `/v2/${endpoint.meta.v2.alias ?? endpoint.name.replace(/-/g, '_')}`;
|
||||||
|
|
||||||
|
infoClone['operationId'] = infoClone['summary'] = route;
|
||||||
|
|
||||||
|
spec.paths[route] = {
|
||||||
|
...spec.paths[route],
|
||||||
|
[endpoint.meta.v2.method]: infoClone,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec;
|
return spec;
|
||||||
|
|
Loading…
Reference in a new issue