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:
91
backend-api/src/services/AssetService.ts
Normal file
91
backend-api/src/services/AssetService.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user