refactor: code efficiency improvements (Issues #9-13, #15, #17-20)
Backend: - Extract shared hard-delete authorization utility (#9) - Extract Prisma include constants per entity (#11) - Fix N+1 query pattern in events findAll (#12) - Extract shared date utility functions (#13) - Move vehicle utilization filtering to DB query (#15) - Add ParseBooleanPipe for query params - Add CurrentDriver decorator + ResolveDriverInterceptor (#20) Frontend: - Extract shared form utilities (toDatetimeLocal) and enum labels (#17) - Replace browser confirm() with styled ConfirmModal (#18) - Add centralized query-keys.ts constants (#19) - Clean up unused imports, add useMemo where needed (#19) - Standardize filter button styling across list pages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
import { Role } from '@prisma/client';
|
||||
import { CreateEventDto, UpdateEventDto, UpdateEventStatusDto } from './dto';
|
||||
import { ParseBooleanPipe } from '../common/pipes';
|
||||
|
||||
@Controller('events')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@@ -59,10 +60,9 @@ export class EventsController {
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
remove(
|
||||
@Param('id') id: string,
|
||||
@Query('hard') hard?: string,
|
||||
@Query('hard', ParseBooleanPipe) hard: boolean,
|
||||
@CurrentUser() user?: any,
|
||||
) {
|
||||
const isHardDelete = hard === 'true';
|
||||
return this.eventsService.remove(id, isHardDelete, user?.role);
|
||||
return this.eventsService.remove(id, hard, user?.role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,24 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateEventDto, UpdateEventDto, UpdateEventStatusDto } from './dto';
|
||||
import { executeHardDelete } from '../common/utils';
|
||||
|
||||
@Injectable()
|
||||
export class EventsService {
|
||||
private readonly logger = new Logger(EventsService.name);
|
||||
|
||||
private readonly eventInclude = {
|
||||
driver: true,
|
||||
vehicle: true,
|
||||
masterEvent: {
|
||||
select: { id: true, title: true, type: true, startTime: true, endTime: true },
|
||||
},
|
||||
childEvents: {
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, title: true, type: true },
|
||||
},
|
||||
} as const;
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(createEventDto: CreateEventDto) {
|
||||
@@ -69,17 +82,7 @@ export class EventsService {
|
||||
startTime: new Date(createEventDto.startTime),
|
||||
endTime: new Date(createEventDto.endTime),
|
||||
},
|
||||
include: {
|
||||
driver: true,
|
||||
vehicle: true,
|
||||
masterEvent: {
|
||||
select: { id: true, title: true, type: true, startTime: true, endTime: true },
|
||||
},
|
||||
childEvents: {
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, title: true, type: true },
|
||||
},
|
||||
},
|
||||
include: this.eventInclude,
|
||||
});
|
||||
|
||||
return this.enrichEventWithVips(event);
|
||||
@@ -88,37 +91,46 @@ export class EventsService {
|
||||
async findAll() {
|
||||
const events = await this.prisma.scheduleEvent.findMany({
|
||||
where: { deletedAt: null },
|
||||
include: {
|
||||
driver: true,
|
||||
vehicle: true,
|
||||
masterEvent: {
|
||||
select: { id: true, title: true, type: true, startTime: true, endTime: true },
|
||||
},
|
||||
childEvents: {
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, title: true, type: true },
|
||||
},
|
||||
},
|
||||
include: this.eventInclude,
|
||||
orderBy: { startTime: 'asc' },
|
||||
});
|
||||
|
||||
return Promise.all(events.map((event) => this.enrichEventWithVips(event)));
|
||||
// Collect all unique VIP IDs from all events
|
||||
const allVipIds = new Set<string>();
|
||||
events.forEach((event) => {
|
||||
event.vipIds?.forEach((vipId) => allVipIds.add(vipId));
|
||||
});
|
||||
|
||||
// Fetch all VIPs in a single query (eliminates N+1)
|
||||
const vipsMap = new Map();
|
||||
if (allVipIds.size > 0) {
|
||||
const vips = await this.prisma.vIP.findMany({
|
||||
where: {
|
||||
id: { in: Array.from(allVipIds) },
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
vips.forEach((vip) => vipsMap.set(vip.id, vip));
|
||||
}
|
||||
|
||||
// Enrich each event with its VIPs from the map (no additional queries)
|
||||
return events.map((event) => {
|
||||
if (!event.vipIds || event.vipIds.length === 0) {
|
||||
return { ...event, vips: [], vip: null };
|
||||
}
|
||||
|
||||
const vips = event.vipIds
|
||||
.map((vipId) => vipsMap.get(vipId))
|
||||
.filter((vip) => vip !== undefined);
|
||||
|
||||
return { ...event, vips, vip: vips[0] || null };
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
const event = await this.prisma.scheduleEvent.findFirst({
|
||||
where: { id, deletedAt: null },
|
||||
include: {
|
||||
driver: true,
|
||||
vehicle: true,
|
||||
masterEvent: {
|
||||
select: { id: true, title: true, type: true, startTime: true, endTime: true },
|
||||
},
|
||||
childEvents: {
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, title: true, type: true },
|
||||
},
|
||||
},
|
||||
include: this.eventInclude,
|
||||
});
|
||||
|
||||
if (!event) {
|
||||
@@ -207,17 +219,7 @@ export class EventsService {
|
||||
const updatedEvent = await this.prisma.scheduleEvent.update({
|
||||
where: { id: event.id },
|
||||
data: updateData,
|
||||
include: {
|
||||
driver: true,
|
||||
vehicle: true,
|
||||
masterEvent: {
|
||||
select: { id: true, title: true, type: true, startTime: true, endTime: true },
|
||||
},
|
||||
childEvents: {
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, title: true, type: true },
|
||||
},
|
||||
},
|
||||
include: this.eventInclude,
|
||||
});
|
||||
|
||||
return this.enrichEventWithVips(updatedEvent);
|
||||
@@ -233,40 +235,27 @@ export class EventsService {
|
||||
const updatedEvent = await this.prisma.scheduleEvent.update({
|
||||
where: { id: event.id },
|
||||
data: { status: updateEventStatusDto.status },
|
||||
include: {
|
||||
driver: true,
|
||||
vehicle: true,
|
||||
masterEvent: {
|
||||
select: { id: true, title: true, type: true, startTime: true, endTime: true },
|
||||
},
|
||||
childEvents: {
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, title: true, type: true },
|
||||
},
|
||||
},
|
||||
include: this.eventInclude,
|
||||
});
|
||||
|
||||
return this.enrichEventWithVips(updatedEvent);
|
||||
}
|
||||
|
||||
async remove(id: string, hardDelete = false, userRole?: string) {
|
||||
if (hardDelete && userRole !== 'ADMINISTRATOR') {
|
||||
throw new ForbiddenException('Only administrators can permanently delete records');
|
||||
}
|
||||
|
||||
const event = await this.findOne(id);
|
||||
|
||||
if (hardDelete) {
|
||||
this.logger.log(`Hard deleting event: ${event.title}`);
|
||||
return this.prisma.scheduleEvent.delete({
|
||||
where: { id: event.id },
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log(`Soft deleting event: ${event.title}`);
|
||||
return this.prisma.scheduleEvent.update({
|
||||
where: { id: event.id },
|
||||
data: { deletedAt: new Date() },
|
||||
return executeHardDelete({
|
||||
id,
|
||||
hardDelete,
|
||||
userRole,
|
||||
findOne: (id) => this.findOne(id),
|
||||
performHardDelete: (id) =>
|
||||
this.prisma.scheduleEvent.delete({ where: { id } }),
|
||||
performSoftDelete: (id) =>
|
||||
this.prisma.scheduleEvent.update({
|
||||
where: { id },
|
||||
data: { deletedAt: new Date() },
|
||||
}),
|
||||
entityName: 'Event',
|
||||
logger: this.logger,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user