Compare commits

...

8 Commits

Author SHA1 Message Date
Johann150 af80492c16
fixup: use structuredClone 2022-12-13 22:07:36 +01:00
Johann150 94182876c6
server: add v2 routes to notes endpoints 2022-12-13 21:02:25 +01:00
Johann150 0d15b74193
improve type definitions for v2 method
The method has to be lowercase because it is used as an index to get
the respective method of the router.
2022-12-13 21:01:26 +01:00
Andy 7fd6ea563f
api-doc: don't override route docs with each new HTTP method 2022-12-13 21:01:26 +01:00
Andy 91b97e4980
improve fetching of endpoint arguments
including support for route parameters (e.g. '/v2/note/:noteId' giving us a 'noteId' value)

Co-authored-by: Johann150 <johann.galle@protonmail.com>
2022-12-13 21:01:14 +01:00
Andy b5b5dd51af
generate OpenAPI spec for v2 endpoints 2022-12-13 20:59:34 +01:00
Andy 5c0cf99b59
WIP: make v2 meta endpoint support GET 2022-12-13 20:59:32 +01:00
Andy 096f2129ab
WIP: Add additional handling of endpoints with v2 options 2022-12-13 20:57:24 +01:00
18 changed files with 120 additions and 5 deletions

View File

@ -5,12 +5,23 @@ import authenticate, { AuthenticationError } from './authenticate.js';
import call from './call.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> {
const body = ctx.is('multipart/form-data')
? (ctx.request as any).body
: ctx.method === 'GET'
? ctx.query
: ctx.request.body;
const body = getRequestArguments(ctx);
const error = (e: ApiError): void => {
ctx.status = e.httpStatusCode;

View File

@ -702,6 +702,24 @@ export interface IEndpointMeta {
* (Cache-Control: public)
*/
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 {

View File

@ -233,6 +233,10 @@ export const meta = {
},
},
},
v2: {
method: 'get'
},
} as const;
export const paramDef = {

View File

@ -21,6 +21,11 @@ export const meta = {
ref: 'Note',
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/children',
},
} as const;
export const paramDef = {

View File

@ -19,6 +19,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/clips',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -19,6 +19,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/conversation',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -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'],
} as const;

View File

@ -18,6 +18,11 @@ export const meta = {
minInterval: SECOND,
},
v2: {
method: 'delete',
alias: 'notes/:noteId',
},
errors: ['ACCESS_DENIED', 'NO_SUCH_NOTE'],
} as const;

View File

@ -18,6 +18,10 @@ export const meta = {
ref: 'Note',
},
},
v2: {
method: 'get',
}
} as const;
export const paramDef = {

View File

@ -23,6 +23,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/reactions/:type?',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -22,6 +22,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/renotes',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -21,6 +21,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/replies',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -14,6 +14,11 @@ export const meta = {
ref: 'Note',
},
v2: {
method: 'get',
alias: 'notes/:noteId',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -27,6 +27,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/status',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -56,6 +56,11 @@ export const meta = {
},
},
v2: {
method: 'get',
alias: 'notes/:noteId/translate/:targetLang/:sourceLang?',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -18,6 +18,11 @@ export const meta = {
minInterval: SECOND,
},
v2: {
method: 'delete',
alias: 'notes/:noteId/renotes',
},
errors: ['NO_SUCH_NOTE'],
} as const;

View File

@ -69,6 +69,11 @@ for (const endpoint of endpoints) {
} else {
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));
}
}
}

View File

@ -207,6 +207,19 @@ export function genOpenapiSpec() {
}
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;