From 644cb9b39f7b61311eef30f81ad489d9f95035c1 Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:36:02 +0200 Subject: [PATCH] Restructured rest api --- src/ep.json | 6 +++ src/node/handler/APIHandler.ts | 18 +++---- src/node/handler/APIKeyHandler.ts | 10 ++++ src/node/handler/RestAPI.ts | 84 +++++++++++++++++++++++++++++++ src/node/types/ArgsExpressType.ts | 6 ++- 5 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 src/node/handler/RestAPI.ts diff --git a/src/ep.json b/src/ep.json index c9b26c175..83dfc509d 100644 --- a/src/ep.json +++ b/src/ep.json @@ -82,6 +82,12 @@ "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling" } }, + { + "name": "restApi", + "hooks": { + "expressCreateServer": "ep_etherpad-lite/node/handler/RestAPI" + } + }, { "name": "socketio", "hooks": { diff --git a/src/node/handler/APIHandler.ts b/src/node/handler/APIHandler.ts index 5feb74eb9..ec1601410 100644 --- a/src/node/handler/APIHandler.ts +++ b/src/node/handler/APIHandler.ts @@ -24,10 +24,10 @@ import {MapArrayType} from "../types/MapType"; const api = require('../db/API'); const padManager = require('../db/PadManager'); import createHTTPError from 'http-errors'; -import {Http2ServerRequest, Http2ServerResponse} from "node:http2"; +import {Http2ServerRequest} from "node:http2"; import {publicKeyExported} from "../security/OAuth2Provider"; import {jwtVerify} from "jose"; -import {apikey} from './APIKeyHandler' +import {APIFields, apikey} from './APIKeyHandler' // a list of all functions const version:MapArrayType = {}; @@ -141,6 +141,13 @@ version['1.3.0'] = { setText: ['padID', 'text', 'authorId'], }; + +version['2.2.2'] = { + ...version['1.3.0'], + +} + + // set the latest available API version here exports.latestApiVersion = '1.3.0'; @@ -148,13 +155,6 @@ exports.latestApiVersion = '1.3.0'; exports.version = version; -type APIFields = { - apikey: string; - api_key: string; - padID: string; - padName: string; - authorization: string; -} /** * Handles an HTTP API call diff --git a/src/node/handler/APIKeyHandler.ts b/src/node/handler/APIKeyHandler.ts index b4e70f6e4..5a00453b1 100644 --- a/src/node/handler/APIKeyHandler.ts +++ b/src/node/handler/APIKeyHandler.ts @@ -7,6 +7,16 @@ const settings = require('../utils/Settings'); const apiHandlerLogger = log4js.getLogger('APIHandler'); + + +export type APIFields = { + apikey: string; + api_key: string; + padID: string; + padName: string; + authorization: string; +} + // ensure we have an apikey export let apikey:string|null = null; const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt'); diff --git a/src/node/handler/RestAPI.ts b/src/node/handler/RestAPI.ts new file mode 100644 index 000000000..38c4c4c68 --- /dev/null +++ b/src/node/handler/RestAPI.ts @@ -0,0 +1,84 @@ +import {ArgsExpressType} from "../types/ArgsExpressType"; +import {MapArrayType} from "../types/MapType"; +import {IncomingForm} from "formidable"; +import {ErrorCaused} from "../types/ErrorCaused"; +import createHTTPError from "http-errors"; +const apiHandler = require('./APIHandler') + +type RestAPIMapping = { + apiVersion: string; + functionName: string +} + + +const mapping = new Map> + + +export const expressCreateServer = async (hookName: string, {app}: ArgsExpressType) => { + mapping.set('GET', {}) + mapping.set('POST', {}) + mapping.set('PUT', {}) + mapping.set('DELETE', {}) + mapping.set('PATCH', {}) + + // Version 1 + mapping.get('POST')!["/groups"] = {apiVersion: '1', functionName: 'createGroup'} + mapping.get('GET')!["/pads"] = {apiVersion: '1', functionName: 'listPads'} + mapping.get('POST')!["/groups/createIfNotExistsFor"] = {apiVersion: '1', functionName: 'createGroupIfNotExistsFor'}; + + app.use('/api/2', async (req, res, next) => { + const method = req.method + const pathToFunction = req.path + // parse fields from request + const {headers, params, query} = req; + + // read form data if method was POST + let formData: MapArrayType = {}; + if (method === 'post') { + const form = new IncomingForm(); + formData = (await form.parse(req))[0]; + for (const k of Object.keys(formData)) { + if (formData[k] instanceof Array) { + formData[k] = formData[k][0]; + } + } + } + + const fields = Object.assign({}, headers, params, query, formData); + + if (mapping.has(method) && pathToFunction in mapping.get(method)!) { + const {apiVersion, functionName} = mapping.get(method)![pathToFunction]! + // pass to api handler + let data; + try { + data = await apiHandler.handle(apiVersion, functionName, fields, req, res); + } catch (err) { + const errCaused = err as ErrorCaused + // convert all errors to http errors + if (createHTTPError.isHttpError(err)) { + // pass http errors thrown by handler forward + throw err; + } else if (errCaused.name === 'apierror') { + // parameters were wrong and the api stopped execution, pass the error + // convert to http error + throw new createHTTPError.BadRequest(errCaused.message); + } else { + // an unknown error happened + // log it and throw internal error + console.error(errCaused.stack || errCaused.toString()); + throw new createHTTPError.InternalServerError('internal error'); + } + } + + // return in common format + const response = {code: 0, message: 'ok', data: data || null}; + + console.debug(`RESPONSE, ${functionName}, ${JSON.stringify(response)}`); + + // return the response data + res.json(response); + } else { + res.json({code: 1, message: 'not found'}); + } + }) +} diff --git a/src/node/types/ArgsExpressType.ts b/src/node/types/ArgsExpressType.ts index 5c0675b97..d8dc700be 100644 --- a/src/node/types/ArgsExpressType.ts +++ b/src/node/types/ArgsExpressType.ts @@ -1,5 +1,7 @@ +import {Express} from "express"; + export type ArgsExpressType = { - app:any, + app:Express, io: any, server:any -} \ No newline at end of file +}