Рефакторинг хранения пользователей: переход с JSON на PostgreSQL через Prisma
This commit is contained in:
@@ -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} ч.`);
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<User[]> {
|
||||
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<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { telegramId: BigInt(telegramId) },
|
||||
});
|
||||
}
|
||||
|
||||
findAll(): User[] {
|
||||
return this.readUsers();
|
||||
async create(user: { id: number; fullName: string; username?: string }): Promise<User> {
|
||||
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<User> & { frequency?: number }): Promise<User> {
|
||||
// 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<User> & { 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<User>): 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user