Assist_Design/apps/bff/src/subscriptions/subscriptions.controller.ts
2025-08-28 16:57:57 +09:00

188 lines
5.4 KiB
TypeScript

import {
Controller,
Get,
Param,
Query,
Request,
ParseIntPipe,
BadRequestException,
} from "@nestjs/common";
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiQuery,
ApiBearerAuth,
ApiParam,
} from "@nestjs/swagger";
import { SubscriptionsService } from "./subscriptions.service";
import { Subscription, SubscriptionList, InvoiceList } from "@customer-portal/shared";
import { RequestWithUser } from "../auth/auth.types";
@ApiTags("subscriptions")
@Controller("subscriptions")
@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<SubscriptionList | Subscription[]> {
// 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<Subscription[]> {
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<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)",
})
@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<InvoiceList> {
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;
}
}