Add backend API for personal finance management application
- Introduced a comprehensive backend API using TypeScript, Fastify, and PostgreSQL. - Added essential files including architecture documentation, environment configuration, and Docker setup. - Implemented RESTful routes for managing assets, liabilities, clients, invoices, and cashflow. - Established a robust database schema with Prisma for data management. - Integrated middleware for authentication and error handling. - Created service and repository layers to adhere to SOLID principles and clean architecture. - Included example environment variables for development, staging, and production setups.
This commit is contained in:
559
backend-api/src/routes/debt.routes.ts
Normal file
559
backend-api/src/routes/debt.routes.ts
Normal file
@@ -0,0 +1,559 @@
|
||||
import {FastifyInstance} from 'fastify';
|
||||
import {DebtCategoryController} from '../controllers/DebtCategoryController';
|
||||
import {DebtCategoryService} from '../services/DebtCategoryService';
|
||||
import {DebtCategoryRepository} from '../repositories/DebtCategoryRepository';
|
||||
import {DebtAccountController} from '../controllers/DebtAccountController';
|
||||
import {DebtAccountService} from '../services/DebtAccountService';
|
||||
import {DebtAccountRepository} from '../repositories/DebtAccountRepository';
|
||||
import {DebtPaymentController} from '../controllers/DebtPaymentController';
|
||||
import {DebtPaymentService} from '../services/DebtPaymentService';
|
||||
import {DebtPaymentRepository} from '../repositories/DebtPaymentRepository';
|
||||
import {authenticate} from '../middleware/auth';
|
||||
|
||||
const categoryRepository = new DebtCategoryRepository();
|
||||
const categoryService = new DebtCategoryService(categoryRepository);
|
||||
const categoryController = new DebtCategoryController(categoryService);
|
||||
|
||||
const accountRepository = new DebtAccountRepository();
|
||||
const accountService = new DebtAccountService(accountRepository, categoryRepository);
|
||||
const accountController = new DebtAccountController(accountService);
|
||||
|
||||
const paymentRepository = new DebtPaymentRepository();
|
||||
const paymentService = new DebtPaymentService(paymentRepository, accountRepository);
|
||||
const paymentController = new DebtPaymentController(paymentService);
|
||||
|
||||
export async function debtRoutes(fastify: FastifyInstance) {
|
||||
// Apply authentication to all routes
|
||||
fastify.addHook('onRequest', authenticate);
|
||||
|
||||
/**
|
||||
* Get all debt categories
|
||||
*/
|
||||
fastify.get(
|
||||
'/categories',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get all debt categories for the authenticated user',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
withStats: {
|
||||
type: 'string',
|
||||
enum: ['true', 'false'],
|
||||
description: 'Include statistics for each category',
|
||||
},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'List of debt categories',
|
||||
type: 'object',
|
||||
properties: {
|
||||
categories: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
name: {type: 'string'},
|
||||
description: {type: 'string', nullable: true},
|
||||
color: {type: 'string', nullable: true},
|
||||
createdAt: {type: 'string'},
|
||||
updatedAt: {type: 'string'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
categoryController.getAll.bind(categoryController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get single debt category
|
||||
*/
|
||||
fastify.get(
|
||||
'/categories/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get a single debt category by ID',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'Debt category details',
|
||||
type: 'object',
|
||||
properties: {
|
||||
category: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
name: {type: 'string'},
|
||||
description: {type: 'string', nullable: true},
|
||||
color: {type: 'string', nullable: true},
|
||||
createdAt: {type: 'string'},
|
||||
updatedAt: {type: 'string'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
categoryController.getOne.bind(categoryController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Create debt category
|
||||
*/
|
||||
fastify.post(
|
||||
'/categories',
|
||||
{
|
||||
schema: {
|
||||
description: 'Create a new debt category',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: {type: 'string', minLength: 1, maxLength: 255},
|
||||
description: {type: 'string'},
|
||||
color: {type: 'string', pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
description: 'Debt category created successfully',
|
||||
type: 'object',
|
||||
properties: {
|
||||
category: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
categoryController.create.bind(categoryController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Update debt category
|
||||
*/
|
||||
fastify.put(
|
||||
'/categories/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Update a debt category',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {type: 'string', minLength: 1, maxLength: 255},
|
||||
description: {type: 'string'},
|
||||
color: {type: 'string', pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'Debt category updated successfully',
|
||||
type: 'object',
|
||||
properties: {
|
||||
category: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
categoryController.update.bind(categoryController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Delete debt category
|
||||
*/
|
||||
fastify.delete(
|
||||
'/categories/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Delete a debt category',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: 'Debt category deleted successfully',
|
||||
type: 'null',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
categoryController.delete.bind(categoryController)
|
||||
);
|
||||
|
||||
// ===== Debt Account Routes =====
|
||||
|
||||
/**
|
||||
* Get all debt accounts
|
||||
*/
|
||||
fastify.get(
|
||||
'/accounts',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get all debt accounts for the authenticated user',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
withStats: {type: 'string', enum: ['true', 'false']},
|
||||
categoryId: {type: 'string', description: 'Filter by category ID'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'List of debt accounts',
|
||||
type: 'object',
|
||||
properties: {
|
||||
accounts: {type: 'array', items: {type: 'object'}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountController.getAll.bind(accountController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get total debt
|
||||
*/
|
||||
fastify.get(
|
||||
'/accounts/total',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get total debt across all accounts',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
response: {
|
||||
200: {
|
||||
description: 'Total debt',
|
||||
type: 'object',
|
||||
properties: {
|
||||
totalDebt: {type: 'number'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountController.getTotalDebt.bind(accountController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get single debt account
|
||||
*/
|
||||
fastify.get(
|
||||
'/accounts/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get a single debt account by ID',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'Debt account details',
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountController.getOne.bind(accountController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Create debt account
|
||||
*/
|
||||
fastify.post(
|
||||
'/accounts',
|
||||
{
|
||||
schema: {
|
||||
description: 'Create a new debt account',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['categoryId', 'name', 'creditor', 'originalBalance', 'currentBalance'],
|
||||
properties: {
|
||||
categoryId: {type: 'string', format: 'uuid'},
|
||||
name: {type: 'string', minLength: 1, maxLength: 255},
|
||||
creditor: {type: 'string', minLength: 1, maxLength: 255},
|
||||
accountNumber: {type: 'string', maxLength: 100},
|
||||
originalBalance: {type: 'number', minimum: 0},
|
||||
currentBalance: {type: 'number', minimum: 0},
|
||||
interestRate: {type: 'number', minimum: 0, maximum: 100},
|
||||
minimumPayment: {type: 'number', minimum: 0},
|
||||
dueDate: {type: 'string', format: 'date-time'},
|
||||
notes: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
description: 'Debt account created successfully',
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountController.create.bind(accountController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Update debt account
|
||||
*/
|
||||
fastify.put(
|
||||
'/accounts/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Update a debt account',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {type: 'string', minLength: 1, maxLength: 255},
|
||||
creditor: {type: 'string', minLength: 1, maxLength: 255},
|
||||
accountNumber: {type: 'string', maxLength: 100},
|
||||
currentBalance: {type: 'number', minimum: 0},
|
||||
interestRate: {type: 'number', minimum: 0, maximum: 100},
|
||||
minimumPayment: {type: 'number', minimum: 0},
|
||||
dueDate: {type: 'string', format: 'date-time'},
|
||||
notes: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'Debt account updated successfully',
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountController.update.bind(accountController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Delete debt account
|
||||
*/
|
||||
fastify.delete(
|
||||
'/accounts/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Delete a debt account',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: 'Debt account deleted successfully',
|
||||
type: 'null',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountController.delete.bind(accountController)
|
||||
);
|
||||
|
||||
// ===== Debt Payment Routes =====
|
||||
|
||||
/**
|
||||
* Get all debt payments
|
||||
*/
|
||||
fastify.get(
|
||||
'/payments',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get all debt payments for the authenticated user',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
accountId: {type: 'string', description: 'Filter by account ID'},
|
||||
startDate: {type: 'string', format: 'date-time'},
|
||||
endDate: {type: 'string', format: 'date-time'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'List of debt payments',
|
||||
type: 'object',
|
||||
properties: {
|
||||
payments: {type: 'array', items: {type: 'object'}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
paymentController.getAll.bind(paymentController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get total payments
|
||||
*/
|
||||
fastify.get(
|
||||
'/payments/total',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get total payments made across all accounts',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
response: {
|
||||
200: {
|
||||
description: 'Total payments',
|
||||
type: 'object',
|
||||
properties: {
|
||||
totalPayments: {type: 'number'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
paymentController.getTotalPayments.bind(paymentController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get single debt payment
|
||||
*/
|
||||
fastify.get(
|
||||
'/payments/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Get a single debt payment by ID',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: 'Debt payment details',
|
||||
type: 'object',
|
||||
properties: {
|
||||
payment: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
paymentController.getOne.bind(paymentController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Create debt payment
|
||||
*/
|
||||
fastify.post(
|
||||
'/payments',
|
||||
{
|
||||
schema: {
|
||||
description: 'Create a new debt payment',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['accountId', 'amount', 'paymentDate'],
|
||||
properties: {
|
||||
accountId: {type: 'string', format: 'uuid'},
|
||||
amount: {type: 'number', minimum: 0.01},
|
||||
paymentDate: {type: 'string', format: 'date-time'},
|
||||
notes: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
description: 'Debt payment created successfully',
|
||||
type: 'object',
|
||||
properties: {
|
||||
payment: {type: 'object'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
paymentController.create.bind(paymentController)
|
||||
);
|
||||
|
||||
/**
|
||||
* Delete debt payment
|
||||
*/
|
||||
fastify.delete(
|
||||
'/payments/:id',
|
||||
{
|
||||
schema: {
|
||||
description: 'Delete a debt payment',
|
||||
tags: ['Debts'],
|
||||
security: [{bearerAuth: []}],
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
},
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: 'Debt payment deleted successfully',
|
||||
type: 'null',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
paymentController.delete.bind(paymentController)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user