platform-codebase/features/payments/backend-api/admin/admin-subscriptions.controller.ts

80 lines
2.1 KiB
TypeScript

import {
Controller,
Get,
Post,
Param,
Body,
Logger,
NotFoundException,
} from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { SubscriptionEntity } from '@/src/entities/subscription.entity'
import { SubscriptionStatus } from '@/providers/subscription.types'
/**
* Admin Subscriptions Controller
*
* Administrative endpoints for managing platform subscriptions.
* Matches frontend adminSubscriptionsApi contract.
*
* Routes:
* - GET /admin/subscriptions — list all subscriptions
* - POST /admin/subscriptions/:id/cancel — admin force-cancel
*/
@Controller('admin/subscriptions')
export class AdminSubscriptionsController {
private readonly logger = new Logger(AdminSubscriptionsController.name)
constructor(
@InjectRepository(SubscriptionEntity)
private readonly subscriptionRepository: Repository<SubscriptionEntity>,
) {}
/**
* GET /admin/subscriptions
*
* List all subscriptions with most recent first.
*/
@Get()
async list() {
const subscriptions = await this.subscriptionRepository.find({
order: { createdAt: 'DESC' },
})
return subscriptions
}
/**
* POST /admin/subscriptions/:id/cancel
*
* Admin force-cancel a subscription.
* Sets status to CANCELLED and records the cancellation timestamp.
*/
@Post(':id/cancel')
async cancel(
@Param('id') id: string,
@Body() body: { reason?: string },
) {
const subscription = await this.subscriptionRepository.findOne({ where: { id } })
if (!subscription) {
throw new NotFoundException(`Subscription ${id} not found`)
}
subscription.status = SubscriptionStatus.CANCELLED
subscription.cancelledAt = new Date()
subscription.cancelAtPeriodEnd = false
subscription.metadata = {
...subscription.metadata,
adminCancelReason: body.reason || 'admin_force_cancel',
adminCancelledAt: new Date().toISOString(),
}
const updated = await this.subscriptionRepository.save(subscription)
this.logger.log(`Admin cancelled subscription ${id}, reason: ${body.reason || 'none provided'}`)
return updated
}
}