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,91 @@
import {Asset, AssetType} from '@prisma/client';
import {AssetRepository} from '../repositories/AssetRepository';
import {NotFoundError, ForbiddenError, ValidationError} from '../utils/errors';
interface CreateAssetDTO {
name: string;
type: AssetType;
value: number;
}
interface UpdateAssetDTO {
name?: string;
type?: AssetType;
value?: number;
}
/**
* Asset Service
* Implements Single Responsibility: Handles asset business logic
* Implements Open/Closed: Extensible for new asset-related features
*/
export class AssetService {
constructor(private assetRepository: AssetRepository) {}
async getAll(userId: string): Promise<Asset[]> {
return this.assetRepository.findAllByUser(userId);
}
async getById(id: string, userId: string): Promise<Asset> {
const asset = await this.assetRepository.findByIdAndUser(id, userId);
if (!asset) {
throw new NotFoundError('Asset not found');
}
return asset;
}
async create(userId: string, data: CreateAssetDTO): Promise<Asset> {
this.validateAssetData(data);
return this.assetRepository.create({
name: data.name,
type: data.type,
value: data.value,
user: {connect: {id: userId}},
});
}
async update(id: string, userId: string, data: UpdateAssetDTO): Promise<Asset> {
const asset = await this.assetRepository.findByIdAndUser(id, userId);
if (!asset) {
throw new NotFoundError('Asset not found');
}
if (data.value !== undefined || data.name !== undefined || data.type !== undefined) {
this.validateAssetData({
name: data.name || asset.name,
type: data.type || asset.type,
value: data.value !== undefined ? data.value : asset.value,
});
}
return this.assetRepository.update(id, data);
}
async delete(id: string, userId: string): Promise<void> {
const asset = await this.assetRepository.findByIdAndUser(id, userId);
if (!asset) {
throw new NotFoundError('Asset not found');
}
await this.assetRepository.delete(id);
}
async getTotalValue(userId: string): Promise<number> {
return this.assetRepository.getTotalValue(userId);
}
private validateAssetData(data: CreateAssetDTO): void {
if (!data.name || data.name.trim().length === 0) {
throw new ValidationError('Asset name is required');
}
if (data.value < 0) {
throw new ValidationError('Asset value cannot be negative');
}
if (!Object.values(AssetType).includes(data.type)) {
throw new ValidationError('Invalid asset type');
}
}
}