166 lines
3.5 KiB
TypeScript
166 lines
3.5 KiB
TypeScript
import { NotFoundException, Logger } from '@nestjs/common';
|
|
import { PrismaService } from '../../database/prisma.service';
|
|
import { PaginationDto } from '../dto/pagination.dto';
|
|
import { PaginationMeta } from '../types/api-response.type';
|
|
|
|
/**
|
|
* Generic base service with common CRUD operations
|
|
* Extend this class for entity-specific services
|
|
*/
|
|
export abstract class BaseService<T, CreateDto, UpdateDto> {
|
|
protected readonly logger: Logger;
|
|
|
|
constructor(
|
|
protected readonly prisma: PrismaService,
|
|
protected readonly modelName: string,
|
|
) {
|
|
this.logger = new Logger(`${modelName}Service`);
|
|
}
|
|
|
|
/**
|
|
* Get the Prisma model delegate
|
|
*/
|
|
protected get model() {
|
|
return (this.prisma as any)[this.modelName.toLowerCase()];
|
|
}
|
|
|
|
/**
|
|
* Find all records with pagination
|
|
*/
|
|
async findAll(
|
|
pagination: PaginationDto,
|
|
where?: any,
|
|
): Promise<{ items: T[]; meta: PaginationMeta }> {
|
|
const { skip, take, orderBy } = pagination;
|
|
|
|
const [items, total] = await Promise.all([
|
|
this.model.findMany({
|
|
where,
|
|
skip,
|
|
take,
|
|
orderBy,
|
|
}),
|
|
this.model.count({ where }),
|
|
]);
|
|
|
|
const totalPages = Math.ceil(total / take);
|
|
|
|
return {
|
|
items,
|
|
meta: {
|
|
total,
|
|
page: pagination.page || 1,
|
|
limit: pagination.limit || 10,
|
|
totalPages,
|
|
hasNextPage: (pagination.page || 1) < totalPages,
|
|
hasPreviousPage: (pagination.page || 1) > 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Find a single record by ID
|
|
*/
|
|
async findOne(id: string, include?: any): Promise<T> {
|
|
const record = await this.model.findUnique({
|
|
where: { id },
|
|
include,
|
|
});
|
|
|
|
if (!record) {
|
|
throw new NotFoundException(`${this.modelName} not found`);
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* Find a single record by custom criteria
|
|
*/
|
|
findOneBy(where: any, include?: any): Promise<T | null> {
|
|
return this.model.findFirst({
|
|
where,
|
|
include,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
create(data: CreateDto, include?: any): Promise<T> {
|
|
return this.model.create({
|
|
data,
|
|
include,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update an existing record
|
|
*/
|
|
async update(id: string, data: UpdateDto, include?: any): Promise<T> {
|
|
// Check if record exists
|
|
await this.findOne(id);
|
|
|
|
return this.model.update({
|
|
where: { id },
|
|
data,
|
|
include,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Soft delete a record (sets deletedAt)
|
|
*/
|
|
async delete(id: string): Promise<T> {
|
|
// Check if record exists
|
|
await this.findOne(id);
|
|
|
|
return this.model.delete({
|
|
where: { id },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hard delete a record (permanently removes)
|
|
*/
|
|
async hardDelete(id: string): Promise<T> {
|
|
// Check if record exists
|
|
await this.findOne(id);
|
|
|
|
return this.prisma.hardDelete(this.modelName, { id });
|
|
}
|
|
|
|
/**
|
|
* Restore a soft-deleted record
|
|
*/
|
|
async restore(id: string): Promise<T> {
|
|
return this.prisma.restore(this.modelName, { id });
|
|
}
|
|
|
|
/**
|
|
* Check if a record exists
|
|
*/
|
|
async exists(id: string): Promise<boolean> {
|
|
const count = await this.model.count({
|
|
where: { id },
|
|
});
|
|
return count > 0;
|
|
}
|
|
|
|
/**
|
|
* Count records matching criteria
|
|
*/
|
|
count(where?: any): Promise<number> {
|
|
return this.model.count({ where });
|
|
}
|
|
|
|
/**
|
|
* Execute a transaction
|
|
*/
|
|
transaction<R>(fn: (prisma: PrismaService) => Promise<R>): Promise<R> {
|
|
return this.prisma.$transaction(async (tx) => {
|
|
return fn(tx as unknown as PrismaService);
|
|
});
|
|
}
|
|
}
|