mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 02:58:11 +00:00
refactor: ( iam ) remove user relations
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
-- 1. Add iamUserId to Agent
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
ALTER TABLE passenger."Agent" ADD COLUMN IF NOT EXISTS "iamUserId" TEXT;
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'Agent_iamUserId_key'
|
||||||
|
AND conrelid = 'passenger."Agent"'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE passenger."Agent" ADD CONSTRAINT "Agent_iamUserId_key" UNIQUE ("iamUserId");
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "Agent_iamUserId_idx" ON passenger."Agent"("iamUserId");
|
||||||
|
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
-- 2. Populate iamUserId for existing agent records
|
||||||
|
-- Match via User.email → iam.users.email
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
UPDATE passenger."Agent" a
|
||||||
|
SET "iamUserId" = iu.id
|
||||||
|
FROM passenger."User" u
|
||||||
|
JOIN iam.users iu ON iu.email = u.email
|
||||||
|
WHERE a."userId" = u.id
|
||||||
|
AND a."iamUserId" IS NULL;
|
||||||
|
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
-- 3. Drop Agent.userId FK and column — iamUserId replaces it entirely
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
ALTER TABLE passenger."Agent" DROP CONSTRAINT IF EXISTS "Agent_userId_fkey";
|
||||||
|
DROP INDEX IF EXISTS passenger."Agent_userId_key";
|
||||||
|
ALTER TABLE passenger."Agent" DROP COLUMN IF EXISTS "userId";
|
||||||
|
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
-- 4. Drop Passenger.userId FK (column stays as plain nullable string)
|
||||||
|
-- ────────────────────────────────────────────────────────────
|
||||||
|
ALTER TABLE passenger."Passenger" DROP CONSTRAINT IF EXISTS "Passenger_userId_fkey";
|
||||||
@@ -260,8 +260,6 @@ model User {
|
|||||||
faydaVerifiedAt DateTime?
|
faydaVerifiedAt DateTime?
|
||||||
faydaSub String? @unique
|
faydaSub String? @unique
|
||||||
|
|
||||||
passenger Passenger?
|
|
||||||
agent Agent?
|
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
|
||||||
@@schema("passenger")
|
@@schema("passenger")
|
||||||
@@ -288,7 +286,6 @@ model Passenger {
|
|||||||
preferredLanguage String?
|
preferredLanguage String?
|
||||||
blockedUntil DateTime?
|
blockedUntil DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
user User? @relation(fields: [userId], references: [id])
|
|
||||||
bookings Booking[]
|
bookings Booking[]
|
||||||
loyalty LoyaltyAccount?
|
loyalty LoyaltyAccount?
|
||||||
wallet WalletAccount?
|
wallet WalletAccount?
|
||||||
@@ -1057,16 +1054,16 @@ model SegmentFareRule {
|
|||||||
|
|
||||||
model Agent {
|
model Agent {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String @unique
|
iamUserId String? @unique
|
||||||
agentCode String @unique
|
agentCode String @unique
|
||||||
stationId String?
|
stationId String?
|
||||||
commissionRate Int @default(5)
|
commissionRate Int @default(5)
|
||||||
active Boolean @default(true)
|
active Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
user User @relation(fields: [userId], references: [id])
|
|
||||||
bookings AgentBooking[]
|
bookings AgentBooking[]
|
||||||
shifts AgentShift[]
|
shifts AgentShift[]
|
||||||
commissions AgentCommission[]
|
commissions AgentCommission[]
|
||||||
|
@@index([iamUserId])
|
||||||
@@schema("passenger")
|
@@schema("passenger")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1 @@
|
|||||||
// Thin adapter: re-exports IAM guard and a stub IamRoles decorator so that
|
|
||||||
// controllers written against the forthcoming IamGuard compile today.
|
|
||||||
export { JwtGuard as IamGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard';
|
export { JwtGuard as IamGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard';
|
||||||
|
|
||||||
export function IamRoles(..._roles: string[]) {
|
|
||||||
return function (_target: any, _key?: any, _descriptor?: any) {};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { SetMetadata } from '@nestjs/common';
|
import { SetMetadata } from '@nestjs/common';
|
||||||
import { UserRole } from '@prisma/client';
|
|
||||||
|
|
||||||
export const ROLES_KEY = 'roles';
|
export const ROLES_KEY = 'roles';
|
||||||
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);
|
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ export class AgentsService {
|
|||||||
constructor(private prisma: PrismaService) {}
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
async createAgentBooking(dto: CreateAgentBookingDto) {
|
async createAgentBooking(dto: CreateAgentBookingDto) {
|
||||||
const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId }, include: { user: { include: { passenger: true } } } });
|
const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId } });
|
||||||
if (!agent || !agent.active) throw new NotFoundException('Agent not found or inactive');
|
if (!agent || !agent.active) throw new NotFoundException('Agent not found or inactive');
|
||||||
if (!agent.user.passenger) throw new BadRequestException('Agent must have passenger account');
|
|
||||||
|
const passenger = agent.iamUserId
|
||||||
|
? await this.prisma.passenger.findUnique({ where: { iamUserId: agent.iamUserId } })
|
||||||
|
: null;
|
||||||
|
if (!passenger) throw new BadRequestException('Agent must have a linked passenger account');
|
||||||
|
|
||||||
const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } });
|
const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } });
|
||||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||||
@@ -30,7 +34,7 @@ export class AgentsService {
|
|||||||
const booking = await this.prisma.booking.create({
|
const booking = await this.prisma.booking.create({
|
||||||
data: {
|
data: {
|
||||||
bookingRef: generateRef(),
|
bookingRef: generateRef(),
|
||||||
passengerId: agent.user.passenger.id,
|
passengerId: passenger.id,
|
||||||
scheduleId: dto.scheduleId,
|
scheduleId: dto.scheduleId,
|
||||||
status: dto.paymentMethod === 'CASH' ? 'CONFIRMED' : 'PENDING_PAYMENT',
|
status: dto.paymentMethod === 'CASH' ? 'CONFIRMED' : 'PENDING_PAYMENT',
|
||||||
totalMinor,
|
totalMinor,
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { Controller, Get, Post, Patch, Delete, Body, Param, HttpCode, UseGuards
|
|||||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
import { CurrenciesService } from './currencies.service';
|
import { CurrenciesService } from './currencies.service';
|
||||||
import { CreateCurrencyDto, UpdateCurrencyDto } from './currencies.dto';
|
import { CreateCurrencyDto, UpdateCurrencyDto } from './currencies.dto';
|
||||||
import { IamGuard, IamRoles } from '../../common/iam-adapter';
|
import { IamGuard } from '../../common/iam-adapter';
|
||||||
|
import { Roles } from '../../common/roles.decorator';
|
||||||
|
import { RolesGuard } from '../../common/roles.guard';
|
||||||
|
|
||||||
@ApiTags('Currencies')
|
@ApiTags('Currencies')
|
||||||
@Controller('currencies')
|
@Controller('currencies')
|
||||||
@@ -15,8 +17,8 @@ export class CurrenciesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN')
|
@Roles('ADMIN')
|
||||||
@ApiBearerAuth('IAM-auth')
|
@ApiBearerAuth('IAM-auth')
|
||||||
@HttpCode(201)
|
@HttpCode(201)
|
||||||
createCurrency(@Body() dto: CreateCurrencyDto) {
|
createCurrency(@Body() dto: CreateCurrencyDto) {
|
||||||
@@ -24,24 +26,24 @@ export class CurrenciesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN')
|
@Roles('ADMIN')
|
||||||
@ApiBearerAuth('IAM-auth')
|
@ApiBearerAuth('IAM-auth')
|
||||||
updateCurrency(@Param('id') id: string, @Body() dto: UpdateCurrencyDto) {
|
updateCurrency(@Param('id') id: string, @Body() dto: UpdateCurrencyDto) {
|
||||||
return this.currenciesService.updateCurrency(id, dto);
|
return this.currenciesService.updateCurrency(id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN')
|
@Roles('ADMIN')
|
||||||
@ApiBearerAuth('IAM-auth')
|
@ApiBearerAuth('IAM-auth')
|
||||||
deleteCurrency(@Param('id') id: string) {
|
deleteCurrency(@Param('id') id: string) {
|
||||||
return this.currenciesService.deleteCurrency(id);
|
return this.currenciesService.deleteCurrency(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('sync-rates')
|
@Post('sync-rates')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN')
|
@Roles('ADMIN')
|
||||||
@ApiBearerAuth('IAM-auth')
|
@ApiBearerAuth('IAM-auth')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
syncRates() {
|
syncRates() {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { Controller, Get, Param, Patch, Post, Body, UseGuards } from '@nestjs/co
|
|||||||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
|
||||||
import { NotificationsService } from './notifications.service';
|
import { NotificationsService } from './notifications.service';
|
||||||
import { JwtGuard } from '../../common/jwt.guard';
|
import { JwtGuard } from '../../common/jwt.guard';
|
||||||
import { IamGuard, IamRoles } from '../../common/iam-adapter';
|
import { IamGuard } from '../../common/iam-adapter';
|
||||||
|
import { Roles } from '../../common/roles.decorator';
|
||||||
|
import { RolesGuard } from '../../common/roles.guard';
|
||||||
import { TestNotificationDto } from './notifications.dto';
|
import { TestNotificationDto } from './notifications.dto';
|
||||||
import { EmailClientService } from './email-client.service';
|
import { EmailClientService } from './email-client.service';
|
||||||
import { SmsClientService } from './sms-client.service';
|
import { SmsClientService } from './sms-client.service';
|
||||||
@@ -39,8 +41,8 @@ export class NotificationsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('send/email')
|
@Post('send/email')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN', 'STAFF')
|
@Roles('ADMIN', 'STAFF')
|
||||||
@ApiOperation({ summary: 'Send a direct email via the email microservice' })
|
@ApiOperation({ summary: 'Send a direct email via the email microservice' })
|
||||||
@ApiBody({ type: SendEmail })
|
@ApiBody({ type: SendEmail })
|
||||||
sendEmail(@Body() dto: SendEmail) {
|
sendEmail(@Body() dto: SendEmail) {
|
||||||
@@ -48,8 +50,8 @@ export class NotificationsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('send/sms')
|
@Post('send/sms')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN', 'STAFF')
|
@Roles('ADMIN', 'STAFF')
|
||||||
@ApiOperation({ summary: 'Send a direct SMS via the SMS microservice' })
|
@ApiOperation({ summary: 'Send a direct SMS via the SMS microservice' })
|
||||||
@ApiBody({ type: SingleMessageDto })
|
@ApiBody({ type: SingleMessageDto })
|
||||||
sendSms(@Body() dto: SingleMessageDto) {
|
sendSms(@Body() dto: SingleMessageDto) {
|
||||||
@@ -57,8 +59,8 @@ export class NotificationsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('send/sms/bulk')
|
@Post('send/sms/bulk')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard, RolesGuard)
|
||||||
@IamRoles('ADMIN', 'STAFF')
|
@Roles('ADMIN', 'STAFF')
|
||||||
@ApiOperation({ summary: 'Send bulk SMS messages via the SMS microservice' })
|
@ApiOperation({ summary: 'Send bulk SMS messages via the SMS microservice' })
|
||||||
@ApiBody({ type: BulkMessagesDto })
|
@ApiBody({ type: BulkMessagesDto })
|
||||||
sendBulkSms(@Body() dto: BulkMessagesDto) {
|
sendBulkSms(@Body() dto: BulkMessagesDto) {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
import { JwtGuard } from "../../common/jwt.guard";
|
import { JwtGuard } from "../../common/jwt.guard";
|
||||||
import { RolesGuard } from "../../common/roles.guard";
|
import { RolesGuard } from "../../common/roles.guard";
|
||||||
import { Roles } from "../../common/roles.decorator";
|
import { Roles } from "../../common/roles.decorator";
|
||||||
import { UserRole } from "@prisma/client";
|
|
||||||
|
|
||||||
@ApiTags("Payment")
|
@ApiTags("Payment")
|
||||||
@Controller("payments")
|
@Controller("payments")
|
||||||
@@ -40,7 +39,7 @@ export class PaymentsController {
|
|||||||
|
|
||||||
@Get("all")
|
@Get("all")
|
||||||
@UseGuards(JwtGuard, RolesGuard)
|
@UseGuards(JwtGuard, RolesGuard)
|
||||||
@Roles(UserRole.ADMIN, UserRole.SUPERVISOR, UserRole.STAFF)
|
@Roles('ADMIN', 'SUPERVISOR', 'STAFF')
|
||||||
@ApiBearerAuth("JWT-auth")
|
@ApiBearerAuth("JWT-auth")
|
||||||
@ApiOperation({ summary: "Get all payments with filters (staff/admin only)" })
|
@ApiOperation({ summary: "Get all payments with filters (staff/admin only)" })
|
||||||
@ApiQuery({ name: "search", required: false })
|
@ApiQuery({ name: "search", required: false })
|
||||||
@@ -103,7 +102,7 @@ export class PaymentsController {
|
|||||||
|
|
||||||
@Post("refund")
|
@Post("refund")
|
||||||
@UseGuards(JwtGuard, RolesGuard)
|
@UseGuards(JwtGuard, RolesGuard)
|
||||||
@Roles(UserRole.ADMIN, UserRole.STAFF, UserRole.AGENT)
|
@Roles('ADMIN', 'STAFF', 'AGENT')
|
||||||
@ApiBearerAuth("JWT-auth")
|
@ApiBearerAuth("JWT-auth")
|
||||||
@ApiOperation({ summary: "Refund a confirmed booking (staff/agent only)" })
|
@ApiOperation({ summary: "Refund a confirmed booking (staff/agent only)" })
|
||||||
refund(@Body() dto: RefundDto) {
|
refund(@Body() dto: RefundDto) {
|
||||||
@@ -112,7 +111,7 @@ export class PaymentsController {
|
|||||||
|
|
||||||
@Post("methods")
|
@Post("methods")
|
||||||
@UseGuards(JwtGuard, RolesGuard)
|
@UseGuards(JwtGuard, RolesGuard)
|
||||||
@Roles(UserRole.ADMIN, UserRole.STAFF)
|
@Roles('ADMIN', 'STAFF')
|
||||||
@ApiBearerAuth("JWT-auth")
|
@ApiBearerAuth("JWT-auth")
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Add a payment system to the platform catalog (admin only)",
|
summary: "Add a payment system to the platform catalog (admin only)",
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectDataSource } from '@nestjs/typeorm';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
import { PrismaService } from '../../common/prisma.service';
|
import { PrismaService } from '../../common/prisma.service';
|
||||||
import { GenerateReportDto, ReportType } from './reports.dto';
|
import { GenerateReportDto, ReportType } from './reports.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReportsService {
|
export class ReportsService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
@InjectDataSource() private dataSource: DataSource,
|
||||||
|
) {}
|
||||||
|
|
||||||
async generateReport(dto: GenerateReportDto) {
|
async generateReport(dto: GenerateReportDto) {
|
||||||
const dateFrom = new Date(dto.dateFrom);
|
const dateFrom = new Date(dto.dateFrom);
|
||||||
@@ -113,13 +118,25 @@ export class ReportsService {
|
|||||||
...(agentId ? { agentId } : {})
|
...(agentId ? { agentId } : {})
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
agent: { include: { user: true } },
|
agent: { select: { id: true, iamUserId: true, agentCode: true } },
|
||||||
booking: true
|
booking: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const iamUserIds = [...new Set(
|
||||||
|
agentBookings.map(ab => ab.agent.iamUserId).filter(Boolean) as string[]
|
||||||
|
)];
|
||||||
|
const iamRows = iamUserIds.length > 0
|
||||||
|
? await this.dataSource.query<{ id: string; name: { en?: string; am?: string } | null }[]>(
|
||||||
|
`SELECT id, name FROM iam.users WHERE id = ANY($1)`,
|
||||||
|
[iamUserIds],
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
const iamMap = new Map(iamRows.map(r => [r.id, r]));
|
||||||
|
|
||||||
const byAgent = agentBookings.reduce((acc, ab) => {
|
const byAgent = agentBookings.reduce((acc, ab) => {
|
||||||
const agentName = ab.agent.user.fullName;
|
const iam = ab.agent.iamUserId ? iamMap.get(ab.agent.iamUserId) : undefined;
|
||||||
|
const agentName = iam?.name?.en ?? iam?.name?.am ?? ab.agent.agentCode;
|
||||||
if (!acc[agentName]) {
|
if (!acc[agentName]) {
|
||||||
acc[agentName] = { bookings: 0, revenueMinor: 0, cashCollected: 0 };
|
acc[agentName] = { bookings: 0, revenueMinor: 0, cashCollected: 0 };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user