diff --git a/apps/web-api/src/app.module.ts b/apps/web-api/src/app.module.ts index aebea6b07..509596275 100644 --- a/apps/web-api/src/app.module.ts +++ b/apps/web-api/src/app.module.ts @@ -34,6 +34,7 @@ import { ProjectsModule } from './projects/projects.module'; import { JoinRequestsModule } from './join-requests/join-requests.module'; import { FocusAreasModule } from './focus-areas/focus-areas.module'; import { PLEventsModule } from './pl-events/pl-events.module'; +import { EmptyStringToNullInterceptor } from './interceptors/empty-string-to-null.interceptor'; @Module({ controllers: [AppController], @@ -98,6 +99,10 @@ import { PLEventsModule } from './pl-events/pl-events.module'; provide: APP_INTERCEPTOR, useClass: ConcealEntityIDInterceptor, }, + { + provide: APP_INTERCEPTOR, + useClass: EmptyStringToNullInterceptor + }, { provide: APP_FILTER, useClass: LogException diff --git a/apps/web-api/src/focus-areas/focus-areas.service.ts b/apps/web-api/src/focus-areas/focus-areas.service.ts index 890850efb..0337f0732 100644 --- a/apps/web-api/src/focus-areas/focus-areas.service.ts +++ b/apps/web-api/src/focus-areas/focus-areas.service.ts @@ -24,6 +24,9 @@ export class FocusAreasService { include: { children: true, ...this.buildAncestorFocusAreasFilterByType(type, query) + }, + orderBy: { + createdAt: "desc" } }; } @@ -31,6 +34,9 @@ export class FocusAreasService { include: { children: this.buildQueryByLevel(level - 1, type, query), ...this.buildAncestorFocusAreasFilterByType(type, query) + }, + orderBy: { + createdAt: "desc" } }; } diff --git a/apps/web-api/src/interceptors/empty-string-to-null.interceptor.ts b/apps/web-api/src/interceptors/empty-string-to-null.interceptor.ts new file mode 100644 index 000000000..e0cc8b170 --- /dev/null +++ b/apps/web-api/src/interceptors/empty-string-to-null.interceptor.ts @@ -0,0 +1,35 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable() +export class EmptyStringToNullInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const method = request.method; + if (method === 'POST' || method === 'PUT' || method === 'PATCH') { + request.body = this.convertEmptyStringsToNull(request.body); + } + return next.handle(); + } + + private convertEmptyStringsToNull(obj: any): any { + if (typeof obj === 'string') { + return obj === '' ? null : obj; + } else if (Array.isArray(obj)) { + return obj.map((item) => this.convertEmptyStringsToNull(item)); + } else if (typeof obj === 'object' && obj !== null) { + const newObj: any = {}; + for (const key of Object.keys(obj)) { + newObj[key] = this.convertEmptyStringsToNull(obj[key]); + } + return newObj; + } + return obj; + } +} + \ No newline at end of file diff --git a/apps/web-api/src/pl-events/pl-events.controller.ts b/apps/web-api/src/pl-events/pl-events.controller.ts index 7fb9ce7a6..57cd7785c 100644 --- a/apps/web-api/src/pl-events/pl-events.controller.ts +++ b/apps/web-api/src/pl-events/pl-events.controller.ts @@ -66,9 +66,10 @@ export class PLEventsController { ): Promise { const userEmail = request["userEmail"]; const member: any = await this.memberService.findMemberByEmail(request["userEmail"]); - const result = await this.memberService.isMemberPartOfTeams(member, [body.teamUid]); + const result = await this.memberService.isMemberPartOfTeams(member, [body.teamUid]) || + await this.memberService.checkIfAdminUser(member); if (!result) { - throw new ForbiddenException(`Member with email ${userEmail} is not part of team with uid ${body.teamUid}`); + throw new ForbiddenException(`Member with email ${userEmail} is not part of team with uid ${body.teamUid} or isn't admin`); } return await this.eventService.createPLEventGuest(body as any, slug, member); } @@ -84,13 +85,30 @@ export class PLEventsController { ) { const userEmail = request["userEmail"]; const member: any = await this.memberService.findMemberByEmail(request["userEmail"]); - const result = await this.memberService.isMemberPartOfTeams(member, [body.teamUid]); + const result = await this.memberService.isMemberPartOfTeams(member, [body.teamUid]) || + await this.memberService.checkIfAdminUser(member); if (!result) { - throw new ForbiddenException(`Member with email ${userEmail} is not part of team with uid ${body.teamUid}`); + throw new ForbiddenException(`Member with email ${userEmail} is not part of team with uid ${body.teamUid} or isn't admin`); } return await this.eventService.modifyPLEventGuestByUid(uid, body as any, slug, member); } + @Api(server.route.deletePLEventGuests) + @UsePipes(ZodValidationPipe) + @UseGuards(UserTokenValidation) + async deletePLEventGuests( + @Body() body, + @Req() request + ) { + const userEmail = request["userEmail"]; + const member: any = await this.memberService.findMemberByEmail(request["userEmail"]); + const result = await this.memberService.checkIfAdminUser(member); + if (!result) { + throw new ForbiddenException(`Member with email ${userEmail} is not admin `); + } + return await this.eventService.deletePLEventGuests(body.guests); + } + @Api(server.route.getPLEventsByLoggedInMember) @ApiQueryFromZod(PLEventQueryParams) @ApiOkResponseFromZod(ResponsePLEventSchemaWithRelationsSchema.array()) diff --git a/apps/web-api/src/pl-events/pl-events.service.ts b/apps/web-api/src/pl-events/pl-events.service.ts index 3bb30389d..af4a62b0c 100644 --- a/apps/web-api/src/pl-events/pl-events.service.ts +++ b/apps/web-api/src/pl-events/pl-events.service.ts @@ -53,7 +53,17 @@ export class PLEventsService { name: true, image: true, telegramHandler: true, - teamMemberRoles: true, + teamMemberRoles: { + select:{ + team: { + select:{ + uid: true, + name: true, + logo: true + } + } + } + }, preferences: true, officeHours: true, projectContributions: { @@ -150,13 +160,13 @@ export class PLEventsService { member ) { try { - const event: any = await this.getPLEventBySlug(slug, true); - await this.memberService.updateTelegramIfChanged(member, guest.telegramId); - await this.memberService.updateOfficeHoursIfChanged(member, guest.officeHours); + const event: any = await this.getPLEventBySlug(slug, true); + const isAdmin = this.memberService.checkIfAdminUser(member); + await this.updateMemberDetails(guest, member, isAdmin); await this.prisma.pLEventGuest.create({ data:{ ...guest, - memberUid: member?.uid, + memberUid: isAdmin ? guest.memberUid : member.uid, eventUid: event?.uid } }); @@ -176,14 +186,14 @@ export class PLEventsService { member ) { try { - const event: any = await this.getPLEventBySlug(slug, true); - await this.memberService.updateTelegramIfChanged(member, guest.telegramId); - await this.memberService.updateOfficeHoursIfChanged(member, guest.officeHours); + const event: any = await this.getPLEventBySlug(slug, true); + const isAdmin = this.memberService.checkIfAdminUser(member); + await this.updateMemberDetails(guest, member, isAdmin); return await this.prisma.pLEventGuest.update({ where:{ uid }, data:{ ...guest, - memberUid: member?.uid, + memberUid: this.memberService.checkIfAdminUser(member) ? guest.memberUid : member.uid, eventUid: event?.uid } }); @@ -192,6 +202,26 @@ export class PLEventsService { } }; + async deletePLEventGuests( + guestUids, + ) { + try { + await this.prisma.pLEventGuest.deleteMany({ + where: { + uid: { + in: guestUids ? guestUids : [] + } + } + }); + await this.cacheService.reset(); + return { + msg: `success` + }; + } catch(err) { + this.handleErrors(err); + } + } + async getPLEventsByMember(member) { try { return this.prisma.pLEvent.findMany({ @@ -208,6 +238,17 @@ export class PLEventsService { } } + async updateMemberDetails(guest, member, isAdmin) { + if (isAdmin) { + const guestMember = await this.memberService.findOne(guest.memberUid); + await this.memberService.updateTelegramIfChanged(guestMember, guest.telegramId); + await this.memberService.updateOfficeHoursIfChanged(guestMember, guest.officeHours); + } else { + await this.memberService.updateTelegramIfChanged(member, guest.telegramId); + await this.memberService.updateOfficeHoursIfChanged(member, guest.officeHours); + } + } + private handleErrors(error, message?) { this.logger.error(error); if (error instanceof Prisma.PrismaClientKnownRequestError) { diff --git a/libs/contracts/src/lib/contract-pl-events.ts b/libs/contracts/src/lib/contract-pl-events.ts index 2911f5757..b34b23a1f 100644 --- a/libs/contracts/src/lib/contract-pl-events.ts +++ b/libs/contracts/src/lib/contract-pl-events.ts @@ -45,6 +45,15 @@ export const apiEvents = contract.router({ }, summary: 'Modify a guest in pl event', }, + deletePLEventGuests: { + method: 'POST', + path: `${getAPIVersionAsPath('1')}/irl/events/:slug/guests`, + body: contract.body(), + responses: { + 200: contract.response(), + }, + summary: 'delete a list of guests in pl event', + }, getPLEventsByLoggedInMember: { method: 'GET', path: `${getAPIVersionAsPath('1')}/irl/me/events`,