Main #5

Merged
Nikiroy78 merged 9 commits from main into laptop 2023-10-02 14:22:56 +03:00
70 changed files with 63748 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
references/
ts.txt
*.log

49
api-tests/test.py Normal file
View 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
View 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
View 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
View 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;

View 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;
}
};

View 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";
};

View 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
View 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,
};
};

View 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";
};

View 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
View 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
View 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
View 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

Binary file not shown.

41
frontend/main.html Normal file
View 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>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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 */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5046
frontend/public/bootstrap/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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;
}

View 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
View 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
View 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);
}

View 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
View 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

File diff suppressed because it is too large Load Diff

10
package.json Normal file
View 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
View 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
View 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}`,
);
}
});