import { Controller, Get, Post, Param, Query, Body, Request, ParseIntPipe, BadRequestException, } from "@nestjs/common"; import { ApiTags, ApiOperation, ApiResponse, ApiQuery, ApiBearerAuth, ApiParam, ApiBody, } from "@nestjs/swagger"; import { SubscriptionsService } from "./subscriptions.service"; import { SimManagementService } from "./sim-management.service"; import { Subscription, SubscriptionList, InvoiceList } from "@customer-portal/shared"; import type { RequestWithUser } from "../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", }) @ApiResponse({ status: 200, description: "List of user subscriptions", type: Object, // Would be SubscriptionList if we had proper DTO decorators }) async getSubscriptions( @Request() req: RequestWithUser, @Query("status") status?: string ): Promise { // Validate status if provided if (status && !["Active", "Suspended", "Terminated", "Cancelled", "Pending"].includes(status)) { throw new BadRequestException("Invalid status filter"); } if (status) { const subscriptions = await this.subscriptionsService.getSubscriptionsByStatus( req.user.id, status ); return subscriptions; } return this.subscriptionsService.getSubscriptions(req.user.id); } @Get("active") @ApiOperation({ summary: "Get active subscriptions only", description: "Retrieves only active subscriptions for the authenticated user", }) @ApiResponse({ status: 200, description: "List of active subscriptions", type: [Object], // Would be Subscription[] if we had proper DTO decorators }) 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", }) @ApiResponse({ status: 200, description: "Subscription statistics", type: Object, }) 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" }) @ApiResponse({ status: 200, description: "Subscription details", type: Object, // Would be Subscription if we had proper DTO decorators }) @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)", }) @ApiResponse({ status: 200, description: "List of invoices for the subscription", type: Object, // Would be InvoiceList if we had proper DTO decorators }) @ApiResponse({ status: 404, description: "Subscription not found" }) async getSubscriptionInvoices( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Query("page") page?: string, @Query("limit") limit?: string ): Promise { if (subscriptionId <= 0) { throw new BadRequestException("Subscription ID must be a positive number"); } // Validate and sanitize input const pageNum = this.validatePositiveInteger(page, 1, "page"); const limitNum = this.validatePositiveInteger(limit, 10, "limit"); // Limit max page size for performance if (limitNum > 100) { throw new BadRequestException("Limit cannot exceed 100 items per page"); } return this.subscriptionsService.getSubscriptionInvoices(req.user.id, subscriptionId, { page: pageNum, limit: limitNum, }); } private validatePositiveInteger( value: string | undefined, defaultValue: number, fieldName: string ): number { if (!value) { return defaultValue; } const parsed = parseInt(value, 10); if (isNaN(parsed) || parsed <= 0) { throw new BadRequestException(`${fieldName} must be a positive integer`); } return parsed; } // ==================== 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 ) { 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") @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: 1024 }, }, required: ["quotaMb"], }, }) @ApiResponse({ status: 200, description: "Top-up successful" }) async topUpSim( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: { quotaMb: number; } ) { await this.simManagementService.topUpSim(req.user.id, subscriptionId, body); return { success: true, message: "SIM top-up completed successfully" }; } @Post(":id/sim/change-plan") @ApiOperation({ summary: "Change SIM plan", description: "Change the SIM service plan", }) @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" }, assignGlobalIp: { type: "boolean", description: "Assign global IP address" }, scheduledAt: { type: "string", description: "Schedule for later (YYYYMMDD)", example: "20241225" }, }, required: ["newPlanCode"], }, }) @ApiResponse({ status: 200, description: "Plan change successful" }) async changeSimPlan( @Request() req: RequestWithUser, @Param("id", ParseIntPipe) subscriptionId: number, @Body() body: { newPlanCode: string; assignGlobalIp?: boolean; scheduledAt?: string; } ) { 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") @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: { scheduledAt?: string } = {} ) { 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)", }) @ApiParam({ name: "id", type: Number, description: "Subscription ID" }) @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 ) { await this.simManagementService.reissueEsimProfile(req.user.id, subscriptionId); return { success: true, message: "eSIM profile reissue completed successfully" }; } @Post(":id/sim/features") @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: { voiceMailEnabled?: boolean; callWaitingEnabled?: boolean; internationalRoamingEnabled?: boolean; networkType?: '4G' | '5G'; } ) { await this.simManagementService.updateSimFeatures(req.user.id, subscriptionId, body); return { success: true, message: "SIM features updated successfully" }; } }