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:
2025-12-07 12:59:09 -05:00
parent 9d493ba82f
commit cd93dcbfd2
70 changed files with 8649 additions and 6 deletions

View File

@@ -0,0 +1,137 @@
import {FastifyRequest, FastifyReply} from 'fastify';
import {InvoiceService} from '../services/InvoiceService';
import {getUserId} from '../middleware/auth';
import {z} from 'zod';
const lineItemSchema = z.object({
description: z.string().min(1),
quantity: z.number().min(1),
unitPrice: z.number().min(0),
amount: z.number().min(0),
});
const createInvoiceSchema = z.object({
clientId: z.string().uuid(),
issueDate: z.string().transform(str => new Date(str)),
dueDate: z.string().transform(str => new Date(str)),
lineItems: z.array(lineItemSchema).min(1),
notes: z.string().optional(),
terms: z.string().optional(),
});
const updateInvoiceSchema = z.object({
issueDate: z.string().transform(str => new Date(str)).optional(),
dueDate: z.string().transform(str => new Date(str)).optional(),
lineItems: z.array(lineItemSchema).min(1).optional(),
notes: z.string().optional(),
terms: z.string().optional(),
});
const updateStatusSchema = z.object({
status: z.enum(['DRAFT', 'SENT', 'PAID', 'OVERDUE', 'CANCELLED']),
});
/**
* Controller for Invoice endpoints
* Implements Single Responsibility Principle - handles only HTTP layer
*/
export class InvoiceController {
constructor(private invoiceService: InvoiceService) {}
/**
* Create a new invoice
*/
async create(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const data = createInvoiceSchema.parse(request.body);
const invoice = await this.invoiceService.create(userId, data);
return reply.status(201).send({invoice});
}
/**
* Get all invoices for the authenticated user
*/
async getAll(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const {clientId, status} = request.query as {clientId?: string; status?: string};
const invoices = await this.invoiceService.getAllByUser(userId, {
clientId,
status,
});
return reply.send({invoices});
}
/**
* Get a single invoice by ID
*/
async getOne(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const {id} = request.params as {id: string};
const invoice = await this.invoiceService.getById(id, userId);
return reply.send({invoice});
}
/**
* Update an invoice
*/
async update(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const {id} = request.params as {id: string};
const data = updateInvoiceSchema.parse(request.body);
const invoice = await this.invoiceService.update(id, userId, data);
return reply.send({invoice});
}
/**
* Update invoice status
*/
async updateStatus(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const {id} = request.params as {id: string};
const {status} = updateStatusSchema.parse(request.body);
const invoice = await this.invoiceService.updateStatus(id, userId, status);
return reply.send({invoice});
}
/**
* Delete an invoice
*/
async delete(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const {id} = request.params as {id: string};
await this.invoiceService.delete(id, userId);
return reply.status(204).send();
}
/**
* Get invoice statistics
*/
async getStats(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const stats = await this.invoiceService.getStats(userId);
return reply.send({stats});
}
/**
* Get overdue invoices
*/
async getOverdue(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
const overdueInvoices = await this.invoiceService.getOverdueInvoices(userId);
return reply.send({invoices: overdueInvoices});
}
}