Mohammed Foud
all
7891679
import {
fetchDataFromTable,
insertDataIntoTable,
updateDataInTable
} from '../../db/supabaseHelper';
import { createLogger } from '../../utils/logger';
import { v4 as uuidv4 } from 'uuid';
import { supabase } from '../../db/supabase';
import { BotContext } from '../types/botTypes';
import { getBotIdFromContext } from '../../utils/botUtils';
const logger = createLogger('AuthService');
interface UserBotTelegraf {
id?: number; // SERIAL in SQL becomes number in TypeScript
telegramId: number; // BIGINT in SQL becomes number in TypeScript
username?: string | null; // VARCHAR(255), optional
firstName?: string | null; // VARCHAR(255), matches SQL column name
lastName?: string | null; // VARCHAR(255), matches SQL column name
email: string; // VARCHAR(255) NOT NULL
passwordHash: string; // VARCHAR(255) NOT NULL, matches SQL column name
language?: string; // VARCHAR(10) DEFAULT 'en'
role?: string; // VARCHAR(10) DEFAULT 'user'
balance?: number; // DECIMAL(10, 2) DEFAULT 0
isBanned?: boolean; // BOOLEAN DEFAULT false
lastLogin?: Date | string | null; // TIMESTAMPTZ (can be Date, ISO string, or null)
botId?: string | null; // UUID (string in TypeScript)
createdAt?: Date | string; // TIMESTAMPTZ DEFAULT NOW()
updatedAt?: Date | string; // TIMESTAMPTZ DEFAULT NOW()
}
export class AuthService {
private static instance: AuthService;
private constructor() {
// Private constructor for singleton pattern
}
public static getInstance(): AuthService {
if (!AuthService.instance) {
AuthService.instance = new AuthService();
}
return AuthService.instance;
}
private generatePassword(): string {
return uuidv4().replace(/-/g, '').substring(0, 12);
}
public async hashPassword(password: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
private generateEmail(telegramId: number, ctx: BotContext): string {
const botData = ctx.botData;
const suffix = botData?.suffix_email || 'saerosms.com';
return `user_${telegramId}@${suffix}`;
}
public async createUser(telegramId: number, name: string, ctx: BotContext): Promise<{ user: UserBotTelegraf; password: string }> {
try {
const botId = getBotIdFromContext(ctx);
if (!botId) {
throw new Error('Could not determine bot ID from context');
}
const email = this.generateEmail(telegramId, ctx);
const password = this.generatePassword();
const passwordHash = await this.hashPassword(password);
const userData: Omit<UserBotTelegraf, 'id'> = {
telegramId: telegramId,
firstName: name,
email,
passwordHash: passwordHash,
botId: botId,
balance: 0,
language: 'ar',
role: 'user',
isBanned: false
};
const user = await insertDataIntoTable('users_bot_telegram', userData);
logger.info(`User ${telegramId} created successfully for bot ${botId}`);
return { user, password };
} catch (error: any) {
logger.error(`Error creating user ${telegramId}: ${error.message}`);
throw new Error(`Failed to create user: ${error.message}`);
}
}
private loggedInUsers: Map<string, boolean> = new Map();
private generateUserKey(telegramId: number, ctx: BotContext): string {
const botId = getBotIdFromContext(ctx);
if (!botId) {
throw new Error('Could not determine bot ID from context');
}
return `${telegramId}_${botId}`;
}
public setUserLoggedIn(telegramId: number, ctx: BotContext, isLoggedIn: boolean = true): void {
const key = this.generateUserKey(telegramId, ctx);
const botId = getBotIdFromContext(ctx);
this.loggedInUsers.set(key, isLoggedIn);
logger.info(`User ${telegramId} for bot ${botId} login state set to ${isLoggedIn}`);
}
public isUserLoggedIn(telegramId: number, ctx: BotContext): boolean {
const key = this.generateUserKey(telegramId, ctx);
return this.loggedInUsers.get(key) || false;
}
public async loginUser(telegramId: number, ctx: BotContext): Promise<UserBotTelegraf | null> {
try {
const botId = getBotIdFromContext(ctx);
if (!botId) {
throw new Error('Could not determine bot ID from context');
}
const { data: user, error } = await supabase
.from('users_bot_telegram')
.select('*')
.eq('telegram_id', telegramId)
.eq('bot_id', botId)
.single();
if (error) {
if (error.code === 'PGRST116') {
return null; // User not found
}
throw error;
}
if (user) {
this.setUserLoggedIn(telegramId, ctx, true);
return user;
}
return null;
} catch (error) {
logger.error('Error in loginUser:', error);
return null;
}
}
public async getUserById(userId: string): Promise<UserBotTelegraf | null> {
try {
const { data: user, error } = await supabase
.from('users_bot_telegram')
.select('*')
.eq('id', userId)
.single();
if (error) {
if (error.code === 'PGRST116') {
return null;
}
throw error;
}
return user;
} catch (error: any) {
logger.error(`Error fetching user ${userId}: ${error.message}`);
throw new Error(`Failed to get user: ${error.message}`);
}
}
async getUserByTelegramId(telegramId: number, ctx: BotContext): Promise<UserBotTelegraf | null> {
try {
const botId = getBotIdFromContext(ctx);
if (!botId) {
throw new Error('Could not determine bot ID from context');
}
const { data: user, error } = await supabase
.from('users_bot_telegram')
.select('*')
.eq('telegram_id', telegramId)
.eq('bot_id', botId)
.single();
if (error) {
if (error.code === 'PGRST116') {
return null;
}
throw error;
}
return user;
} catch (error: any) {
logger.error(`Error getting user by Telegram ID: ${error.message}`);
return null;
}
}
async updateUserBalance(telegramId: number, ctx: BotContext, newBalance: number): Promise<boolean> {
try {
const botId = getBotIdFromContext(ctx);
if (!botId) {
throw new Error('Could not determine bot ID from context');
}
const { error } = await supabase
.from('users_bot_telegram')
.update({ balance: newBalance })
.eq('telegram_id', telegramId)
.eq('bot_id', botId);
if (error) throw error;
return true;
} catch (error: any) {
logger.error(`Error updating user balance: ${error.message}`);
return false;
}
}
}
// Singleton instance
export const authService = AuthService.getInstance();