From 1ac12251f1e3fef04ce2af1cab8dc608bd86ef10 Mon Sep 17 00:00:00 2001 From: Zwuck Date: Tue, 16 Dec 2025 15:52:20 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=B9:=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=20=D1=81=20JSON=20=D0=BD=D0=B0=20PostgreSQL?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Prisma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bot/bot.service.ts | 4 +- src/scheduler/scheduler.service.ts | 26 +++------ src/users/users.service.ts | 91 +++++++++++------------------- 3 files changed, 41 insertions(+), 80 deletions(-) diff --git a/src/bot/bot.service.ts b/src/bot/bot.service.ts index 8dda9c3..bab2b4f 100644 --- a/src/bot/bot.service.ts +++ b/src/bot/bot.service.ts @@ -13,7 +13,7 @@ export class BotService { async start(@Ctx() ctx: Context) { const user = ctx.from; if (user) { - this.usersService.create({ + await this.usersService.create({ id: user.id, fullName: `${user.first_name} ${user.last_name || ''}`.trim(), }); @@ -41,7 +41,7 @@ export class BotService { if (!user) return; const hours = parseInt(ctx.match[1]); - this.usersService.update(user.id, { frequency: hours }); + await this.usersService.update(user.id, { frequency: hours }); await ctx.answerCbQuery(); await ctx.editMessageText(`Отлично! Теперь я буду присылать цитаты каждые ${hours} ч.`); diff --git a/src/scheduler/scheduler.service.ts b/src/scheduler/scheduler.service.ts index 32b8a2f..517be05 100644 --- a/src/scheduler/scheduler.service.ts +++ b/src/scheduler/scheduler.service.ts @@ -2,8 +2,9 @@ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { InjectBot } from 'nestjs-telegraf'; import { Context, Telegraf } from 'telegraf'; -import { UsersService, User } from '../users/users.service'; +import { UsersService } from '../users/users.service'; import { QuotesService } from '../quotes/quotes.service'; +import { User } from '@prisma/client'; @Injectable() export class SchedulerService { @@ -19,7 +20,7 @@ export class SchedulerService { @Cron('0 * * * *') async handleCron() { this.logger.debug('Hourly cron triggered. Checking users...'); - const users = this.usersService.findAll(); + const users = await this.usersService.findAll(); const now = new Date(); for (const user of users) { @@ -33,24 +34,11 @@ export class SchedulerService { const diffMs = now.getTime() - lastSent.getTime(); const diffHours = diffMs / (1000 * 60 * 60); - // Allow a small buffer for timing execution, e.g. if diff is 0.99 hours but frequency is 1? - // Cron is exact hour. If last sent was 10:00:05 and now is 11:00:00. diff is slightly less than 1. - // But we update `lastQuoteSentAt` to `now` (execution time). - // If we update to `now`, then next time it will be exact hour difference. - // Let's use strict >= logic but maybe `frequency - 0.1`? - // Actually, if we update `lastQuoteSentAt` to `now.toISOString()` which is the cron time approx. - // It should be fine. If we missed by a second, it waits another hour. - // User won't mind waiting 1 more hour initially or we can be generous. - // Let's stick to `>= user.frequency`. - // Initial user `lastQuoteSentAt` is registration time. - // If reg at 10:30. Cron at 11:00. Diff 0.5. No quote. - // Cron at 12:00. Diff 1.5. Quote! (if freq 1). - // Seems correct usage. - if (diffHours >= user.frequency) { await this.sendQuote(user); // Updating user lastQuoteSentAt to now - this.usersService.update(user.id, { lastQuoteSentAt: now.toISOString() }); + // Update expects telegramId. user.telegramId is BigInt. + await this.usersService.update(Number(user.telegramId), { lastQuoteSentAt: now }); } } @@ -62,9 +50,9 @@ export class SchedulerService { message += `\n\n- ${quote.author}`; } - await this.bot.telegram.sendMessage(user.id, message); + await this.bot.telegram.sendMessage(user.telegramId.toString(), message); } catch (error) { - this.logger.error(`Failed to send quote to user ${user.id}: ${error.message}`); + this.logger.error(`Failed to send quote to user ${user.telegramId}: ${error.message}`); } } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index aa395af..5d17912 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,73 +1,46 @@ import { Injectable } from '@nestjs/common'; -import * as fs from 'fs'; -import * as path from 'path'; - -export interface User { - id: number; - fullName: string; - createdAt: string; - frequency: number; // in hours, default 1 - lastQuoteSentAt: string; // ISO string -} +import { PrismaService } from '../prisma/prisma.service'; +import { User } from '@prisma/client'; @Injectable() export class UsersService { - private readonly filePath = path.resolve(__dirname, '..', 'data', 'users.json'); + constructor(private readonly prisma: PrismaService) { } - private readUsers(): User[] { - if (!fs.existsSync(this.filePath)) { - return []; - } - const data = fs.readFileSync(this.filePath, 'utf8'); - try { - return JSON.parse(data); - } catch (e) { - return []; - } + async findAll(): Promise { + return this.prisma.user.findMany(); } - private writeUsers(users: User[]): void { - // Ensure directory exists - const dir = path.dirname(this.filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - fs.writeFileSync(this.filePath, JSON.stringify(users, null, 2), 'utf8'); + async findOne(telegramId: number): Promise { + return this.prisma.user.findUnique({ + where: { telegramId: BigInt(telegramId) }, + }); } - findAll(): User[] { - return this.readUsers(); + async create(user: { id: number; fullName: string; username?: string }): Promise { + return this.prisma.user.upsert({ + where: { telegramId: BigInt(user.id) }, + update: { + fio: user.fullName, + username: user.username, + }, + create: { + telegramId: BigInt(user.id), + fio: user.fullName, + username: user.username, + frequency: 1, + // createdAt and lastQuoteSentAt have defaults + }, + }); } - findOne(id: number): User | undefined { - return this.readUsers().find((u) => u.id === id); - } + async update(telegramId: number, updateData: Partial & { frequency?: number }): Promise { + // We need to be careful with BigInt serialization if we blindly spread updateData + // For now, we only expect frequency updates from the bot. + const data: any = { ...updateData }; - create(user: Partial & { id: number; fullName: string }): void { - const users = this.readUsers(); - const existingUser = users.find((u) => u.id === user.id); - const newUser: User = { - ...user, - createdAt: user.createdAt || new Date().toISOString(), - frequency: user.frequency || 1, - lastQuoteSentAt: user.lastQuoteSentAt || new Date().toISOString(), - }; - - if (!existingUser) { - users.push(newUser); - this.writeUsers(users); - } else { - // Option: update existing user if re-registering? - // For now, assume id persistence. - } - } - - update(id: number, updateData: Partial): void { - const users = this.readUsers(); - const index = users.findIndex(u => u.id === id); - if (index !== -1) { - users[index] = { ...users[index], ...updateData }; - this.writeUsers(users); - } + return this.prisma.user.update({ + where: { telegramId: BigInt(telegramId) }, + data: data, + }); } }