Assist_Design/apps/bff/src/modules/subscriptions/subscriptions.controller.ts

394 lines
13 KiB
TypeScript

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<SubscriptionList | Subscription[]> {
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<Subscription[]> {
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<Subscription> {
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<InvoiceList> {
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<Record<string, unknown>> {
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" };
}
}