Змінено директорії
Додано скрипти CRON Поліпшено механізм запису стендів та їх редагування
This commit is contained in:
11
cron/Dockerfile
Normal file
11
cron/Dockerfile
Normal 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
17
cron/config/db.js
Normal 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
13
cron/cron.js
Normal 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
3594
cron/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
cron/package.json
Normal file
18
cron/package.json
Normal 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
33
cron/tasks/backup.js
Normal 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
152
cron/utils/notification.js
Normal 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();
|
||||
Reference in New Issue
Block a user