Add api-spec.json5

This commit is contained in:
Louis Lam 2023-10-08 08:17:03 +08:00
parent 6d2f624242
commit d4f9acee6a
7 changed files with 162 additions and 9 deletions

68
extra/api-spec.json5 Normal file
View file

@ -0,0 +1,68 @@
[
{
"name": "getPushExample",
"description": "Get a push example.",
"params": [
{
"name": "language",
"type": "string",
"description": "The programming language such as `javascript-fetch` or `python`. See the directory ./extra/push-examples for a list of available languages."
}
],
"returnType": "response-json",
"okReturn": [
{
"name": "code",
"type": "string",
"description": "The push example."
}
],
"possibleErrorReasons": [
"The parameter `language` is not available"
],
},
{
"name": "checkApprise",
"description": "Check if the apprise library is installed.",
"params": [],
"returnType": "boolean",
},
{
"name": "getSettings",
"description": "",
"params": [],
"returnType": "response-json",
"okReturn": [
{
"name": "data",
"type": "object",
"description": "The setting object. It does not contain default values."
}
],
"possibleErrorReasons": [],
},
{
"name": "changePassword",
"description": "",
"params": [
{
"name": "password",
"type": "object",
"description": "The password object with the following properties: `currentPassword` and `newPassword`"
}
],
"returnType": "response-json",
"okReturn": [
{
"name": "data",
"type": "object",
"description": "The setting object. It does not contain default values."
}
],
"possibleErrorReasons": [
"Incorrect current password",
"Invalid new password",
"Password is too weak"
],
}
]

2
package-lock.json generated
View file

@ -39,6 +39,7 @@
"iconv-lite": "~0.6.3",
"isomorphic-ws": "^5.0.0",
"jsesc": "~3.0.2",
"json5": "~2.2.3",
"jsonata": "^2.0.3",
"jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2",
@ -13206,7 +13207,6 @@
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
},

View file

@ -106,6 +106,7 @@
"iconv-lite": "~0.6.3",
"isomorphic-ws": "^5.0.0",
"jsesc": "~3.0.2",
"json5": "~2.2.3",
"jsonata": "^2.0.3",
"jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2",

View file

@ -16,6 +16,9 @@ const ioClient = require("socket.io-client").io;
const Socket = require("socket.io-client").Socket;
const { headerAuthMiddleware } = require("../auth");
const jwt = require("jsonwebtoken");
const fs = require("fs");
const JSON5 = require("json5");
const apiSpec = JSON5.parse(fs.readFileSync("./extra/api-spec.json5", "utf8"));
let router = express.Router();
@ -142,11 +145,11 @@ router.post("/api", headerAuthMiddleware, async (request, response) => {
try {
let result = await socketClientHandler(socket, token, requestData);
let status = 404;
let status = 200;
if (result.status) {
status = result.status;
} else if (result.ok) {
status = 200;
} else if (typeof result === "object" && result.ok === false) {
status = 404;
}
response.status(status).json(result);
} catch (e) {
@ -170,6 +173,50 @@ function socketClientHandler(socket, token, requestData) {
socket.on("connect", () => {
socket.emit("loginByToken", token, (res) => {
if (res.ok) {
let matched = false;
// Find the action in the API spec
for (let actionObj of apiSpec) {
// Find it
if (action === actionObj.name) {
matched = true;
let flatParams = [];
// Check if required parameters are provided
if (actionObj.params.length > 0 && !params) {
reject({
status: 400,
ok: false,
msg: "Missing \"params\" property in request body",
});
return;
}
// Check if required parameters are valid
for (let paramObj of actionObj.params) {
let value = params[paramObj.name];
// Check if required parameter is in a correct data type
if (typeof value !== paramObj.type) {
reject({
status: 400,
ok: false,
msg: `Parameter "${paramObj.name}" should be "${paramObj.type}". Got "${typeof value}" instead.`
});
return;
}
flatParams.push(value);
}
socket.emit(actionObj.name, ...flatParams, (res) => {
resolve(res);
});
break;
}
}
if (action === "getPushExample") {
if (params.length <= 0) {
@ -183,8 +230,9 @@ function socketClientHandler(socket, token, requestData) {
resolve(res);
});
}
}
} else {
if (!matched) {
reject({
status: 404,
ok: false,

View file

@ -1255,6 +1255,10 @@ let needSetup = false;
try {
checkLogin(socket);
if (typeof password.currentPassword === "undefined") {
throw new Error("Incorrect current password");
}
if (!password.newPassword) {
throw new Error("Invalid new password");
}

View file

@ -833,7 +833,7 @@ exports.checkLogin = (socket) => {
*/
exports.doubleCheckPassword = async (socket, currentPassword) => {
if (typeof currentPassword !== "string") {
throw new Error("Wrong data type?");
throw new Error("Wrong data type of current password");
}
let user = await R.findOne("user", " id = ? AND active = 1 ", [

View file

@ -4,8 +4,40 @@ Content-Type: application/json
{
"action": "getPushExample",
"params": [
"javascript-fetch"
]
"params": {
"language": "javascript-fetch"
}
}
###
POST http://localhost:3001/api
Authorization: Bearer uk1_1HaQRETls-E5KlhB6yCtf8WJRW57KwFMuKkya-Tj
Content-Type: application/json
{
"action": "checkApprise"
}
###
POST http://localhost:3001/api
Authorization: Bearer uk1_1HaQRETls-E5KlhB6yCtf8WJRW57KwFMuKkya-Tj
Content-Type: application/json
{
"action": "getSettings"
}
###
POST http://localhost:3001/api
Authorization: Bearer uk1_1HaQRETls-E5KlhB6yCtf8WJRW57KwFMuKkya-Tj
Content-Type: application/json
{
"action": "changePassword",
"params": {
"password": {
"currentPassword": "123456",
"newPassword": "1sfdsf234567"
}
}
}