sequelize-game-bank 5.0.2 → 5.1.0

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.
@@ -0,0 +1 @@
1
+ {"last_found_secrets": [{"match": "34d9bf2fadeb3b27d67300951ca6eba96da51655834e1db45519bcbdd4bf9bee", "name": "Generic Password - /home/pl/Desktop/sequelize-game-bank/config/setup-env.js"}]}
@@ -1,34 +1,53 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const process = require('process');
4
- const envPath = path.resolve(process.cwd(), '.env');
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const process = require("process");
4
+ const envPath = path.resolve(process.cwd(), ".env");
5
5
  const logger = require("custom-color-logs").print;
6
6
  const defaultEnvContent = `
7
7
  # ==============================================================================
8
- # sequelize-game-bank v4.0.0
8
+ # sequelize-game-bank v5.0.0
9
9
  # ==============================================================================
10
+
11
+ # тег инициализации
10
12
  SEQUELIZE_GAME_BANK_INITIALIZED = true
11
- JWT_SECRET = 0000
12
- ROOT_PASSWORD = bank-root
13
- GMAIL = example@gmail.com
14
- GOOGLE_APP_PASSWORD = abcd efgh ijkl mnop
13
+
14
+ # токен который будет зашиваться в cooki
15
+ JWT_SECRET = "0000"
16
+
17
+ # пароль от аккаунта админестратора
18
+ ROOT_PASSWORD = "bank-root"
19
+
20
+ # почта админестратора
21
+ GMAIL = "example@gmail.com"
22
+
23
+ # пароль от приложения gmail
24
+ GOOGLE_APP_PASSWORD = "abcd efgh ijkl mnop"
25
+
26
+ # интервал проверки кредита
27
+ CRON_CREDIT_INTERVAL_MINUTES = 5
28
+
15
29
  `;
16
30
  function initializeEnvironment() {
17
- try {
18
- if (!fs.existsSync(envPath)) {
19
- fs.writeFileSync(envPath, defaultEnvContent, 'utf8');
20
- } else {
21
- const currentEnv = fs.readFileSync(envPath, 'utf8');
22
- if (currentEnv.includes('SEQUELIZE_GAME_BANK_INITIALIZED')) {
23
- require("dotenv").config({ path: envPath });
24
- return;
25
- }
26
- fs.appendFileSync(envPath, `\n\n${defaultEnvContent}`, 'utf8');
27
- }
28
- } catch (err) {
29
- console.log(logger.ServerError('Не удалось проверить или обновить .env:', err.message));
31
+ try {
32
+ if (!fs.existsSync(envPath)) {
33
+ fs.writeFileSync(envPath, defaultEnvContent, "utf8");
34
+ } else {
35
+ const currentEnv = fs.readFileSync(envPath, "utf8");
36
+ if (currentEnv.includes("SEQUELIZE_GAME_BANK_INITIALIZED")) {
37
+ require("dotenv").config({ path: envPath });
38
+ return;
39
+ }
40
+ fs.appendFileSync(envPath, `\n\n${defaultEnvContent}`, "utf8");
30
41
  }
31
- require("dotenv").config({ path: envPath });
42
+ } catch (err) {
43
+ console.log(
44
+ logger.ServerError(
45
+ "Не удалось проверить или обновить .env:",
46
+ err.message,
47
+ ),
48
+ );
49
+ }
50
+ require("dotenv").config({ path: envPath });
32
51
  }
33
52
  initializeEnvironment();
34
- module.exports = { envPath };
53
+ module.exports = { envPath };
@@ -1,37 +1,50 @@
1
- const { DataTypes } = require('sequelize');
1
+ const { DataTypes } = require("sequelize");
2
2
  const path = require("path");
3
- const sequelize = require(path.join(__dirname, '..', 'config', 'init-db.js'));
4
- const Account = require(path.join(__dirname, 'Account.js'));
5
- const Credit = sequelize.define('credit', {
3
+ const sequelize = require(path.join(__dirname, "..", "config", "init-db.js"));
4
+ const Account = require(path.join(__dirname, "Account.js"));
5
+
6
+ const Credit = sequelize.define("credit", {
6
7
  id: {
7
8
  type: DataTypes.INTEGER,
8
9
  autoIncrement: true,
9
- primaryKey: true
10
+ primaryKey: true,
10
11
  },
11
- account_id: {
12
+ accountId: {
12
13
  type: DataTypes.INTEGER,
13
14
  allowNull: false,
14
- references: { model: Account, key: 'id' },
15
- onDelete: 'CASCADE'
15
+ references: { model: Account, key: "id" },
16
+ onDelete: "CASCADE",
16
17
  },
17
- total_payments: {
18
- type: DataTypes.INTEGER,
19
- allowNull: false
18
+ amount: {
19
+ type: DataTypes.DECIMAL(15, 2),
20
+ allowNull: false,
20
21
  },
21
- remaining_payments: {
22
- type: DataTypes.INTEGER,
23
- allowNull: false
22
+ totalRepay: {
23
+ type: DataTypes.DECIMAL(15, 2),
24
+ allowNull: false,
24
25
  },
25
- payment_amount: {
26
+ remainingRepay: {
26
27
  type: DataTypes.DECIMAL(15, 2),
27
- allowNull: false
28
+ allowNull: false,
28
29
  },
29
- auto_withdrawal: {
30
+ termMonths: {
31
+ type: DataTypes.INTEGER,
32
+ allowNull: false,
33
+ },
34
+ autoRepay: {
30
35
  type: DataTypes.BOOLEAN,
31
36
  allowNull: false,
32
- defaultValue: false
33
- }
37
+ defaultValue: false,
38
+ },
39
+ status: {
40
+ type: DataTypes.STRING,
41
+ allowNull: false,
42
+ defaultValue: "active",
43
+ },
34
44
  });
35
- Account.hasMany(Credit, { foreignKey: 'account_id' });
36
- Credit.belongsTo(Account, { foreignKey: 'account_id', as: 'linkedAccount' });
37
- module.exports = Credit;
45
+
46
+ // Настройка связей с учетом нового имени внешнего ключа accountId
47
+ Account.hasMany(Credit, { foreignKey: "accountId" });
48
+ Credit.belongsTo(Account, { foreignKey: "accountId", as: "linkedAccount" });
49
+
50
+ module.exports = Credit;
@@ -1,50 +1,92 @@
1
1
  const path = require("path");
2
- const Account = require(path.join(__dirname, '..', '..', 'models', 'Account.js'));
3
- const Credit = require(path.join(__dirname, '..', '..', 'models', 'Credit.js'));
4
- const sequelize = require(path.join(__dirname, '..', '..', 'config', 'init-db.js'));
5
- async function createCredit({ accountId, amount, autoRepay = false, rate = 0.1, termMonths = 12 }) {
2
+ const Account = require(
3
+ path.join(__dirname, "..", "..", "models", "Account.js"),
4
+ );
5
+ const Credit = require(path.join(__dirname, "..", "..", "models", "Credit.js"));
6
+ const sequelize = require(
7
+ path.join(__dirname, "..", "..", "config", "init-db.js"),
8
+ );
9
+ const bcrypt = require("bcrypt");
10
+ // Функция теперь принимает аргументы по порядку, как они передаются из роутера
11
+ async function createCredit(
12
+ username,
13
+ accountId,
14
+ amount,
15
+ pinCode,
16
+ termMonths = 12,
17
+ autoRepay = false,
18
+ ) {
6
19
  const numericAmount = parseFloat(amount);
7
- const numericRate = parseFloat(rate);
8
20
  const numericTerm = parseInt(termMonths, 10);
21
+ const numericRate = 0.1;
9
22
  if (!accountId || isNaN(numericAmount) || numericAmount <= 0) {
10
- return { success: false, error: 'Некорректные параметры для создания кредита' };
23
+ return {
24
+ success: false,
25
+ error: "Некорректные параметры для создания кредита",
26
+ };
11
27
  }
12
- if (isNaN(numericRate) || numericRate < 0 || isNaN(numericTerm) || numericTerm <= 0) {
13
- return { success: false, error: 'Некорректная процентная ставка или срок кредитования' };
28
+ if (isNaN(numericTerm) || numericTerm <= 0) {
29
+ return { success: false, error: "Некорректный срок кредитования" };
14
30
  }
31
+ if (!pinCode) {
32
+ return {
33
+ success: false,
34
+ error: "Укажите PIN-код для подтверждения операции",
35
+ };
36
+ }
37
+
15
38
  const transaction = await sequelize.transaction();
16
39
  try {
17
40
  const account = await Account.findOne({
18
41
  where: { id: accountId },
19
42
  lock: transaction.LOCK.UPDATE,
20
- transaction
43
+ transaction,
21
44
  });
22
- if (!account) throw new Error('Аккаунт не найден');
23
- if (account.status === 'blocked') throw new Error('Аккаунт заблокирован');
45
+
46
+ if (!account) throw new Error("Аккаунт не найден");
47
+
48
+ if (account.user !== username)
49
+ throw new Error("Нет доступа к данному счету");
50
+
51
+ if (account.status === "blocked") throw new Error("Аккаунт заблокирован");
52
+
53
+ const isPinValid = await bcrypt.compare(
54
+ pinCode,
55
+ account.pincode || account.pin_code,
56
+ );
57
+ if (!isPinValid) throw new Error("Неверный PIN-код");
58
+
24
59
  const existingCredit = await Credit.findOne({
25
- where: { accountId: account.id, status: 'active' },
60
+ where: { accountId: account.id, status: "active" },
26
61
  transaction,
27
- lock: transaction.LOCK.UPDATE
62
+ lock: transaction.LOCK.UPDATE,
28
63
  });
29
64
  if (existingCredit) {
30
- throw new Error('У этого аккаунта уже есть активный кредит');
65
+ throw new Error("У этого аккаунта уже есть активный кредит");
31
66
  }
67
+
32
68
  const totalRepay = numericAmount * (1 + numericRate);
33
- const credit = await Credit.create({
34
- accountId: account.id,
35
- amount: numericAmount,
36
- totalRepay: totalRepay,
37
- remainingRepay: totalRepay,
38
- status: 'active',
39
- autoRepay: !!autoRepay,
40
- termMonths: numericTerm
41
- }, { transaction });
42
- await account.increment('balance', { by: numericAmount, transaction });
69
+
70
+ const credit = await Credit.create(
71
+ {
72
+ accountId: account.id,
73
+ amount: numericAmount,
74
+ totalRepay: totalRepay,
75
+ remainingRepay: totalRepay,
76
+ status: "active",
77
+ autoRepay: !!autoRepay,
78
+ termMonths: numericTerm,
79
+ },
80
+ { transaction },
81
+ );
82
+
83
+ await account.increment("manny", { by: numericAmount, transaction });
84
+
43
85
  await transaction.commit();
44
86
  return {
45
87
  success: true,
46
- message: 'Кредит успешно оформлен',
47
- creditId: credit.id
88
+ message: "Кредит успешно оформлен",
89
+ creditId: credit.id,
48
90
  };
49
91
  } catch (error) {
50
92
  if (transaction && !transaction.finished) {
@@ -53,4 +95,5 @@ async function createCredit({ accountId, amount, autoRepay = false, rate = 0.1,
53
95
  return { success: false, error: error.message };
54
96
  }
55
97
  }
56
- module.exports = createCredit;
98
+
99
+ module.exports = createCredit;
@@ -1,14 +1,20 @@
1
- const cron = require('node-cron');
2
- const nodemailer = require('nodemailer');
3
- const { Op } = require('sequelize');
1
+ const cron = require("node-cron");
2
+ const nodemailer = require("nodemailer");
3
+ const { Op } = require("sequelize");
4
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'));
5
+ const sequelize = require(
6
+ path.join(__dirname, "..", "..", "config", "init-db"),
7
+ );
8
+ const Account = require(path.join(__dirname, "..", "..", "models", "Account"));
9
+ const Credit = require(path.join(__dirname, "..", "..", "models", "Credit"));
10
+ const User = require(path.join(__dirname, "..", "..", "models", "User"));
9
11
  const logger = require("custom-color-logs").print;
12
+ if (!Account.associations["User"]) {
13
+ Account.belongsTo(User, { foreignKey: "user", as: "User" });
14
+ }
15
+
10
16
  const transporter = nodemailer.createTransport({
11
- service: 'gmail',
17
+ service: "gmail",
12
18
  auth: {
13
19
  user: process.env.GMAIL,
14
20
  pass: process.env.GOOGLE_APP_PASSWORD,
@@ -16,8 +22,11 @@ const transporter = nodemailer.createTransport({
16
22
  });
17
23
 
18
24
  transporter.verify((err) => {
19
- if (err) console.log(logger.NodemailerError('Ошибка конфигурации почты:', err.message));
20
- else console.log(logger.NodemailerInfo('Сервер готов к отправке писем'));
25
+ if (err)
26
+ console.log(
27
+ logger.NodemailerError("Ошибка конфигурации почты:", err.message),
28
+ );
29
+ else console.log(logger.NodemailerInfo("Сервер готов к отправке писем"));
21
30
  });
22
31
 
23
32
  async function sendEmailNotification(userEmail, subject, htmlContent) {
@@ -29,63 +38,158 @@ async function sendEmailNotification(userEmail, subject, htmlContent) {
29
38
  subject: subject,
30
39
  html: htmlContent,
31
40
  });
32
- console.log(logger.NodemailerFunctionPositivePerformance(`Письмо успешно отправлено на ${userEmail}`));
41
+ console.log(
42
+ logger.ServerCreditNodemailerFunctionPositivePerformance(
43
+ `Письмо успешно отправлено на ${userEmail}`,
44
+ ),
45
+ );
33
46
  } catch (error) {
34
- console.log(logger.NodemailerError(`Не удалось отправить письмо на ${userEmail}:\n ${error.message}`));
47
+ console.log(
48
+ logger.ServerCreditNodemailerError(
49
+ `Не удалось отправить письмо на ${userEmail}:\n ${error.message}`,
50
+ ),
51
+ );
35
52
  }
36
53
  }
37
- cron.schedule('0 0 * * *', async () => {
38
- console.log(logger.NodemailerFunctionInfo(`Проверка активных кредитов и обработка платежей...`));
39
-
54
+ const envMinutes = parseInt(process.env.CRON_CREDIT_INTERVAL_MINUTES, 10);
55
+ let cronExpression = "0 0 * * *";
56
+
57
+ if (!isNaN(envMinutes) && envMinutes > 0) {
58
+ cronExpression = `*/${envMinutes} * * * *`;
59
+ console.log(
60
+ logger.ServerCreditInfo(
61
+ `Установлен кастомный интервал проверки: каждые ${envMinutes} мин. (${cronExpression})`,
62
+ ),
63
+ );
64
+ } else {
65
+ console.log(
66
+ logger.ServerCreditInfo(
67
+ `Переменная CRON_CREDIT_INTERVAL_MINUTES не задана. Переход на режим: раз в сутки (${cronExpression})`,
68
+ ),
69
+ );
70
+ }
71
+ cron.schedule(cronExpression, async () => {
72
+ console.log(
73
+ logger.ServerCreditInfo(`Проверка active кредитов и обработка платежей...`),
74
+ );
75
+
40
76
  try {
41
77
  const activeCredits = await Credit.findAll({
42
78
  where: {
43
- status: 'active',
44
- remainingRepay: { [Op.gt]: 0 }
45
- }
79
+ status: "active",
80
+ remainingRepay: { [Op.gt]: 0 },
81
+ },
46
82
  });
83
+
84
+ if (activeCredits.length === 0) {
85
+ console.log(
86
+ logger.ServerCreditInfo(`Активных кредитов для обработки не найдено.`),
87
+ );
88
+ return;
89
+ }
90
+
47
91
  for (const activeCreditData of activeCredits) {
48
92
  let emailToSend = null;
49
- let emailSubject = '';
50
- let emailHtml = '';
93
+ let emailSubject = "";
94
+ let emailHtml = "";
95
+
96
+ const creditId =
97
+ activeCreditData.id ||
98
+ activeCreditData.get("id") ||
99
+ (activeCreditData.dataValues ? activeCreditData.dataValues.id : null);
100
+ if (!creditId) {
101
+ console.log(
102
+ logger.ServerCreditError(
103
+ `Не удалось определить ID записи кредита. Пропускаем.`,
104
+ ),
105
+ );
106
+ continue;
107
+ }
108
+
51
109
  const transaction = await sequelize.transaction();
110
+
52
111
  try {
53
- const credit = await Credit.findByPk(activeCreditData.id, {
112
+ const credit = await Credit.findByPk(creditId, {
54
113
  transaction,
55
- lock: transaction.LOCK.UPDATE
114
+ lock: transaction.LOCK.UPDATE,
56
115
  });
57
- if (!credit || credit.status !== 'active' || credit.remainingRepay <= 0) {
116
+
117
+ if (
118
+ !credit ||
119
+ credit.status !== "active" ||
120
+ credit.remainingRepay <= 0
121
+ ) {
58
122
  await transaction.rollback();
59
123
  continue;
60
124
  }
125
+
61
126
  const calculatedPayment = credit.totalRepay / credit.termMonths;
62
- const paymentAmount = Math.min(calculatedPayment, credit.remainingRepay);
127
+ const paymentAmount = Math.min(
128
+ calculatedPayment,
129
+ credit.remainingRepay,
130
+ );
131
+
132
+ const targetAccountId =
133
+ credit.accountId ||
134
+ credit.account_id ||
135
+ credit.get("accountId") ||
136
+ credit.get("account_id");
137
+
138
+ if (!targetAccountId) {
139
+ console.log(
140
+ logger.ServerCreditError(
141
+ `У кредита ID #${credit.id} не найден связанный ID аккаунта`,
142
+ ),
143
+ );
144
+ await transaction.rollback();
145
+ continue;
146
+ }
147
+
63
148
  const account = await Account.findOne({
64
- where: { id: credit.accountId },
65
- include: [{ model: User, as: 'User' }],
149
+ where: { id: targetAccountId },
150
+ include: [{ model: User, as: "User" }],
66
151
  transaction,
67
- lock: transaction.LOCK.UPDATE
152
+ lock: transaction.LOCK.UPDATE,
68
153
  });
69
- if (!account || !account.User || !account.User.gmail) {
70
- console.error(`[CRON Warning] Не найден Email или аккаунт для кредита ID #${credit.id}`);
154
+
155
+ if (!account || !account.User || !account.User.user_gmail) {
156
+ console.log(
157
+ logger.ServerCreditError(
158
+ `Не найден Email (user_gmail) или аккаунт для кредита ID #${credit.id}`,
159
+ ),
160
+ );
71
161
  await transaction.rollback();
72
162
  continue;
73
163
  }
74
- const userEmail = account.User.gmail;
164
+
165
+ const userEmail = account.User.user_gmail;
166
+
75
167
  if (credit.autoRepay) {
76
- if (account.status === 'blocked') {
77
- throw new Error('Аккаунт заблокирован');
168
+ if (account.status === "blocked") {
169
+ throw new Error("Аккаунт заблокирован");
78
170
  }
79
- if (account.balance >= paymentAmount) {
80
- await account.decrement('balance', { by: paymentAmount, transaction });
171
+
172
+ if (
173
+ account.balance >= paymentAmount ||
174
+ account.manny >= paymentAmount
175
+ ) {
176
+ const balanceField =
177
+ account.manny !== undefined ? "manny" : "balance";
178
+ await account.decrement(balanceField, {
179
+ by: paymentAmount,
180
+ transaction,
181
+ });
182
+
81
183
  credit.remainingRepay -= paymentAmount;
82
184
  if (credit.remainingRepay <= 0) {
83
- credit.status = 'paid';
185
+ credit.status = "paid";
84
186
  }
187
+
85
188
  await credit.save({ transaction });
86
189
  await transaction.commit();
190
+
87
191
  emailToSend = userEmail;
88
- emailSubject = 'Успешное автосписание по кредиту — Game Bank';
192
+ emailSubject = "Успешное автосписание по кредиту — Game Bank";
89
193
  emailHtml = `<h3>Уважаемый клиент!</h3>
90
194
  <p>С вашего баланса было автоматически списано <b>${paymentAmount.toFixed(2)} USD</b> в счет погашения кредита.</p>
91
195
  <p>Остаток задолженности: <b>${credit.remainingRepay.toFixed(2)} USD</b>.</p>`;
@@ -93,7 +197,8 @@ cron.schedule('0 0 * * *', async () => {
93
197
  await transaction.rollback();
94
198
 
95
199
  emailToSend = userEmail;
96
- emailSubject = 'Ошибка автосписания: недостаточно средств — Game Bank';
200
+ emailSubject =
201
+ "Ошибка автосписания: недостаточно средств — Game Bank";
97
202
  emailHtml = `<h3>Внимание! Недостаточно средств</h3>
98
203
  <p>Мы не смогли списать регулярный платеж по кредиту в размере <b>${paymentAmount.toFixed(2)} USD</b>.</p>
99
204
  <p>Пожалуйста, пополните баланс вашего аккаунта, чтобы избежать просрочки.</p>`;
@@ -101,12 +206,13 @@ cron.schedule('0 0 * * *', async () => {
101
206
  } else {
102
207
  await transaction.rollback();
103
208
  emailToSend = userEmail;
104
- emailSubject = 'Напоминание о платеже по кредиту — Game Bank';
209
+ emailSubject = "Напоминание о платеже по кредиту — Game Bank";
105
210
  emailHtml = `<h3>Здравствуйте!</h3>
106
211
  <p>Напоминаем, что вам необходимо внести обязательный платеж по кредиту в размере <b>${paymentAmount.toFixed(2)} USD</b>.</p>
107
212
  <p>Текущий остаток по кредиту: <b>${credit.remainingRepay.toFixed(2)} USD</b>.</p>
108
213
  <p>Вы можете оплатить его в личном кабинете.</p>`;
109
214
  }
215
+
110
216
  if (emailToSend) {
111
217
  await sendEmailNotification(emailToSend, emailSubject, emailHtml);
112
218
  }
@@ -114,10 +220,18 @@ cron.schedule('0 0 * * *', async () => {
114
220
  if (transaction && !transaction.finished) {
115
221
  await transaction.rollback();
116
222
  }
117
- console.log(logger.NodemailerError(`Ошибка обработки кредита ID ${activeCreditData.id}: ${err.message}`));
223
+ console.log(
224
+ logger.ServerCreditError(
225
+ `Ошибка обработки кредита ID ${activeCreditData.id}: ${err.message}`,
226
+ ),
227
+ );
118
228
  }
119
229
  }
120
230
  } catch (error) {
121
- console.log(logger.NodemailerError(`Ошибка кредитного планировщика: ${error.message}`));
231
+ console.log(
232
+ logger.ServerCreditError(
233
+ `Ошибка кредитного планировщика: ${error.message}`,
234
+ ),
235
+ );
122
236
  }
123
- });
237
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequelize-game-bank",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
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",