Update backend API configuration and schema for improved functionality
- Modified TypeScript configuration to disable strict mode and allow importing TypeScript extensions. - Updated Prisma schema to enhance the User model with a new debtAccounts field and refined asset/liability types. - Adjusted environment variable parsing for PORT to use coercion for better type handling. - Refactored various controllers and repositories to utilize type imports for better clarity and maintainability. - Enhanced service layers with new methods for retrieving assets by type and calculating invoice statistics. - Introduced new types for invoice statuses and asset types to ensure consistency across the application.
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import {Asset, Prisma} from '@prisma/client';
|
||||
import type {Asset, Prisma} from '@prisma/client';
|
||||
import {prisma} from '../config/database';
|
||||
import {IUserScopedRepository} from './interfaces/IRepository';
|
||||
|
||||
/**
|
||||
* Asset Repository
|
||||
* Implements Single Responsibility: Only handles Asset data access
|
||||
*/
|
||||
export class AssetRepository implements IUserScopedRepository<Asset> {
|
||||
export class AssetRepository {
|
||||
async findById(id: string): Promise<Asset | null> {
|
||||
return prisma.asset.findUnique({where: {id}});
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import {IncomeSource, Expense, Transaction, Prisma} from '@prisma/client';
|
||||
import type {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> {
|
||||
export class IncomeSourceRepository {
|
||||
async findById(id: string): Promise<IncomeSource | null> {
|
||||
return prisma.incomeSource.findUnique({where: {id}});
|
||||
}
|
||||
|
||||
async findByIdAndUser(id: string, userId: string): Promise<IncomeSource | null> {
|
||||
return prisma.incomeSource.findFirst({where: {id, userId}});
|
||||
}
|
||||
|
||||
async findAllByUser(userId: string): Promise<IncomeSource[]> {
|
||||
return prisma.incomeSource.findMany({
|
||||
where: {userId},
|
||||
@@ -43,11 +46,15 @@ export class IncomeSourceRepository implements IUserScopedRepository<IncomeSourc
|
||||
/**
|
||||
* Repository for Expense data access
|
||||
*/
|
||||
export class ExpenseRepository implements IUserScopedRepository<Expense> {
|
||||
export class ExpenseRepository {
|
||||
async findById(id: string): Promise<Expense | null> {
|
||||
return prisma.expense.findUnique({where: {id}});
|
||||
}
|
||||
|
||||
async findByIdAndUser(id: string, userId: string): Promise<Expense | null> {
|
||||
return prisma.expense.findFirst({where: {id, userId}});
|
||||
}
|
||||
|
||||
async findAllByUser(userId: string): Promise<Expense[]> {
|
||||
return prisma.expense.findMany({
|
||||
where: {userId},
|
||||
@@ -88,11 +95,15 @@ export class ExpenseRepository implements IUserScopedRepository<Expense> {
|
||||
/**
|
||||
* Repository for Transaction data access
|
||||
*/
|
||||
export class TransactionRepository implements IUserScopedRepository<Transaction> {
|
||||
export class TransactionRepository {
|
||||
async findById(id: string): Promise<Transaction | null> {
|
||||
return prisma.transaction.findUnique({where: {id}});
|
||||
}
|
||||
|
||||
async findByIdAndUser(id: string, userId: string): Promise<Transaction | null> {
|
||||
return prisma.transaction.findFirst({where: {id, userId}});
|
||||
}
|
||||
|
||||
async findAllByUser(userId: string): Promise<Transaction[]> {
|
||||
return prisma.transaction.findMany({
|
||||
where: {userId},
|
||||
@@ -104,6 +115,10 @@ export class TransactionRepository implements IUserScopedRepository<Transaction>
|
||||
return prisma.transaction.create({data});
|
||||
}
|
||||
|
||||
async update(id: string, data: Prisma.TransactionUpdateInput): Promise<Transaction> {
|
||||
return prisma.transaction.update({where: {id}, data});
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await prisma.transaction.delete({where: {id}});
|
||||
}
|
||||
@@ -133,11 +148,11 @@ export class TransactionRepository implements IUserScopedRepository<Transaction>
|
||||
const transactions = await this.getByDateRange(userId, startDate, endDate);
|
||||
|
||||
const totalIncome = transactions
|
||||
.filter(t => t.type === 'INCOME')
|
||||
.filter(t => t.type === 'income')
|
||||
.reduce((sum, t) => sum + t.amount, 0);
|
||||
|
||||
const totalExpenses = transactions
|
||||
.filter(t => t.type === 'EXPENSE')
|
||||
.filter(t => t.type === 'expense')
|
||||
.reduce((sum, t) => sum + t.amount, 0);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {Client, Prisma} from '@prisma/client';
|
||||
import type {Client, Prisma} from '@prisma/client';
|
||||
import {DatabaseConnection} from '../config/database';
|
||||
import {IUserScopedRepository} from './interfaces/IRepository';
|
||||
|
||||
const prisma = DatabaseConnection.getInstance();
|
||||
|
||||
@@ -8,7 +7,7 @@ const prisma = DatabaseConnection.getInstance();
|
||||
* Repository for Client data access
|
||||
* Implements Single Responsibility Principle - handles only database operations
|
||||
*/
|
||||
export class ClientRepository implements IUserScopedRepository<Client> {
|
||||
export class ClientRepository {
|
||||
async findById(id: string): Promise<Client | null> {
|
||||
return prisma.client.findUnique({
|
||||
where: {id},
|
||||
@@ -18,6 +17,15 @@ export class ClientRepository implements IUserScopedRepository<Client> {
|
||||
});
|
||||
}
|
||||
|
||||
async findByIdAndUser(id: string, userId: string): Promise<Client | null> {
|
||||
return prisma.client.findFirst({
|
||||
where: {id, userId},
|
||||
include: {
|
||||
invoices: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAllByUser(userId: string): Promise<Client[]> {
|
||||
return prisma.client.findMany({
|
||||
where: {userId},
|
||||
@@ -76,7 +84,7 @@ export class ClientRepository implements IUserScopedRepository<Client> {
|
||||
client: {
|
||||
userId,
|
||||
},
|
||||
status: 'PAID',
|
||||
status: 'paid',
|
||||
},
|
||||
_sum: {
|
||||
total: true,
|
||||
@@ -108,12 +116,12 @@ export class ClientRepository implements IUserScopedRepository<Client> {
|
||||
...client,
|
||||
stats: {
|
||||
totalInvoices: client.invoices.length,
|
||||
paidInvoices: client.invoices.filter(inv => inv.status === 'PAID').length,
|
||||
paidInvoices: client.invoices.filter(inv => inv.status === 'paid').length,
|
||||
totalRevenue: client.invoices
|
||||
.filter(inv => inv.status === 'PAID')
|
||||
.filter(inv => inv.status === 'paid')
|
||||
.reduce((sum, inv) => sum + inv.total, 0),
|
||||
outstandingAmount: client.invoices
|
||||
.filter(inv => inv.status !== 'PAID')
|
||||
.filter(inv => inv.status !== 'paid')
|
||||
.reduce((sum, inv) => sum + inv.total, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DebtAccount, Prisma} from '@prisma/client';
|
||||
import type {DebtAccount, Prisma} from '@prisma/client';
|
||||
import {DatabaseConnection} from '../config/database';
|
||||
|
||||
const prisma = DatabaseConnection.getInstance();
|
||||
@@ -14,7 +14,19 @@ export class DebtAccountRepository {
|
||||
include: {
|
||||
category: true,
|
||||
payments: {
|
||||
orderBy: {paymentDate: 'desc'},
|
||||
orderBy: {date: 'desc'},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByIdAndUser(id: string, userId: string): Promise<DebtAccount | null> {
|
||||
return prisma.debtAccount.findFirst({
|
||||
where: {id, userId},
|
||||
include: {
|
||||
category: true,
|
||||
payments: {
|
||||
orderBy: {date: 'desc'},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -22,15 +34,11 @@ export class DebtAccountRepository {
|
||||
|
||||
async findAllByUser(userId: string): Promise<DebtAccount[]> {
|
||||
return prisma.debtAccount.findMany({
|
||||
where: {
|
||||
category: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
where: {userId},
|
||||
include: {
|
||||
category: true,
|
||||
payments: {
|
||||
orderBy: {paymentDate: 'desc'},
|
||||
orderBy: {date: 'desc'},
|
||||
},
|
||||
},
|
||||
orderBy: {createdAt: 'desc'},
|
||||
@@ -42,7 +50,7 @@ export class DebtAccountRepository {
|
||||
where: {categoryId},
|
||||
include: {
|
||||
payments: {
|
||||
orderBy: {paymentDate: 'desc'},
|
||||
orderBy: {date: 'desc'},
|
||||
},
|
||||
},
|
||||
orderBy: {createdAt: 'desc'},
|
||||
@@ -81,11 +89,7 @@ export class DebtAccountRepository {
|
||||
*/
|
||||
async getTotalDebt(userId: string): Promise<number> {
|
||||
const result = await prisma.debtAccount.aggregate({
|
||||
where: {
|
||||
category: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
where: {userId},
|
||||
_sum: {
|
||||
currentBalance: true,
|
||||
},
|
||||
@@ -98,10 +102,18 @@ export class DebtAccountRepository {
|
||||
* Get accounts with payment statistics
|
||||
*/
|
||||
async getWithStats(userId: string): Promise<any[]> {
|
||||
const accounts = await this.findAllByUser(userId);
|
||||
const accounts = await prisma.debtAccount.findMany({
|
||||
where: {userId},
|
||||
include: {
|
||||
category: true,
|
||||
payments: {
|
||||
orderBy: {date: 'desc'},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return accounts.map(account => {
|
||||
const totalPaid = account.payments.reduce((sum, payment) => sum + payment.amount, 0);
|
||||
const totalPaid = account.payments.reduce((sum: number, payment: {amount: number}) => sum + payment.amount, 0);
|
||||
const lastPayment = account.payments[0];
|
||||
|
||||
return {
|
||||
@@ -109,7 +121,7 @@ export class DebtAccountRepository {
|
||||
stats: {
|
||||
totalPaid,
|
||||
numberOfPayments: account.payments.length,
|
||||
lastPaymentDate: lastPayment?.paymentDate || null,
|
||||
lastPaymentDate: lastPayment?.date || null,
|
||||
lastPaymentAmount: lastPayment?.amount || null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DebtPayment, Prisma} from '@prisma/client';
|
||||
import type {DebtPayment, Prisma} from '@prisma/client';
|
||||
import {DatabaseConnection} from '../config/database';
|
||||
|
||||
const prisma = DatabaseConnection.getInstance();
|
||||
@@ -24,7 +24,7 @@ export class DebtPaymentRepository {
|
||||
async findByAccount(accountId: string): Promise<DebtPayment[]> {
|
||||
return prisma.debtPayment.findMany({
|
||||
where: {accountId},
|
||||
orderBy: {paymentDate: 'desc'},
|
||||
orderBy: {date: 'desc'},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class DebtPaymentRepository {
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {paymentDate: 'desc'},
|
||||
orderBy: {date: 'desc'},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export class DebtPaymentRepository {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
paymentDate: {
|
||||
date: {
|
||||
gte: startDate,
|
||||
lte: endDate,
|
||||
},
|
||||
@@ -124,7 +124,7 @@ export class DebtPaymentRepository {
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {paymentDate: 'desc'},
|
||||
orderBy: {date: 'desc'},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Invoice, Prisma, InvoiceStatus} from '@prisma/client';
|
||||
import type {Invoice, Prisma} from '@prisma/client';
|
||||
type InvoiceStatus = 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled';
|
||||
import {prisma} from '../config/database';
|
||||
import {IUserScopedRepository} from './interfaces/IRepository';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {Liability, Prisma} from '@prisma/client';
|
||||
import type {Liability, Prisma} from '@prisma/client';
|
||||
import {DatabaseConnection} from '../config/database';
|
||||
import {IUserScopedRepository} from './interfaces/IRepository';
|
||||
|
||||
const prisma = DatabaseConnection.getInstance();
|
||||
|
||||
@@ -8,13 +7,19 @@ const prisma = DatabaseConnection.getInstance();
|
||||
* Repository for Liability data access
|
||||
* Implements Single Responsibility Principle - handles only database operations
|
||||
*/
|
||||
export class LiabilityRepository implements IUserScopedRepository<Liability> {
|
||||
export class LiabilityRepository {
|
||||
async findById(id: string): Promise<Liability | null> {
|
||||
return prisma.liability.findUnique({
|
||||
where: {id},
|
||||
});
|
||||
}
|
||||
|
||||
async findByIdAndUser(id: string, userId: string): Promise<Liability | null> {
|
||||
return prisma.liability.findFirst({
|
||||
where: {id, userId},
|
||||
});
|
||||
}
|
||||
|
||||
async findAllByUser(userId: string): Promise<Liability[]> {
|
||||
return prisma.liability.findMany({
|
||||
where: {userId},
|
||||
@@ -48,11 +53,11 @@ export class LiabilityRepository implements IUserScopedRepository<Liability> {
|
||||
const result = await prisma.liability.aggregate({
|
||||
where: {userId},
|
||||
_sum: {
|
||||
currentBalance: true,
|
||||
balance: true,
|
||||
},
|
||||
});
|
||||
|
||||
return result._sum.currentBalance || 0;
|
||||
return result._sum.balance || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Implements Interface Segregation: Base interface for common operations
|
||||
* Implements Dependency Inversion: Depend on abstractions, not concretions
|
||||
*/
|
||||
export interface IRepository<T> {
|
||||
export interface IRepository<T, CreateInput = unknown, UpdateInput = unknown> {
|
||||
findById(id: string): Promise<T | null>;
|
||||
findAll(filters?: Record<string, any>): Promise<T[]>;
|
||||
create(data: Partial<T>): Promise<T>;
|
||||
update(id: string, data: Partial<T>): Promise<T>;
|
||||
findAll(filters?: Record<string, unknown>): Promise<T[]>;
|
||||
create(data: CreateInput): Promise<T>;
|
||||
update(id: string, data: UpdateInput): Promise<T>;
|
||||
delete(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export interface IRepository<T> {
|
||||
* User-scoped repository interface
|
||||
* For entities that belong to a specific user
|
||||
*/
|
||||
export interface IUserScopedRepository<T> extends Omit<IRepository<T>, 'findAll'> {
|
||||
findAllByUser(userId: string, filters?: Record<string, any>): Promise<T[]>;
|
||||
export interface IUserScopedRepository<T, CreateInput = unknown, UpdateInput = unknown>
|
||||
extends Omit<IRepository<T, CreateInput, UpdateInput>, 'findAll'> {
|
||||
findAllByUser(userId: string, filters?: Record<string, unknown>): Promise<T[]>;
|
||||
findByIdAndUser(id: string, userId: string): Promise<T | null>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user