import { Controller, Get, Param, Query, UseGuards, Request, ParseIntPipe, BadRequestException, } from "@nestjs/common"; import { ApiTags, ApiOperation, ApiResponse, ApiQuery, ApiBearerAuth, ApiParam, } from "@nestjs/swagger"; import { SubscriptionsService } from "./subscriptions.service"; import { JwtAuthGuard } from "../auth/guards/jwt-auth.guard"; import { Subscription, SubscriptionList, InvoiceList } from "@customer-portal/shared"; import { RequestWithUser } from "../auth/auth.types"; @ApiTags("subscriptions") @Controller("subscriptions") @UseGuards(JwtAuthGuard) @ApiBearerAuth() export class SubscriptionsController { constructor(private readonly subscriptionsService: SubscriptionsService) {} @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; } }