Merge branch 'main' into 'laptop'
Main See merge request Nikiroy78/kodex-muzic-catalog!5
This commit is contained in:
commit
b91280517c
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
references/
|
||||
ts.txt
|
||||
*.log
|
49
api-tests/test.py
Normal file
49
api-tests/test.py
Normal file
@ -0,0 +1,49 @@
|
||||
import requests as req
|
||||
|
||||
|
||||
class CreateItemChecks:
|
||||
def createAuthor (this, result):
|
||||
return 'response' in result
|
||||
|
||||
def invalidType (this, result):
|
||||
return 'response' in result
|
||||
|
||||
|
||||
class CreateItemActions:
|
||||
def createAuthor (this):
|
||||
return req.post(
|
||||
"http://127.0.0.1:8080/api/v/1.0/create-item?response_format=json",
|
||||
json={
|
||||
"type" : "author",
|
||||
"name" : "TestAuthor"
|
||||
}
|
||||
).json()
|
||||
|
||||
def invalidType (this):
|
||||
return req.post(
|
||||
"http://127.0.0.1:8080/api/v/1.0/create-item?response_format=json",
|
||||
json={
|
||||
"type" : "asdasdsad",
|
||||
"name" : "TestAuthor"
|
||||
}
|
||||
).json()
|
||||
|
||||
createItemActions = CreateItemActions()
|
||||
createItemChecks = CreateItemChecks()
|
||||
|
||||
def test (id, action, isDone, nextTest=lambda:None):
|
||||
try:
|
||||
print("""Test {id} runned..""")
|
||||
result = action()
|
||||
if isDone(result):
|
||||
print("""Test {id} success!""")
|
||||
nextTest()
|
||||
else:
|
||||
print("""Test {id} ERROR""")
|
||||
print(result)
|
||||
except Exception as exc:
|
||||
print(exc)
|
||||
|
||||
test_3 = lambda : test(3, createItemActions.createAuthor, createItemChecks.createAuthor)
|
||||
test_2 = lambda : test(2, createItemActions.createAuthor, createItemChecks.createAuthor)
|
||||
test_1 = test(1, createItemActions.invalidType, createItemChecks.invalidType, test)
|
4
api/index.js
Normal file
4
api/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
const router = require("express").Router();
|
||||
router.use("/v/1.0", require("./v1"));
|
||||
|
||||
module.exports = router;
|
8
api/v1/errorClass.js
Normal file
8
api/v1/errorClass.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = class ApiError extends Error {
|
||||
constructor(message, details = {}, ...args) {
|
||||
super(message, ...args);
|
||||
this.message = message;
|
||||
this.details = details;
|
||||
this.name = "ApiError";
|
||||
}
|
||||
};
|
58
api/v1/index.js
Normal file
58
api/v1/index.js
Normal file
@ -0,0 +1,58 @@
|
||||
const router = require("express").Router();
|
||||
const response = require("./response-wrapper");
|
||||
// const config = require('../../config-handler');
|
||||
|
||||
// Парсинг куки
|
||||
//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 } },
|
||||
);
|
||||
}
|
||||
|
||||
// Подгрузка с файла
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
87
api/v1/methods/create-item.js
Normal file
87
api/v1/methods/create-item.js
Normal file
@ -0,0 +1,87 @@
|
||||
const ApiError = require("../errorClass");
|
||||
const config = require("../../../config-handler");
|
||||
|
||||
async function isAuthorExists(authorId) {
|
||||
if (!authorId) return false;
|
||||
return (
|
||||
(
|
||||
await global.database.authors
|
||||
.promiseMode()
|
||||
.get({ where: { id: authorId } })
|
||||
).length !== 0
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
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"],
|
||||
});
|
||||
}
|
||||
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,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
90
api/v1/methods/edit-item.js
Normal file
90
api/v1/methods/edit-item.js
Normal file
@ -0,0 +1,90 @@
|
||||
const ApiError = require("../errorClass");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 ? true : typeof req.json.data === "string")
|
||||
: true)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
if (req.method !== "POST") {
|
||||
throw new ApiError("METHOD_MUST_BE_POST_JSON", {
|
||||
request_method: req.method,
|
||||
});
|
||||
}
|
||||
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,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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 ? undefined : Buffer.from(req.json.data, "base64"),
|
||||
},
|
||||
{
|
||||
where: {
|
||||
id: req.json.id,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
return "ok";
|
||||
};
|
81
api/v1/methods/get-authors.js
Normal file
81
api/v1/methods/get-authors.js
Normal file
@ -0,0 +1,81 @@
|
||||
const ApiError = require("../errorClass");
|
||||
|
||||
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 всегда строка, необязательный параметр)
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
result = result.filter((i) => {
|
||||
const search = req.query.q.toLowerCase();
|
||||
return i.name.toLowerCase().includes(search);
|
||||
});
|
||||
}
|
||||
|
||||
result = result.slice(offset, countFromNull);
|
||||
return {
|
||||
count: result.length,
|
||||
items: result,
|
||||
};
|
||||
};
|
142
api/v1/methods/get-muzic.js
Normal file
142
api/v1/methods/get-muzic.js
Normal file
@ -0,0 +1,142 @@
|
||||
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,
|
||||
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,
|
||||
};
|
||||
};
|
72
api/v1/methods/remove-item.js
Normal file
72
api/v1/methods/remove-item.js
Normal file
@ -0,0 +1,72 @@
|
||||
const ApiError = require("../errorClass");
|
||||
const config = require("../../../config-handler");
|
||||
|
||||
async function isAuthorExists(authorId) {
|
||||
if (!authorId) return false;
|
||||
return (
|
||||
(
|
||||
await global.database.authors
|
||||
.promiseMode()
|
||||
.get({ where: { id: authorId } })
|
||||
).length !== 0
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
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"],
|
||||
});
|
||||
}
|
||||
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,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
return "ok";
|
||||
};
|
61
api/v1/response-wrapper.js
Normal file
61
api/v1/response-wrapper.js
Normal file
@ -0,0 +1,61 @@
|
||||
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);
|
||||
}
|
||||
};
|
8
config-handler.js
Normal file
8
config-handler.js
Normal file
@ -0,0 +1,8 @@
|
||||
const fs = require("fs");
|
||||
module.exports = () => {
|
||||
const config = JSON.parse(
|
||||
fs.readFileSync("./config.json", { encoding: "utf-8" }),
|
||||
);
|
||||
// Проверить конфиг на целостность
|
||||
return config;
|
||||
};
|
24
config.json
Normal file
24
config.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"address" : "127.0.0.1",
|
||||
"port" : 8080,
|
||||
"ssl" : {
|
||||
"enabled" : false,
|
||||
"public" : "",
|
||||
"private" : ""
|
||||
},
|
||||
"database" : {
|
||||
"port" : 5433,
|
||||
"address" : "localhost",
|
||||
"username" : "postgres",
|
||||
"password" : "root",
|
||||
"database" : "kodex-db"
|
||||
},
|
||||
"logger_mode" : true,
|
||||
"logger_folder" : "./logger",
|
||||
|
||||
"error_logs_folder" : "./errors",
|
||||
|
||||
"authors_blacklist" : [
|
||||
"Монеточка", "Monetochka"
|
||||
]
|
||||
}
|
136
database.js
Normal file
136
database.js
Normal file
@ -0,0 +1,136 @@
|
||||
const { Sequelize, DataTypes } = require("sequelize");
|
||||
|
||||
class Table {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
promiseMode() {
|
||||
return new TablePromise(this.model);
|
||||
}
|
||||
|
||||
get(cb, condition = null) {
|
||||
this.model
|
||||
.findAll({ ...(!condition ? {} : condition), raw: true })
|
||||
.then((data) => cb(null, data))
|
||||
.catch((err) => cb(err, null));
|
||||
}
|
||||
|
||||
add(item, cb) {
|
||||
this.model
|
||||
.create(item)
|
||||
.then((i) => cb(null, i))
|
||||
.catch((err) => cb(err, null));
|
||||
}
|
||||
|
||||
remove(condition, cb) {
|
||||
this.model
|
||||
.destroy(condition)
|
||||
.then((i) => cb(null, i))
|
||||
.catch((err) => cb(err, null));
|
||||
}
|
||||
|
||||
edit(data, condition, cb) {
|
||||
this.model
|
||||
.update(data, condition)
|
||||
.then((i) => cb(null, i))
|
||||
.catch((err) => cb(err, null));
|
||||
}
|
||||
}
|
||||
|
||||
class TablePromise extends Table {
|
||||
async get(condition) {
|
||||
return await this.model.findAll({
|
||||
...(!condition ? {} : condition),
|
||||
raw: true,
|
||||
});
|
||||
}
|
||||
|
||||
async add(item) {
|
||||
//setTimeout(() => this.model.findAll(), 0);
|
||||
return await this.model.create(item);
|
||||
}
|
||||
|
||||
async remove(condition) {
|
||||
return await this.model.destroy(condition);
|
||||
}
|
||||
|
||||
async edit(data, condition) {
|
||||
return await this.model.update(data, condition);
|
||||
}
|
||||
}
|
||||
|
||||
class Database {
|
||||
constructor(address, port, username, password, database) {
|
||||
this.sequelize = new Sequelize(
|
||||
`postgres://${username}:${password}@${address}:${port}/${database}`,
|
||||
);
|
||||
this.sequelize.authenticate().then(
|
||||
() => {
|
||||
this.authors = new Table(
|
||||
this.sequelize.define(
|
||||
"authors",
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
freezeTableName: true,
|
||||
timestamps: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
this.muzic = new Table(
|
||||
this.sequelize.define(
|
||||
"muzic",
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
author_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
data: {
|
||||
type: DataTypes.BLOB("long"),
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
freezeTableName: true,
|
||||
timestamps: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
console.log("Database successful connected!");
|
||||
},
|
||||
(err) => {
|
||||
throw err;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Database };
|
BIN
db-dump/install-it.sql
Normal file
BIN
db-dump/install-it.sql
Normal file
Binary file not shown.
41
frontend/main.html
Normal file
41
frontend/main.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Kodex Muzic</title>
|
||||
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/main.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- <h3><center>Kodex Muzic</center></h3> -->
|
||||
<nav class="navbar navbar-light" style="background-color: #e3f2fd;">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand mb-0 h1">Kodex Muzic</span>
|
||||
</div>
|
||||
<p style="margin-left: 10px;"><b>Поиск:</b> <input type="text" placeholder="Поиск по ключевым словам" class="form-control" id="search-input" style="width: 250%"></p>
|
||||
</nav>
|
||||
|
||||
<div id="app-window">
|
||||
<p><center>Включите поддержку javascript!</center></p>
|
||||
</div>
|
||||
<!-- <div class="author-element" id="author-0"> -->
|
||||
<!-- <h3>Автор_Нейм</h3> -->
|
||||
<!-- <p>Произведения</p> -->
|
||||
<!-- <hr/> -->
|
||||
<!-- <div id="playlist-author-0"> -->
|
||||
<!-- <p> -->
|
||||
<!-- <h6>Muzic</h6> -->
|
||||
<!-- <button class="btn btn-primary" onclick="playMuzic(4)" id="play-4">Прослушать</button> -->
|
||||
<!-- <button class="btn btn-primary" onclick="" id="pause-4" disabled>Пауза</button> -->
|
||||
<!-- <input type="range" id="road-4" name="stop" min="0" max="0" value="0" style="width: 70%;" readonly/> -->
|
||||
<!-- </p> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<script src="/js/api-module.js"></script>
|
||||
<script src="/js/sound.js"></script>
|
||||
<script src="/js/menu.js"></script>
|
||||
<script src="/js/main.js"></script>
|
||||
<script src="/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
5051
frontend/public/bootstrap/css/bootstrap-grid.css
vendored
Normal file
5051
frontend/public/bootstrap/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/css/bootstrap-grid.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap-grid.css.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/css/bootstrap-grid.min.css
vendored
Normal file
7
frontend/public/bootstrap/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/public/bootstrap/css/bootstrap-grid.min.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap-grid.min.css.map
Normal file
File diff suppressed because one or more lines are too long
5050
frontend/public/bootstrap/css/bootstrap-grid.rtl.css
vendored
Normal file
5050
frontend/public/bootstrap/css/bootstrap-grid.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/css/bootstrap-grid.rtl.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap-grid.rtl.css.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/css/bootstrap-grid.rtl.min.css
vendored
Normal file
7
frontend/public/bootstrap/css/bootstrap-grid.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
485
frontend/public/bootstrap/css/bootstrap-reboot.css
vendored
Normal file
485
frontend/public/bootstrap/css/bootstrap-reboot.css
vendored
Normal file
@ -0,0 +1,485 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.1.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
:root {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-bg: #fff;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
background-color: currentColor;
|
||||
border: 0;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
hr:not([size]) {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-bs-original-title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.2em;
|
||||
background-color: #fcf8e3;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0d6efd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
color: #0a58ca;
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
direction: ltr /* rtl:ignore */;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: #d63384;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.875em;
|
||||
color: #fff;
|
||||
background-color: #212529;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
1
frontend/public/bootstrap/css/bootstrap-reboot.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap-reboot.css.map
Normal file
File diff suppressed because one or more lines are too long
8
frontend/public/bootstrap/css/bootstrap-reboot.min.css
vendored
Normal file
8
frontend/public/bootstrap/css/bootstrap-reboot.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
482
frontend/public/bootstrap/css/bootstrap-reboot.rtl.css
vendored
Normal file
482
frontend/public/bootstrap/css/bootstrap-reboot.rtl.css
vendored
Normal file
@ -0,0 +1,482 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.1.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
:root {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-bg: #fff;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
background-color: currentColor;
|
||||
border: 0;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
hr:not([size]) {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-bs-original-title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.2em;
|
||||
background-color: #fcf8e3;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0d6efd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
color: #0a58ca;
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
direction: ltr ;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: #d63384;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.875em;
|
||||
color: #fff;
|
||||
background-color: #212529;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: #6c757d;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
File diff suppressed because one or more lines are too long
8
frontend/public/bootstrap/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
8
frontend/public/bootstrap/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4866
frontend/public/bootstrap/css/bootstrap-utilities.css
vendored
Normal file
4866
frontend/public/bootstrap/css/bootstrap-utilities.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/css/bootstrap-utilities.min.css
vendored
Normal file
7
frontend/public/bootstrap/css/bootstrap-utilities.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4857
frontend/public/bootstrap/css/bootstrap-utilities.rtl.css
vendored
Normal file
4857
frontend/public/bootstrap/css/bootstrap-utilities.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
7
frontend/public/bootstrap/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11266
frontend/public/bootstrap/css/bootstrap.css
vendored
Normal file
11266
frontend/public/bootstrap/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/css/bootstrap.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap.css.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/css/bootstrap.min.css
vendored
Normal file
7
frontend/public/bootstrap/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/public/bootstrap/css/bootstrap.min.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
11242
frontend/public/bootstrap/css/bootstrap.rtl.css
vendored
Normal file
11242
frontend/public/bootstrap/css/bootstrap.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/css/bootstrap.rtl.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap.rtl.css.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/css/bootstrap.rtl.min.css
vendored
Normal file
7
frontend/public/bootstrap/css/bootstrap.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/public/bootstrap/css/bootstrap.rtl.min.css.map
Normal file
1
frontend/public/bootstrap/css/bootstrap.rtl.min.css.map
Normal file
File diff suppressed because one or more lines are too long
6812
frontend/public/bootstrap/js/bootstrap.bundle.js
vendored
Normal file
6812
frontend/public/bootstrap/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/js/bootstrap.bundle.js.map
Normal file
1
frontend/public/bootstrap/js/bootstrap.bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
7
frontend/public/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/public/bootstrap/js/bootstrap.bundle.min.js.map
Normal file
1
frontend/public/bootstrap/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
4999
frontend/public/bootstrap/js/bootstrap.esm.js
vendored
Normal file
4999
frontend/public/bootstrap/js/bootstrap.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/js/bootstrap.esm.js.map
Normal file
1
frontend/public/bootstrap/js/bootstrap.esm.js.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/js/bootstrap.esm.min.js
vendored
Normal file
7
frontend/public/bootstrap/js/bootstrap.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/public/bootstrap/js/bootstrap.esm.min.js.map
Normal file
1
frontend/public/bootstrap/js/bootstrap.esm.min.js.map
Normal file
File diff suppressed because one or more lines are too long
5046
frontend/public/bootstrap/js/bootstrap.js
vendored
Normal file
5046
frontend/public/bootstrap/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/bootstrap/js/bootstrap.js.map
Normal file
1
frontend/public/bootstrap/js/bootstrap.js.map
Normal file
File diff suppressed because one or more lines are too long
7
frontend/public/bootstrap/js/bootstrap.min.js
vendored
Normal file
7
frontend/public/bootstrap/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/public/bootstrap/js/bootstrap.min.js.map
Normal file
1
frontend/public/bootstrap/js/bootstrap.min.js.map
Normal file
File diff suppressed because one or more lines are too long
14
frontend/public/css/main.css
Normal file
14
frontend/public/css/main.css
Normal file
@ -0,0 +1,14 @@
|
||||
body {
|
||||
background-color : #fff;
|
||||
}
|
||||
|
||||
.author-element {
|
||||
background-color : #e5f4fe;
|
||||
border-radius : 25px;
|
||||
padding-left : 30px;
|
||||
padding-right : 30px;
|
||||
padding-top : 7px;
|
||||
padding-bottom : 5px;
|
||||
margin : 15px;
|
||||
margin-top : 15px;
|
||||
}
|
152
frontend/public/js/api-module.js
Normal file
152
frontend/public/js/api-module.js
Normal file
@ -0,0 +1,152 @@
|
||||
class Api {
|
||||
getAuthors(params, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
params = Object.entries(params)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("&");
|
||||
params = params !== "" ? `&${params}` : "";
|
||||
xhr.open(
|
||||
"GET",
|
||||
`/api/v/1.0/get-authors?response_format=json${params}`,
|
||||
true,
|
||||
);
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
getMuzic(params, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
params = Object.entries(params)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("&");
|
||||
params = params !== "" ? `&${params}` : "";
|
||||
xhr.open("GET", `/api/v/1.0/get-muzic?response_format=json${params}`, true);
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
deleteMuzic(id, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/v/1.0/remove-item?response_format=json`, true);
|
||||
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
if (JSON.parse(this.responseText).response === undefined) {
|
||||
throw this.responseText;
|
||||
}
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
type: "muzic",
|
||||
id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
editMuzic(id, name, data, author_id, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/v/1.0/edit-item?response_format=json`, true);
|
||||
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
if (JSON.parse(this.responseText).response === undefined) {
|
||||
throw this.responseText;
|
||||
}
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
type: "muzic",
|
||||
id,
|
||||
name,
|
||||
author_id,
|
||||
data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
createMuzic(author_id, name, data = null, cb) {
|
||||
data = data === null ? undefined : data;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/v/1.0/create-item?response_format=json`, true);
|
||||
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
type: "muzic",
|
||||
name,
|
||||
author_id,
|
||||
data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
createAuthor(name, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/v/1.0/create-item?response_format=json`, true);
|
||||
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
type: "author",
|
||||
name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
removeAuthor(id, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/v/1.0/remove-item?response_format=json`, true);
|
||||
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
type: "author",
|
||||
id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
editAuthor(id, name, cb) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/v/1.0/edit-item?response_format=json`, true);
|
||||
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
|
||||
xhr.onreadystatechange = function (event) {
|
||||
//console.log(event);
|
||||
if (this.readyState != 4) return;
|
||||
cb(JSON.parse(this.responseText).response);
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
type: "author",
|
||||
id,
|
||||
name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const api = new Api();
|
199
frontend/public/js/main.js
Normal file
199
frontend/public/js/main.js
Normal file
@ -0,0 +1,199 @@
|
||||
document.getElementById("app-window").innerHTML =
|
||||
"<p><center>Загрузка произведений...</center></p>";
|
||||
|
||||
function toggleShow(authorId) {
|
||||
document.getElementById(`playlist-author-${authorId}`).hidden =
|
||||
!document.getElementById(`playlist-author-${authorId}`).hidden;
|
||||
}
|
||||
|
||||
function showAudio(settingsAuthors, settingsAudio) {
|
||||
showAudio.settingsAuthors = settingsAuthors;
|
||||
showAudio.settingsAudio = settingsAudio;
|
||||
|
||||
const step2 = (muzic) => {
|
||||
//console.log(muzic.items);
|
||||
const showedAuthors = [
|
||||
...new Set(muzic.items.map((i) => `author-${i.author_id}`)),
|
||||
];
|
||||
[...document.getElementsByClassName("author-element")].forEach((el) => {
|
||||
if (
|
||||
JSON.stringify(settingsAuthors) === "{}" &&
|
||||
JSON.stringify(settingsAudio) === "{}"
|
||||
) {
|
||||
if (!showedAuthors.includes(el.id)) {
|
||||
const id = el.id.slice(7);
|
||||
document.getElementById(`loader-muzic-${id}`).innerHTML =
|
||||
"<p><b>Пусто. Добавьте произведения</b></p>";
|
||||
}
|
||||
return;
|
||||
}
|
||||
el.hidden = !showedAuthors.includes(el.id);
|
||||
});
|
||||
|
||||
muzic.items
|
||||
.sort((a, b) => a.date - b.date)
|
||||
.forEach((muzic) => {
|
||||
document.getElementById(
|
||||
`loader-muzic-${muzic.author_id}`,
|
||||
).hidden = true;
|
||||
if (muzic.is_data_exists)
|
||||
document.getElementById(
|
||||
`playlist-author-${muzic.author_id}`,
|
||||
).innerHTML += `<p><div id="muzic-item-${muzic.id}">
|
||||
<h6><button class="btn btn-secondary" id="edit-muzic-item-${
|
||||
muzic.id
|
||||
}" onclick='showEditMuzic(${muzic.id}, ${JSON.stringify(
|
||||
muzic.name,
|
||||
)}, ${
|
||||
muzic.is_data_exists
|
||||
});'><i class="bi bi-pencil-square"></i></button> ${muzic.name}</h6>
|
||||
<button class="btn btn-primary play-button" onclick="playMuzic(${
|
||||
muzic.id
|
||||
})" id="play-${muzic.id}">Прослушать</button>
|
||||
<button class="btn btn-primary pause-button" onclick="" id="pause-${
|
||||
muzic.id
|
||||
}" disabled>Пауза</button>
|
||||
<input class="road-muzic" type="range" id="road-${
|
||||
muzic.id
|
||||
}" name="stop" min="0" max="0" value="0" step="0.01" style="width: 70%;"/>
|
||||
</div></p>`;
|
||||
else
|
||||
document.getElementById(
|
||||
`playlist-author-${muzic.author_id}`,
|
||||
).innerHTML += `<p><div id="muzic-item-${muzic.id}">
|
||||
<h6>${muzic.name}</h6>
|
||||
<button class="btn btn-secondary" onclick='showEditMuzic(${
|
||||
muzic.id
|
||||
}, ${JSON.stringify(muzic.name)}, ${
|
||||
muzic.is_data_exists
|
||||
});' id="add-file-${muzic.id}" id="edit-muzic-item-${
|
||||
muzic.id
|
||||
}">Добавить звуковой файл</button>
|
||||
</div></p>`;
|
||||
});
|
||||
};
|
||||
api.getAuthors(settingsAuthors, (authors) => {
|
||||
document.getElementById("app-window").innerHTML = "";
|
||||
authors.items
|
||||
.sort((a, b) => a.date - b.date)
|
||||
.forEach((author) => {
|
||||
document.getElementById(
|
||||
"app-window",
|
||||
).innerHTML += `<div class="author-element" id="author-${author.id}">
|
||||
<div id="author-info-${author.id}"><h3>${author.name} <button class="btn btn-primary" id="edit-author-${author.id}" onclick="showAuthorEditor(${author.id}, '${author.name}');">Редактировать</button></h3></div>
|
||||
<p>Произведения</p>
|
||||
<hr/>
|
||||
<a href='#' onclick="toggleShow(${author.id})">Показать/Скрыть</a>
|
||||
<div id="playlist-author-${author.id}">
|
||||
<p id="loader-muzic-${author.id}">Загрузка..</p>
|
||||
</div>
|
||||
<div id="add-muzic-${author.id}" style="margin-bottom: 15px;"><center><button class="btn btn-primary" id="add-muzic-button-${author.id}">Добавить произведение</button></center></div>
|
||||
</div>`;
|
||||
const addMuzicButtonFunct = () =>
|
||||
setTimeout(
|
||||
() =>
|
||||
(document.getElementById(
|
||||
`add-muzic-button-${author.id}`,
|
||||
).onclick = async () => {
|
||||
// console.log('>>', author.id);
|
||||
document.getElementById(
|
||||
`add-muzic-${author.id}`,
|
||||
).innerHTML = `<p><input class="form-control" type="text" placeholder="Введите название произведения" id="muzic-name-input-${author.id}" style="width: 90%"></p>
|
||||
<p><button class="btn btn-primary" id="attach-file-${author.id}">Добавить файл (.mp3)</button>
|
||||
<button class="btn btn-primary" id="add-muzic-${author.id}">Добавить произведение</button>
|
||||
<button class="btn btn-danger" id="cancel-add-muzic-${author.id}">Отменить</button></p>`;
|
||||
setTimeout(() => {
|
||||
document.getElementById(
|
||||
`cancel-add-muzic-${author.id}`,
|
||||
).onclick = function () {
|
||||
document.getElementById(
|
||||
`add-muzic-${author.id}`,
|
||||
).innerHTML = `<center><button class="btn btn-primary" id="add-muzic-button-${author.id}">Добавить произведение</button></center>`;
|
||||
addMuzicButtonFunct();
|
||||
};
|
||||
|
||||
document.getElementById(`add-muzic-${author.id}`).onclick =
|
||||
function () {
|
||||
if (
|
||||
document.getElementById(`muzic-name-input-${author.id}`)
|
||||
.value.length >= 2
|
||||
) {
|
||||
api.createMuzic(
|
||||
author.id,
|
||||
document.getElementById(
|
||||
`muzic-name-input-${author.id}`,
|
||||
).value,
|
||||
document.getElementById(`attach-file-${author.id}`)
|
||||
.filedata,
|
||||
() => {
|
||||
showAudio(settingsAuthors, settingsAudio);
|
||||
},
|
||||
);
|
||||
document.getElementById(
|
||||
`add-muzic-${author.id}`,
|
||||
).innerHTML = "Добавление файла...";
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById(`attach-file-${author.id}`).onclick =
|
||||
function () {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "audio/mpeg";
|
||||
input.click();
|
||||
input.onchange = function (e) {
|
||||
function arrayBufferToBase64(buffer) {
|
||||
let binary = "";
|
||||
let bytes = new Uint8Array(buffer);
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
const file = e.target.files[0];
|
||||
// console.log(file);
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = function () {
|
||||
// console.log('reader.result', reader.result);
|
||||
if (file.type === "audio/mpeg") {
|
||||
document.getElementById(
|
||||
`attach-file-${author.id}`,
|
||||
).filedata = arrayBufferToBase64(reader.result);
|
||||
document.getElementById(
|
||||
`attach-file-${author.id}`,
|
||||
).innerText = "Загружен файл: " + file.name;
|
||||
}
|
||||
// console.log(arrayBufferToBase64(reader.result));
|
||||
};
|
||||
};
|
||||
};
|
||||
}, 0);
|
||||
}),
|
||||
0,
|
||||
);
|
||||
addMuzicButtonFunct();
|
||||
});
|
||||
api.getMuzic(settingsAudio, step2);
|
||||
document.getElementById("app-window").innerHTML +=
|
||||
'<div id="add-author" style="margin-bottom: 15px;"><center><button class="btn btn-primary" id="add-author-button" onclick="addAuthorMenu();">Добавить исполнителя</button></center></div>';
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => showAudio({}, {}), 0);
|
||||
|
||||
document.getElementById("search-input").onchange = function () {
|
||||
console.log(this.value);
|
||||
setTimeout(
|
||||
() =>
|
||||
showAudio(
|
||||
{},
|
||||
{
|
||||
q: this.value,
|
||||
},
|
||||
),
|
||||
0,
|
||||
);
|
||||
};
|
185
frontend/public/js/menu.js
Normal file
185
frontend/public/js/menu.js
Normal file
@ -0,0 +1,185 @@
|
||||
function removeAuthor(authorId) {
|
||||
api.removeAuthor(authorId, () => {
|
||||
showAudio(showAudio.settingsAuthors, showAudio.settingsAudio);
|
||||
});
|
||||
}
|
||||
|
||||
function editAuthor(authorId) {
|
||||
const name = document.getElementById(`input-author-name-${authorId}`).value;
|
||||
api.editAuthor(authorId, name, (res) => {
|
||||
if (res !== undefined) removeAuthorEditor(authorId, name);
|
||||
});
|
||||
}
|
||||
|
||||
function removeAuthorEditor(authorId, authorName) {
|
||||
document.getElementById(
|
||||
`author-info-${authorId}`,
|
||||
).innerHTML = `<h3>${authorName} <button class="btn btn-primary" id="edit-author-${authorId}" onclick="showAuthorEditor(${authorId}, '${authorName}');">Редактировать</button></h3>`;
|
||||
setTimeout(() => {
|
||||
document.getElementById(`edit-author-${authorId}`).onclick = () =>
|
||||
showAuthorEditor(authorId, authorName);
|
||||
}, 5);
|
||||
}
|
||||
|
||||
function showAuthorEditorButtonInit(authorId) {
|
||||
const authorName = document.getElementById(
|
||||
`author-info-${authorId}`,
|
||||
).lastValue;
|
||||
document.getElementById(`cancel-edit-author-${authorId}`).onclick = () =>
|
||||
removeAuthorEditor(authorId, authorName);
|
||||
document.getElementById(`remove-author-${authorId}`).onclick = () =>
|
||||
removeAuthor(authorId);
|
||||
document.getElementById(`edit-author-${authorId}`).onclick = () =>
|
||||
editAuthor(authorId);
|
||||
// document.getElementById(`add-author-button`).onclick = addAuthor;
|
||||
}
|
||||
|
||||
function showAuthorEditor(authorId, lastValue) {
|
||||
document.getElementById(`author-info-${authorId}`).lastValue = lastValue;
|
||||
document.getElementById(
|
||||
`author-info-${authorId}`,
|
||||
).innerHTML = `<h3><input id="input-author-name-${authorId}" class="form-control" type="text", value=${JSON.stringify(
|
||||
lastValue,
|
||||
)}/>
|
||||
<button class="btn btn-primary" id="edit-author-${authorId}">Сохранить</button>
|
||||
<button class="btn btn-secondary" id="cancel-edit-author-${authorId}">Отмена</button>
|
||||
<button class="btn btn-danger" id="remove-author-${authorId}">Удалить</button>
|
||||
</h3>`;
|
||||
setTimeout(() => showAuthorEditorButtonInit(authorId), 5);
|
||||
}
|
||||
|
||||
function addAuthor() {
|
||||
const name = document.getElementById(`author-name-input`).value;
|
||||
document.getElementById("add-author").innerHTML =
|
||||
"<center><p>Добавляем..</center></p>";
|
||||
api.createAuthor(name, () => {
|
||||
showAudio(showAudio.settingsAuthors, showAudio.settingsAudio);
|
||||
});
|
||||
}
|
||||
|
||||
function rmAuthorMenuButtonInit() {
|
||||
document.getElementById(`add-author-button`).onclick = addAuthorMenu;
|
||||
}
|
||||
|
||||
function removeAuthorMenu() {
|
||||
document.getElementById(
|
||||
"add-author",
|
||||
).innerHTML = `<center><button class="btn btn-primary" id="add-author-button">Добавить исполнителя</button></center>`;
|
||||
setTimeout(rmAuthorMenuButtonInit, 5);
|
||||
}
|
||||
|
||||
function authorMenuButtonInit() {
|
||||
document.getElementById(`cancel-add-author`).onclick = removeAuthorMenu;
|
||||
document.getElementById(`add-author-button`).onclick = addAuthor;
|
||||
}
|
||||
|
||||
function addAuthorMenu() {
|
||||
document.getElementById(
|
||||
"add-author",
|
||||
).innerHTML = `<center><p><input class="form-control" type="text" placeholder="Введите название исполнителя" id="author-name-input" style="width: 90%"></p>
|
||||
<button class="btn btn-primary" id="add-author-button">Добавить исполнителя</button>
|
||||
<button class="btn btn-danger" id="cancel-add-author">Отменить</button></p></center>`;
|
||||
|
||||
setTimeout(authorMenuButtonInit, 5);
|
||||
}
|
||||
|
||||
function selectFile(muzicId) {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "audio/mpeg";
|
||||
input.click();
|
||||
input.onchange = function (e) {
|
||||
function arrayBufferToBase64(buffer) {
|
||||
let binary = "";
|
||||
let bytes = new Uint8Array(buffer);
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
const file = e.target.files[0];
|
||||
// console.log(file);
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = function () {
|
||||
// console.log('reader.result', reader.result);
|
||||
if (file.type === "audio/mpeg") {
|
||||
document.getElementById(`edit-attach-file-${muzicId}`).filedata =
|
||||
arrayBufferToBase64(reader.result);
|
||||
document.getElementById(`edit-attach-file-${muzicId}`).innerText =
|
||||
"Загружен файл: " + file.name;
|
||||
document.getElementById(`delete-attach-${muzicId}`).disabled = false;
|
||||
}
|
||||
// console.log(arrayBufferToBase64(reader.result));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function editMuzic(muzicId, muzicName) {
|
||||
api.editMuzic(
|
||||
muzicId,
|
||||
muzicName,
|
||||
document.getElementById(`edit-attach-file-${muzicId}`).filedata,
|
||||
undefined,
|
||||
(res) => {
|
||||
showAudio(showAudio.settingsAuthors, showAudio.settingsAudio);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function removeMuzic(muzicId) {
|
||||
api.deleteMuzic(muzicId, (res) => {
|
||||
showAudio(showAudio.settingsAuthors, showAudio.settingsAudio);
|
||||
});
|
||||
}
|
||||
|
||||
function bindShowEditMuzicButtons(muzicId, muzicName, dataExists) {
|
||||
document.getElementById(`cancel-edit-muzic-${muzicId}`).onclick = () => {
|
||||
document.getElementById(`muzic-item-${muzicId}`).innerHTML = dataExists
|
||||
? `<h6><button class="btn btn-secondary" id="edit-muzic-item-${muzicId}" onclick='showEditMuzic(${muzicId}, ${JSON.stringify(
|
||||
muzicName,
|
||||
)}, ${dataExists});'><i class="bi bi-pencil-square"></i></button> ${muzicName}</h6>
|
||||
<button class="btn btn-primary play-button" onclick="playMuzic(${muzicId})" id="play-${muzicId}">Прослушать</button>
|
||||
<button class="btn btn-primary pause-button" onclick="" id="pause-${muzicId}" disabled>Пауза</button>
|
||||
<input class="road-muzic" type="range" id="road-${muzicId}" name="stop" min="0" max="0" value="0" step="0.01" style="width: 70%;"/>`
|
||||
: `<p><div id="muzic-item-${muzicId}">
|
||||
<h6>${muzicName}</h6>
|
||||
<button class="btn btn-secondary" onclick='showEditMuzic(${muzicId}, ${JSON.stringify(
|
||||
muzicName,
|
||||
)}, ${dataExists});' id="add-file-${muzicId}" id="edit-muzic-item-${muzicId}">Добавить звуковой файл</button>
|
||||
</div></p>`;
|
||||
};
|
||||
document.getElementById(`edit-attach-file-${muzicId}`).onclick = () =>
|
||||
selectFile(muzicId);
|
||||
document.getElementById(`delete-attach-${muzicId}`).onclick = () => {
|
||||
document.getElementById(`edit-attach-file-${muzicId}`).innerText =
|
||||
"Добавить файл (.mp3)";
|
||||
document.getElementById(`edit-attach-file-${muzicId}`).filedata = null;
|
||||
document.getElementById(`delete-attach-${muzicId}`).disabled = true;
|
||||
};
|
||||
document.getElementById(`edit-muzic-${muzicId}`).onclick = () =>
|
||||
editMuzic(muzicId, muzicName);
|
||||
document.getElementById(`delete-muzic-${muzicId}`).onclick = () =>
|
||||
removeMuzic(muzicId);
|
||||
}
|
||||
|
||||
function showEditMuzic(muzicId, muzicName, dataExists) {
|
||||
document.getElementById(
|
||||
`muzic-item-${muzicId}`,
|
||||
).innerHTML = `<p><input class="form-control" type="text" placeholder="Введите название произведения" value=${JSON.stringify(
|
||||
muzicName,
|
||||
)} id="muzic-edit-name-input-${muzicId}" style="width: 90%"></p>
|
||||
<p><button class="btn btn-primary" id="edit-attach-file-${muzicId}">${
|
||||
dataExists ? "Сменить файл (.mp3)" : "Добавить файл (.mp3)"
|
||||
}</button>
|
||||
<button class="btn btn-danger" id="delete-attach-${muzicId}"${
|
||||
dataExists ? "" : " disabled"
|
||||
}>Удалить файл</button>
|
||||
<button class="btn btn-primary" id="edit-muzic-${muzicId}">Редактировать произведение</button>
|
||||
<button class="btn btn-secondary" id="cancel-edit-muzic-${muzicId}">Отменить</button>
|
||||
<button class="btn btn-danger" id="delete-muzic-${muzicId}">Удалить произведение</button></p>`;
|
||||
|
||||
setTimeout(() => bindShowEditMuzicButtons(muzicId, muzicName, dataExists), 5);
|
||||
}
|
59
frontend/public/js/sound.js
Normal file
59
frontend/public/js/sound.js
Normal file
@ -0,0 +1,59 @@
|
||||
let audio = new Audio("/api/v/1.0/muzic");
|
||||
|
||||
function stopMuzic(id, interval) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
document.getElementById(`play-${id}`).innerText = "Прослушать";
|
||||
document.getElementById(`pause-${id}`).disabled = true;
|
||||
document.getElementById(`play-${id}`).onclick = () => playMuzic(id);
|
||||
|
||||
clearInterval(interval);
|
||||
document.getElementById(`road-${id}`).value = 0;
|
||||
document.getElementById(`road-${id}`).max = 0;
|
||||
}
|
||||
|
||||
function pauseMuzic(id) {
|
||||
document.getElementById(`play-${id}`).disabled = true;
|
||||
document.getElementById(`pause-${id}`).innerText = "Продолжить";
|
||||
document.getElementById(`pause-${id}`).onclick = () => resumeMuzic(id);
|
||||
audio.pause();
|
||||
}
|
||||
|
||||
function resumeMuzic(id) {
|
||||
document.getElementById(`play-${id}`).disabled = false;
|
||||
document.getElementById(`pause-${id}`).innerText = "Пауза";
|
||||
document.getElementById(`pause-${id}`).onclick = () => pauseMuzic(id);
|
||||
audio.play();
|
||||
}
|
||||
|
||||
function changeTime(value, id) {
|
||||
audio.currentTime = value;
|
||||
}
|
||||
|
||||
function playMuzic(id) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
if (audio.muzId !== undefined) stopMuzic(audio.muzId, audio.muzTimer);
|
||||
|
||||
audio = new Audio(`/api/v/1.0/muzic?id=${id}`);
|
||||
audio.onloadedmetadata = function () {
|
||||
audio.muzId = id;
|
||||
document.getElementById(`play-${id}`).innerText = "Остановить";
|
||||
document.getElementById(`pause-${id}`).disabled = false;
|
||||
document.getElementById(`road-${id}`).max = audio.duration;
|
||||
audio.play();
|
||||
|
||||
const interval = setInterval(() => {
|
||||
document.getElementById(`road-${id}`).value = audio.currentTime;
|
||||
}, 750);
|
||||
audio.muzTimer = interval;
|
||||
document.getElementById(`play-${id}`).onclick = () =>
|
||||
stopMuzic(id, interval);
|
||||
document.getElementById(`pause-${id}`).onclick = () => pauseMuzic(id);
|
||||
|
||||
document.getElementById(`road-${id}`).onchange = function () {
|
||||
// console.log('value', this.value);
|
||||
changeTime(this.value, id);
|
||||
};
|
||||
};
|
||||
}
|
40
logger.js
Normal file
40
logger.js
Normal file
@ -0,0 +1,40 @@
|
||||
const config = require("./config-handler");
|
||||
const fs = require("fs");
|
||||
|
||||
function log(date, req, ip, res) {
|
||||
const requestBody = !req.byteBody.toString("utf-8")
|
||||
? ""
|
||||
: `
|
||||
~~~~~~~~~~~~~~
|
||||
[REQUEST BODY]
|
||||
~~~~~~~~~~~~~~
|
||||
${req.byteBody.toString("utf-8")}`;
|
||||
let action = `HTTP ${req.httpVersion} ${req.method} ${req.originalUrl}
|
||||
~~~~~~~~~
|
||||
[HEADERS]
|
||||
~~~~~~~~~
|
||||
${Object.entries(req.headers)
|
||||
.map(([header, value]) => header + ": " + value)
|
||||
.join("\n")}${requestBody}`;
|
||||
let response = !res.isError
|
||||
? ""
|
||||
: `\n----------------\nError raised:\n----------------\n${JSON.stringify(
|
||||
res.responseData,
|
||||
)}`;
|
||||
//console.log(`================================\nREPORT\n================================\n\nIP: ${ip}\n----------------\nACTION:\n----------------\n${action}${response}`);
|
||||
let loggerFolder = config().logger_folder;
|
||||
loggerFolder = !["/", "\\"].includes(loggerFolder.at(-1))
|
||||
? loggerFolder + "/"
|
||||
: loggerFolder;
|
||||
|
||||
fs.writeFileSync(
|
||||
`${loggerFolder}${date.getTime()}.log`,
|
||||
`================================\nREPORT\n================================\n\nIP: ${ip}\n----------------\nACTION:\n----------------\n${action}${response}`,
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
// console.log('ip', req.ip);
|
||||
log(new Date(), req, req.ip, res);
|
||||
next();
|
||||
};
|
1862
package-lock.json
generated
Normal file
1862
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
package.json
Normal file
10
package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"body": "^5.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express": "^4.18.2",
|
||||
"pg": "^8.11.3",
|
||||
"sequelize": "^6.33.0"
|
||||
}
|
||||
}
|
12
page-view.js
Normal file
12
page-view.js
Normal file
@ -0,0 +1,12 @@
|
||||
const router = require("express").Router();
|
||||
const fs = require("fs");
|
||||
router.use(require("express").static("./frontend/public"));
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
fs.readFile("./frontend/main.html", { encoding: "utf-8" }, function (err, data) {
|
||||
// Display the file content
|
||||
res.send(data);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
93
server.js
Normal file
93
server.js
Normal file
@ -0,0 +1,93 @@
|
||||
"use strict";
|
||||
|
||||
const express = require("express");
|
||||
const bodyHand = require("body");
|
||||
const config = require("./config-handler");
|
||||
const http = require("http");
|
||||
const { Database } = require("./database");
|
||||
|
||||
const app = express();
|
||||
|
||||
global.database = new Database(
|
||||
config().database.address,
|
||||
config().database.port,
|
||||
config().database.username,
|
||||
config().database.password,
|
||||
config().database.database,
|
||||
);
|
||||
|
||||
http.ServerResponse.prototype.errorModeOn = function () {
|
||||
this.isError = true;
|
||||
};
|
||||
|
||||
http.ServerResponse.prototype.bindNext = function (next) {
|
||||
this.next = next;
|
||||
};
|
||||
|
||||
http.ServerResponse.prototype.sendModed = function (sendData) {
|
||||
// Модифицируем res.send
|
||||
if (sendData !== undefined && config().logger_mode) {
|
||||
this.responseData = sendData;
|
||||
require("./logger")(this.req, this, this.next);
|
||||
}
|
||||
this.send(sendData);
|
||||
};
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.bindNext(next);
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// Для добавления оригинального тела запроса
|
||||
const body = bodyHand(
|
||||
req,
|
||||
res,
|
||||
{
|
||||
limit: 9999999999,
|
||||
cache: false,
|
||||
encoding: "base64",
|
||||
},
|
||||
(err, body) => {
|
||||
if (!err) {
|
||||
req.byteBody = Buffer.from(body, "base64");
|
||||
|
||||
// Запись в req.json при json
|
||||
if (!!req.headers && req.headers["content-type"]?.includes("json")) {
|
||||
try {
|
||||
req.json = JSON.parse(req.byteBody.toString("utf8"));
|
||||
} catch (_e) {}
|
||||
}
|
||||
}
|
||||
next();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
app.use("/", require("./page-view"));
|
||||
app.use("/api", require("./api"));
|
||||
|
||||
// Подключение через HTTPS
|
||||
let server;
|
||||
if (!config().ssl.enabled) {
|
||||
server = app;
|
||||
} else {
|
||||
const https = require("https");
|
||||
server = https.createServer(
|
||||
{
|
||||
cert: fs.readFileSync(config().ssl["public"], "utf-8"),
|
||||
key: fs.readFileSync(config().ssl["private"], "utf-8"),
|
||||
},
|
||||
app,
|
||||
);
|
||||
}
|
||||
|
||||
server.listen(config().port, config().address, async (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
console.log(
|
||||
`Kodex Muzic catalog runned at ${config().address}:${config().port}`,
|
||||
);
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user