- Deleted migration file that removed cached profile fields from the users table, centralizing profile data retrieval from WHMCS. - Updated CsrfMiddleware to include new public authentication endpoints for password reset, setting password, and WHMCS account linking. - Enhanced error handling in password and WHMCS linking workflows to provide clearer feedback on missing mappings and improve user experience. - Adjusted user creation and update methods in UsersFacade to handle cases where WHMCS mappings are not yet available, ensuring smoother account setup.
314 lines
11 KiB
TypeScript
314 lines
11 KiB
TypeScript
// SIM Management Controller Endpoints
|
|
// These endpoints should be added to your subscriptions controller
|
|
// Location: apps/bff/src/modules/subscriptions/subscriptions.controller.ts
|
|
|
|
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Body,
|
|
Param,
|
|
Query,
|
|
Request,
|
|
ParseIntPipe,
|
|
BadRequestException,
|
|
} from "@nestjs/common";
|
|
import { ApiTags, ApiOperation, ApiParam, ApiQuery, ApiBody, ApiResponse, ApiBearerAuth } from "@nestjs/swagger";
|
|
import { ZodValidationPipe } from "@bff/core/validation";
|
|
import type { RequestWithUser } from "@bff/modules/auth/auth.types";
|
|
import { SimManagementService } from "../sim-management.service";
|
|
import {
|
|
simTopupRequestSchema,
|
|
simChangePlanRequestSchema,
|
|
simCancelRequestSchema,
|
|
simFeaturesRequestSchema,
|
|
type SimTopupRequest,
|
|
type SimChangePlanRequest,
|
|
type SimCancelRequest,
|
|
type SimFeaturesRequest,
|
|
} from "../sim-management/types/sim-requests.types";
|
|
|
|
// ==================== SIM Management Endpoints ====================
|
|
// Add these methods to your SubscriptionsController class
|
|
|
|
@ApiTags("subscriptions")
|
|
@Controller("subscriptions")
|
|
@ApiBearerAuth()
|
|
export class SimEndpointsController {
|
|
constructor(private readonly simManagementService: SimManagementService) {}
|
|
|
|
@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/info")
|
|
@ApiOperation({
|
|
summary: "Get SIM information (alias for /sim)",
|
|
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 getSimInfoAlias(
|
|
@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: 1000 },
|
|
amount: {
|
|
type: "number",
|
|
description: "Amount to charge in JPY (optional, defaults to calculated amount)",
|
|
example: 500,
|
|
},
|
|
currency: {
|
|
type: "string",
|
|
description: "ISO currency code (optional, defaults to JPY)",
|
|
example: "JPY",
|
|
},
|
|
},
|
|
required: ["quotaMb"],
|
|
},
|
|
})
|
|
@ApiResponse({ status: 200, description: "Top-up successful" })
|
|
async topUpSim(
|
|
@Request() req: RequestWithUser,
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
@Body(new ZodValidationPipe(simTopupRequestSchema)) 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")
|
|
@ApiOperation({
|
|
summary: "Change SIM plan",
|
|
description:
|
|
"Change the SIM service plan. The change will be automatically scheduled for the 1st of the next month. Available plans: 5GB, 10GB, 25GB, 50GB.",
|
|
})
|
|
@ApiParam({ name: "id", type: Number, description: "Subscription ID" })
|
|
@ApiBody({
|
|
description: "Plan change request",
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
newPlanCode: {
|
|
type: "string",
|
|
description: "New plan code",
|
|
enum: ["5GB", "10GB", "25GB", "50GB"],
|
|
example: "25GB"
|
|
},
|
|
},
|
|
required: ["newPlanCode"],
|
|
},
|
|
})
|
|
@ApiResponse({ status: 200, description: "Plan change successful" })
|
|
async changeSimPlan(
|
|
@Request() req: RequestWithUser,
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
@Body(new ZodValidationPipe(simChangePlanRequestSchema)) 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")
|
|
@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(new ZodValidationPipe(simCancelRequestSchema)) 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")
|
|
@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(new ZodValidationPipe(simFeaturesRequestSchema)) body: SimFeaturesRequest
|
|
) {
|
|
await this.simManagementService.updateSimFeatures(req.user.id, subscriptionId, body);
|
|
return { success: true, message: "SIM features updated successfully" };
|
|
}
|
|
|
|
@Get("debug/sim-details/:account")
|
|
// @Public() // Uncomment if you have a Public decorator for debug endpoints
|
|
@ApiOperation({
|
|
summary: "[DEBUG] Get SIM details from Freebit",
|
|
description: "Query Freebit API directly to see plan code and details for any account",
|
|
})
|
|
@ApiParam({ name: "account", description: "SIM account number (e.g., 02000215161147)" })
|
|
async debugSimDetails(@Param("account") account: string) {
|
|
return await this.simManagementService.getSimDetailsDebug(account);
|
|
}
|
|
}
|
|
|