sequelize-game-bank 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +44 -0
- package/database/models/Account.js +3 -5
- package/database/models/Credit.js +4 -6
- package/database/models/Transaction.js +4 -5
- package/database/models/User.js +8 -10
- package/database/services/account/add-manny.js +4 -7
- package/database/services/account/change-pincode.js +3 -7
- package/database/services/account/change-status.js +3 -2
- package/database/services/account/create-credit.js +50 -100
- package/database/services/account/create.js +4 -7
- package/database/services/account/delete.js +2 -1
- package/database/services/account/find-manny.js +1 -3
- package/database/services/account/logs.js +6 -12
- package/database/services/account/remove-manny.js +4 -6
- package/database/services/account/transfer.js +44 -41
- package/database/services/bot/bot.js +5 -20
- package/database/services/bot/cron-schedule.js +123 -0
- package/database/services/bot/generators/register-bot.html +1 -1
- package/database/services/bot/generators/register-bot.txt +2 -2
- package/database/services/bot/write.js +3 -12
- package/database/services/user/change-avatar.js +1 -5
- package/database/services/user/change-gmail.js +1 -3
- package/database/services/user/change-password.js +3 -4
- package/database/services/user/create.js +3 -10
- package/database/services/user/delete.js +3 -4
- package/database/services/user/disable-2FA.js +5 -7
- package/database/services/user/enable-2FA.js +4 -6
- package/database/services/user/login.js +6 -17
- package/database/services/user/math-manny.js +3 -3
- package/index.js +27 -48
- package/package.json +21 -5
- package/public/credit.html +26 -0
- package/public/js/js.js +160 -26
- package/public/transfer.html +41 -39
- package/public/uploads/avatars/default.png +0 -0
- package/server/middleware/auth.js +1 -6
- package/server/server.js +8 -25
- package/src/logo/icon.png +0 -0
- package/.env +0 -114
- package/database/citybank.db +0 -0
- package/database/config/init-db.js +0 -33
- package/logs/errors.log +0 -14
- package/public/uploads/avatars/Pavel_1790269874496.png +0 -0
- package/public/uploads/avatars/Pavel_1790275457821.png +0 -0
- package/public/uploads/avatars/Pavel_1790867570006.png +0 -0
- package/public/uploads/avatars/Romanenko_1790270954677.png +0 -0
- package/public/uploads/avatars/root_1790275537324.png +0 -0
- package/public/uploads/avatars/root_1790275537381.png +0 -0
- package/public/uploads/avatars/root_1790349234549.png +0 -0
- package/public/uploads/avatars/root_1790519734671.png +0 -0
- package/public/uploads/avatars/root_1790879784620.png +0 -0
- package/public/uploads/avatars/root_1790879790948.png +0 -0
- package/public/uploads/avatars/root_1790879796755.png +0 -0
- package/public/uploads/avatars/root_1790962800544.png +0 -0
- package/public/uploads/avatars/root_1791009911239.png +0 -0
- package/test.js +0 -20
|
@@ -1,38 +1,23 @@
|
|
|
1
|
-
// database/services/bot/bot.js
|
|
2
1
|
const fs = require('fs').promises;
|
|
3
2
|
const path = require('path');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Вспомогательный метод для асинхронного рендеринга и подстановки данных
|
|
7
|
-
*/
|
|
3
|
+
const logger = require("custom-color-logs").print;
|
|
8
4
|
async function renderTemplate(templateName, ext, vars) {
|
|
9
5
|
try {
|
|
10
|
-
// 🔑 СТРОГО ВЫЧИСЛЯЕМ АБСОЛЮТНЫЙ ПУТЬ к папке generators
|
|
11
6
|
const filePath = path.resolve(__dirname, 'generators', `${templateName}.${ext}`);
|
|
12
|
-
|
|
13
|
-
// Читаем файл с диска
|
|
14
7
|
let data = await fs.readFile(filePath, 'utf-8');
|
|
15
|
-
|
|
16
|
-
// Подставляем переменные
|
|
17
8
|
for (const [key, value] of Object.entries(vars)) {
|
|
18
9
|
data = data.replace(new RegExp(`{${key}}`, 'g'), value);
|
|
19
10
|
}
|
|
20
11
|
return data;
|
|
21
12
|
} catch (err) {
|
|
22
|
-
console.
|
|
23
|
-
return "";
|
|
13
|
+
console.log(logger.NodemailerError(`[Bot Render Error] Не найден файл ${templateName}.${ext}:`, err.message))
|
|
14
|
+
return "";
|
|
24
15
|
}
|
|
25
16
|
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Главный движок компиляции сообщений бота
|
|
29
|
-
*/
|
|
30
17
|
async function sendBotNotification(templateName, variables) {
|
|
31
18
|
const txtContent = await renderTemplate(templateName, 'txt', variables);
|
|
32
19
|
const htmlContent = await renderTemplate(templateName, 'html', variables);
|
|
33
|
-
|
|
34
|
-
console.log(`\n🤖 [City-Bank Bot Engine]: Сгенерировано событие уведомления [${templateName}]`);
|
|
20
|
+
console.log(logger.NodemailerInfo(`Сгенерировано событие уведомления [${templateName}]`))
|
|
35
21
|
return { txt: txtContent, html: htmlContent };
|
|
36
22
|
}
|
|
37
|
-
|
|
38
|
-
module.exports = { sendBotNotification };
|
|
23
|
+
module.exports = { sendBotNotification };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const cron = require('node-cron');
|
|
2
|
+
const nodemailer = require('nodemailer');
|
|
3
|
+
const { Op } = require('sequelize');
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const Account = require(path.join(__dirname, '..', '..', 'models', 'Account'));
|
|
6
|
+
const Credit = require(path.join(__dirname, '..', '..', 'models', 'Credit'));
|
|
7
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
8
|
+
const sequelize = require(path.join(__dirname, '..', '..', 'config', 'init-db'));
|
|
9
|
+
const logger = require("custom-color-logs").print;
|
|
10
|
+
const transporter = nodemailer.createTransport({
|
|
11
|
+
service: 'gmail',
|
|
12
|
+
auth: {
|
|
13
|
+
user: process.env.GMAIL,
|
|
14
|
+
pass: process.env.GOOGLE_APP_PASSWORD,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
transporter.verify((err) => {
|
|
19
|
+
if (err) console.log(logger.NodemailerError('Ошибка конфигурации почты:', err.message));
|
|
20
|
+
else console.log(logger.NodemailerInfo('Сервер готов к отправке писем'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
async function sendEmailNotification(userEmail, subject, htmlContent) {
|
|
24
|
+
if (!userEmail) return;
|
|
25
|
+
try {
|
|
26
|
+
await transporter.sendMail({
|
|
27
|
+
from: `"Game Bank" <${process.env.GMAIL}>`,
|
|
28
|
+
to: userEmail,
|
|
29
|
+
subject: subject,
|
|
30
|
+
html: htmlContent,
|
|
31
|
+
});
|
|
32
|
+
console.log(logger.NodemailerFunctionPositivePerformance(`Письмо успешно отправлено на ${userEmail}`));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(logger.NodemailerError(`Не удалось отправить письмо на ${userEmail}:\n ${error.message}`));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
cron.schedule('0 0 * * *', async () => {
|
|
38
|
+
console.log(logger.NodemailerFunctionInfo(`Проверка активных кредитов и обработка платежей...`));
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const activeCredits = await Credit.findAll({
|
|
42
|
+
where: {
|
|
43
|
+
status: 'active',
|
|
44
|
+
remainingRepay: { [Op.gt]: 0 }
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
for (const activeCreditData of activeCredits) {
|
|
48
|
+
let emailToSend = null;
|
|
49
|
+
let emailSubject = '';
|
|
50
|
+
let emailHtml = '';
|
|
51
|
+
const transaction = await sequelize.transaction();
|
|
52
|
+
try {
|
|
53
|
+
const credit = await Credit.findByPk(activeCreditData.id, {
|
|
54
|
+
transaction,
|
|
55
|
+
lock: transaction.LOCK.UPDATE
|
|
56
|
+
});
|
|
57
|
+
if (!credit || credit.status !== 'active' || credit.remainingRepay <= 0) {
|
|
58
|
+
await transaction.rollback();
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const calculatedPayment = credit.totalRepay / credit.termMonths;
|
|
62
|
+
const paymentAmount = Math.min(calculatedPayment, credit.remainingRepay);
|
|
63
|
+
const account = await Account.findOne({
|
|
64
|
+
where: { id: credit.accountId },
|
|
65
|
+
include: [{ model: User, as: 'User' }],
|
|
66
|
+
transaction,
|
|
67
|
+
lock: transaction.LOCK.UPDATE
|
|
68
|
+
});
|
|
69
|
+
if (!account || !account.User || !account.User.gmail) {
|
|
70
|
+
console.error(`[CRON Warning] Не найден Email или аккаунт для кредита ID #${credit.id}`);
|
|
71
|
+
await transaction.rollback();
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const userEmail = account.User.gmail;
|
|
75
|
+
if (credit.autoRepay) {
|
|
76
|
+
if (account.status === 'blocked') {
|
|
77
|
+
throw new Error('Аккаунт заблокирован');
|
|
78
|
+
}
|
|
79
|
+
if (account.balance >= paymentAmount) {
|
|
80
|
+
await account.decrement('balance', { by: paymentAmount, transaction });
|
|
81
|
+
credit.remainingRepay -= paymentAmount;
|
|
82
|
+
if (credit.remainingRepay <= 0) {
|
|
83
|
+
credit.status = 'paid';
|
|
84
|
+
}
|
|
85
|
+
await credit.save({ transaction });
|
|
86
|
+
await transaction.commit();
|
|
87
|
+
emailToSend = userEmail;
|
|
88
|
+
emailSubject = 'Успешное автосписание по кредиту — Game Bank';
|
|
89
|
+
emailHtml = `<h3>Уважаемый клиент!</h3>
|
|
90
|
+
<p>С вашего баланса было автоматически списано <b>${paymentAmount.toFixed(2)} USD</b> в счет погашения кредита.</p>
|
|
91
|
+
<p>Остаток задолженности: <b>${credit.remainingRepay.toFixed(2)} USD</b>.</p>`;
|
|
92
|
+
} else {
|
|
93
|
+
await transaction.rollback();
|
|
94
|
+
|
|
95
|
+
emailToSend = userEmail;
|
|
96
|
+
emailSubject = 'Ошибка автосписания: недостаточно средств — Game Bank';
|
|
97
|
+
emailHtml = `<h3>Внимание! Недостаточно средств</h3>
|
|
98
|
+
<p>Мы не смогли списать регулярный платеж по кредиту в размере <b>${paymentAmount.toFixed(2)} USD</b>.</p>
|
|
99
|
+
<p>Пожалуйста, пополните баланс вашего аккаунта, чтобы избежать просрочки.</p>`;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
await transaction.rollback();
|
|
103
|
+
emailToSend = userEmail;
|
|
104
|
+
emailSubject = 'Напоминание о платеже по кредиту — Game Bank';
|
|
105
|
+
emailHtml = `<h3>Здравствуйте!</h3>
|
|
106
|
+
<p>Напоминаем, что вам необходимо внести обязательный платеж по кредиту в размере <b>${paymentAmount.toFixed(2)} USD</b>.</p>
|
|
107
|
+
<p>Текущий остаток по кредиту: <b>${credit.remainingRepay.toFixed(2)} USD</b>.</p>
|
|
108
|
+
<p>Вы можете оплатить его в личном кабинете.</p>`;
|
|
109
|
+
}
|
|
110
|
+
if (emailToSend) {
|
|
111
|
+
await sendEmailNotification(emailToSend, emailSubject, emailHtml);
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (transaction && !transaction.finished) {
|
|
115
|
+
await transaction.rollback();
|
|
116
|
+
}
|
|
117
|
+
console.log(logger.NodemailerError(`Ошибка обработки кредита ID ${activeCreditData.id}: ${err.message}`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.log(logger.NodemailerError(`Ошибка кредитного планировщика: ${error.message}`));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<h2>Здравствуйте, {surname} {realName}!</h2><p>На ваш счёт зарегистрировали аккаунт в банке City-bank.</p><p>{
|
|
1
|
+
<h2>Здравствуйте, {surname} {realName}!</h2><p>На ваш счёт зарегистрировали аккаунт в банке City-bank.</p><p>{twoFaBlock}</p><p>Если забыли пароль используйте токен - <b>[{token}]</b></p><p>Ваш пароль - <b>[{password}]</b></p><br><p>С уважением, система банка City-bank ©</p>
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
// database/services/bot/write.js
|
|
2
1
|
const fs = require('fs').promises;
|
|
3
2
|
const path = require('path');
|
|
4
|
-
|
|
5
|
-
// 🔑 Путь должен вести строго в текущую подпапку generators
|
|
3
|
+
const logger = require("custom-color-logs").print;
|
|
6
4
|
const generatorsDir = path.resolve(__dirname, 'generators');
|
|
7
|
-
|
|
8
5
|
const textTemplates = {
|
|
9
6
|
'register-bot': "Здравствуйте, {surname} {realName}!\nНа ваш ник {name} зарегистрирован аккаунт в City-bank.\n{twoFaBlock}\nЕсли забыли пароль используйте токен - [{token}]\nВаш пароль - [{password}]\n\nС уважением, система банка City-bank ©",
|
|
10
7
|
'login-bot': "🔔 Вход в систему: Профиль {name} успешно авторизован.",
|
|
@@ -15,7 +12,6 @@ const textTemplates = {
|
|
|
15
12
|
'ban-bot': "🚫 Счет #{accountId} ЗАБЛОКИРОВАН администратором.",
|
|
16
13
|
'unban-bot': "✅ Счет #{accountId} РАЗБЛОКИРОВАН администратором."
|
|
17
14
|
};
|
|
18
|
-
|
|
19
15
|
const htmlTemplates = {
|
|
20
16
|
'register-bot': "<h2>Здравствуйте, {surname} {realName}!</h2><p>На ваш счёт зарегистрировали аккаунт в банке City-bank.</p><p>{twoFaBlock}</p><p>Если забыли пароль используйте токен - <b>[{token}]</b></p><p>Ваш пароль - <b>[{password}]</b></p><br><p>С уважением, система банка City-bank ©</p>",
|
|
21
17
|
'login-bot': "<h3>🔔 Уведомление безопасности</h3><p>Успешный вход в личный кабинет: <b>{name}</b></p>",
|
|
@@ -26,24 +22,19 @@ const htmlTemplates = {
|
|
|
26
22
|
'ban-bot': "<h3 style='color:red;'>🚫 Заморозка счета</h3><p>Ваш банковский счет #{accountId} заблокирован администрацией.</p>",
|
|
27
23
|
'unban-bot': "<h3 style='color:green;'>✅ Разблокировка счета</h3><p>Ваш банковский счет #{accountId} успешно активирован.</p>"
|
|
28
24
|
};
|
|
29
|
-
|
|
30
25
|
async function writeTemplatesIfNotExist() {
|
|
31
26
|
try {
|
|
32
|
-
// Рекурсивно создаем папку generators, если её ещё нет
|
|
33
27
|
await fs.mkdir(generatorsDir, { recursive: true });
|
|
34
|
-
|
|
35
28
|
for (const [name, content] of Object.entries(textTemplates)) {
|
|
36
29
|
const p = path.join(generatorsDir, `${name}.txt`);
|
|
37
30
|
try { await fs.access(p); } catch { await fs.writeFile(p, content, 'utf-8'); }
|
|
38
31
|
}
|
|
39
|
-
|
|
40
32
|
for (const [name, content] of Object.entries(htmlTemplates)) {
|
|
41
33
|
const p = path.join(generatorsDir, `${name}.html`);
|
|
42
34
|
try { await fs.access(p); } catch { await fs.writeFile(p, content, 'utf-8'); }
|
|
43
35
|
}
|
|
44
36
|
} catch (err) {
|
|
45
|
-
console.
|
|
37
|
+
console.log(logger.NodemailerError("Ошибка автозаписи генераторов:", err.message))
|
|
46
38
|
}
|
|
47
39
|
}
|
|
48
|
-
|
|
49
|
-
module.exports = { writeTemplatesIfNotExist };
|
|
40
|
+
module.exports = { writeTemplatesIfNotExist };
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
// database/services/user/change-avatar.js
|
|
2
1
|
const User = require('../../models/User');
|
|
3
|
-
|
|
4
2
|
module.exports = async function changeAvatar(name, avatarUrl) {
|
|
5
3
|
try {
|
|
6
4
|
const user = await User.findByPk(name);
|
|
7
5
|
if (!user) return { status: false, statusCode: -2, message: 'Пользователь не найден' };
|
|
8
|
-
|
|
9
6
|
user.avatar = avatarUrl;
|
|
10
7
|
await user.save();
|
|
11
|
-
|
|
12
8
|
return { status: true, statusCode: 1, avatar: avatarUrl };
|
|
13
9
|
} catch (err) {
|
|
14
10
|
return { status: false, statusCode: -10, message: err.message };
|
|
15
11
|
}
|
|
16
|
-
};
|
|
12
|
+
};
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
const User = require('../../models/User');
|
|
2
|
-
|
|
3
2
|
module.exports = async function changeGmail(name, newGmail) {
|
|
4
3
|
try {
|
|
5
4
|
if (await User.findOne({ where: { user_gmail: newGmail } })) return { status: false, statusCode: -21 };
|
|
6
5
|
const user = await User.findByPk(name);
|
|
7
6
|
if (!user) return { status: false, statusCode: -2 };
|
|
8
|
-
|
|
9
7
|
user.user_gmail = newGmail;
|
|
10
8
|
await user.save();
|
|
11
9
|
return { status: true, statusCode: 1 };
|
|
12
10
|
} catch { return { status: false, statusCode: -10 }; }
|
|
13
|
-
};
|
|
11
|
+
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
const bcrypt = require('bcrypt');
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
4
4
|
module.exports = async function changePassword(name, oldPassword, newPassword, totp = null) {
|
|
5
5
|
try {
|
|
6
6
|
const user = await User.findByPk(name);
|
|
7
7
|
if (!user || !(await bcrypt.compare(oldPassword, user.password))) return { status: false, statusCode: -2 };
|
|
8
|
-
|
|
9
8
|
if (user.two_fa_status) {
|
|
10
9
|
if (!totp || !(await bcrypt.compare(totp, user.two_fa_token))) return { status: false, statusCode: -2 };
|
|
11
10
|
}
|
|
@@ -13,4 +12,4 @@ module.exports = async function changePassword(name, oldPassword, newPassword, t
|
|
|
13
12
|
await user.save();
|
|
14
13
|
return { status: true, statusCode: 1 };
|
|
15
14
|
} catch { return { status: false, statusCode: -10 }; }
|
|
16
|
-
};
|
|
15
|
+
};
|
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
const bcrypt = require('bcrypt');
|
|
2
2
|
const crypto = require('crypto');
|
|
3
|
-
const
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
4
5
|
const nodemailer = require('nodemailer');
|
|
5
6
|
const { sendBotNotification } = require('../bot/bot');
|
|
6
|
-
|
|
7
7
|
module.exports = async function createUser(data) {
|
|
8
8
|
try {
|
|
9
9
|
if (await User.findByPk(data.name)) return { status: false, statusCode: -20 };
|
|
10
10
|
if (await User.findOne({ where: { user_gmail: data.gmail } })) return { status: false, statusCode: -21 };
|
|
11
|
-
|
|
12
11
|
if (data.password.length < 8 || !/[A-Z]/.test(data.password) || !/[0-9]/.test(data.password)) {
|
|
13
12
|
return { status: false, statusCode: -22 };
|
|
14
13
|
}
|
|
15
|
-
|
|
16
14
|
const emergencyToken = crypto.randomBytes(10).toString('hex');
|
|
17
15
|
const raw2faToken = data.enable2FA ? crypto.randomBytes(10).toString('hex') : '';
|
|
18
16
|
const faText = data.enable2FA ? `В вашем аккаунте включена защита 2FA. — при крупном переводе средств запрашивать код подтверждения, отправленный на почту\nВаш токен - [${raw2faToken}].` : '';
|
|
19
|
-
|
|
20
17
|
const botMsg = await sendBotNotification('register-bot', {
|
|
21
18
|
surname: data.userSurname, realName: data.userName, name: data.name, password: data.password, token: emergencyToken, faText
|
|
22
19
|
});
|
|
23
|
-
|
|
24
20
|
const transporter = nodemailer.createTransport({
|
|
25
21
|
service: 'gmail',
|
|
26
22
|
auth: { user: process.env.GMAIL, pass: process.env.GOOGLE_APP_PASSWORD }
|
|
27
23
|
});
|
|
28
|
-
|
|
29
24
|
try {
|
|
30
25
|
await transporter.sendMail({
|
|
31
26
|
from: `"City-bank" <${process.env.GMAIL}>`,
|
|
@@ -37,7 +32,6 @@ module.exports = async function createUser(data) {
|
|
|
37
32
|
} catch {
|
|
38
33
|
return { status: false, statusCode: -111 };
|
|
39
34
|
}
|
|
40
|
-
|
|
41
35
|
await User.create({
|
|
42
36
|
name: data.name,
|
|
43
37
|
user_name: await bcrypt.hash(data.userName, 10),
|
|
@@ -48,9 +42,8 @@ module.exports = async function createUser(data) {
|
|
|
48
42
|
token: await bcrypt.hash(emergencyToken, 10),
|
|
49
43
|
password: await bcrypt.hash(data.password, 10)
|
|
50
44
|
});
|
|
51
|
-
|
|
52
45
|
return { status: true, statusCode: 1, secret2FA: raw2faToken };
|
|
53
46
|
} catch {
|
|
54
47
|
return { status: false, statusCode: -10 };
|
|
55
48
|
}
|
|
56
|
-
};
|
|
49
|
+
};
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
const bcrypt = require('bcrypt');
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
4
4
|
module.exports = async function deleteUser(name, password, totp = null) {
|
|
5
5
|
try {
|
|
6
6
|
const user = await User.findByPk(name);
|
|
7
7
|
if (!user || !(await bcrypt.compare(password, user.password))) return { status: false, statusCode: -2 };
|
|
8
|
-
|
|
9
8
|
if (user.two_fa_status) {
|
|
10
9
|
if (!totp || !(await bcrypt.compare(totp, user.two_fa_token))) return { status: false, statusCode: -2 };
|
|
11
10
|
}
|
|
12
11
|
await user.destroy();
|
|
13
12
|
return { status: true, statusCode: 1 };
|
|
14
13
|
} catch { return { status: false, statusCode: -10 }; }
|
|
15
|
-
};
|
|
14
|
+
};
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
3
|
+
const { sendBotNotification } = require(path.join(__dirname, '..', 'bot', 'bot'));
|
|
4
4
|
module.exports = async function disable2FA(name) {
|
|
5
5
|
try {
|
|
6
6
|
const user = await User.findByPk(name);
|
|
7
7
|
if (!user) return { status: false, statusCode: -2 };
|
|
8
|
-
|
|
9
8
|
user.two_fa_status = false;
|
|
10
9
|
user.two_fa_token = null;
|
|
11
|
-
await user.save();
|
|
12
|
-
|
|
10
|
+
await user.save();
|
|
13
11
|
await sendBotNotification('disable-2FA-bot', { name });
|
|
14
12
|
return { status: true, statusCode: 1 };
|
|
15
13
|
} catch { return { status: false, statusCode: -10 }; }
|
|
16
|
-
};
|
|
14
|
+
};
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const bcrypt = require('bcrypt');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
5
|
+
const { sendBotNotification } = require(path.join(__dirname, '..', 'bot', 'bot'));
|
|
6
6
|
module.exports = async function enable2FA(name) {
|
|
7
7
|
try {
|
|
8
8
|
const user = await User.findByPk(name);
|
|
9
9
|
if (!user) return { status: false, statusCode: -2 };
|
|
10
|
-
|
|
11
10
|
const rawSecret = crypto.randomBytes(10).toString('hex');
|
|
12
11
|
user.two_fa_status = true;
|
|
13
12
|
user.two_fa_token = await bcrypt.hash(rawSecret, 10);
|
|
14
13
|
await user.save();
|
|
15
|
-
|
|
16
14
|
await sendBotNotification('enable-2FA-bot', { name });
|
|
17
15
|
return { status: true, statusCode: 1, secret2FA: rawSecret };
|
|
18
16
|
} catch { return { status: false, statusCode: -10 }; }
|
|
19
|
-
};
|
|
17
|
+
};
|
|
@@ -1,31 +1,24 @@
|
|
|
1
|
-
// database/services/user/login.js
|
|
2
1
|
const bcrypt = require('bcrypt');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const User = require(path.join(__dirname, '..', '..', 'models', 'User'));
|
|
4
|
+
const { sendBotNotification } = require(path.join(__dirname, '..', 'bot', 'bot'));
|
|
5
|
+
const logger = require("custom-color-logs").print;
|
|
6
6
|
module.exports = async function loginUser(name, password, totpCode = null) {
|
|
7
7
|
try {
|
|
8
8
|
const user = await User.findByPk(name);
|
|
9
9
|
if (!user) {
|
|
10
10
|
return { status: false, statusCode: -2, message: 'Неверный логин или пароль' };
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
// Проверяем хэш пароля
|
|
14
12
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
15
13
|
if (!isMatch) {
|
|
16
14
|
return { status: false, statusCode: -2, message: 'Неверный логин или пароль' };
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
// 👑 ИСКЛЮЧЕНИЕ ДЛЯ АДМИНА: У пользователя root никогда не требуем 2FA при входе
|
|
20
16
|
if (name === 'root') {
|
|
21
17
|
await sendBotNotification('login-bot', { name });
|
|
22
18
|
return { status: true, statusCode: 1, message: 'Успешный вход администрации' };
|
|
23
19
|
}
|
|
24
|
-
|
|
25
|
-
// Проверка 2FA для остальных игроков
|
|
26
20
|
if (user.two_fa_status) {
|
|
27
21
|
if (!totpCode) {
|
|
28
|
-
// 🔑 Исправлено: гарантируем передачу четкого сообщения вместо undefined
|
|
29
22
|
return {
|
|
30
23
|
status: false,
|
|
31
24
|
statusCode: -2,
|
|
@@ -33,19 +26,15 @@ module.exports = async function loginUser(name, password, totpCode = null) {
|
|
|
33
26
|
message: 'Аккаунт защищен. Пожалуйста, введите временный токен 2FA из приложения:'
|
|
34
27
|
};
|
|
35
28
|
}
|
|
36
|
-
|
|
37
29
|
const isTotpValid = await bcrypt.compare(totpCode, user.two_fa_token);
|
|
38
30
|
if (!isTotpValid) {
|
|
39
31
|
return { status: false, statusCode: -2, message: 'Неверный код 2FA! Попробуйте снова.' };
|
|
40
32
|
}
|
|
41
33
|
}
|
|
42
|
-
|
|
43
|
-
// Уведомление бота о входе игрока
|
|
44
34
|
await sendBotNotification('login-bot', { name });
|
|
45
|
-
|
|
46
35
|
return { status: true, statusCode: 1, message: 'Вход выполнен успешно' };
|
|
47
36
|
} catch (err) {
|
|
48
|
-
console.
|
|
37
|
+
console.log(logger.DatabaseError("Ошибка сервиса авторизации:", err.message))
|
|
49
38
|
return { status: false, statusCode: -10, message: 'Ошибка базы данных' };
|
|
50
39
|
}
|
|
51
|
-
};
|
|
40
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const Account = require(path.join(__dirname, '..', '..', 'models', 'Account'));
|
|
3
3
|
module.exports = async function mathManny(username) {
|
|
4
4
|
try {
|
|
5
5
|
const total = await Account.sum('manny', { where: { user: username } });
|
|
6
6
|
return { status: true, statusCode: 1, totalManny: parseFloat(total) || 0.00 };
|
|
7
7
|
} catch { return { status: false, statusCode: -10 }; }
|
|
8
|
-
};
|
|
8
|
+
};
|
package/index.js
CHANGED
|
@@ -1,83 +1,62 @@
|
|
|
1
1
|
const logger = require("custom-color-logs").print;
|
|
2
2
|
console.log(logger.ServerBankInfo("Модуль банка запущен!"))
|
|
3
3
|
require('dotenv').config();
|
|
4
|
+
const path = require("path")
|
|
4
5
|
const express = require('express');
|
|
5
6
|
const cookieParser = require('cookie-parser');
|
|
6
7
|
const bcrypt = require('bcrypt');
|
|
7
|
-
|
|
8
|
-
const sequelize = require('./database/config/init-db');
|
|
8
|
+
const sequelize = require(path.join(__dirname, 'database', 'config', 'init-db'));
|
|
9
9
|
const User = require('./database/models/User');
|
|
10
|
-
const { writeTemplatesIfNotExist } = require('
|
|
11
|
-
const registerRoutes = require('
|
|
12
|
-
const Transaction = require('
|
|
10
|
+
const { writeTemplatesIfNotExist } = require(path.join(__dirname, 'database', 'services', 'bot', 'write'));
|
|
11
|
+
const registerRoutes = require(path.join(__dirname, 'server', 'server'));
|
|
12
|
+
const Transaction = require(path.join(__dirname, 'database', 'models', 'Transaction'));
|
|
13
13
|
const app = express();
|
|
14
|
-
|
|
15
|
-
const { verifySession } = require('
|
|
14
|
+
|
|
15
|
+
const { verifySession } = require(path.join(__dirname, 'server', 'server'));
|
|
16
16
|
app.use(express.json());
|
|
17
17
|
app.use(cookieParser());
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async function initBankModule() {
|
|
18
|
+
require(path.join(__dirname, 'database', 'services', 'bot', 'cron-schedule'));
|
|
19
|
+
async function Bank() {
|
|
22
20
|
try {
|
|
23
21
|
await sequelize.sync();
|
|
24
22
|
console.log(logger.ServerBankInfo("База данных синхронизирована."));
|
|
25
|
-
|
|
26
23
|
await writeTemplatesIfNotExist();
|
|
27
|
-
|
|
28
24
|
const rootPassword = process.env.ROOT_PASSWORD || 'bank-root';
|
|
29
25
|
const rootPasswordHash = await bcrypt.hash(rootPassword, 10);
|
|
30
26
|
const systemHash = await bcrypt.hash('SystemAdmin', 10);
|
|
31
27
|
const rootEmergencyToken = await bcrypt.hash('RootEmergencyTokenSafe2026', 10);
|
|
32
|
-
|
|
33
28
|
await User.findOrCreate({
|
|
34
29
|
where: { name: 'root' },
|
|
35
30
|
defaults: {
|
|
36
31
|
user_name: systemHash,
|
|
37
32
|
user_surname: systemHash,
|
|
38
|
-
user_gmail:
|
|
33
|
+
user_gmail: process.env.GMAIL,
|
|
39
34
|
two_fa_status: false,
|
|
40
35
|
token: rootEmergencyToken,
|
|
41
36
|
password: rootPasswordHash
|
|
42
37
|
}
|
|
43
38
|
});
|
|
44
39
|
console.log(logger.ServerBankInfo(`Супер пользвотель создан`))
|
|
45
|
-
|
|
46
40
|
const configuredApp = registerRoutes(app);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// 3. Открытые страницы, доступные БЕЗ авторизации
|
|
67
|
-
app.get('/login.html', (req, res) => res.sendFile(path.join(__dirname, 'public', 'login.html')));
|
|
68
|
-
app.get('/register.html', (req, res) => res.sendFile(path.join(__dirname, 'public', 'register.html')));
|
|
69
|
-
app.get('/home.html', (req, res) => res.sendFile(path.join(__dirname, 'public', 'home.html')));
|
|
70
|
-
app.get('/favicon.ico', (req, res) => res.sendFile(path.join(__dirname, 'public', 'icon', 'icon.png')));
|
|
71
|
-
// Перенаправление с глав,,,,,,ного адреса на главную страницу банка
|
|
72
|
-
app.get('/', verifySession, (req, res) => res.redirect('/general.html'));
|
|
73
|
-
app.get('/', (req, res) => res.redirect('/home.html'));
|
|
74
|
-
// ... (остальной код возврата configuredApp)
|
|
75
|
-
|
|
41
|
+
app.use('/css', express.static(path.join(__dirname, 'public', 'css')));
|
|
42
|
+
app.use('/js', express.static(path.join(__dirname, 'public', 'js')));
|
|
43
|
+
app.use('/uploads', express.static(path.join(__dirname, 'public', 'uploads')));
|
|
44
|
+
const { verifyPageSession } = require(path.join(__dirname, 'server', 'middleware', 'auth'));
|
|
45
|
+
const pages = ['general.html', 'profile.html', 'accounts.html', 'transfer.html', 'history.html', 'credit.html', 'admin.html'];
|
|
46
|
+
pages.forEach(page => {
|
|
47
|
+
app.get(`/${page}`, verifyPageSession, (req, res) => {
|
|
48
|
+
res.sendFile(path.join(__dirname, 'public', page));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
app.get('/login.html', (req, res) => res.sendFile(path.join(__dirname, 'public', 'login.html')));
|
|
52
|
+
app.get('/register.html', (req, res) => res.sendFile(path.join(__dirname, 'public', 'register.html')));
|
|
53
|
+
app.get('/home.html', (req, res) => res.sendFile(path.join(__dirname, 'public', 'home.html')));
|
|
54
|
+
app.get('/favicon.ico', (req, res) => res.sendFile(path.join(__dirname, 'public', 'icon', 'icon.png')));
|
|
55
|
+
app.get('/', (req, res) => res.redirect('/home.html'));
|
|
76
56
|
return configuredApp;
|
|
77
57
|
} catch (err) {
|
|
78
|
-
console.log(logger.ServerBankError("
|
|
58
|
+
console.log(logger.ServerBankError("Критическая ошибка инициализации модулей", err));
|
|
79
59
|
throw err;
|
|
80
60
|
}
|
|
81
61
|
}
|
|
82
|
-
|
|
83
|
-
module.exports = initBankModule;
|
|
62
|
+
module.exports = Bank ;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequelize-game-bank",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A Sequelize-based gaming economy and banking system featuring user profiles, credits, admin management, and detailed transaction history.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"start": "node test.js"
|
|
8
|
+
"start": "node test.js",
|
|
9
|
+
"ship": "npm publish"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
12
|
"bcrypt": "^6.0.0",
|
|
@@ -14,9 +15,11 @@
|
|
|
14
15
|
"custom-color-logs": "^3.1.0",
|
|
15
16
|
"dotenv": "^18.0.0",
|
|
16
17
|
"express": "^5.2.1",
|
|
18
|
+
"express-rate-limit": "^8.7.0",
|
|
17
19
|
"jsonwebtoken": "^9.0.3",
|
|
18
20
|
"load-stopper": "^1.1.0",
|
|
19
21
|
"multer": "^2.4.0",
|
|
22
|
+
"node-cron": "^4.6.0",
|
|
20
23
|
"nodemailer": "^10.0.10",
|
|
21
24
|
"otplib": "^12.0.1",
|
|
22
25
|
"qrcode": "^1.5.4",
|
|
@@ -25,8 +28,21 @@
|
|
|
25
28
|
},
|
|
26
29
|
"keywords": [
|
|
27
30
|
"sequelize",
|
|
31
|
+
"game-bank",
|
|
32
|
+
"game-economy",
|
|
33
|
+
"economy",
|
|
28
34
|
"bank",
|
|
29
|
-
"
|
|
35
|
+
"credits",
|
|
36
|
+
"loans",
|
|
37
|
+
"profiles",
|
|
38
|
+
"admin",
|
|
39
|
+
"transactions",
|
|
40
|
+
"transfers",
|
|
41
|
+
"history",
|
|
42
|
+
"ledger",
|
|
43
|
+
"rpg-economy",
|
|
44
|
+
"orm",
|
|
45
|
+
"sqlite"
|
|
30
46
|
],
|
|
31
47
|
"homepage": "https://github.com/RPM-programmer/sequelize-game-bank#readme",
|
|
32
48
|
"bugs": {
|
|
@@ -37,5 +53,5 @@
|
|
|
37
53
|
"url": "git+https://github.com/RPM-programmer/sequelize-game-bank.git"
|
|
38
54
|
},
|
|
39
55
|
"license": "ISC",
|
|
40
|
-
"author": "prm-programmer"
|
|
56
|
+
"author": "prm-programer p7841744@gmail.com (https://github.com/RPM-programmer)"
|
|
41
57
|
}
|