Files
personal-finance/backend-api/src/services/AssetService.ts
Alexander Zinn 40210c454e Add lock files for package management and update architecture documentation
- Introduced bun.lock and package-lock.json to manage dependencies for the project.
- Enhanced backend API architecture documentation with additional security and documentation guidelines.
- Made minor formatting adjustments across various files for consistency and clarity.
2025-12-11 02:11:43 -05:00

110 lines
3.0 KiB
TypeScript

import type {Asset} from '@prisma/client';
import {AssetRepository} from '../repositories/AssetRepository';
import {NotFoundError, ValidationError} from '../utils/errors';
type AssetType = 'cash' | 'investment' | 'property' | 'vehicle' | 'other';
const VALID_ASSET_TYPES: AssetType[] = ['cash', 'investment', 'property', 'vehicle', 'other'];
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) as AssetType,
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);
}
async getByType(userId: string): Promise<Record<string, Asset[]>> {
const assets = await this.assetRepository.findAllByUser(userId);
return assets.reduce(
(acc, asset) => {
const type = asset.type;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push(asset);
return acc;
},
{} as Record<string, Asset[]>
);
}
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 (!VALID_ASSET_TYPES.includes(data.type)) {
throw new ValidationError('Invalid asset type');
}
}
}