Compare commits

...

4 commits

Author SHA1 Message Date
4c055456d2 improve fetching of endpoint arguments
including support for route parameters (e.g. '/v2/note/:noteId' giving us a 'noteId' value)
2022-09-25 21:12:50 +02:00
aefe15e8ed generate OpenAPI spec for v2 endpoints 2022-09-25 17:31:32 +02:00
fad8dad5d8 WIP: make v2 meta endpoint support GET 2022-09-16 21:51:03 +02:00
ca6156fe71 WIP: Add additional handling of endpoints with v2 options 2022-09-16 21:50:05 +02:00
5 changed files with 55 additions and 5 deletions

View file

@ -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): 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 default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => { export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => {
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 reply = (x?: any, y?: ApiError) => { const reply = (x?: any, y?: ApiError) => {
if (x == null) { if (x == null) {

View file

@ -703,6 +703,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: string;
/**
* Path alias for v2 endpoint
*
* @example (v0) /api/notes/create -> /api/v2/notes
*/
readonly alias?: string;
};
} }
export interface IEndpoint { export interface IEndpoint {

View file

@ -10,6 +10,10 @@ export const meta = {
requireCredential: false, requireCredential: false,
v2: {
method: 'get'
},
res: { res: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,

View file

@ -74,6 +74,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));
}
} }
} }

View file

@ -203,6 +203,18 @@ 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 = JSON.parse(JSON.stringify(info));
const route = `/v2/${endpoint.meta.v2.alias ?? endpoint.name.replace(/-/g, '_')}`;
infoClone['operationId'] = infoClone['summary'] = route;
spec.paths[route] = {
[endpoint.meta.v2.method]: infoClone,
};
}
} }
return spec; return spec;