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,149 @@
import {IncomeSource, Expense, Transaction, Prisma} from '@prisma/client';
import {DatabaseConnection} from '../config/database';
import {IUserScopedRepository} from './interfaces/IRepository';
const prisma = DatabaseConnection.getInstance();
/**
* Repository for IncomeSource data access
*/
export class IncomeSourceRepository implements IUserScopedRepository<IncomeSource> {
async findById(id: string): Promise<IncomeSource | null> {
return prisma.incomeSource.findUnique({where: {id}});
}
async findAllByUser(userId: string): Promise<IncomeSource[]> {
return prisma.incomeSource.findMany({
where: {userId},
orderBy: {createdAt: 'desc'},
});
}
async create(data: Prisma.IncomeSourceCreateInput): Promise<IncomeSource> {
return prisma.incomeSource.create({data});
}
async update(id: string, data: Prisma.IncomeSourceUpdateInput): Promise<IncomeSource> {
return prisma.incomeSource.update({where: {id}, data});
}
async delete(id: string): Promise<void> {
await prisma.incomeSource.delete({where: {id}});
}
async getTotalMonthlyIncome(userId: string): Promise<number> {
const result = await prisma.incomeSource.aggregate({
where: {userId},
_sum: {amount: true},
});
return result._sum.amount || 0;
}
}
/**
* Repository for Expense data access
*/
export class ExpenseRepository implements IUserScopedRepository<Expense> {
async findById(id: string): Promise<Expense | null> {
return prisma.expense.findUnique({where: {id}});
}
async findAllByUser(userId: string): Promise<Expense[]> {
return prisma.expense.findMany({
where: {userId},
orderBy: {createdAt: 'desc'},
});
}
async create(data: Prisma.ExpenseCreateInput): Promise<Expense> {
return prisma.expense.create({data});
}
async update(id: string, data: Prisma.ExpenseUpdateInput): Promise<Expense> {
return prisma.expense.update({where: {id}, data});
}
async delete(id: string): Promise<void> {
await prisma.expense.delete({where: {id}});
}
async getTotalMonthlyExpenses(userId: string): Promise<number> {
const result = await prisma.expense.aggregate({
where: {userId},
_sum: {amount: true},
});
return result._sum.amount || 0;
}
async getByCategory(userId: string): Promise<Record<string, Expense[]>> {
const expenses = await this.findAllByUser(userId);
return expenses.reduce((acc, expense) => {
if (!acc[expense.category]) acc[expense.category] = [];
acc[expense.category].push(expense);
return acc;
}, {} as Record<string, Expense[]>);
}
}
/**
* Repository for Transaction data access
*/
export class TransactionRepository implements IUserScopedRepository<Transaction> {
async findById(id: string): Promise<Transaction | null> {
return prisma.transaction.findUnique({where: {id}});
}
async findAllByUser(userId: string): Promise<Transaction[]> {
return prisma.transaction.findMany({
where: {userId},
orderBy: {date: 'desc'},
});
}
async create(data: Prisma.TransactionCreateInput): Promise<Transaction> {
return prisma.transaction.create({data});
}
async delete(id: string): Promise<void> {
await prisma.transaction.delete({where: {id}});
}
async getByDateRange(userId: string, startDate: Date, endDate: Date): Promise<Transaction[]> {
return prisma.transaction.findMany({
where: {
userId,
date: {gte: startDate, lte: endDate},
},
orderBy: {date: 'desc'},
});
}
async getByType(userId: string, type: string): Promise<Transaction[]> {
return prisma.transaction.findMany({
where: {userId, type},
orderBy: {date: 'desc'},
});
}
async getCashflowSummary(userId: string, startDate: Date, endDate: Date): Promise<{
totalIncome: number;
totalExpenses: number;
netCashflow: number;
}> {
const transactions = await this.getByDateRange(userId, startDate, endDate);
const totalIncome = transactions
.filter(t => t.type === 'INCOME')
.reduce((sum, t) => sum + t.amount, 0);
const totalExpenses = transactions
.filter(t => t.type === 'EXPENSE')
.reduce((sum, t) => sum + t.amount, 0);
return {
totalIncome,
totalExpenses,
netCashflow: totalIncome - totalExpenses,
};
}
}