90 lines
3.3 KiB
TypeScript
90 lines
3.3 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Query,
|
|
Headers,
|
|
UnauthorizedException,
|
|
ParseIntPipe,
|
|
DefaultValuePipe,
|
|
} from '@nestjs/common';
|
|
import { GetReferralListUseCase } from '../../../application/use-cases/get-referral-list.use-case';
|
|
import { ReferralRelationshipRepository } from '../../../infrastructure/repositories/referral-relationship.repository';
|
|
import { ReferralRewardRepository } from '../../../infrastructure/repositories/referral-reward.repository';
|
|
import { ReferralStatus } from '../../../domain/entities/referral-relationship.entity';
|
|
import { RewardStatus } from '../../../domain/entities/referral-reward.entity';
|
|
import * as jwt from 'jsonwebtoken';
|
|
|
|
/**
|
|
* Platform-admin endpoints for managing the referral system.
|
|
* Requires JWT with platform_admin or platform_super_admin role.
|
|
*/
|
|
@Controller('api/v1/referral/admin')
|
|
export class ReferralAdminController {
|
|
constructor(
|
|
private readonly getReferralList: GetReferralListUseCase,
|
|
private readonly relationshipRepo: ReferralRelationshipRepository,
|
|
private readonly rewardRepo: ReferralRewardRepository,
|
|
) {}
|
|
|
|
/** GET /api/v1/referral/admin/relationships — all referral relationships */
|
|
@Get('relationships')
|
|
async listRelationships(
|
|
@Headers('authorization') auth: string,
|
|
@Query('status') status: ReferralStatus | undefined,
|
|
@Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit: number,
|
|
@Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
|
) {
|
|
this.requireAdmin(auth);
|
|
return this.relationshipRepo.findAll(status, Math.min(limit, 200), offset);
|
|
}
|
|
|
|
/** GET /api/v1/referral/admin/rewards — all reward records */
|
|
@Get('rewards')
|
|
async listRewards(
|
|
@Headers('authorization') auth: string,
|
|
@Query('status') status: RewardStatus | undefined,
|
|
@Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit: number,
|
|
@Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
|
) {
|
|
this.requireAdmin(auth);
|
|
return this.rewardRepo.findAll(status, Math.min(limit, 200), offset);
|
|
}
|
|
|
|
/** GET /api/v1/referral/admin/stats — global referral statistics */
|
|
@Get('stats')
|
|
async getStats(@Headers('authorization') auth: string) {
|
|
this.requireAdmin(auth);
|
|
const [totalRel, activeRel, pendingRewards] = await Promise.all([
|
|
this.relationshipRepo.findAll(undefined, 1, 0).then((r) => r.total),
|
|
this.relationshipRepo.findAll('ACTIVE', 1, 0).then((r) => r.total),
|
|
this.rewardRepo.findAll('PENDING', 1, 0).then((r) => r.total),
|
|
]);
|
|
return {
|
|
totalReferrals: totalRel,
|
|
activeReferrals: activeRel,
|
|
pendingRewards,
|
|
};
|
|
}
|
|
|
|
private requireAdmin(auth: string) {
|
|
if (!auth?.startsWith('Bearer ')) {
|
|
throw new UnauthorizedException('Missing authorization header');
|
|
}
|
|
const token = auth.slice(7);
|
|
const secret = process.env.JWT_SECRET || 'dev-secret';
|
|
try {
|
|
const payload = jwt.verify(token, secret) as any;
|
|
const roles: string[] = Array.isArray(payload.roles) ? payload.roles : [];
|
|
if (
|
|
!roles.includes('platform_admin') &&
|
|
!roles.includes('platform_super_admin')
|
|
) {
|
|
throw new UnauthorizedException('Admin role required');
|
|
}
|
|
} catch (err) {
|
|
if (err instanceof UnauthorizedException) throw err;
|
|
throw new UnauthorizedException('Invalid JWT');
|
|
}
|
|
}
|
|
}
|