feat: member portal Sprint 4 — Events + RSVP
- Event + EventRsvp models + migration (RsvpStatus enum: GOING/MAYBE/NOT_GOING) - EventsModule: admin CRUD (create/update/delete/publish) + RSVP list - GET /my/events + POST /my/events/:id/rsvp in MyController/MyService - Admin BFF routes: GET/POST /api/admin/events, PATCH/DELETE /api/admin/events/[id] - Member BFF routes: GET /api/my/events, POST /api/my/events/[id]/rsvp - /my/events page: upcoming/past split, RSVP button (client component, optimistic) - Register DigestModule, OrgModule, ThreadsModule, EventsModule in AppModule - Add EVENT_CREATED/DELETED/UPDATED and DIGEST_SENT to AuditAction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { AuditAction } from '../audit/audit.types';
|
||||
|
||||
@Injectable()
|
||||
export class EventsService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly audit: AuditService,
|
||||
) {}
|
||||
|
||||
async list(tenantId: string, includeUnpublished = false) {
|
||||
return this.prisma.event.findMany({
|
||||
where: {
|
||||
tenantId,
|
||||
...(includeUnpublished ? {} : { isPublished: true }),
|
||||
},
|
||||
orderBy: { startsAt: 'asc' },
|
||||
include: { _count: { select: { rsvps: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
async create(tenantId: string, adminId: string, dto: {
|
||||
title: string;
|
||||
description?: string;
|
||||
location?: string;
|
||||
startsAt: string;
|
||||
endsAt?: string;
|
||||
isPublished?: boolean;
|
||||
}) {
|
||||
const event = await this.prisma.event.create({
|
||||
data: {
|
||||
tenantId,
|
||||
title: dto.title,
|
||||
description: dto.description ?? null,
|
||||
location: dto.location ?? null,
|
||||
startsAt: new Date(dto.startsAt),
|
||||
endsAt: dto.endsAt ? new Date(dto.endsAt) : null,
|
||||
createdBy: adminId,
|
||||
isPublished: dto.isPublished ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
await this.audit.log({
|
||||
tenantId,
|
||||
actorId: adminId,
|
||||
action: AuditAction.EVENT_CREATED,
|
||||
resourceType: 'Event',
|
||||
resourceId: event.id,
|
||||
payload: { title: event.title, isPublished: event.isPublished },
|
||||
});
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
async update(tenantId: string, adminId: string, eventId: string, dto: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
location?: string;
|
||||
startsAt?: string;
|
||||
endsAt?: string;
|
||||
isPublished?: boolean;
|
||||
}) {
|
||||
const event = await this.prisma.event.findFirst({ where: { id: eventId, tenantId } });
|
||||
if (!event) throw new NotFoundException('Event not found');
|
||||
|
||||
return this.prisma.event.update({
|
||||
where: { id: eventId },
|
||||
data: {
|
||||
...(dto.title !== undefined && { title: dto.title }),
|
||||
...(dto.description !== undefined && { description: dto.description }),
|
||||
...(dto.location !== undefined && { location: dto.location }),
|
||||
...(dto.startsAt !== undefined && { startsAt: new Date(dto.startsAt) }),
|
||||
...(dto.endsAt !== undefined && { endsAt: new Date(dto.endsAt) }),
|
||||
...(dto.isPublished !== undefined && { isPublished: dto.isPublished }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async remove(tenantId: string, adminId: string, eventId: string) {
|
||||
const event = await this.prisma.event.findFirst({ where: { id: eventId, tenantId } });
|
||||
if (!event) throw new NotFoundException('Event not found');
|
||||
await this.prisma.event.delete({ where: { id: eventId } });
|
||||
|
||||
await this.audit.log({
|
||||
tenantId,
|
||||
actorId: adminId,
|
||||
action: AuditAction.EVENT_DELETED,
|
||||
resourceType: 'Event',
|
||||
resourceId: eventId,
|
||||
payload: { deleted: true },
|
||||
});
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
async getRsvps(tenantId: string, eventId: string) {
|
||||
const event = await this.prisma.event.findFirst({ where: { id: eventId, tenantId } });
|
||||
if (!event) throw new NotFoundException('Event not found');
|
||||
|
||||
return this.prisma.eventRsvp.findMany({
|
||||
where: { eventId },
|
||||
include: { user: { select: { id: true, displayName: true, jid: true } } },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user