import { Injectable } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; import { UserReferralCode } from '../../domain/entities/user-referral-code.entity'; /** Generates a random user referral code: USR-XXXX-XXXX */ function generateUserCode(): string { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; const seg = (n: number) => Array.from({ length: n }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); return `USR-${seg(4)}-${seg(4)}`; } @Injectable() export class UserReferralCodeRepository { constructor(@InjectDataSource() private readonly ds: DataSource) {} /** Create a referral code for a user (idempotent — skips if already exists). */ async createForUser(userId: string): Promise { // Try up to 5 times to find a unique code for (let attempt = 0; attempt < 5; attempt++) { const code = generateUserCode(); try { const rows = await this.ds.query( `INSERT INTO public.user_referral_codes (user_id, code) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE SET user_id = EXCLUDED.user_id RETURNING *`, [userId, code], ); return this.map(rows[0]); } catch { // code collision — retry } } // Fallback: just return whatever exists return this.findByUserId(userId) as Promise; } async findByCode(code: string): Promise { const rows = await this.ds.query( `SELECT * FROM public.user_referral_codes WHERE code = $1`, [code], ); return rows[0] ? this.map(rows[0]) : null; } async findByUserId(userId: string): Promise { const rows = await this.ds.query( `SELECT * FROM public.user_referral_codes WHERE user_id = $1`, [userId], ); return rows[0] ? this.map(rows[0]) : null; } /** Increment click count asynchronously (fire-and-forget safe). */ async incrementClickCount(code: string): Promise { await this.ds.query( `UPDATE public.user_referral_codes SET click_count = click_count + 1 WHERE code = $1`, [code], ); } private map(r: any): UserReferralCode { return { userId: r.user_id, code: r.code, clickCount: r.click_count, createdAt: r.created_at, }; } }