Змінено директорії

Додано скрипти CRON
Поліпшено механізм запису стендів та їх редагування
This commit is contained in:
2025-10-27 00:11:18 +02:00
parent 1d9f9a1468
commit 04f39da611
196 changed files with 4962 additions and 4065 deletions

11
cron/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM node:20.18
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD npm start

17
cron/config/db.js Normal file
View File

@@ -0,0 +1,17 @@
const sqlite3 = require("sqlite3");
const path = require("path");
const dbPath = process.env.DATABASE_PATH || path.join(__dirname, "..");
const fullPath = path.join(dbPath, "database.sqlite");
const db = new sqlite3.Database(fullPath, (err) => {
if (err) {
console.error("❌ Failed to open sqlite database:", err);
} else {
console.log("✅ SQLite DB opened at", fullPath);
}
});
db.exec("PRAGMA foreign_keys = ON;");
module.exports = db;

13
cron/cron.js Normal file
View File

@@ -0,0 +1,13 @@
const cron = require("node-cron");
const Backup = require("./tasks/backup");
// Завдання: резервна копія БД кожен день в 22:30
cron.schedule("30 22 * * *", () => {
Backup.database();
const now = new Date().toLocaleString();
console.log(`[${now}] Завдання «Backup» виконане!`);
});
console.log("Cron-завдання запущено.");

3594
cron/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
cron/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "CRON Sheep Service",
"version": "1.0.0",
"main": "cron.js",
"scripts": {
"start": "node cron.js"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"node-cron": "^4.2.1",
"sqlite3": "^5.1.7",
"web-push": "^3.6.7",
"dotenv": "^17.2.0",
"node-telegram-bot-api": "^0.66.0"
}
}

33
cron/tasks/backup.js Normal file
View File

@@ -0,0 +1,33 @@
const fs = require("fs");
const path = require("path");
const TelegramBot = require("node-telegram-bot-api");
const TOKEN = process.env.TELEGRAM_TOKEN;
const CHAT_ID = process.env.CHAT_ID;
const DB_PATH = process.env.DATABASE_PATH || "../";
const FILE = path.join(DB_PATH, "database.sqlite");
const bot = new TelegramBot(TOKEN, { polling: false });
class Backup {
async database() {
try {
if (!fs.existsSync(FILE)) {
console.log("❌ Файл бази даних не знайдено:", FILE);
return;
}
console.log(`📤 Надсилаю файл: ${FILE}`);
await bot.sendDocument(CHAT_ID, fs.createReadStream(FILE), {
caption: "📦 Резервна копія бази даних",
});
console.log("✅ Файл успішно надіслано!");
} catch (err) {
console.error("❌ Помилка при надсиланні файлу:", err.message);
}
}
}
module.exports = new Backup();

152
cron/utils/notification.js Normal file
View File

@@ -0,0 +1,152 @@
const db = require("../config/db");
const webpush = require('web-push');
const VAPID_PUBLIC_KEY = process.env.VAPID_PUBLIC_KEY;
const VAPID_PRIVATE_KEY = process.env.VAPID_PRIVATE_KEY;
webpush.setVapidDetails(
'mailto:rozenrod320@gmail.com',
VAPID_PUBLIC_KEY,
VAPID_PRIVATE_KEY
);
class Notification {
async sendSheep({ sheep_id, title, body, page }) {
const sql = `
SELECT * FROM subscription
WHERE sheep_id = ?
ORDER BY id
`;
db.all(sql, [sheep_id], async (err, rows) => {
if (err) {
console.error('DB error:', err.message);
return;
}
if (!rows.length) {
console.log(`🐑 No subscriptions found for sheep_id: ${sheep_id}`);
return;
}
console.log(`📨 Sending notification to ${rows.length} subscriptions...`);
const payload = JSON.stringify({
title: title ?? "Тестове повідомлення",
body: body ?? "Ви успішно підписалися на отримання push повідомлень!",
url: `https://${process.env.DOMAIN}${page ?? ""}`
});
const results = await Promise.allSettled(rows.map(row => {
const subscription = {
endpoint: row.endpoint,
keys: JSON.parse(row.keys),
};
return webpush.sendNotification(subscription, payload);
}));
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`✅ Sent: ${rows.length - failed}, ❌ Failed: ${failed}`);
});
}
async sendGroup({ group_id, title, body, page }) {
const sql = `
SELECT
subscription.*
FROM
subscription
JOIN
sheeps
ON
sheeps.id = subscription.sheep_id
WHERE
sheeps.group_id = ?
ORDER BY
subscription.id;
`;
db.all(sql, [group_id], async (err, rows) => {
if (err) {
console.error('DB error:', err.message);
return;
}
if (!rows.length) {
console.log(`🐑 No subscriptions found for sheep_id: ${sheep_id}`);
return;
}
console.log(`📨 Sending notification to ${rows.length} subscriptions...`);
const payload = JSON.stringify({
title: title ?? "Тестове повідомлення",
body: body ?? "Ви успішно підписалися на отримання push повідомлень!",
url: `https://${process.env.DOMAIN}${page ?? ""}`
});
const results = await Promise.allSettled(rows.map(row => {
const subscription = {
endpoint: row.endpoint,
keys: JSON.parse(row.keys),
};
return webpush.sendNotification(subscription, payload);
}));
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`✅ Sent: ${rows.length - failed}, ❌ Failed: ${failed}`);
});
}
async sendStand({ title, body, page }) {
const sql = `
SELECT
subscription.*
FROM
subscription
JOIN
sheeps
ON sheeps.id = subscription.sheep_id
JOIN
possibilities
ON possibilities.sheep_id = sheeps.id
WHERE
possibilities.can_view_stand = '1'
ORDER BY
subscription.id;
`;
db.all(sql, async (err, rows) => {
if (err) {
console.error('DB error:', err.message);
return;
}
if (!rows.length) {
console.log(`🐑 No subscriptions found for sheep_id: ${sheep_id}`);
return;
}
console.log(`📨 Sending notification to ${rows.length} subscriptions...`);
const payload = JSON.stringify({
title: title ?? "Тестове повідомлення",
body: body ?? "Ви успішно підписалися на отримання push повідомлень!",
url: `https://${process.env.DOMAIN}${page ?? ""}`
});
const results = await Promise.allSettled(rows.map(row => {
const subscription = {
endpoint: row.endpoint,
keys: JSON.parse(row.keys),
};
return webpush.sendNotification(subscription, payload);
}));
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`✅ Sent: ${rows.length - failed}, ❌ Failed: ${failed}`);
});
}
};
module.exports = new Notification();