Initial commit of edr-passenger-api alpha version

This commit is contained in:
Stephanos A
2026-05-13 16:58:49 +03:00
parent 199a3eba11
commit 39ba561d8f
113 changed files with 3602 additions and 1035 deletions

View File

@@ -0,0 +1,15 @@
import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { LoyaltyService } from './loyalty.service';
import { JwtGuard } from '../../common/jwt.guard';
@ApiTags('Loyalty')
@Controller('loyalty')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
export class LoyaltyController {
constructor(private service: LoyaltyService) {}
@Get(':passengerId') @ApiOperation({ summary: 'Get loyalty account with tier progress' }) getAccount(@Param('passengerId') id: string) { return this.service.getAccount(id); }
@Get(':passengerId/rewards') @ApiOperation({ summary: 'Get available rewards' }) getRewards(@Param('passengerId') id: string) { return this.service.getRewards(id); }
@Post(':passengerId/rewards/:rewardId/redeem') @ApiOperation({ summary: 'Redeem a loyalty reward' }) redeemReward(@Param('passengerId') pid: string, @Param('rewardId') rid: string) { return this.service.redeemReward(pid, rid); }
}

View File

@@ -0,0 +1,6 @@
import { Module } from '@nestjs/common';
import { LoyaltyController } from './loyalty.controller';
import { LoyaltyService } from './loyalty.service';
@Module({ controllers: [LoyaltyController], providers: [LoyaltyService] })
export class LoyaltyModule {}

View File

@@ -0,0 +1,43 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
@Injectable()
export class LoyaltyService {
constructor(private prisma: PrismaService) {}
async getAccount(passengerId: string) {
const account = await this.prisma.loyaltyAccount.findUnique({ where: { passengerId }, include: { ledger: { orderBy: { createdAt: 'desc' }, take: 20 } } });
if (!account) throw new NotFoundException('Loyalty account not found');
const tiers = ['BRONZE', 'SILVER', 'GOLD', 'PLATINUM'];
const thresholds: Record<string, number> = { BRONZE: 0, SILVER: 2000, GOLD: 5000, PLATINUM: 10000 };
const idx = tiers.indexOf(account.tier);
const nextTier = tiers[idx + 1] ?? null;
const nextThreshold = nextTier ? thresholds[nextTier] : null;
return {
...account, nextTier,
points: account.pointsBalance,
nextTierPoints: nextThreshold ?? account.pointsBalance,
pointsToNextTier: nextThreshold ? nextThreshold - account.pointsBalance : 0,
tierProgressPercent: nextThreshold ? +((account.pointsBalance - thresholds[account.tier]) / (nextThreshold - thresholds[account.tier]) * 100).toFixed(2) : 100,
};
}
async getRewards(passengerId: string) {
const account = await this.prisma.loyaltyAccount.findUnique({ where: { passengerId } });
if (!account) throw new NotFoundException('Loyalty account not found');
return this.prisma.loyaltyReward.findMany({ where: { accountId: account.id, available: true } });
}
async redeemReward(passengerId: string, rewardId: string) {
const account = await this.prisma.loyaltyAccount.findUnique({ where: { passengerId } });
if (!account) throw new NotFoundException('Loyalty account not found');
const reward = await this.prisma.loyaltyReward.findUnique({ where: { id: rewardId } });
if (!reward?.available) throw new NotFoundException('Reward not available');
if (account.pointsBalance < reward.costPoints) throw new BadRequestException('Insufficient points');
const newBalance = account.pointsBalance - reward.costPoints;
await this.prisma.loyaltyAccount.update({ where: { passengerId }, data: { pointsBalance: newBalance } });
await this.prisma.loyaltyLedgerEntry.create({ data: { accountId: account.id, delta: -reward.costPoints, reason: 'REWARD_REDEEMED', balanceAfter: newBalance } });
await this.prisma.loyaltyReward.update({ where: { id: rewardId }, data: { available: false } });
return { redeemed: true, pointsUsed: reward.costPoints, balanceAfter: newBalance };
}
}