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.
package/server/server.js CHANGED
@@ -1,234 +1,450 @@
1
- const path = require('path');
2
- const jwt = require('jsonwebtoken');
3
- const bcrypt = require('bcrypt');
4
- const User = require(path.join(__dirname, '..', 'database', 'models', 'User.js'));
5
- const Account = require(path.join(__dirname, '..', 'database', 'models', 'Account.js'));
6
- const multer = require('multer');
7
- const fs = require('fs');
1
+ const path = require("path");
2
+ const jwt = require("jsonwebtoken");
3
+ const bcrypt = require("bcrypt");
4
+ const User = require(
5
+ path.join(__dirname, "..", "database", "models", "User.js"),
6
+ );
7
+ const Account = require(
8
+ path.join(__dirname, "..", "database", "models", "Account.js"),
9
+ );
10
+ const Credit = require(
11
+ path.join(__dirname, "..", "database", "models", "Credit.js"),
12
+ );
13
+ const sequelize = require(
14
+ path.join(__dirname, "..", "database", "config", "init-db.js"),
15
+ );
16
+ const multer = require("multer");
17
+ const fs = require("fs");
8
18
  const express = require("express");
9
19
  const logger = require("custom-color-logs").print;
10
20
  const storage = multer.diskStorage({
11
21
  destination: function (req, file, cb) {
12
- const dir = path.join(__dirname, '..', 'public', 'uploads', 'avatars');
22
+ const dir = path.join(__dirname, "..", "public", "uploads", "avatars");
13
23
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
14
24
  cb(null, dir);
15
25
  },
16
26
  filename: function (req, file, cb) {
17
27
  const ext = path.extname(file.originalname);
18
28
  cb(null, `${req.user.name}_${Date.now()}${ext}`);
19
- }
29
+ },
20
30
  });
21
31
  const upload = multer({
22
32
  storage: storage,
23
33
  fileFilter: function (req, file, cb) {
24
34
  const filetypes = /jpeg|jpg|png|gif/;
25
- const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
35
+ const extname = filetypes.test(
36
+ path.extname(file.originalname).toLowerCase(),
37
+ );
26
38
  if (extname) return cb(null, true);
27
- cb(new Error('Разрешены только изображения (png, jpg, jpeg, gif)!'));
39
+ cb(new Error("Разрешены только изображения (png, jpg, jpeg, gif)!"));
28
40
  },
29
- limits: { fileSize: 2 * 1024 * 1024 }
41
+ limits: { fileSize: 2 * 1024 * 1024 },
30
42
  });
31
- const usersHub = require(path.join(__dirname, '..', 'database', 'services', 'users.js'));
32
- const accountsHub = require(path.join(__dirname, '..', 'database', 'services', 'accounts.js'));
43
+ const usersHub = require(
44
+ path.join(__dirname, "..", "database", "services", "users.js"),
45
+ );
46
+ const accountsHub = require(
47
+ path.join(__dirname, "..", "database", "services", "accounts.js"),
48
+ );
33
49
 
34
50
  function verifySession(req, res, next) {
35
51
  const token = req.cookies.token;
36
52
  if (!token) {
37
- return res.status(401).json({ status: false, statusCode: -2, message: 'Доступ запрещен. Вы не авторизованы.' });
53
+ return res.status(401).json({
54
+ status: false,
55
+ statusCode: -2,
56
+ message: "Доступ запрещен. Вы не авторизованы.",
57
+ });
38
58
  }
39
59
  try {
40
60
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
41
61
  req.user = decoded;
42
62
  next();
43
63
  } catch (err) {
44
- return res.status(403).json({ status: false, statusCode: -2, message: 'Сессия устарела. Войдите снова.' });
64
+ return res.status(403).json({
65
+ status: false,
66
+ statusCode: -2,
67
+ message: "Сессия устарела. Войдите снова.",
68
+ });
45
69
  }
46
70
  }
47
71
  module.exports = function registerRoutes() {
48
72
  const get = express.Router();
49
73
  const post = express.Router();
50
- post.post('/users/generate-temporary-2fa', async (req, res) => {
74
+ post.post("/users/generate-temporary-2fa", async (req, res) => {
51
75
  try {
52
- const otplib = require('otplib');
76
+ const otplib = require("otplib");
53
77
  const { name } = req.body;
54
78
  const temporarySecret = otplib.authenticator.generateSecret();
55
- const otpauthUrl = otplib.authenticator.keyuri(name || 'User', 'City-Bank', temporarySecret);
56
- return res.json({ status: true, secret: temporarySecret, otpauthUrl: otpauthUrl });
79
+ const otpauthUrl = otplib.authenticator.keyuri(
80
+ name || "User",
81
+ "City-Bank",
82
+ temporarySecret,
83
+ );
84
+ return res.json({
85
+ status: true,
86
+ secret: temporarySecret,
87
+ otpauthUrl: otpauthUrl,
88
+ });
57
89
  } catch (err) {
58
90
  return res.status(500).json({ status: false, message: err.message });
59
91
  }
60
- })
61
- post.post('/users/verify-temporary-2fa', async (req, res) => {
92
+ });
93
+ post.post("/users/verify-temporary-2fa", async (req, res) => {
62
94
  try {
63
- const otplib = require('otplib');
95
+ const otplib = require("otplib");
64
96
  const { secret, code } = req.body;
65
97
  if (!secret || !code) {
66
- return res.json({ status: false, message: 'Введите 6-значный код подтверждения' });
98
+ return res.json({
99
+ status: false,
100
+ message: "Введите 6-значный код подтверждения",
101
+ });
67
102
  }
68
103
  const isValid = otplib.authenticator.check(code, secret);
69
104
  if (isValid) {
70
- return res.json({ status: true, message: 'Код успешно подтвержден' });
105
+ return res.json({ status: true, message: "Код успешно подтвержден" });
71
106
  } else {
72
- return res.json({ status: false, message: 'Неверный одноразовый код токена! Проверьте время на устройстве.' });
107
+ return res.json({
108
+ status: false,
109
+ message:
110
+ "Неверный одноразовый код токена! Проверьте время на устройстве.",
111
+ });
73
112
  }
74
113
  } catch (err) {
75
114
  return res.status(500).json({ status: false, message: err.message });
76
115
  }
77
116
  });
78
- post.post('/users/avatar', verifySession, upload.single('avatar'), async (req, res) => {
79
- if (!req.file) {
80
- return res.status(400).json({ status: false, statusCode: -2, message: 'Файл не загружен' });
81
- }
82
- const avatarUrl = `/api/bank/uploads/avatars/${req.file.filename}`;
83
- const result = await usersHub.changeAvatar(req.user.name, avatarUrl);
84
- return res.json(result);
85
- });
86
- post.get('/users/profile', verifySession, async (req, res) => {
87
- const user = await User.findByPk(req.user.name, { attributes: ['name', 'user_gmail', 'avatar'] });
117
+ post.post(
118
+ "/users/avatar",
119
+ verifySession,
120
+ upload.single("avatar"),
121
+ async (req, res) => {
122
+ if (!req.file) {
123
+ return res
124
+ .status(400)
125
+ .json({ status: false, statusCode: -2, message: "Файл не загружен" });
126
+ }
127
+ const avatarUrl = `/api/bank/uploads/avatars/${req.file.filename}`;
128
+ const result = await usersHub.changeAvatar(req.user.name, avatarUrl);
129
+ return res.json(result);
130
+ },
131
+ );
132
+ post.get("/users/profile", verifySession, async (req, res) => {
133
+ const user = await User.findByPk(req.user.name, {
134
+ attributes: ["name", "user_gmail", "avatar"],
135
+ });
88
136
  if (!user) return res.status(404).json({ status: false });
89
137
  return res.json({ status: true, user });
90
138
  });
91
- post.get('/users/check-nik', async (req, res) => {
139
+ post.get("/users/check-nik", async (req, res) => {
92
140
  const user = await User.findByPk(req.query.name);
93
141
  if (user) {
94
- return res.json({ status: false, statusCode: -20, message: 'Никнейм уже занят!' });
142
+ return res.json({
143
+ status: false,
144
+ statusCode: -20,
145
+ message: "Никнейм уже занят!",
146
+ });
95
147
  }
96
148
  return res.json({ status: true, statusCode: 1 });
97
149
  });
98
- post.get('/users/check-gmail', async (req, res) => {
150
+ post.get("/users/check-gmail", async (req, res) => {
99
151
  const user = await User.findOne({ where: { user_gmail: req.query.gmail } });
100
152
  if (user) {
101
- return res.json({ status: false, statusCode: -21, message: 'Данный email уже используется!' });
153
+ return res.json({
154
+ status: false,
155
+ statusCode: -21,
156
+ message: "Данный email уже используется!",
157
+ });
102
158
  }
103
159
  return res.json({ status: true, statusCode: 1 });
104
160
  });
105
- post.post('/users/register', async (req, res) => {
161
+ post.post("/users/register", async (req, res) => {
106
162
  const result = await usersHub.registerUser(req.body);
107
163
  if (!result.status) return res.status(400).json(result);
108
164
  return res.status(201).json(result);
109
165
  });
110
- post.post('/users/login', async (req, res) => {
166
+ post.post("/users/login", async (req, res) => {
111
167
  const { name, password, totpCode } = req.body;
112
168
  const result = await usersHub.login(name, password, totpCode);
113
169
  if (!result.status) {
114
170
  if (result.require2FA) {
115
- return res.status(200).json(result);
171
+ return res.status(200).json(result);
116
172
  }
117
173
  return res.status(401).json(result);
118
174
  }
119
- const sessionToken = jwt.sign({ name: name }, process.env.JWT_SECRET, { expiresIn: '2h' });
120
- res.cookie('token', sessionToken, { httpOnly: true, maxAge: 2 * 60 * 60 * 1000 });
175
+ const sessionToken = jwt.sign({ name: name }, process.env.JWT_SECRET, {
176
+ expiresIn: "2h",
177
+ });
178
+ res.cookie("token", sessionToken, {
179
+ httpOnly: true,
180
+ maxAge: 2 * 60 * 60 * 1000,
181
+ });
121
182
  return res.json(result);
122
183
  });
123
- post.post('/users/logout', (req, res) => {
124
- res.clearCookie('token');
184
+ post.post("/users/logout", (req, res) => {
185
+ res.clearCookie("token");
125
186
  return res.json({ status: true, statusCode: 1 });
126
187
  });
127
- post.get('/users/my-total-manny', verifySession, async (req, res) => {
188
+ post.get("/users/my-total-manny", verifySession, async (req, res) => {
128
189
  const result = await usersHub.mathManny(req.user.name);
129
- return res.json({ status: result.status, statusCode: result.statusCode, totalManny: result.totalManny, username: req.user.name });
190
+ return res.json({
191
+ status: result.status,
192
+ statusCode: result.statusCode,
193
+ totalManny: result.totalManny,
194
+ username: req.user.name,
195
+ });
130
196
  });
131
- post.post('/accounts', verifySession, async (req, res) => {
197
+ post.post("/accounts", verifySession, async (req, res) => {
132
198
  const { pinCode, enableAccount2FA } = req.body;
133
- const result = await accountsHub.create(req.user.name, pinCode, enableAccount2FA);
199
+ const result = await accountsHub.create(
200
+ req.user.name,
201
+ pinCode,
202
+ enableAccount2FA,
203
+ );
134
204
  return res.json(result);
135
205
  });
136
- post.get('/accounts/my', verifySession, async (req, res) => {
206
+ post.get("/accounts/my", verifySession, async (req, res) => {
137
207
  try {
138
- const myAccounts = await Account.findAll({ where: { user: req.user.name } });
208
+ const myAccounts = await Account.findAll({
209
+ where: { user: req.user.name },
210
+ });
139
211
  return res.json({ status: true, statusCode: 1, accounts: myAccounts });
140
212
  } catch (e) {
141
213
  return res.status(500).json({ status: false, statusCode: -10 });
142
214
  }
143
215
  });
144
- post.delete('/accounts/:id', verifySession, async (req, res) => {
216
+ post.delete("/accounts/:id", verifySession, async (req, res) => {
145
217
  try {
146
218
  const result = await accountsHub.delete(req.params.id, req.user.name);
147
219
  if (!result.status) {
148
220
  return res.status(400).json({
149
221
  status: false,
150
222
  statusCode: result.statusCode || -11,
151
- message: result.message || "Не удалось удалить счет"
223
+ message: result.message || "Не удалось удалить счет",
152
224
  });
153
225
  }
154
- return res.json({ status: true, statusCode: 1, message: "Счет успешно удален" });
226
+ return res.json({
227
+ status: true,
228
+ statusCode: 1,
229
+ message: "Счет успешно удален",
230
+ });
155
231
  } catch (err) {
156
- console.log(logger.ServerError("Ошибка роута удаления счета:", err.message))
157
- return res.status(500).json({ status: false, statusCode: -10, message: "Внутренняя ошибка сервера" });
232
+ console.log(
233
+ logger.ServerError("Ошибка роута удаления счета:", err.message),
234
+ );
235
+ return res.status(500).json({
236
+ status: false,
237
+ statusCode: -10,
238
+ message: "Внутренняя ошибка сервера",
239
+ });
158
240
  }
159
241
  });
160
- post.post('/accounts/transfer', verifySession, async (req, res) => {
242
+ post.post("/accounts/transfer", verifySession, async (req, res) => {
161
243
  const { fromId, toId, amount, pinCode, totpCode } = req.body;
162
- const result = await accountsHub.transfer(fromId, toId, amount, pinCode, totpCode);
244
+ const result = await accountsHub.transfer(
245
+ fromId,
246
+ toId,
247
+ amount,
248
+ pinCode,
249
+ totpCode,
250
+ );
163
251
  return res.json(result);
164
252
  });
165
- post.post('/accounts/credit', verifySession, async (req, res) => {
166
- const { targetAccountId, amount, paymentsCount, autoWithdrawal, pinCode } = req.body;
167
- const result = await accountsHub.creatCredit(req.user.name, targetAccountId, amount, pinCode, paymentsCount, autoWithdrawal);
253
+ post.post("/accounts/credit", verifySession, async (req, res) => {
254
+ const { targetAccountId, amount, paymentsCount, autoWithdrawal, pinCode } =
255
+ req.body;
256
+ const result = await accountsHub.creatCredit(
257
+ req.user.name,
258
+ targetAccountId,
259
+ amount,
260
+ pinCode,
261
+ paymentsCount,
262
+ autoWithdrawal,
263
+ );
168
264
  return res.json(result);
169
265
  });
170
- post.post('/admin/accounts/:id/status', verifySession, async (req, res) => {
171
- if (req.user.name !== 'root') return res.status(403).json({ status: false, statusCode: -2, message: 'Отказано в доступе' });
172
- const result = await accountsHub.changeAccountStatus(req.params.id, req.body.active);
266
+ post.post("/admin/accounts/:id/status", verifySession, async (req, res) => {
267
+ if (req.user.name !== "root")
268
+ return res
269
+ .status(403)
270
+ .json({ status: false, statusCode: -2, message: "Отказано в доступе" });
271
+ const result = await accountsHub.changeAccountStatus(
272
+ req.params.id,
273
+ req.body.active,
274
+ );
173
275
  return res.json(result);
174
276
  });
175
- post.post('/admin/accounts/:id/modify', verifySession, async (req, res) => {
176
- if (req.user.name !== 'root') return res.status(403).json({ status: false, statusCode: -2, message: 'Отказано в доступе' });
277
+ post.post("/admin/accounts/:id/modify", verifySession, async (req, res) => {
278
+ if (req.user.name !== "root")
279
+ return res
280
+ .status(403)
281
+ .json({ status: false, statusCode: -2, message: "Отказано в доступе" });
177
282
  const { amount, type } = req.body;
178
283
  let result;
179
- if (type === 'add') {
284
+ if (type === "add") {
180
285
  result = await accountsHub.addManny(req.params.id, amount);
181
286
  } else {
182
287
  result = await accountsHub.removeManny(req.params.id, amount);
183
288
  }
184
289
  return res.json(result);
185
290
  });
186
- post.get('/admin/accounts/all', verifySession, async (req, res) => {
187
- if (req.user.name !== 'root') return res.status(403).json({ status: false, statusCode: -2, message: 'Отказано в доступе' });
291
+ post.get("/admin/accounts/all", verifySession, async (req, res) => {
292
+ if (req.user.name !== "root")
293
+ return res
294
+ .status(403)
295
+ .json({ status: false, statusCode: -2, message: "Отказано в доступе" });
188
296
  try {
189
- const allAccounts = await Account.findAll({ order: [['id', 'ASC']] });
297
+ const allAccounts = await Account.findAll({ order: [["id", "ASC"]] });
190
298
  return res.json({ status: true, statusCode: 1, accounts: allAccounts });
191
299
  } catch (e) {
192
300
  return res.status(500).json({ status: false, statusCode: -10 });
193
301
  }
194
302
  });
195
- post.get('/users/profile/settings', verifySession, async (req, res) => {
303
+ post.get("/users/profile/settings", verifySession, async (req, res) => {
196
304
  try {
197
- const user = await User.findByPk(req.user.name, { attributes: ['name', 'user_gmail', 'two_fa_status', 'avatar'] });
198
- if (!user) return res.status(404).json({ status: false, message: 'Юзер не найден' });
305
+ const user = await User.findByPk(req.user.name, {
306
+ attributes: ["name", "user_gmail", "two_fa_status", "avatar"],
307
+ });
308
+ if (!user)
309
+ return res
310
+ .status(404)
311
+ .json({ status: false, message: "Юзер не найден" });
199
312
  return res.json({ status: true, user });
200
- } catch(e) {
201
- return res.status(500).json({ status: false, message: 'Внутренняя ошибка бэка' });
313
+ } catch (e) {
314
+ return res
315
+ .status(500)
316
+ .json({ status: false, message: "Внутренняя ошибка бэка" });
202
317
  }
203
318
  });
204
- post.post('/users/enable-2fa', verifySession, async (req, res) => {
319
+ post.post("/users/enable-2fa", verifySession, async (req, res) => {
205
320
  const result = await usersHub.enable2FA(req.user.name);
206
321
  return res.json(result);
207
322
  });
208
- post.post('/users/disable-2fa', verifySession, async (req, res) => {
323
+ post.post("/users/disable-2fa", verifySession, async (req, res) => {
209
324
  const result = await usersHub.disable2FA(req.user.name);
210
325
  return res.json(result);
211
326
  });
212
- post.get('/accounts/my-logs', verifySession, async (req, res) => {
327
+ post.get("/accounts/my-logs", verifySession, async (req, res) => {
213
328
  const result = await accountsHub.getLogs(req.user.name);
214
329
  return res.json(result);
215
330
  });
216
- post.post('/terminal/register', async(req, res)=>{
217
- if(!req.body) return res.sendStatus(400)
331
+ post.post("/terminal/register", async (req, res) => {
332
+ if (!req.body) return res.sendStatus(400);
218
333
  const id = req.body.id;
219
334
  const pincode = req.body.pincode;
220
335
  const card = req.body.card;
221
- const result = await accountsHub.linkACard(card, id, pincode)
222
- if(!result.status) return res.sendStatus(422)
336
+ const result = await accountsHub.linkACard(card, id, pincode);
337
+ if (!result.status) return res.sendStatus(422);
223
338
  return res.sendStatus(200);
224
339
  });
225
- post.post('/terminal/transfer', async(req, res)=>{
340
+ post.post("/terminal/transfer", async (req, res) => {
226
341
  const card = req.body.card;
227
342
  const to = req.body.to;
228
343
  const pincode = req.body.pincode;
229
344
  const manny = req.body.manny;
230
- const result = await accountsHub.cardTransfer(card, to, manny, pincode)
345
+ const result = await accountsHub.cardTransfer(card, to, manny, pincode);
346
+ });
347
+ post.post("/accounts/credit/pay", verifySession, async (req, res) => {
348
+ const { creditId, amount, pinCode } = req.body;
349
+ const numericAmount = parseFloat(amount);
350
+
351
+ if (!creditId || isNaN(numericAmount) || numericAmount <= 0) {
352
+ return res
353
+ .status(400)
354
+ .json({ status: false, message: "Некорректная сумма или ID кредита" });
355
+ }
356
+
357
+ const transaction = await sequelize.transaction();
358
+ try {
359
+ const credit = await Credit.findByPk(creditId, {
360
+ transaction,
361
+ lock: transaction.LOCK.UPDATE,
362
+ });
363
+ if (!credit || credit.status !== "active")
364
+ throw new Error("Активный кредит не найден");
365
+
366
+ const targetAccountId = credit.accountId || credit.account_id;
367
+ const account = await Account.findByPk(targetAccountId, {
368
+ transaction,
369
+ lock: transaction.LOCK.UPDATE,
370
+ });
371
+ if (!account) throw new Error("Счет не найден");
372
+ if (account.user !== req.user.name)
373
+ throw new Error("Нет доступа к этому счету");
374
+ if (account.status === "blocked") throw new Error("Счет заблокирован");
375
+
376
+ const bcrypt = require("bcrypt");
377
+ const isPinValid = await bcrypt.compare(
378
+ pinCode,
379
+ account.pincode || account.pin_code,
380
+ );
381
+ if (!isPinValid) throw new Error("Неверный PIN-код счета");
382
+
383
+ const balanceField = account.manny !== undefined ? "manny" : "balance";
384
+ const currentBalance = parseFloat(account[balanceField]);
385
+
386
+ if (currentBalance < numericAmount)
387
+ throw new Error("Недостаточно средств на счете");
388
+
389
+ const finalPayment = Math.min(
390
+ numericAmount,
391
+ parseFloat(credit.remainingRepay),
392
+ );
393
+
394
+ await account.decrement(balanceField, { by: finalPayment, transaction });
395
+
396
+ credit.remainingRepay = parseFloat(credit.remainingRepay) - finalPayment;
397
+ if (credit.remainingRepay <= 0) {
398
+ credit.status = "paid";
399
+ }
400
+ await credit.save({ transaction });
401
+
402
+ if (typeof accountsHub.getLogs === "function") {
403
+ }
404
+
405
+ await transaction.commit();
406
+ return res.json({
407
+ status: true,
408
+ message:
409
+ credit.status === "paid"
410
+ ? "Кредит полностью погашен!"
411
+ : `Успешно оплачено ${finalPayment.toFixed(2)} руб.`,
412
+ remainingRepay: credit.remainingRepay,
413
+ });
414
+ } catch (error) {
415
+ if (transaction && !transaction.finished) await transaction.rollback();
416
+ return res.status(400).json({ status: false, message: error.message });
417
+ }
231
418
  });
232
- return {GET:get, POST:post};
419
+ post.get("/accounts/my-current-credit", verifySession, async (req, res) => {
420
+ try {
421
+ const myAccounts = await Account.findAll({
422
+ where: { user: req.user.name },
423
+ });
424
+
425
+ if (!myAccounts || myAccounts.length === 0) {
426
+ return res.json({ status: true, activeCredit: null });
427
+ }
428
+
429
+ const accountIds = myAccounts.map((acc) => acc.id);
430
+
431
+ const { Op } = require("sequelize");
432
+ const activeCredit = await Credit.findOne({
433
+ where: {
434
+ status: "active",
435
+ accountId: accountIds,
436
+ },
437
+ });
438
+
439
+ return res.json({ status: true, activeCredit: activeCredit || null });
440
+ } catch (error) {
441
+ console.error("Ошибка при получении текущего кредита:", error.message);
442
+ return res
443
+ .status(500)
444
+ .json({ status: false, message: "Внутренняя ошибка сервера" });
445
+ }
446
+ });
447
+
448
+ return { GET: get, POST: post };
233
449
  };
234
- module.exports.verifySession = verifySession;
450
+ module.exports.verifySession = verifySession;