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:
137
backend-api/src/controllers/InvoiceController.ts
Normal file
137
backend-api/src/controllers/InvoiceController.ts
Normal 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});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user