import { Controller, Get, Post, Param, Query, Body, Request, ParseIntPipe, BadRequestException, UsePipes, } from "@nestjs/common"; import { ApiTags, ApiOperation, ApiResponse, ApiOkResponse, ApiQuery, ApiBearerAuth, ApiParam, ApiBody, } from "@nestjs/swagger"; import { SubscriptionsService } from "./subscriptions.service"; import { SimManagementService } from "./sim-management.service"; import { Subscription, SubscriptionList, InvoiceList, simTopupRequestSchema, simChangePlanRequestSchema, simCancelRequestSchema, simFeaturesRequestSchema, subscriptionQuerySchema, invoiceListQuerySchema, type SimTopupRequest, type SimChangePlanRequest, type SimCancelRequest, type SimFeaturesRequest, type SubscriptionQuery, type InvoiceListQuery, } from "@customer-portal/domain"; import { ZodValidationPipe } from "@bff/core/validation"; import type { RequestWithUser } from "@bff/modules/auth/auth.types"; @ApiTags("subscriptions") @Controller("subscriptions") @ApiBearerAuth() export class SubscriptionsController { constructor( private readonly subscriptionsService: SubscriptionsService, private readonly simManagementService: SimManagementService ) {} @Get() @ApiOperation({ summary: "Get all user subscriptions", description: "Retrieves all subscriptions/services for the authenticated user", }) @ApiQuery({ name: "status", required: false, type: String, description: "Filter by subscription status", }) @ApiOkResponse({ description: "List of user subscriptions" }) @UsePipes(new ZodValidationPipe(subscriptionQuerySchema)) async getSubscriptions( @Request() req: RequestWithUser, @Query() query: SubscriptionQuery ): Promise { if (query.status) { return this.subscriptionsService.getSubscriptionsByStatus(req.user.id, query.status); } return this.subscriptionsService.getSubscriptions(req.user.id); } @Get("active") @ApiOperation({ summary: "Get active subscriptions only", description: "Retrieves only active subscriptions for the authenticated user", }) @ApiOkResponse({ description: "List of active subscriptions" }) async getActiveSubscriptions(@Request() req: RequestWithUser): Promise { return this.subscriptionsService.getActiveSubscriptions(req.user.id); } @Get("stats") @ApiOperation({ summary: "Get subscription statistics", description: "Retrieves subscription count statistics by status", }) @ApiOkResponse({ description: "Subscription statistics" }) async getSubscriptionStats(@Request() req: RequestWithUser): Promise<{ total: number; active: number; suspended: number; cancelled: number; pending: number; }> { return this.subscriptionsService.getSubscriptionStats(req.user.id); } @Get(":id") @ApiOperation({ summary: "Get subscription details by ID", description: "Retrieves detailed information for a specific subscription", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiOkResponse({ description: "Subscription details" }) @ApiResponse({ status: 404, description: "Subscription not found" }) async getSubscriptionById( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number ): Promise { if (subscriptionId <= 0) { throw new BadRequestException("Subscription ID must be a positive number"); } return this.subscriptionsService.getSubscriptionById(req.user.id, subscriptionId); } @Get(":id/invoices") @ApiOperation({ summary: "Get invoices for a specific subscription", description: "Retrieves all invoices related to a specific subscription", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiQuery({ name: "page", required: false, type: Number, description: "Page number (default: 1)", }) @ApiQuery({ name: "limit", required: false, type: Number, description: "Items per page (default: 10)", }) @ApiOkResponse({ description: "List of invoices for the subscription" }) @ApiResponse({ status: 404, description: "Subscription not found" }) @UsePipes(new ZodValidationPipe(invoiceListQuerySchema)) async getSubscriptionInvoices( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Query() query: InvoiceListQuery ): Promise { if (subscriptionId <= 0) { throw new BadRequestException("Subscription ID must be a positive number"); } return this.subscriptionsService.getSubscriptionInvoices(req.user.id, subscriptionId, query); } // ==================== SIM Management Endpoints ==================== @Get(":id/sim/debug") @ApiOperation({ summary: "Debug SIM subscription data", description: "Retrieves subscription data to help debug SIM management issues", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiResponse({ status: 200, description: "Subscription debug data" }) async debugSimSubscription( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number ): Promise> { return this.simManagementService.debugSimSubscription(req.user.id, subscriptionId); } @Get(":id/sim") @ApiOperation({ summary: "Get SIM details and usage", description: "Retrieves comprehensive SIM information including details and current usage", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiResponse({ status: 200, description: "SIM information" }) @ApiResponse({ status: 400, description: "Not a SIM subscription" }) @ApiResponse({ status: 404, description: "Subscription not found" }) async getSimInfo( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number ) { return this.simManagementService.getSimInfo(req.user.id, subscriptionId); } @Get(":id/sim/details") @ApiOperation({ summary: "Get SIM details", description: "Retrieves detailed SIM information including ICCID, plan, status, etc.", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiResponse({ status: 200, description: "SIM details" }) async getSimDetails( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number ) { return this.simManagementService.getSimDetails(req.user.id, subscriptionId); } @Get(":id/sim/usage") @ApiOperation({ summary: "Get SIM data usage", description: "Retrieves current data usage and recent usage history", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiResponse({ status: 200, description: "SIM usage data" }) async getSimUsage( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number ) { return this.simManagementService.getSimUsage(req.user.id, subscriptionId); } @Get(":id/sim/top-up-history") @ApiOperation({ summary: "Get SIM top-up history", description: "Retrieves data top-up history for the specified date range", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiQuery({ name: "fromDate", description: "Start date (YYYYMMDD)", example: "20240101" }) @ApiQuery({ name: "toDate", description: "End date (YYYYMMDD)", example: "20241231" }) @ApiResponse({ status: 200, description: "Top-up history" }) async getSimTopUpHistory( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Query("fromDate") fromDate: string, @Query("toDate") toDate: string ) { if (!fromDate || !toDate) { throw new BadRequestException("fromDate and toDate are required"); } return this.simManagementService.getSimTopUpHistory(req.user.id, subscriptionId, { fromDate, toDate, }); } @Post(":id/sim/top-up") @UsePipes(new ZodValidationPipe(simTopupRequestSchema)) @ApiOperation({ summary: "Top up SIM data quota", description: "Add data quota to the SIM service", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiBody({ description: "Top-up request", schema: { type: "object", properties: { quotaMb: { type: "number", description: "Quota in MB", example: 1000 }, }, required: ["quotaMb"], }, }) @ApiResponse({ status: 200, description: "Top-up successful" }) async topUpSim( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: SimTopupRequest ) { await this.simManagementService.topUpSim(req.user.id, subscriptionId, body); return { success: true, message: "SIM top-up completed successfully" }; } @Post(":id/sim/change-plan") @UsePipes(new ZodValidationPipe(simChangePlanRequestSchema)) @ApiOperation({ summary: "Change SIM plan", description: "Change the SIM service plan. The change will be automatically scheduled for the 1st of the next month.", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiBody({ description: "Plan change request", schema: { type: "object", properties: { newPlanCode: { type: "string", description: "New plan code", example: "LTE3G_P01" }, }, required: ["newPlanCode"], }, }) @ApiResponse({ status: 200, description: "Plan change successful" }) async changeSimPlan( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: SimChangePlanRequest ) { const result = await this.simManagementService.changeSimPlan(req.user.id, subscriptionId, body); return { success: true, message: "SIM plan change completed successfully", ...result, }; } @Post(":id/sim/cancel") @UsePipes(new ZodValidationPipe(simCancelRequestSchema)) @ApiOperation({ summary: "Cancel SIM service", description: "Cancel the SIM service (immediate or scheduled)", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiBody({ description: "Cancellation request", schema: { type: "object", properties: { scheduledAt: { type: "string", description: "Schedule cancellation (YYYYMMDD)", example: "20241231", }, }, }, required: false, }) @ApiResponse({ status: 200, description: "Cancellation successful" }) async cancelSim( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: SimCancelRequest ) { await this.simManagementService.cancelSim(req.user.id, subscriptionId, body); return { success: true, message: "SIM cancellation completed successfully" }; } @Post(":id/sim/reissue-esim") @ApiOperation({ summary: "Reissue eSIM profile", description: "Reissue a downloadable eSIM profile (eSIM only). Optionally provide a new EID to transfer to.", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiBody({ description: "Optional new EID to transfer the eSIM to", schema: { type: "object", properties: { newEid: { type: "string", description: "32-digit EID", example: "89049032000001000000043598005455", }, }, required: [], }, }) @ApiResponse({ status: 200, description: "eSIM reissue successful" }) @ApiResponse({ status: 400, description: "Not an eSIM subscription" }) async reissueEsimProfile( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: { newEid?: string } = {} ) { await this.simManagementService.reissueEsimProfile(req.user.id, subscriptionId, body.newEid); return { success: true, message: "eSIM profile reissue completed successfully" }; } @Post(":id/sim/features") @UsePipes(new ZodValidationPipe(simFeaturesRequestSchema)) @ApiOperation({ summary: "Update SIM features", description: "Enable/disable voicemail, call waiting, international roaming, and switch network type (4G/5G)", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @ApiBody({ description: "Features update request", schema: { type: "object", properties: { voiceMailEnabled: { type: "boolean" }, callWaitingEnabled: { type: "boolean" }, internationalRoamingEnabled: { type: "boolean" }, networkType: { type: "string", enum: ["4G", "5G"] }, }, }, }) @ApiResponse({ status: 200, description: "Features update successful" }) async updateSimFeatures( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: SimFeaturesRequest ) { await this.simManagementService.updateSimFeatures(req.user.id, subscriptionId, body); return { success: true, message: "SIM features updated successfully" }; } }