/** * Tier Controller for SentryAgent.ai AgentIdP. * HTTP handlers for tier status and upgrade endpoints. * No business logic — delegates entirely to TierService. */ import { Request, Response, NextFunction } from 'express'; import { TierService } from '../services/TierService.js'; import { AuthenticationError, ValidationError } from '../utils/errors.js'; import { isTierName } from '../config/tiers.js'; /** * Controller for tenant tier management endpoints. * Receives TierService via constructor injection. */ export class TierController { /** * @param tierService - The tier management service. */ constructor(private readonly tierService: TierService) {} /** * Handles GET /api/tiers/status — returns the current tier, limits, and usage. * * Response: 200 ITierStatus * Errors: 401 when unauthenticated. * * @param req - Express request. Must have req.user populated. * @param res - Express response. * @param next - Express next function. */ getStatus = async (req: Request, res: Response, next: NextFunction): Promise => { try { if (!req.user) { throw new AuthenticationError(); } const orgId = req.user.organization_id; if (!orgId) { throw new AuthenticationError('organization_id is required in token.'); } const status = await this.tierService.getStatus(orgId); res.status(200).json(status); } catch (err) { next(err); } }; /** * Handles POST /api/tiers/upgrade — initiates a Stripe checkout session for a tier upgrade. * * Request body: { target_tier: 'pro' | 'enterprise' } * Response: 200 { checkoutUrl: string } * Errors: 400 when target_tier is missing/invalid or is not an upgrade. * 401 when unauthenticated. * * @param req - Express request. Must have req.user populated. * @param res - Express response. * @param next - Express next function. */ initiateUpgrade = async (req: Request, res: Response, next: NextFunction): Promise => { try { if (!req.user) { throw new AuthenticationError(); } const orgId = req.user.organization_id; if (!orgId) { throw new AuthenticationError('organization_id is required in token.'); } const body = req.body as { target_tier?: unknown }; const rawTargetTier = body.target_tier; if (!rawTargetTier || typeof rawTargetTier !== 'string') { throw new ValidationError('target_tier is required.', { received: rawTargetTier }); } if (!isTierName(rawTargetTier)) { throw new ValidationError( `target_tier must be one of: free, pro, enterprise.`, { received: rawTargetTier }, ); } const result = await this.tierService.initiateUpgrade(orgId, rawTargetTier); res.status(200).json(result); } catch (err) { next(err); } }; }