Додані повідомлення та перепрацьована структура застосунку та api

This commit is contained in:
2026-03-15 00:25:10 +02:00
parent 85483b85bb
commit 4bc9c11512
101 changed files with 5763 additions and 2546 deletions

View File

@@ -0,0 +1,91 @@
const HomesteadJointService = require('../services/homestead.joint.service');
class HomesteadJointController {
async getList(req, res) {
const { homestead_id } = req.params;
if (homestead_id) {
if (req.possibilities.can_joint_territory) {
let result = await HomesteadJointService.getList(homestead_id);
if (result) {
return res
.status(200)
.send(result);
} else {
return res
.status(500)
.send({ message: 'Internal server error.' });
}
} else {
return res
.status(403)
.send({ message: 'The user does not have enough rights.' });
}
} else {
return res
.status(404)
.send({ message: 'Users not found.' });
}
}
async createJoint(req, res) {
const { homestead_id } = req.params;
const data = req.body;
if (homestead_id) {
if (req.possibilities.can_joint_territory) {
let result = await HomesteadJointService.createJoint(
homestead_id,
data
);
if (result) {
return res.status(200).send(result);
} else {
return res.status(500).send({
message: 'Unable create joint homestead.',
});
}
} else {
return res
.status(404)
.send({ message: 'User not found.' });
}
} else {
return res
.status(404)
.send({ message: 'Users not found.' });
}
}
async deleteJoint(req, res) {
const { homestead_id } = req.params;
const data = req.body;
if (homestead_id) {
if (req.possibilities.can_joint_territory) {
let result = await HomesteadJointService.deleteJoint(
homestead_id,
data
);
if (result) {
return res.status(200).send(result);
} else {
return res.status(500).send({
message: 'Unable delete joint homestead.',
});
}
} else {
return res
.status(404)
.send({ message: 'User not found.' });
}
} else {
return res
.status(404)
.send({ message: 'Users not found.' });
}
}
}
module.exports = new HomesteadJointController();

View File

@@ -13,6 +13,8 @@ class HomesteadsController {
id = req.sheepId;
} else if (mode == "group") {
id = req.group_id;
} else if (mode == "joint") {
id = req.sheepId;
}
let result = await HomesteadsService.getList(mode, id);

View File

@@ -45,7 +45,7 @@ class SheepsController {
}
async getListStand(req, res) {
if (req.possibilities.can_view_stand) {
if (req.possibilities.can_view_stand) {
const result = await SheepsService.getListStand(req.mode);
if (result) {
@@ -86,7 +86,7 @@ class SheepsController {
const data = req.body;
if (req.mode == 2) {
let result = await SheepsService.updateSheep(data);
let result = await SheepsService.updateSheep(data, 2);
if (result) {
return res.status(200).send(result);
@@ -95,10 +95,24 @@ class SheepsController {
message: 'Unable update sheep.',
});
}
} if (req.possibilities.can_manager_sheeps) {
if (Number(data.mode) == 2) {
return res.status(403).send({ message: 'The sheep does not have enough rights.' });
} else {
let result = await SheepsService.updateSheep(data, 1);
if (result) {
return res.status(200).send(result);
} else {
return res.status(500).send({
message: 'Unable update sheep.',
});
}
}
} else {
return res
.status(403)
.send({ message: 'Sheep not foundThe sheep does not have enough rights.' });
.send({ message: 'The sheep does not have enough rights.' });
}
}

View File

@@ -9,9 +9,11 @@ const authenticate = (req, res, next) => {
sheeps.*,
possibilities.can_add_sheeps AS can_add_sheeps,
possibilities.can_view_sheeps AS can_view_sheeps,
possibilities.can_manager_sheeps AS can_manager_sheeps,
possibilities.can_add_territory AS can_add_territory,
possibilities.can_view_territory AS can_view_territory,
possibilities.can_manager_territory AS can_manager_territory,
possibilities.can_joint_territory AS can_joint_territory,
possibilities.can_add_stand AS can_add_stand,
possibilities.can_view_stand AS can_view_stand,
possibilities.can_manager_stand AS can_manager_stand,
@@ -33,9 +35,11 @@ const authenticate = (req, res, next) => {
req.possibilities = {
can_add_sheeps: moderator.can_add_sheeps == 1 ? true : false,
can_view_sheeps: moderator.can_view_sheeps == 1 ? true : false,
can_manager_sheeps: moderator.can_manager_sheeps == 1 ? true : false,
can_add_territory: moderator.can_add_territory == 1 ? true : false,
can_view_territory: moderator.can_view_territory == 1 ? true : false,
can_manager_territory: moderator.can_manager_territory == 1 ? true : false,
can_joint_territory: moderator.can_joint_territory == 1 ? true : false,
can_add_stand: moderator.can_add_stand == 1 ? true : false,
can_view_stand: moderator.can_view_stand == 1 ? true : false,
can_manager_stand: moderator.can_manager_stand == 1 ? true : false,
@@ -51,6 +55,7 @@ const authenticate = (req, res, next) => {
sheeps.*,
possibilities.can_add_sheeps AS can_add_sheeps,
possibilities.can_view_sheeps AS can_view_sheeps,
possibilities.can_manager_sheeps AS can_manager_sheeps,
possibilities.can_add_territory AS can_add_territory,
possibilities.can_view_territory AS can_view_territory,
possibilities.can_manager_territory AS can_manager_territory,
@@ -76,8 +81,10 @@ const authenticate = (req, res, next) => {
req.possibilities = {
can_add_sheeps: false,
can_view_sheeps: false,
can_manager_sheeps: false,
can_add_territory: false,
can_manager_territory: false,
can_joint_territory: false,
can_add_stand: false,
can_manager_stand: false,
can_add_schedule: false,

View File

@@ -1,22 +0,0 @@
module.exports = (req, res, next) => {
const start = performance.now();
res.on("finish", () => {
const duration = performance.now() - start;
fetch("http://metrics:4005/push", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "rest",
path: req.originalUrl,
method: req.method,
status: res.statusCode,
time: duration,
timestamp: Date.now()
})
}).catch(err => console.error(err));
});
next();
};

View File

@@ -0,0 +1,12 @@
const express = require('express');
const router = express.Router({ mergeParams: true });
const HomesteadJointController = require('../controllers/homestead.joint.controller');
const authenticate = require("../middleware/auth");
router
.route('/')
.get(authenticate, HomesteadJointController.getList)
.post(authenticate, HomesteadJointController.createJoint)
.delete(authenticate, HomesteadJointController.deleteJoint);
module.exports = router;

View File

@@ -1,13 +1,12 @@
const express = require('express');
const router = express.Router();
const metrics = require('../middleware/metrics');
const authRoutes = require('./auth.routes');
const sheepsRoutes = require('./sheeps.routes');
const constructorRoutes = require('./constructor.routes');
const housesRoutes = require('./houses.routes');
const homesteadsRoutes = require('./homesteads.routes');
const homesteadJointRoutes = require('./homestead.joint.routes');
const buildingsRoutes = require('./buildings.routes');
const entrancesRoutes = require('./entrances.routes');
const apartmentsRoutes = require('./apartments.routes');
@@ -20,13 +19,11 @@ const pushRoutes = require('./push.routes');
const generatorCardsRoutes = require('./generator.cards.routes');
const generatorReportTerritoriesRoutes = require('./generator.report.territories.routes');
router.use(metrics);
router.use('/auth', authRoutes);
router.use('/sheeps?', sheepsRoutes);
router.use('/constructor', constructorRoutes);
router.use('/houses?', housesRoutes);
router.use('/homestead/joint/:homestead_id', homesteadJointRoutes);
router.use('/homesteads?', homesteadsRoutes);
router.use('/buildings?', buildingsRoutes);
router.use('/house/:house_id/entrances', entrancesRoutes);

View File

@@ -8,9 +8,11 @@ class AuthService {
sheeps.*,
possibilities.can_add_sheeps AS can_add_sheeps,
possibilities.can_view_sheeps AS can_view_sheeps,
possibilities.can_manager_sheeps AS can_manager_sheeps,
possibilities.can_add_territory AS can_add_territory,
possibilities.can_view_territory AS can_view_territory,
possibilities.can_manager_territory AS can_manager_territory,
possibilities.can_joint_territory AS can_joint_territory,
possibilities.can_add_stand AS can_add_stand,
possibilities.can_view_stand AS can_view_stand,
possibilities.can_manager_stand AS can_manager_stand,
@@ -42,8 +44,10 @@ class AuthService {
possibilities: {
can_add_sheeps: false,
can_view_sheeps: false,
can_manager_sheeps: false,
can_add_territory: false,
can_manager_territory: false,
can_joint_territory: false,
can_add_stand: false,
can_manager_stand: false,
can_add_schedule: false,
@@ -55,8 +59,10 @@ class AuthService {
if (mode && (mode == 1 || mode == 2)) {
data.possibilities.can_add_sheeps = sheep.can_add_sheeps == 1 ? true : false;
data.possibilities.can_view_sheeps = sheep.can_view_sheeps == 1 ? true : false;
data.possibilities.can_manager_sheeps = sheep.can_manager_sheeps == 1 ? true : false;
data.possibilities.can_add_territory = sheep.can_add_territory == 1 ? true : false;
data.possibilities.can_manager_territory = sheep.can_manager_territory == 1 ? true : false;
data.possibilities.can_joint_territory = sheep.can_joint_territory == 1 ? true : false;
data.possibilities.can_add_stand = sheep.can_add_stand == 1 ? true : false;
data.possibilities.can_manager_stand = sheep.can_manager_stand == 1 ? true : false;
data.possibilities.can_add_schedule = sheep.can_add_schedule == 1 ? true : false;

View File

@@ -6,6 +6,8 @@ class EntrancesService {
let sql = `
SELECT
entrance.*,
(SELECT house.title FROM house WHERE house.id = entrance.house_id) AS address_title,
(SELECT house.number FROM house WHERE house.id = entrance.house_id) AS address_number,
COALESCE((SELECT entrance_history.working FROM entrance_history WHERE entrance_history.entrance_id = entrance.id ORDER BY entrance_history.date_start DESC LIMIT 1), 0) AS working,
(SELECT entrance_history.name FROM entrance_history WHERE entrance_history.entrance_id = entrance.id ORDER BY entrance_history.date_start DESC LIMIT 1) AS entrance_history_name,
(SELECT entrance_history.group_id FROM entrance_history WHERE entrance_history.entrance_id = entrance.id ORDER BY entrance_history.date_start DESC LIMIT 1) AS entrance_history_group_id,
@@ -30,6 +32,11 @@ class EntrancesService {
"house_id": Number(row.house_id),
"entrance_number": Number(row.entrance_number),
"title": row.title,
"address": {
"title": row.address_title,
"number": row.address_number,
"points_number": JSON.parse(row.points_number)
},
"description": row.description,
"created_at": Number(row.created_at),
"updated_at": Number(row.updated_at),

View File

@@ -1,4 +1,5 @@
const db = require("../config/db");
const Notification = require("../utils/notification.js");
class HistoryHomesteadService {
getHistoryHomestead(homestead_id) {

View File

@@ -0,0 +1,87 @@
const db = require("../config/db");
const Notification = require("../utils/notification.js");
class HomesteadJointService {
getList(homestead_id) {
return new Promise((res, rej) => {
let sql = `
SELECT
*
FROM
homestead_joint
WHERE
homestead_joint.homestead_id = '${homestead_id}'
ORDER BY
homestead_joint.created_at
`;
db.all(sql, (err, rows) => {
if (err) {
console.error(err.message);
return res(false);
} else {
let data = rows.map((row) => {
return {
"id": Number(row.id),
"homestead_id": Number(row.homestead_id),
"sheep_id": Number(row.sheep_id),
"created_at": Number(row.created_at)
}
})
return res(data);
}
});
});
}
createJoint(homestead_id, data) {
return new Promise((res, rej) => {
let sql = 'INSERT INTO homestead_joint(homestead_id, sheep_id, created_at) VALUES (?, ?, ?)';
db.run(sql, [
Number(homestead_id),
Number(data.sheep_id),
Date.now()
], function (err) {
if (err) {
console.error(err.message);
return res(false);
} else if (this.changes === 0) {
return res(false);
} else {
Notification.sendSheep({
sheep_id: Number(data.sheep_id),
title: "Тимчасова територія",
body: "Вам надали спільний доступ до території"
});
res({ "create": "ok", "id": this.lastID });
}
});
});
}
deleteJoint(homestead_id, data) {
return new Promise((res, rej) => {
db.run('DELETE FROM homestead_joint WHERE homestead_id = ? AND sheep_id = ?', [Number(homestead_id), Number(data.sheep_id)], function (err) {
if (err) {
console.error(err.message);
return res(false);
} else if (this.changes === 0) {
return res(false);
} else {
Notification.sendSheep({
sheep_id: Number(data.sheep_id),
title: "Тимчасова територія",
body: "Вам відкликанно спільний доступ до території"
});
res({ "delete": "ok", "homestead_id": Number(homestead_id), "sheep_id": Number(data.sheep_id)});
}
});
});
}
}
module.exports = new HomesteadJointService();

View File

@@ -1,4 +1,5 @@
const db = require("../config/db");
const genCards = require("../middleware/genCards");
class HomesteadsService {
getList(mode, id) {
@@ -63,6 +64,27 @@ class HomesteadsService {
AND
homestead_history.sheep_id = '${id}';
`;
} else if (mode == "joint") {
sql = `
SELECT
homestead.*,
homestead_history.id AS homestead_history_id,
homestead_history.name AS homestead_history_name,
homestead_history.group_id AS homestead_history_group_id,
homestead_history.sheep_id AS homestead_history_sheep_id,
homestead_history.date_start AS homestead_history_date_start,
homestead_history.date_end AS homestead_history_date_end
FROM
homestead
JOIN
homestead_history ON homestead.id = homestead_history.homestead_id
JOIN
homestead_joint ON homestead.id = homestead_joint.homestead_id
WHERE
homestead_joint.sheep_id = '${id}'
ORDER BY
homestead_history.date_start DESC;
`;
}
db.all(sql, (err, rows) => {
@@ -88,7 +110,7 @@ class HomesteadsService {
"id": row.homestead_history_id ? Number(row.homestead_history_id) : null,
"name": row.homestead_history_name,
"group_id": row.homestead_history_group_id ? Number(row.homestead_history_group_id) : null,
"sheep_id": row.entrance_history_sheep_id ? Number(row.entrance_history_sheep_id) : null,
"sheep_id": row.homestead_history_sheep_id ? Number(row.homestead_history_sheep_id) : null,
"date": {
"start": row.homestead_history_date_start ? Number(row.homestead_history_date_start) : null,
"end": row.homestead_history_date_end ? Number(row.homestead_history_date_end) : null
@@ -111,6 +133,7 @@ class HomesteadsService {
COALESCE((SELECT homestead_history.working FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1), 0) AS working,
(SELECT homestead_history.name FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1) AS homestead_history_name,
(SELECT homestead_history.group_id FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1) AS homestead_history_group_id,
(SELECT homestead_history.sheep_id FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1) AS homestead_history_sheep_id,
(SELECT homestead_history.id FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1) AS homestead_history_id,
(SELECT homestead_history.date_start FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1) AS homestead_history_date_start,
(SELECT homestead_history.date_end FROM homestead_history WHERE homestead_history.homestead_id = homestead.id ORDER BY homestead_history.date_start DESC LIMIT 1) AS homestead_history_date_end
@@ -144,6 +167,7 @@ class HomesteadsService {
"id": row.homestead_history_id ? Number(row.homestead_history_id) : null,
"name": row.homestead_history_name,
"group_id": row.homestead_history_group_id ? Number(row.homestead_history_group_id) : null,
"sheep_id": row.homestead_history_sheep_id ? Number(row.homestead_history_sheep_id) : null,
"date": {
"start": row.homestead_history_date_start ? Number(row.homestead_history_date_start) : null,
"end": row.homestead_history_date_end ? Number(row.homestead_history_date_end) : null
@@ -192,6 +216,7 @@ class HomesteadsService {
return res(false);
} else {
res({ "status": "ok", "id": this.lastID });
genCards({type: "homestead", id: this.lastID});
}
});
});
@@ -234,6 +259,7 @@ class HomesteadsService {
return res(false);
} else {
res({ "status": "ok", "id": homestead_id });
genCards({type: "homestead", id: homestead_id});
}
});
});

View File

@@ -1,4 +1,5 @@
const db = require("../config/db");
const genCards = require("../middleware/genCards");
class HousesService {
getListEntrance() {
@@ -228,6 +229,7 @@ class HousesService {
return res(false);
} else {
res({ "status": "ok", "id": this.lastID });
genCards({type: "house", id: this.lastID});
}
});
});
@@ -272,6 +274,8 @@ class HousesService {
return res(false);
} else {
res({ "status": "ok", "id": house_id });
genCards({type: "house", id: house_id});
}
});
});

View File

@@ -9,9 +9,11 @@ class SheepService {
sheeps.*,
possibilities.can_add_sheeps,
possibilities.can_view_sheeps,
possibilities.can_manager_sheeps,
possibilities.can_add_territory,
possibilities.can_view_territory,
possibilities.can_manager_territory,
possibilities.can_joint_territory,
possibilities.can_add_stand,
possibilities.can_view_stand,
possibilities.can_manager_stand,
@@ -38,9 +40,11 @@ class SheepService {
const fields = [
"can_add_sheeps",
"can_view_sheeps",
"can_manager_sheeps",
"can_add_territory",
"can_view_territory",
"can_manager_territory",
"can_joint_territory",
"can_add_stand",
"can_view_stand",
"can_manager_stand",
@@ -83,9 +87,11 @@ class SheepService {
sheeps.*,
possibilities.can_add_sheeps,
possibilities.can_view_sheeps,
possibilities.can_manager_sheeps,
possibilities.can_add_territory,
possibilities.can_view_territory,
possibilities.can_manager_territory,
possibilities.can_joint_territory,
possibilities.can_add_stand,
possibilities.can_view_stand,
possibilities.can_manager_stand,
@@ -108,9 +114,11 @@ class SheepService {
const fields = [
"can_add_sheeps",
"can_view_sheeps",
"can_manager_sheeps",
"can_add_territory",
"can_view_territory",
"can_manager_territory",
"can_joint_territory",
"can_add_stand",
"can_view_stand",
"can_manager_stand",
@@ -156,9 +164,11 @@ class SheepService {
sheeps.*,
possibilities.can_add_sheeps,
possibilities.can_view_sheeps,
possibilities.can_manager_sheeps,
possibilities.can_add_territory,
possibilities.can_view_territory,
possibilities.can_manager_territory,
possibilities.can_joint_territory,
possibilities.can_add_stand,
possibilities.can_view_stand,
possibilities.can_manager_stand,
@@ -183,9 +193,11 @@ class SheepService {
const fields = [
"can_add_sheeps",
"can_view_sheeps",
"can_manager_sheeps",
"can_add_territory",
"can_view_territory",
"can_manager_territory",
"can_joint_territory",
"can_add_stand",
"can_view_stand",
"can_manager_stand",
@@ -253,71 +265,120 @@ class SheepService {
});
}
updateSheep(data) {
const stmt1 = db.prepare(`
UPDATE
sheeps
SET
name = ?,
group_id = ?,
mode = ?,
mode_title = ?,
uuid_manager = ?
WHERE
uuid = ?
`);
updateSheep(data, mode) {
if (mode == 2) {
const stmt1 = db.prepare(`
UPDATE
sheeps
SET
name = ?,
group_id = ?,
mode = ?,
mode_title = ?,
uuid_manager = ?
WHERE
uuid = ?
`);
const stmt2 = db.prepare(`
UPDATE
possibilities
SET
can_add_sheeps = ?,
can_view_sheeps = ?,
can_add_territory = ?,
can_view_territory = ?,
can_manager_territory = ?,
can_add_stand = ?,
can_view_stand = ?,
can_manager_stand = ?,
can_add_schedule = ?,
can_view_schedule = ?
WHERE
sheep_id = (SELECT id FROM sheeps WHERE uuid = ? LIMIT 1)
`);
const stmt2 = db.prepare(`
UPDATE
possibilities
SET
can_add_sheeps = ?,
can_view_sheeps = ?,
can_manager_sheeps = ?,
can_add_territory = ?,
can_view_territory = ?,
can_manager_territory = ?,
can_joint_territory = ?,
can_add_stand = ?,
can_view_stand = ?,
can_manager_stand = ?,
can_add_schedule = ?,
can_view_schedule = ?
WHERE
sheep_id = (SELECT id FROM sheeps WHERE uuid = ? LIMIT 1)
`);
return new Promise((res, rej) => {
db.serialize(() => {
let uuid_manager = crypto.randomUUID();
return new Promise((res, rej) => {
db.serialize(() => {
let uuid_manager = crypto.randomUUID();
stmt1.run([
data.name,
Number(data.group_id),
Number(data.mode),
data.mode_title,
Number(data.mode) == 0 ? null : (data.uuid_manager ? data.uuid_manager : uuid_manager),
data.uuid
], (err) => {
if (err) return rej(err);
stmt2.run([
data.possibilities.can_add_sheeps,
data.possibilities.can_view_sheeps,
data.possibilities.can_add_territory,
data.possibilities.can_view_territory,
data.possibilities.can_manager_territory,
data.possibilities.can_add_stand,
data.possibilities.can_view_stand,
data.possibilities.can_manager_stand,
data.possibilities.can_add_schedule,
data.possibilities.can_view_schedule,
stmt1.run([
data.name,
Number(data.group_id),
Number(data.mode),
data.mode_title,
Number(data.mode) == 0 ? null : (data.uuid_manager ? data.uuid_manager : uuid_manager),
data.uuid
], (err2) => {
if (err2) return rej(err2);
res({ status: "ok", id: data.id });
], (err) => {
if (err) return rej(err);
stmt2.run([
data.possibilities.can_add_sheeps,
data.possibilities.can_view_sheeps,
data.possibilities.can_manager_sheeps,
data.possibilities.can_add_territory,
data.possibilities.can_view_territory,
data.possibilities.can_manager_territory,
data.possibilities.can_joint_territory,
data.possibilities.can_add_stand,
data.possibilities.can_view_stand,
data.possibilities.can_manager_stand,
data.possibilities.can_add_schedule,
data.possibilities.can_view_schedule,
data.uuid
], (err2) => {
if (err2) return rej(err2);
res({ status: "ok", id: data.id });
});
});
});
});
});
} else if(mode == 1){
const stmt1 = db.prepare(`
UPDATE
sheeps
SET
name = ?,
group_id = ?
WHERE
uuid = ?
`);
const stmt2 = db.prepare(`
UPDATE
possibilities
SET
can_view_territory = ?,
can_view_stand = ?,
can_view_schedule = ?
WHERE
sheep_id = (SELECT id FROM sheeps WHERE uuid = ? LIMIT 1)
`);
return new Promise((res, rej) => {
db.serialize(() => {
stmt1.run([
data.name,
Number(data.group_id),
data.uuid
], (err) => {
if (err) return rej(err);
stmt2.run([
data.possibilities.can_view_territory,
data.possibilities.can_view_stand,
data.possibilities.can_view_schedule,
data.uuid
], (err2) => {
if (err2) return rej(err2);
res({ status: "ok", id: data.id });
});
});
});
});
}
}
deleteSheep(data) {

View File

@@ -221,7 +221,7 @@ class StandService {
}
const normalized = normalizeTs(row && row.max_date ? row.max_date : null);
if (normalized) {
if (normalized && normalized > Date.now()) {
date_start = getNextMonday(normalized);
} else {
date_start = getNextMonday(Date.now());
@@ -272,9 +272,26 @@ class StandService {
console.error(err.message);
return res(false);
}
let text = [
`Стенд «${stand.title}» отримав новий графік. Можна сміливо записуватися 🙂`,
`Для «${stand.title}» відкрито новий розклад. Хто планував — саме час.`,
`Новий графік для «${stand.title}» вже доступний. Обирайте зручний час 👍`,
`Стенд «${stand.title}» оновив розклад. Запис розпочато.`,
`З’явилися нові дати у «${stand.title}». Встигніть обрати свою 😉`,
`«${stand.title}» відкрив новий період запису. Плануємо заздалегідь 🙂`,
`Оновлення для «${stand.title}». Додано новий графік.`,
`Новий розклад для «${stand.title}» вже чекає на охочих 📋`,
`Стенд «${stand.title}» додав нові години для запису ⏰`,
`Графік «${stand.title}» поповнено. Можна бронювати час 😊`,
`У «${stand.title}» з’явилися нові можливості для запису`,
`Свіжий графік для «${stand.title}» уже доступний 🚀`
];
let randomMessage = text[Math.floor(Math.random() * text.length)];
Notification.sendStand({
title: "Додан новий день служіння",
body: `Стенд «${stand.title}» поповнився, встигніть записатися.`,
title: "Додано новий день служіння",
body: randomMessage,
page: `/stand/card/${stand.id}`
});
@@ -301,7 +318,7 @@ class StandService {
WHERE
ss.stand_id = ?
AND
date(ss.date / 1000, 'unixepoch') >= date('now')
date(ss.date / 1000, 'unixepoch', 'localtime') >= date('now', 'localtime')
ORDER BY
ss.id;
`;

View File

@@ -1,5 +1,9 @@
const db = require("../config/db");
const webpush = require('web-push');
const TelegramBot = require("node-telegram-bot-api");
const util = require('util');
const dbRun = util.promisify(db.run).bind(db);
const VAPID_PUBLIC_KEY = process.env.VAPID_PUBLIC_KEY;
const VAPID_PRIVATE_KEY = process.env.VAPID_PRIVATE_KEY;
@@ -10,6 +14,11 @@ webpush.setVapidDetails(
VAPID_PRIVATE_KEY
);
const TOKEN = process.env.TELEGRAM_TOKEN;
const STAND_CHAT_ID = process.env.STAND_CHAT_ID;
const bot = new TelegramBot(TOKEN, { polling: false });
class Notification {
async sendSheep({ sheep_id, title, body, page }) {
const sql = `
@@ -123,7 +132,7 @@ class Notification {
}
if (!rows.length) {
console.log(`🐑 No subscriptions found for sheep_id: ${sheep_id}`);
console.log(`🐑 No subscriptions`);
return;
}
@@ -146,6 +155,25 @@ class Notification {
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`✅ Sent: ${rows.length - failed}, ❌ Failed: ${failed}`);
});
// Формуємо повне повідомлення
const fullMessage = `📢 <b>${title}</b>\n\n${body.replace('«', '«<b>').replace('»', '</b>»')}`;
try {
const sentMessage = await bot.sendMessage(STAND_CHAT_ID, fullMessage, {
parse_mode: 'HTML'
});
// Зберігаємо ID нового повідомлення у базі
await dbRun(
`INSERT INTO sent_messages (last_message_id, created_at) VALUES (?, ?)`, [sentMessage.message_id, Date.now()]
);
console.log(`✅ Сповіщення надіслано для стенду: ${stand.title}`);
} catch (err) {
console.error('❌ Помилка відправки тексту:', err.message);
}
}
};