- 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.
110 lines
3.0 KiB
TypeScript
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');
|
|
}
|
|
}
|
|
}
|