From 86c2a35a31052b5c4903f37d6613fa66bacb5826 Mon Sep 17 00:00:00 2001
From: Nikiroy78 <35032449+Nikiroy78@users.noreply.github.com>
Date: Fri, 6 Oct 2023 04:41:18 +0300
Subject: [PATCH] Add backend fixes
---
api/v1/index.js | 145 ++++++++++++++++---------
api/v1/methods/create-item.js | 142 +++++++++++-------------
api/v1/methods/edit-item.js | 158 ++++++++++++++-------------
api/v1/methods/get-authors.js | 168 +++++++++++++++++------------
api/v1/methods/get-music.js | 127 ++++++++++++++++++++++
api/v1/methods/get-muzic.js | 142 ------------------------
api/v1/methods/index.js | 29 ++++-
api/v1/methods/remove-item.js | 122 ++++++++++-----------
api/v1/response-wrapper.js | 61 -----------
api/v1/typeChecker.js | 180 +++++++++++++++++++++++++++++--
db-dump/install-it.sql | Bin 4270 -> 4270 bytes
frontend/main.html | 4 +-
frontend/public/js/api-module.js | 16 +--
frontend/public/js/main.js | 102 +++++++++---------
frontend/public/js/menu.js | 90 ++++++++--------
frontend/public/js/sound.js | 24 ++---
logger.js | 22 ++--
server.js | 1 +
18 files changed, 855 insertions(+), 678 deletions(-)
create mode 100644 api/v1/methods/get-music.js
delete mode 100644 api/v1/methods/get-muzic.js
delete mode 100644 api/v1/response-wrapper.js
diff --git a/api/v1/index.js b/api/v1/index.js
index 42ee87a..bb59336 100644
--- a/api/v1/index.js
+++ b/api/v1/index.js
@@ -1,63 +1,110 @@
const router = require("express").Router();
-const response = require("./response-wrapper");
-// const config = require('../../config-handler');
+const fs = require("fs");
+const config = require('../../config-handler');
+const database = require('../../database');
+const bodyParser = require('body-parser');
// Парсинг куки
//router.use(require('cookie-parser')());
-// Загрузка музыки при помощи спец. метода
-function muzicLoad(req, res) {
- res.setHeader("Content-Type", "audio/mpeg");
- global.database.muzic.get(
- (err, data) => {
- data = data[0]?.data;
- if (err) {
- res.send(Buffer.from([]));
- } else {
- res.send(!data ? Buffer.from([]) : data);
- }
- },
- { where: { id: !Number.isInteger(+req.query.id) ? 0 : +req.query.id } },
- );
-}
+// Парсим JSON
+router.use(bodyParser.json({ type: 'application/*+json' }))
+router.use(bodyParser.json({ type: 'application/json' }))
+const unknownError = (err) => {
+ const stackId = new Date().getTime();
+ let errorLoggingFolder = config().error_logs_folder;
+ errorLoggingFolder = !["/", "\\"].includes(errorLoggingFolder.at(-1))
+ ? errorLoggingFolder + "/"
+ : errorLoggingFolder;
+ fs.writeFileSync(
+ `${errorLoggingFolder}error_${stackId}.log`,
+ `ERROR:
+Date: ${new Date()}
+Name: ${err.name}
+Message: ${err.message}
+Stack:
+${err.stack}`,
+ );
+
+ return {
+ error: "UNKNOWN_ERROR",
+ details: {
+ trace_id: stackId,
+ },
+ };
+};
+
+// Загрузка музыки при помощи спец. метода
+router.get('/music', async (req, res) => {
+ res.setHeader("Content-Type", "audio/mpeg");
+ database.music.findAll({
+ where: { id: !Number.isInteger(+req.query.id) ? 0 : +req.query.id }, raw: true
+ }).then((data) => {
+ data = data[0]?.data;
+ res.send(!data ? Buffer.from([]) : data);
+ }).catch((err) => {
+ res.send(Buffer.from([]));
+ });
+});
+
+// middleware (Обработка типа ответа)
+router.use((req, res, next) => {
+ const unknownResponseFormat = "UNKNOWN_RESPONSE_FORMAT";
+
+ if (!req.query.response_format) {
+ res.send(unknownResponseFormat);
+ }
+ else if (req.query.response_format === 'json') {
+ res.result = function (data, isErr = false, code = 200) {
+ this.status(code).json(!isErr ? {
+ response : data
+ } : {
+ error : data
+ });
+ };
+ next();
+ }
+ else {
+ res.send(unknownResponseFormat);
+ }
+});
+
+// Методы
router.use(
require('./methods')
);
-// Подгрузка с файла
-/*router.use("/:method_name", async (req, res, next, ...etc) => {
- if (req.params.method_name === "muzic") {
- muzicLoad(req, res);
- return;
- }
-
- try {
- const methodFunct = require(`./methods/${req.params.method_name}`);
- response(methodFunct, req, res);
- } catch (e) {
- if (e.message.includes("Cannot find module")) {
- const ApiError = require("./errorClass");
- res.status(400).sendModed(
- await response(
- (req, res) => {
- throw new ApiError("METHOD_NOT_FOUNDED");
- },
- req,
- res,
- ),
- );
- } else {
- response(
- async () => {
- throw e;
- },
- req,
- res,
- );
+// Обработка ошибок
+router.use(
+ async function (err, req, res, next) {
+ if (err) {
+ if (err.name === "ApiError") {
+ req.logger.log(
+ new Date(),
+ req.ip,
+ `Ошибка API.\n${err.stack}`,
+ true
+ );
+ return res.result({
+ error : err.message,
+ details : err.details
+ }, true, 400);
+ }
+ else {
+ req.logger.log(
+ new Date(),
+ req.ip,
+ `Критическая ошибка API\n${err.stack}`,
+ true
+ );
+ return res.result({
+ error : unknownError(err)
+ }, true, 500);
+ }
}
+ else next();
}
-});
-*/
+);
module.exports = router;
diff --git a/api/v1/methods/create-item.js b/api/v1/methods/create-item.js
index 15e31cb..0e03dbf 100644
--- a/api/v1/methods/create-item.js
+++ b/api/v1/methods/create-item.js
@@ -1,87 +1,75 @@
const ApiError = require("../errorClass");
const config = require("../../../config-handler");
+const database = require("../../../database");
+const apiTypes = require("../typeChecker");
-async function isAuthorExists(authorId) {
- if (!authorId) return false;
- return (
- (
- await global.database.authors
- .promiseMode()
- .get({ where: { id: authorId } })
- ).length !== 0
- );
-}
+const router = require('express').Router();
async function checkSyntaxArgs(req, res) {
- return !!(
- // Проверка поля type.
- ["author", "muzic"].indexOf(req.json?.type) !== -1 && // Проверка поля name.
- typeof req.json.name === "string" &&
- req.json.name.length >= 2 &&
- (req.json.type === "muzic"
- ? true
- : config().authors_blacklist.filter((blacklisted) => {
- return req.json.name
- .toLowerCase()
- .includes(blacklisted.toLowerCase());
- }).length === 0) && // Дополнительные поля для muzic
- (req.json.type === "muzic"
- ? !!(
- // Для `muzic`
- (
- // Проверка поля author_id.
- (await isAuthorExists(req.json.author_id)) && // Проверка поля data. (Передаётся либо ничего, либо строка с base64)
- (req.json.data === undefined
- ? true
- : typeof req.json.data === "string")
- )
- )
- : true)
- );
+ if (req.body === undefined || !req.headers['content-type'].includes('json')) {
+ throw new ApiError("METHOD_MUST_BE_POST_JSON", { method : 'create-item', no_post : false });
+ }
+ const checker = new apiTypes.TypeChecker();
+
+ await checker.checkRequired(req.body.type, apiTypes.ItemType, 'type');
+ await checker.checkRequired(req.body.name, apiTypes.NameType, 'name');
+
+ if (req.body.type === 'music') {
+ await checker.checkRequired(req.body.author_id, apiTypes.AuthorType, 'author_id');
+ await checker.checkAdditional(req.body.data, apiTypes.Base64FileType, 'data');
+ }
+
+ const result = await checker.calculate();
+ if (!result.success) {
+ throw new ApiError("UNSYNTAX_PARAMS_OR_MISSED_REQUIRED_PARAMS", result);
+ }
}
-module.exports = async (req, res) => {
- if (req.json === undefined) {
- // console.log(req.headers);
- throw new ApiError("METHOD_MUST_BE_POST_JSON", {
- request_method: req.method,
- "content-type": !req.headers["content-type"]
- ? null
- : req.headers["content-type"],
- });
+router.post('/create-item', async (req, res, next) => {
+ try {
+ await checkSyntaxArgs(req, res);
+
+ if (req.body.type === "author") {
+ if (
+ config().authors_blacklist.filter((blacklisted) => {
+ return req.body.name
+ .toLowerCase()
+ .includes(blacklisted.toLowerCase());
+ }).length !== 0
+ ) {
+ throw new ApiError("AUTHOR_BLACKLISTED");
+ }
+
+ const result = await database.authors.create({
+ name: req.body.name,
+ time: Math.round(new Date().getTime() / 1000),
+ });
+ res.result(result.dataValues.id);
+ } else {
+ const result = await database.music.create({
+ name: req.body.name,
+ author_id: req.body.author_id,
+ data:
+ (req.body.data === undefined || req.body.data === null)
+ ? undefined
+ : Buffer.from(req.body.data, "base64"),
+ time: Math.round(new Date().getTime() / 1000),
+ });
+ res.result(result.dataValues.id);
+ }
}
- if (!(await checkSyntaxArgs(req, res))) {
- throw new ApiError("INVALID_OR_UNSYNTAX_PARAMS", {
- request_method: req.method,
- params: {
- type: req.json?.type === undefined ? null : req.json.type,
- name: req.json?.name === undefined ? null : req.json.name,
- ...(req.json?.type === "muzic"
- ? {
- author_id:
- req.json?.author_id === undefined ? null : req.json?.author_id,
- data: req.json?.data === undefined ? null : req.json.data,
- }
- : {}),
- },
- });
+ catch (err) {
+ return next(err);
}
- if (req.json.type === "author") {
- let result = await global.database.authors.promiseMode().add({
- name: req.json.name,
- time: Math.round(new Date().getTime() / 1000),
- });
- return result.dataValues.id;
- } else {
- let result = await global.database.muzic.promiseMode().add({
- name: req.json.name,
- author_id: req.json.author_id,
- data:
- req.json.data === undefined
- ? undefined
- : Buffer.from(req.json.data, "base64"),
- time: Math.round(new Date().getTime() / 1000),
- });
- return result.dataValues.id;
+});
+
+router.use('/create-item', async (req, res, next) => {
+ try {
+ return next(new ApiError("METHOD_MUST_BE_POST_JSON", { method : 'create-item', no_post : true }));
}
-};
\ No newline at end of file
+ catch (err) {
+ return next(err);
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/api/v1/methods/edit-item.js b/api/v1/methods/edit-item.js
index 7599fb0..9ee5b61 100644
--- a/api/v1/methods/edit-item.js
+++ b/api/v1/methods/edit-item.js
@@ -1,90 +1,88 @@
const ApiError = require("../errorClass");
+const database = require("../../../database");
+const config = require("../../../config-handler");
+const apiTypes = require("../typeChecker");
-async function isAuthorExists(id) {
- if (!id || !Number.isInteger(id)) return false;
- return (
- (await global.database.authors.promiseMode().get({ where: { id } }))
- .length !== 0
- );
-}
-
-async function isMuzicExists(id) {
- if (!id || !Number.isInteger(id)) return false;
- let result =
- (await global.database.muzic.promiseMode().get({ where: { id } }))
- .length !== 0;
- console.log("isMuzicExists", result);
- return result;
-}
+const router = require('express').Router();
async function checkSyntaxArgs(req, res) {
- return !!(
- // Проверка поля type
- ["author", "muzic"].includes(req.json.type) && // Проверка поля name
- (req.json.name === undefined
- ? true
- : typeof req.json.name === "string" && req.json.name.length >= 2) && // Проверка id
- (req.json.type === "author"
- ? await isAuthorExists(req.json.id)
- : await isMuzicExists(req.json.id)) && // Проверка при type=muzic
- (req.json.type === "muzic"
- ? // Проверка поля author_id
- (req.json.author_id === undefined
- ? true
- : await isAuthorExists(req.json.author_id)) && // Проверка поля data. (Передаётся либо ничего, либо строка с base64)
- ((req.json.data === undefined || req.json.data === null) ? true : typeof req.json.data === "string")
- : true)
- );
+ if (req.body === undefined || !req.headers['content-type'].includes('json')) {
+ throw new ApiError("METHOD_MUST_BE_POST_JSON", { method : 'edit-item', no_post : false });
+ }
+ const checker = new apiTypes.TypeChecker();
+
+ await checker.checkRequired(req.body.type, apiTypes.ItemType, 'type');
+ await checker.checkAdditional(req.body.name, apiTypes.NameType, 'name');
+
+ if (req.body.type === 'music') {
+ await checker.checkRequired(req.body.id, apiTypes.MusicType, 'id');
+
+ await checker.checkAdditional(req.body.author_id, apiTypes.AuthorType, 'author_id');
+ await checker.checkAdditional(req.body.data, apiTypes.Base64FileType, 'data');
+ }
+ else if (req.body.type === 'author') {
+ await checker.checkRequired(req.body.id, apiTypes.AuthorType, 'id');
+ }
+
+ const result = await checker.calculate();
+ if (!result.success) {
+ throw new ApiError("UNSYNTAX_PARAMS_OR_MISSED_REQUIRED_PARAMS", result);
+ }
}
-module.exports = async (req, res) => {
- if (req.method !== "POST") {
- throw new ApiError("METHOD_MUST_BE_POST_JSON", {
- request_method: req.method,
- });
+router.post('/edit-item', async (req, res, next) => {
+ try {
+ await checkSyntaxArgs(req, res);
+
+ if (req.body.type === "author") {
+ if (
+ config().authors_blacklist.filter((blacklisted) => {
+ return req.body.name
+ .toLowerCase()
+ .includes(blacklisted.toLowerCase());
+ }).length !== 0
+ ) {
+ throw new ApiError("AUTHOR_BLACKLISTED");
+ }
+
+ await database.authors.update(
+ {
+ name: req.body.name,
+ },
+ {
+ where: {
+ id: req.body.id,
+ },
+ },
+ );
+ } else {
+ await database.music.update(
+ {
+ name: req.body.name,
+ author_id: req.body.author_id,
+ data: !req.body.data ? (req.body.data === null ? null : undefined) : Buffer.from(req.body.data, "base64"),
+ },
+ {
+ where: {
+ id: req.body.id,
+ },
+ },
+ );
+ }
+ res.result("ok");
}
- console.log(await checkSyntaxArgs(req, res));
- if (!(await checkSyntaxArgs(req, res))) {
- throw new ApiError("INVALID_OR_UNSYNTAX_PARAMS", {
- request_method: req.method,
- params: {
- id: req.json.id === undefined ? null : req.json.id,
- type: req.json.type === undefined ? null : req.json.type,
- name: req.json.name === undefined ? null : req.json.name,
- ...(req.json.type === "muzic"
- ? {
- author_id: req.json.author_id,
- data: req.json.data,
- }
- : {}),
- },
- });
+ catch (err) {
+ return next(err);
}
+});
- if (req.json.type === "author") {
- await global.database.authors.promiseMode().edit(
- {
- name: req.json.name,
- },
- {
- where: {
- id: req.json.id,
- },
- },
- );
- } else {
- await global.database.muzic.promiseMode().edit(
- {
- name: req.json.name,
- author_id: req.json.author_id,
- data: !req.json.data ? (req.json.data === null ? null : undefined) : Buffer.from(req.json.data, "base64"),
- },
- {
- where: {
- id: req.json.id,
- },
- },
- );
+router.use('/edit-item', async (req, res, next) => {
+ try {
+ return next(new ApiError("METHOD_MUST_BE_POST_JSON", { method : 'edit-item', no_post : true }));
}
- return "ok";
-};
\ No newline at end of file
+ catch (err) {
+ return next(err);
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/api/v1/methods/get-authors.js b/api/v1/methods/get-authors.js
index 1f55801..9bcd0bd 100644
--- a/api/v1/methods/get-authors.js
+++ b/api/v1/methods/get-authors.js
@@ -1,81 +1,109 @@
+const { Op } = require("sequelize");
const ApiError = require("../errorClass");
+const database = require("../../../database");
+const apiTypes = require("../typeChecker");
+
+const router = require('express').Router();
async function checkSyntaxArgs(req, res) {
- return !!(
- // Проверка поля offset
- (req.query.id === undefined ? true : Number.isInteger(+req.query.offset)) && // Проверка поля offset
- (req.query.offset === undefined
- ? true
- : Number.isInteger(+req.query.offset)) && // Проверка поля count
- (req.query.count === undefined
- ? true
- : Number.isInteger(+req.query.count)) && // Проверка поля min_date
- (req.query.min_date === undefined
- ? true
- : Number.isInteger(+req.query.min_date)) && // Проверка поля max_date
- (req.query.max_date === undefined
- ? true
- : Number.isInteger(+req.query.max_date)) && // Проверка поля q. (Ключевые слова для поиска)
- true // (Проверки нет, query всегда строка, необязательный параметр)
- );
+ const checker = new apiTypes.TypeChecker(false);
+
+ await checker.checkAdditional(req.query.id, apiTypes.AuthorType, 'id');
+ await checker.checkAdditional(req.query.offset, apiTypes.IntType, 'offset');
+ await checker.checkAdditional(req.query.count, apiTypes.IntType, 'count');
+ await checker.checkAdditional(req.query.min_date, apiTypes.IntType, 'min_date');
+ await checker.checkAdditional(req.query.max_date, apiTypes.IntType, 'max_date');
+ await checker.checkAdditional(req.query.q, apiTypes.StrType, 'q');
+
+ const result = await checker.calculate();
+ if (!result.success) {
+ throw new ApiError("UNSYNTAX_PARAMS_OR_MISSED_REQUIRED_PARAMS", result);
+ }
}
-module.exports = async (req, res) => {
- if (req.method !== "GET") {
- throw new ApiError("METHOD_MUST_BE_GET", {
- request_method: req.method,
- });
- }
- if (!(await checkSyntaxArgs(req, res))) {
- throw new ApiError("INVALID_OR_UNSYNTAX_PARAMS", {
- request_method: req.method,
- params: {
- id: req.query?.id === undefined ? null : req.query.id,
- q: req.query?.q === undefined ? null : req.query.q,
- offset: req.query?.offset === undefined ? null : req.query.offset,
- count: req.query?.count === undefined ? null : req.query.count,
- min_date: req.query?.min_date === undefined ? null : req.query.min_date,
- max_date: req.query?.max_date === undefined ? null : req.query.max_date,
- },
- });
- }
-
- const offset = req.query.offset === undefined ? 0 : +req.query.offset;
- const countFromNull =
- req.query.count === undefined ? undefined : offset + +req.query.count;
-
- let result = (
- await global.database.authors.promiseMode().get(
- req.query.id === undefined
- ? {}
- : {
- id: +req.query.id,
- },
- )
- ).map((i) => ({
- id: i.id,
- name: i.name,
- date: i.time,
- }));
- // Если просят выборку по времени
- if (req.query.min_date || req.query.max_date) {
+router.get('/get-authors', async (req, res, next) => {
+ try {
+ await checkSyntaxArgs(req, res);
+
+ const offset = req.query.offset === undefined ? 0 : +req.query.offset;
+ const countFromNull =
+ req.query.count === undefined ? undefined : offset + +req.query.count;
+
+ // Выборка по времени
+ const requiredTimeSearch = req.query.min_date || req.query.max_date;
const minDate = req.query.min_date === undefined ? 0 : +req.query.min_date;
- const maxDate =
- req.query.max_date === undefined ? 1e32 : +req.query.max_date;
+ const maxDate = req.query.max_date === undefined ? 9999999999 : +req.query.max_date;
+
+ let result = (
+ await database.authors.findAll(
+ req.query.id === undefined
+ ? {
+ order: [
+ ['id', 'ASC'],
+ ['name', 'ASC'],
+ ],
+ attributes: ['id', 'name', 'time'],
+ ...(requiredTimeSearch ? ({
+ // Запрос на выборку по времени
+ where : {
+ time : {
+ [Op.gte] : minDate,
+ [Op.lte] : maxDate
+ }
+ }
+ }) : {}),
+ raw : true
+ }
+ : {
+ order: [
+ ['id', 'ASC'],
+ ['name', 'ASC'],
+ ],
+ attributes: ['id', 'name', 'time'],
+ where : {
+ id: +req.query.id,
+ ...(requiredTimeSearch ? ({
+ // Запрос на выборку по времени
+ time : {
+ [Op.gte] : minDate,
+ [Op.lte] : maxDate
+ }
+ }) : {})
+ },
+ raw : true
+ },
+ )
+ ).map((i) => ({
+ id: i.id,
+ name: i.name,
+ date: i.time,
+ }));
- result = result.filter((res) => minDate <= res.date && res.date <= maxDate);
- }
+ if (req.query?.q !== undefined) {
+ result = result.filter((i) => {
+ const search = req.query.q.toLowerCase();
+ return i.name.toLowerCase().includes(search);
+ });
+ }
- if (req.query?.q !== undefined) {
- result = result.filter((i) => {
- const search = req.query.q.toLowerCase();
- return i.name.toLowerCase().includes(search);
+ result = result.slice(offset, countFromNull);
+ res.result({
+ count: result.length,
+ items: result,
});
}
+ catch (err) {
+ return next(err);
+ }
+});
- result = result.slice(offset, countFromNull);
- return {
- count: result.length,
- items: result,
- };
-};
\ No newline at end of file
+router.use('/get-authors', async (req, res, next) => {
+ try {
+ return next(new ApiError("METHOD_MUST_BE_GET", { method : 'get-authors' }));
+ }
+ catch (err) {
+ return next(err);
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/api/v1/methods/get-music.js b/api/v1/methods/get-music.js
new file mode 100644
index 0000000..eb50ce6
--- /dev/null
+++ b/api/v1/methods/get-music.js
@@ -0,0 +1,127 @@
+const { Op } = require("sequelize");
+const ApiError = require("../errorClass");
+const database = require("../../../database");
+const apiTypes = require("../typeChecker");
+
+const router = require('express').Router();
+
+async function checkSyntaxArgs(req, res) {
+ const checker = new apiTypes.TypeChecker(false);
+
+ await checker.checkAdditional(req.query.id, apiTypes.MusicType, 'id');
+ await checker.checkAdditional(req.query.author, apiTypes.AuthorType, 'author');
+ await checker.checkAdditional(req.query.authors, apiTypes.AuthorsType, 'authors');
+ await checker.checkAdditional(req.query.offset, apiTypes.IntType, 'offset');
+ await checker.checkAdditional(req.query.count, apiTypes.IntType, 'count');
+ await checker.checkAdditional(req.query.min_date, apiTypes.IntType, 'min_date');
+ await checker.checkAdditional(req.query.max_date, apiTypes.IntType, 'max_date');
+ await checker.checkAdditional(req.query.q, apiTypes.StrType, 'q');
+ await checker.checkAdditional(req.query.searchByAuthor, apiTypes.StrType, 'searchByAuthor');
+ await checker.checkAdditional(req.query.searchByName, apiTypes.StrType, 'searchByName');
+
+ const result = await checker.calculate();
+ if (!result.success) {
+ throw new ApiError("UNSYNTAX_PARAMS_OR_MISSED_REQUIRED_PARAMS", result);
+ }
+}
+
+router.get('/get-music', async (req, res, next) => {
+ try {
+ await checkSyntaxArgs(req, res);
+
+ // Оптимизация: флаг searchByAuthor выставится на 0, если была запрошена выборка по музыке определённого автора
+ // Тем самым, мы ускоряем время ответа, поскольку мы не перебираем массив searchedAuthors с целью поиска совпадений
+ let searchByAuthor =
+ req.query.author === undefined ? req.query.searchByAuthor : "0";
+
+ const offset = req.query.offset === undefined ? 0 : +req.query.offset;
+ const countFromNull =
+ req.query.count === undefined ? undefined : offset + +req.query.count;
+
+ const searchAuthors = !req.query.authors ? [] : [...req.query.authors.split(',').map(i => +i)];
+ if (req.query.author) searchAuthors.push(+req.query.author);
+
+ const minDate = req.query.min_date === undefined ? 0 : +req.query.min_date;
+ const maxDate = req.query.max_date === undefined ? 9999999999 : +req.query.max_date;
+
+ let searchedParams = [];
+ if (searchAuthors > 0) {
+ searchAuthors.forEach(author_id => {
+ console.log(author_id);
+ searchedParams.push({ [Op.and] : [
+ req.query.id !== undefined ? {id : +req.query.id} : undefined,
+ { author_id },
+ {time : {
+ [Op.gte]: minDate,
+ [Op.lte]: maxDate,
+ }},
+ ] });
+ });
+ }
+ else {
+ searchedParams.push({ [Op.and] : [
+ req.query.id !== undefined ? {id : +req.query.id} : undefined,
+ {time : {
+ [Op.gte]: minDate,
+ [Op.lte]: maxDate,
+ }},
+ ] });
+ }
+
+ console.log('searchedParams', searchedParams);
+
+ let result = (
+ await database.music.findAll({
+ where : { [Op.or]: searchedParams },
+ raws : true
+ })
+ ).map((i) => ({
+ id: i.id,
+ name: i.name,
+ author_id: i.author_id,
+ is_data_exists: i.data !== null && i.data.length > 0,
+ date: i.time,
+ }));
+
+ if (req.query?.q !== undefined) {
+ let authors = await database.authors.findAll({raws : true});
+ let searchedAuthors = result.map((i) => {
+ let author_id = i.author_id;
+ return authors.filter((a) => a.id === author_id)[0];
+ });
+ result = result.filter((i) => {
+ const search = req.query.q.toLowerCase();
+ return (
+ (req.query.searchByName !== "0" &&
+ i.name.toLowerCase().includes(search)) ||
+ !!(
+ searchByAuthor !== "0" &&
+ searchedAuthors
+ .filter((a) => a.id === i.author_id)[0]
+ ?.name.toLowerCase()
+ .includes(search)
+ )
+ );
+ });
+ }
+ result = result.slice(offset, countFromNull);
+ res.result({
+ count: result.length,
+ items: result,
+ });
+ }
+ catch (err) {
+ return next(err);
+ }
+});
+
+router.use('/get-music', async (req, res, next) => {
+ try {
+ return next(new ApiError("METHOD_MUST_BE_GET", { method : 'get-music' }));
+ }
+ catch (err) {
+ return next(err);
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/api/v1/methods/get-muzic.js b/api/v1/methods/get-muzic.js
deleted file mode 100644
index e1ad971..0000000
--- a/api/v1/methods/get-muzic.js
+++ /dev/null
@@ -1,142 +0,0 @@
-const ApiError = require("../errorClass");
-
-async function isAuthorExists(authorId) {
- if (!authorId) return false;
- return (
- (
- await global.database.authors
- .promiseMode()
- .get({ where: { id: authorId } })
- ).length !== 0
- );
-}
-
-async function isAuthorsExists(authorsId) {
- if (!Array.isArray(authorsId)) return false;
- const authors = (await global.database.authors.promiseMode().get()).map(
- (i) => i.id,
- );
- return authorsId.map((authorId) => authors.includes(authorId));
-}
-
-async function checkSyntaxArgs(req, res) {
- return !!(
- // Проверка поля author.
- (req.query.author === undefined
- ? true
- : await isAuthorExists(+req.query.author)) && // Проверка поля authors.
- (req.query.authors === undefined
- ? true
- : await isAuthorsExists(req.query.authors.split(",").map((i) => +i))) && // Проверка поля offset
- (req.query.offset === undefined
- ? true
- : Number.isInteger(+req.query.offset)) && // Проверка поля count
- (req.query.count === undefined
- ? true
- : Number.isInteger(+req.query.count)) && // Проверка поля min_date
- (req.query.min_date === undefined
- ? true
- : Number.isInteger(+req.query.min_date)) && // Проверка поля max_date
- (req.query.max_date === undefined
- ? true
- : Number.isInteger(+req.query.max_date)) && // Проверка поля q. (Ключевые слова для поиска)
- true && // (Проверки нет, query всегда строка, необязательный параметр) // Проверка поля searchByAuthor (Флаг для 'q'. 0 - отключить)
- true && // (Проверки нет, query всегда строка, необязательный параметр) // Проверка поля searchByName (Флаг для 'q'. 0 - отключить)
- true // (Проверки нет, query всегда строка, необязательный параметр)
- );
-}
-
-module.exports = async (req, res) => {
- if (req.method !== "GET") {
- throw new ApiError("METHOD_MUST_BE_GET", {
- request_method: req.method,
- });
- }
- if (!(await checkSyntaxArgs(req, res))) {
- throw new ApiError("INVALID_OR_UNSYNTAX_PARAMS", {
- request_method: req.method,
- params: {
- author: req.query?.author === undefined ? null : req.query.author,
- authors: req.query?.authors === undefined ? null : req.query.authors,
- q: req.query?.q === undefined ? null : req.query.q,
- offset: req.query?.offset === undefined ? null : req.query.offset,
- count: req.query?.count === undefined ? null : req.query.count,
- searchByAuthor:
- req.query?.searchByAuthor === undefined
- ? null
- : req.query.searchByAuthor,
- searchByName:
- req.query?.searchByName === undefined ? null : req.query.searchByName,
- min_date: req.query?.min_date === undefined ? null : req.query.min_date,
- max_date: req.query?.max_date === undefined ? null : req.query.max_date,
- },
- });
- }
-
- // Оптимизация: флаг searchByAuthor выставится на 0, если была запрошена выборка по музыке определённого автора
- // Тем самым, мы ускоряем время ответа, поскольку мы не перебираем массив searchedAuthors с целью поиска совпадений
- let searchByAuthor =
- req.query.author === undefined ? req.query.searchByAuthor : "0";
-
- const offset = req.query.offset === undefined ? 0 : +req.query.offset;
- const countFromNull =
- req.query.count === undefined ? undefined : offset + +req.query.count;
-
- let result = (
- await global.database.muzic.promiseMode().get(
- req.query?.author === undefined
- ? {}
- : {
- where: {
- author_id: +req.query.author,
- },
- },
- )
- ).map((i) => ({
- id: i.id,
- name: i.name,
- author_id: i.author_id,
- is_data_exists: i.data !== null && i.data.length > 0,
- date: i.time,
- }));
- // Если просят выборку по authors
- if (req.query.authors) {
- let authors = req.query.authors.split(",").map((author) => +author);
- result = result.filter((res) => authors.includes(res.author_id));
- }
- // Если просят выборку по времени
- if (req.query.min_date || req.query.max_date) {
- const minDate = req.query.min_date === undefined ? 0 : +req.query.min_date;
- const maxDate =
- req.query.max_date === undefined ? 1e32 : +req.query.max_date;
-
- result = result.filter((res) => minDate <= res.date && res.date <= maxDate);
- }
-
- if (req.query?.q !== undefined) {
- let authors = await global.database.authors.promiseMode().get();
- let searchedAuthors = result.map((i) => {
- let author_id = i.author_id;
- return authors.filter((a) => a.id === author_id)[0];
- });
- result = result.filter((i) => {
- const search = req.query.q.toLowerCase();
- return (
- (req.query.searchByName !== "0" &&
- i.name.toLowerCase().includes(search)) ||
- !!(
- searchByAuthor !== "0" &&
- searchedAuthors
- .filter((a) => a.id === i.author_id)[0]
- ?.name.toLowerCase()
- .includes(search)
- )
- );
- });
- }
- result = result.slice(offset, countFromNull);
- return {
- count: result.length,
- items: result,
- };
-};
\ No newline at end of file
diff --git a/api/v1/methods/index.js b/api/v1/methods/index.js
index 3418369..eeba932 100644
--- a/api/v1/methods/index.js
+++ b/api/v1/methods/index.js
@@ -1,3 +1,30 @@
const router = require('express').Router();
-router.use();
+router.use(
+ require('./create-item')
+);
+
+router.use(
+ require('./edit-item')
+);
+
+router.use(
+ require('./remove-item')
+);
+
+router.use(
+ require('./get-authors')
+);
+
+router.use(
+ require('./get-music')
+);
+
+router.use('*', async (req, res) => {
+ res.result({
+ error : "UNKNOWN_METHOD",
+ details : {}
+ }, true, 400);
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/api/v1/methods/remove-item.js b/api/v1/methods/remove-item.js
index b0c288a..996b286 100644
--- a/api/v1/methods/remove-item.js
+++ b/api/v1/methods/remove-item.js
@@ -1,72 +1,68 @@
const ApiError = require("../errorClass");
-const config = require("../../../config-handler");
+const database = require("../../../database");
+const apiTypes = require("../typeChecker");
-async function isAuthorExists(authorId) {
- if (!authorId) return false;
- return (
- (
- await global.database.authors
- .promiseMode()
- .get({ where: { id: authorId } })
- ).length !== 0
- );
-}
+const router = require('express').Router();
async function checkSyntaxArgs(req, res) {
- return !!(
- // Проверка поля type.
- ["author", "muzic"].indexOf(req.json?.type) !== -1 && // Проверка поля id.
- (Number.isInteger(req.json.id) && req.json.type === "author"
- ? await isAuthorExists(req.json.id)
- : true)
- );
+ if (req.body === undefined || !req.headers['content-type'].includes('json')) {
+ throw new ApiError("METHOD_MUST_BE_POST_JSON", { method : 'remove-item', no_post : false });
+ }
+ const checker = new apiTypes.TypeChecker();
+
+ await checker.checkRequired(req.body.type, apiTypes.ItemType, 'type');
+
+ if (req.body.type === 'music') {
+ await checker.checkRequired(req.body.id, apiTypes.MusicType, 'id');
+ }
+ else if (req.body.type === 'author') {
+ await checker.checkRequired(req.body.id, apiTypes.AuthorType, 'id');
+ }
+
+ const result = await checker.calculate();
+ if (!result.success) {
+ throw new ApiError("UNSYNTAX_PARAMS_OR_MISSED_REQUIRED_PARAMS", result);
+ }
}
-module.exports = async (req, res) => {
- if (req.json === undefined) {
- // console.log(req.headers);
- throw new ApiError("METHOD_MUST_BE_POST_JSON", {
- request_method: req.method,
- "content-type": !req.headers["content-type"]
- ? null
- : req.headers["content-type"],
- });
+router.post('/remove-item', async (req, res, next) => {
+ try {
+ await checkSyntaxArgs(req, res);
+
+ if (req.body.type === "author") {
+ // Удаляем всю музыку этого исполнителя
+ await database.music.destroy({
+ where: {
+ author_id: req.body.id,
+ },
+ });
+ // Удаляем исполнителя
+ await database.authors.destroy({
+ where: {
+ id: req.body.id,
+ },
+ });
+ } else {
+ await database.music.destroy({
+ where: {
+ id: req.body.id,
+ },
+ });
+ }
+ res.result("ok");
}
- if (!(await checkSyntaxArgs(req, res))) {
- throw new ApiError("INVALID_OR_UNSYNTAX_PARAMS", {
- request_method: req.method,
- params: {
- type: req.json?.type === undefined ? null : req.json.type,
- name: req.json?.name === undefined ? null : req.json.name,
- ...(req.json?.type === "muzic"
- ? {
- author_id:
- req.json?.author_id === undefined ? null : req.json?.author_id,
- data: req.json?.data === undefined ? null : req.json.data,
- }
- : {}),
- },
- });
+ catch (err) {
+ return next(err);
}
- if (req.json.type === "author") {
- // Удаляем всю музыку этого исполнителя
- await global.database.muzic.promiseMode().remove({
- where: {
- author_id: req.json.id,
- },
- });
- // Удаляем исполнителя
- await global.database.authors.promiseMode().remove({
- where: {
- id: req.json.id,
- },
- });
- } else {
- await global.database.muzic.promiseMode().remove({
- where: {
- id: req.json.id,
- },
- });
+});
+
+router.use('/remove-item', async (req, res, next) => {
+ try {
+ return next(new ApiError("METHOD_MUST_BE_POST_JSON", { method : 'remove-item', no_post : true }));
}
- return "ok";
-};
\ No newline at end of file
+ catch (err) {
+ return next(err);
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/api/v1/response-wrapper.js b/api/v1/response-wrapper.js
deleted file mode 100644
index c40a381..0000000
--- a/api/v1/response-wrapper.js
+++ /dev/null
@@ -1,61 +0,0 @@
-const fs = require("fs");
-const config = require("../../config-handler");
-
-const unknownError = (err) => {
- const stackId = new Date().getTime();
- let errorLoggingFolder = config().error_logs_folder;
- errorLoggingFolder = !["/", "\\"].includes(errorLoggingFolder.at(-1))
- ? errorLoggingFolder + "/"
- : errorLoggingFolder;
- fs.writeFileSync(
- `${errorLoggingFolder}error_${stackId}.log`,
- `ERROR:
-Date: ${new Date()}
-Name: ${err.name}
-Message: ${err.message}
-Stack:
-${err.stack}`,
- );
-
- return {
- error: "UNKNOWN_ERROR",
- details: {
- trace_id: stackId,
- },
- };
-};
-
-const unknownResponseFormat = "UNKNOWN_RESPONSE_FORMAT";
-
-async function handlingError(funct, success, error) {
- try {
- success(await funct());
- } catch (e) {
- // console.log('error', e);
- error(
- e.name === "ApiError"
- ? {
- error: e.message,
- details: e.details,
- }
- : unknownError(e),
- );
- }
-}
-
-module.exports = async (method, req, res) => {
- if (req.query.response_format === "json") {
- handlingError(
- async () => ({ response: await method(req, res) }),
- async (data) => {
- res.sendModed(data);
- },
- async (errBody) => {
- res.errorModeOn();
- res.status(400).sendModed(errBody);
- },
- );
- } else {
- res.status(400).sendModed(unknownResponseFormat);
- }
-};
\ No newline at end of file
diff --git a/api/v1/typeChecker.js b/api/v1/typeChecker.js
index 6bdefba..b53949c 100644
--- a/api/v1/typeChecker.js
+++ b/api/v1/typeChecker.js
@@ -1,35 +1,203 @@
+const { Op } = require("sequelize");
+const database = require("../../database");
+
+// TypeChecker class
+class TypeChecker {
+ constructor (strictMode = true) {
+ this.strict = strictMode;
+
+ this.requiredMissed = [];
+ this.additionalMissed = [];
+
+ this.requiredUnsyntax = [];
+ this.additionalUnsyntax = [];
+
+ this.unsyntax = false;
+ }
+
+ async checkRequired (param, Type, paramName) {
+ if (param === undefined) {
+ this.requiredMissed.push(paramName);
+ this.unsyntax = true;
+ }
+ else {
+ const type = new Type(param);
+ if (this.strict && !(await type.checkStrict())) {
+ this.requiredUnsyntax.push(paramName);
+ this.unsyntax = true;
+ }
+ else if (!this.strict && !(await type.checkNoStrict())) {
+ this.requiredUnsyntax.push(paramName);
+ this.unsyntax = true;
+ }
+ }
+ }
+
+ async checkAdditional (param, Type, paramName) {
+ if (param === undefined) {
+ this.additionalMissed.push(paramName);
+ }
+ else {
+ const type = new Type(param);
+ if (this.strict && !(await type.checkStrict())) {
+ this.additionalUnsyntax.push(paramName);
+ this.unsyntax = true;
+ }
+ else if (!this.strict && !(await type.checkNoStrict())) {
+ this.additionalUnsyntax.push(paramName);
+ this.unsyntax = true;
+ }
+ }
+ }
+
+ async calculate () {
+ return {
+ success : !this.unsyntax,
+ missed : {
+ required : this.requiredMissed,
+ additional : this.additionalMissed
+ },
+ unsyntax : {
+ required : this.requiredUnsyntax,
+ additional : this.additionalUnsyntax
+ }
+ }
+ }
+}
+
+
+// Types
+
class ParamType {
constructor (value) {
this.value = value;
}
- checkNoStrict () {
+ async checkNoStrict () {
return true;
}
- checkStrict () {
+ async checkStrict () {
return true;
}
}
class StrType extends ParamType {
- checkStrict () {
+ async checkStrict () {
+ return typeof this.value === 'string';
+ }
+
+ async checkNoStrict () {
return typeof this.value === 'string';
}
}
class IntType extends ParamType {
- checkNoStrict () {
+ async checkNoStrict () {
return Number.isInteger(+this.value);
}
- checkStrict () {
+ async checkStrict () {
return Number.isInteger(this.value);
}
}
+// Custom types
+
+class ItemType extends StrType {
+ async checkStrict () {
+ if (await super.checkStrict()) return ['music', 'author'].includes(this.value);
+ return false;
+ }
+
+ async checkNoStrict () {
+ if (await super.checkNoStrict()) return ['music', 'author'].includes(this.value);
+ return false;
+ }
+}
+
+class NameType extends StrType {
+ async checkStrict () {
+ if (await super.checkStrict()) return this.value.length >= 2;
+ return false;
+ }
+
+ async checkNoStrict () {
+ if (await super.checkNoStrict()) return this.value.length >= 2;
+ return false;
+ }
+}
+
+class AuthorType extends IntType {
+ async checkStrict () {
+ if (await super.checkStrict()) {
+ const authorId = this.value;
+ return (await database.authors.findAll({ where: { id: authorId }, raw : true })).length !== 0;
+ }
+ return false;
+ }
+
+ async checkNoStrict () {
+ if (await super.checkNoStrict()) {
+ const authorId = +this.value;
+ return (await database.authors.findAll({ where: { id: authorId }, raw : true })).length !== 0;
+ }
+ return false;
+ }
+}
+
+class AuthorsType extends AuthorType {
+ async checkStrict () {
+ if (Array.isArray(this.value) && !this.value.map(i => Number.isInteger(i)).includes(false)) {
+ // return (await database.authors.findAll({ where: [Op.or]: [
+ // { id: 12 },
+ // { id: 13 }
+ // ], raw : true })).length === this.value;
+ return (await database.authors.findAll({ where: { [Op.or]: this.value.map(id => ({ id })) }, raw : true })).length === this.value;
+ }
+ return false;
+ }
+
+ async checkNoStrict () {
+ if (await(new StrType(this.value)).checkStrict()) {
+ const values = this.value.split(',').map(i => +i);
+ if (!values.map(i => Number.isInteger(i)).includes(false)) {
+ return (await database.authors.findAll({ where: { [Op.or]: values.map(id => ({ id })) }, raw : true })).length === this.value.length;
+ }
+ return false;
+ }
+ return false;
+ }
+}
+
+class MusicType extends IntType {
+ async checkStrict () {
+ if (await super.checkStrict()) {
+ const musicId = this.value;
+ return (await database.music.findAll({ where: { id: musicId }, raw : true })).length !== 0;
+ }
+ return false;
+ }
+
+ async checkNoStrict () {
+ if (await super.checkNoStrict()) {
+ const musicId = +this.value;
+ return (await database.music.findAll({ where: { id: musicId }, raw : true })).length !== 0;
+ }
+ return false;
+ }
+}
+
+class Base64FileType extends StrType {
+ async checkStrict () {
+ return this.value === null || (await super.checkStrict());
+ }
+}
+
module.exports = {
ParamType,
- StrType, IntType
+ StrType, IntType,
+ ItemType, NameType, AuthorType, AuthorsType, MusicType, Base64FileType,
+ TypeChecker
};
diff --git a/db-dump/install-it.sql b/db-dump/install-it.sql
index c033bb6589006de231d188009bdeb2573cbcea0b..30df35b7ad9cc163b6dd92953d92c070b41f6c5e 100644
GIT binary patch
delta 269
zcmZ3dxK44yIabEv$>&*hfaD+6c|dYDTQ-nnXEz0sw(Nn7#hd%tI~bXZGm|IBamY`$
z Пусто. Добавьте произведения
${muzic.name}
-
+ music.is_data_exists
+ });'> ${music.name}
+
-