135 lines
3.4 KiB
TypeScript
135 lines
3.4 KiB
TypeScript
import {
|
|
Injectable,
|
|
OnModuleInit,
|
|
OnModuleDestroy,
|
|
Logger,
|
|
} from '@nestjs/common';
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
// Models that support soft delete
|
|
const SOFT_DELETE_MODELS = ['user', 'role', 'tenant'];
|
|
|
|
// Type for Prisma model delegate with common operations
|
|
interface PrismaDelegate {
|
|
delete: (args: { where: Record<string, unknown> }) => Promise<unknown>;
|
|
findMany: (args?: Record<string, unknown>) => Promise<unknown[]>;
|
|
update: (args: {
|
|
where: Record<string, unknown>;
|
|
data: Record<string, unknown>;
|
|
}) => Promise<unknown>;
|
|
}
|
|
|
|
@Injectable()
|
|
export class PrismaService
|
|
extends PrismaClient
|
|
implements OnModuleInit, OnModuleDestroy
|
|
{
|
|
private readonly logger = new Logger(PrismaService.name);
|
|
|
|
constructor() {
|
|
super({
|
|
log: [
|
|
{ emit: 'event', level: 'query' },
|
|
{ emit: 'event', level: 'error' },
|
|
{ emit: 'event', level: 'warn' },
|
|
],
|
|
});
|
|
}
|
|
|
|
async onModuleInit() {
|
|
this.logger.log(
|
|
`Connecting to database... URL: ${process.env.DATABASE_URL?.split('@')[1]}`,
|
|
); // Mask password
|
|
try {
|
|
await this.$connect();
|
|
this.logger.log('✅ Database connected successfully');
|
|
} catch (error) {
|
|
this.logger.error(
|
|
`❌ Database connection failed: ${error.message}`,
|
|
error.stack,
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async onModuleDestroy() {
|
|
await this.$disconnect();
|
|
this.logger.log('🔌 Database disconnected');
|
|
}
|
|
|
|
/**
|
|
* Check if model has soft delete (deletedAt field)
|
|
*/
|
|
hasSoftDelete(model: string | undefined): boolean {
|
|
return model ? SOFT_DELETE_MODELS.includes(model.toLowerCase()) : false;
|
|
}
|
|
|
|
/**
|
|
* Hard delete - actually remove from database
|
|
*/
|
|
hardDelete<T>(model: string, where: Record<string, unknown>): Promise<T> {
|
|
const delegate = this.getModelDelegate(model);
|
|
return delegate.delete({ where }) as Promise<T>;
|
|
}
|
|
|
|
/**
|
|
* Find including soft deleted records
|
|
*/
|
|
findWithDeleted<T>(
|
|
model: string,
|
|
args?: Record<string, unknown>,
|
|
): Promise<T[]> {
|
|
const delegate = this.getModelDelegate(model);
|
|
return delegate.findMany(args) as Promise<T[]>;
|
|
}
|
|
|
|
/**
|
|
* Restore a soft deleted record
|
|
*/
|
|
restore<T>(model: string, where: Record<string, unknown>): Promise<T> {
|
|
const delegate = this.getModelDelegate(model);
|
|
return delegate.update({
|
|
where,
|
|
data: { deletedAt: null },
|
|
}) as Promise<T>;
|
|
}
|
|
|
|
/**
|
|
* Soft delete - set deletedAt to current date
|
|
*/
|
|
softDelete<T>(model: string, where: Record<string, unknown>): Promise<T> {
|
|
const delegate = this.getModelDelegate(model);
|
|
return delegate.update({
|
|
where,
|
|
data: { deletedAt: new Date() },
|
|
}) as Promise<T>;
|
|
}
|
|
|
|
/**
|
|
* Find many excluding soft deleted records
|
|
*/
|
|
findManyActive<T>(
|
|
model: string,
|
|
args?: Record<string, unknown>,
|
|
): Promise<T[]> {
|
|
const delegate = this.getModelDelegate(model);
|
|
const whereWithDeleted = {
|
|
...args,
|
|
where: {
|
|
...(args?.where as Record<string, unknown> | undefined),
|
|
deletedAt: null,
|
|
},
|
|
};
|
|
return delegate.findMany(whereWithDeleted) as Promise<T[]>;
|
|
}
|
|
|
|
/**
|
|
* Get Prisma model delegate by name
|
|
*/
|
|
private getModelDelegate(model: string): PrismaDelegate {
|
|
const modelKey = model.charAt(0).toLowerCase() + model.slice(1);
|
|
|
|
return (this as any)[modelKey] as PrismaDelegate;
|
|
}
|
|
}
|