Spaces:
Paused
Paused
| 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(); |