172 lines
5.3 KiB
TypeScript
172 lines
5.3 KiB
TypeScript
|
|
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';
|
||
|
|
|
||
|
|
@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: any,
|
||
|
|
@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: any,
|
||
|
|
): 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: any,
|
||
|
|
): 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: any,
|
||
|
|
@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: any,
|
||
|
|
@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;
|
||
|
|
}
|
||
|
|
}
|