Files
flyer-crawler.projectium.com/src/controllers/budget.controller.ts
Torben Sorensen 2d2cd52011
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 3m58s
Massive Dependency Modernization Project
2026-02-13 00:34:22 -08:00

234 lines
7.9 KiB
TypeScript

// src/controllers/budget.controller.ts
// ============================================================================
// BUDGET CONTROLLER
// ============================================================================
// Provides endpoints for managing user budgets, including CRUD operations
// and spending analysis. All endpoints require authentication.
//
// Implements ADR-028 (API Response Format) via BaseController.
// ============================================================================
import {
Get,
Post,
Put,
Delete,
Route,
Tags,
Security,
Body,
Path,
Query,
Request,
SuccessResponse,
Response,
} from 'tsoa';
import type { Request as ExpressRequest } from 'express';
import { BaseController } from './base.controller';
import type { SuccessResponse as SuccessResponseType, ErrorResponse } from './types';
import { budgetRepo } from '../services/db/index.db';
import type { Budget, SpendingByCategory, UserProfile } from '../types';
// ============================================================================
// REQUEST/RESPONSE TYPES
// ============================================================================
/**
* Request body for creating a new budget.
*/
interface CreateBudgetRequest {
/** Budget name */
name: string;
/** Budget amount in cents (must be positive) */
amount_cents: number;
/** Budget period - weekly or monthly */
period: 'weekly' | 'monthly';
/** Budget start date in YYYY-MM-DD format */
start_date: string;
}
/**
* Request body for updating a budget.
* All fields are optional, but at least one must be provided.
*/
interface UpdateBudgetRequest {
/** Budget name */
name?: string;
/** Budget amount in cents (must be positive) */
amount_cents?: number;
/** Budget period - weekly or monthly */
period?: 'weekly' | 'monthly';
/** Budget start date in YYYY-MM-DD format */
start_date?: string;
}
// ============================================================================
// BUDGET CONTROLLER
// ============================================================================
/**
* Controller for managing user budgets.
*
* All endpoints require JWT authentication. Users can only access
* their own budgets - the user ID is extracted from the JWT token.
*/
@Route('budgets')
@Tags('Budgets')
@Security('bearerAuth')
export class BudgetController extends BaseController {
// ==========================================================================
// LIST BUDGETS
// ==========================================================================
/**
* Get all budgets for the authenticated user.
*
* Returns a list of all budgets owned by the authenticated user,
* ordered by start date descending (newest first).
*
* @summary Get all budgets
* @param request Express request with authenticated user
* @returns List of user budgets
*/
@Get()
@SuccessResponse(200, 'List of user budgets')
@Response<ErrorResponse>(401, 'Unauthorized - invalid or missing token')
public async getBudgets(
@Request() request: ExpressRequest,
): Promise<SuccessResponseType<Budget[]>> {
const userProfile = request.user as UserProfile;
const budgets = await budgetRepo.getBudgetsForUser(userProfile.user.user_id, request.log);
return this.success(budgets);
}
// ==========================================================================
// CREATE BUDGET
// ==========================================================================
/**
* Create a new budget for the authenticated user.
*
* Creates a budget with the specified name, amount, period, and start date.
* The budget is automatically associated with the authenticated user.
*
* @summary Create budget
* @param request Express request with authenticated user
* @param body Budget creation data
* @returns The newly created budget
*/
@Post()
@SuccessResponse(201, 'Budget created')
@Response<ErrorResponse>(400, 'Validation error')
@Response<ErrorResponse>(401, 'Unauthorized - invalid or missing token')
public async createBudget(
@Request() request: ExpressRequest,
@Body() body: CreateBudgetRequest,
): Promise<SuccessResponseType<Budget>> {
const userProfile = request.user as UserProfile;
const newBudget = await budgetRepo.createBudget(userProfile.user.user_id, body, request.log);
return this.created(newBudget);
}
// ==========================================================================
// UPDATE BUDGET
// ==========================================================================
/**
* Update an existing budget.
*
* Updates the specified budget with the provided fields. At least one
* field must be provided. The user must own the budget to update it.
*
* @summary Update budget
* @param id Budget ID
* @param request Express request with authenticated user
* @param body Fields to update
* @returns The updated budget
*/
@Put('{id}')
@SuccessResponse(200, 'Budget updated')
@Response<ErrorResponse>(400, 'Validation error - at least one field required')
@Response<ErrorResponse>(401, 'Unauthorized - invalid or missing token')
@Response<ErrorResponse>(404, 'Budget not found')
public async updateBudget(
@Path() id: number,
@Request() request: ExpressRequest,
@Body() body: UpdateBudgetRequest,
): Promise<SuccessResponseType<Budget>> {
const userProfile = request.user as UserProfile;
// Validate at least one field is provided
if (Object.keys(body).length === 0) {
this.setStatus(400);
throw new Error('At least one field to update must be provided.');
}
const updatedBudget = await budgetRepo.updateBudget(
id,
userProfile.user.user_id,
body,
request.log,
);
return this.success(updatedBudget);
}
// ==========================================================================
// DELETE BUDGET
// ==========================================================================
/**
* Delete a budget.
*
* Permanently deletes the specified budget. The user must own
* the budget to delete it.
*
* @summary Delete budget
* @param id Budget ID
* @param request Express request with authenticated user
*/
@Delete('{id}')
@SuccessResponse(204, 'Budget deleted')
@Response<ErrorResponse>(401, 'Unauthorized - invalid or missing token')
@Response<ErrorResponse>(404, 'Budget not found')
public async deleteBudget(@Path() id: number, @Request() request: ExpressRequest): Promise<void> {
const userProfile = request.user as UserProfile;
await budgetRepo.deleteBudget(id, userProfile.user.user_id, request.log);
return this.noContent();
}
// ==========================================================================
// SPENDING ANALYSIS
// ==========================================================================
/**
* Get spending analysis by category.
*
* Returns a breakdown of spending by category for the specified date range.
* This helps users understand their spending patterns relative to their budgets.
*
* @summary Get spending analysis
* @param startDate Start date in YYYY-MM-DD format
* @param endDate End date in YYYY-MM-DD format
* @param request Express request with authenticated user
* @returns Spending breakdown by category
*/
@Get('spending-analysis')
@SuccessResponse(200, 'Spending breakdown by category')
@Response<ErrorResponse>(400, 'Invalid date format')
@Response<ErrorResponse>(401, 'Unauthorized - invalid or missing token')
public async getSpendingAnalysis(
@Query() startDate: string,
@Query() endDate: string,
@Request() request: ExpressRequest,
): Promise<SuccessResponseType<SpendingByCategory[]>> {
const userProfile = request.user as UserProfile;
const spendingData = await budgetRepo.getSpendingByCategory(
userProfile.user.user_id,
startDate,
endDate,
request.log,
);
return this.success(spendingData);
}
}