73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
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<UserReferralCode> {
|
|
// 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<UserReferralCode>;
|
|
}
|
|
|
|
async findByCode(code: string): Promise<UserReferralCode | null> {
|
|
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<UserReferralCode | null> {
|
|
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<void> {
|
|
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,
|
|
};
|
|
}
|
|
}
|