From c2c7a3a27a86be4d48ce28a8432b6f207d7164fd Mon Sep 17 00:00:00 2001 From: Julien Dufresne Date: Tue, 2 Apr 2019 22:07:06 -0400 Subject: [PATCH 1/3] Lot of stuff --- .../attendees/attendees.controller.ts | 11 ++++ .../database/events/events.controller.ts | 8 +++ src/modules/database/events/events.module.ts | 5 +- src/modules/database/events/events.service.ts | 42 +++++++++--- .../puzzle-heroes/puzzle-heroes.service.ts | 21 ++++-- .../registrations/registration.exception.ts | 2 +- .../registrations/registrations.dto.ts | 8 ++- .../registrations/registrations.service.ts | 64 ++++++++++++++----- src/modules/database/teams/teams.dto.ts | 10 ++- src/modules/database/teams/teams.model.ts | 7 +- src/modules/database/teams/teams.service.ts | 4 ++ 11 files changed, 149 insertions(+), 33 deletions(-) diff --git a/src/modules/database/attendees/attendees.controller.ts b/src/modules/database/attendees/attendees.controller.ts index 1d77c79..765a24e 100644 --- a/src/modules/database/attendees/attendees.controller.ts +++ b/src/modules/database/attendees/attendees.controller.ts @@ -76,6 +76,17 @@ export class AttendeesController { }); } + @Get(':id/cv/url') + @Permissions('csgames-api:get-all:attendee') + public async getCvUrlById(@Param('id') id: string): Promise<{ url: string }> { + const attendee = await this.attendeesService.findOne({ _id: id }); + if (attendee.cv) { + return { url: await this.storageService.getDownloadUrl(attendee.cv) }; + } else { + return { url: null }; + } + } + @Put() @Permissions('csgames-api:update:attendee') public async update(@UploadedFile() file, @User() user: UserModel, diff --git a/src/modules/database/events/events.controller.ts b/src/modules/database/events/events.controller.ts index a51ee57..26fcf4d 100644 --- a/src/modules/database/events/events.controller.ts +++ b/src/modules/database/events/events.controller.ts @@ -7,6 +7,7 @@ import { Permissions } from '../../../decorators/permission.decorator'; import { User } from '../../../decorators/user.decorator'; import { CodeExceptionFilter } from '../../../filters/code-error/code.filter'; import { PermissionsGuard } from '../../../guards/permission.guard'; +import { BufferInterceptor } from '../../../interceptors/buffer.interceptor'; import { DataGridDownloadInterceptor } from '../../../interceptors/data-grid-download.interceptor'; import { UserModel } from '../../../models/user.model'; import { ArrayPipe } from '../../../pipes/array.pipe'; @@ -141,6 +142,13 @@ export class EventsController { return await this.eventsService.getAttendeesData(eventId, type, roles); } + @Get('attendee/cv') + @UseInterceptors(new BufferInterceptor("application/zip")) + @Permissions('csgames-api:get-all:attendee') + public async getAttendeeCv(@EventId() eventId: string): Promise { + return await this.eventsService.getAttendeeCv(eventId); + } + @Post('sms') @Permissions('csgames-api:update:event') public async sendSms(@EventId() eventId: string, @Body(new ValidationPipe()) dto: SendSmsDto) { diff --git a/src/modules/database/events/events.module.ts b/src/modules/database/events/events.module.ts index 3d88913..dbf5e31 100644 --- a/src/modules/database/events/events.module.ts +++ b/src/modules/database/events/events.module.ts @@ -7,7 +7,7 @@ import { EventsController } from './events.controller'; import { EventsSchema } from './events.model'; import { EventsService } from './events.service'; import { AttendeesModule } from '../attendees/attendees.module'; -import { STSModule } from '@polyhx/nest-services'; +import { StorageModule, STSModule } from '@polyhx/nest-services'; import { EmailModule } from '../../email/email.module'; import { TeamsSchema } from '../teams/teams.model'; import { MessagingModule } from '../../messaging/messaging.module'; @@ -35,7 +35,8 @@ import { FlashOutsModule } from '../flash-out/flash-out.module'; forwardRef(() => TeamsModule), MessagingModule, NotificationsModule, - FlashOutsModule + FlashOutsModule, + StorageModule ], controllers: [ EventsController diff --git a/src/modules/database/events/events.service.ts b/src/modules/database/events/events.service.ts index 19e84e1..d74bdd7 100644 --- a/src/modules/database/events/events.service.ts +++ b/src/modules/database/events/events.service.ts @@ -1,5 +1,7 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; +import { StorageService } from '@polyhx/nest-services'; +import * as AdmZip from 'adm-zip'; import * as mongoose from 'mongoose'; import { Model } from 'mongoose'; import { isNullOrUndefined } from 'util'; @@ -8,17 +10,17 @@ import { BaseService } from '../../../services/base.service'; import { CreateActivityDto } from '../activities/activities.dto'; import { Activities } from '../activities/activities.model'; import { ActivitiesService } from '../activities/activities.service'; +import { UpdateAttendeeDto } from '../attendees/attendees.dto'; import { AttendeeNotifications, Attendees } from '../attendees/attendees.model'; import { AttendeesService } from '../attendees/attendees.service'; import { Competitions, CompetitionsUtils } from '../competitions/competitions.model'; import { Notifications } from '../notifications/notifications.model'; import { NotificationsService } from '../notifications/notifications.service'; -import { AddScannedAttendee, AddSponsorDto, CreateEventDto, SendNotificationDto } from './events.dto'; +import { Schools } from '../schools/schools.model'; +import { Teams } from '../teams/teams.model'; +import { AddSponsorDto, CreateEventDto, SendNotificationDto } from './events.dto'; import { AttendeeAlreadyRegisteredException, EventNotFoundException, UserNotAttendeeException } from './events.exception'; import { Events, EventSponsorDetails } from './events.model'; -import { UpdateAttendeeDto } from '../attendees/attendees.dto'; -import { Teams } from '../teams/teams.model'; -import { Schools } from '../schools/schools.model'; export interface EventScore { overall: TeamScore[]; @@ -39,6 +41,7 @@ export class EventsService extends BaseService { @InjectModel('competitions') private readonly competitionsModel: Model, private readonly attendeeService: AttendeesService, private readonly activitiesService: ActivitiesService, + private readonly storageService: StorageService, private readonly notificationService: NotificationsService) { super(eventsModel); } @@ -85,7 +88,7 @@ export class EventsService extends BaseService { } let registered = false; - if (role === "admin" || role === "volunteer" || role === "director") { + if (role === "admin" || role === "volunteer" || role === "director" || role === "sponsor") { registered = true; } @@ -368,6 +371,26 @@ export class EventsService extends BaseService { return await this.attendeeService.getFromEvent(eventId, attendees, type); } + public async getAttendeeCv(eventId: string): Promise { + const attendees = await this.getAttendeesData(eventId, 'json', ['attendee', 'captain', 'godparent']); + + let files = await this.storageService.getDirectory('cv'); + files = files.filter(file => attendees.some(a => a.cv === file.name)); + + const zip = new AdmZip(); + for (const file of files) { + const attendee = attendees.find(a => a.cv === file.name); + if (!attendee) { + continue; + } + + const [buffer] = await file.download(); + zip.addFile(`${attendee.firstName}_${attendee.lastName}-${file.metadata.metadata.name}`, buffer); + } + + return zip.toBuffer(); + } + public async getScore(eventId: string): Promise { const competitions = await this.competitionsModel.find({ event: eventId @@ -413,7 +436,7 @@ export class EventsService extends BaseService { return { ...x, name, - results: x.results.map(result => { + results: x.results.filter(x => x.teamId.school).map(result => { const team = result.teamId as Teams; return { teamId: team._id.toHexString(), @@ -429,7 +452,10 @@ export class EventsService extends BaseService { private async getOverallScore(eventId: string, competitions: Competitions[]): Promise { const teams = await this.teamsModel.find({ - event: eventId + event: eventId, + school: { + $ne: null + } }).select(['_id', 'name', 'school']) .populate([{ path: 'school', diff --git a/src/modules/database/puzzle-heroes/puzzle-heroes.service.ts b/src/modules/database/puzzle-heroes/puzzle-heroes.service.ts index 5692491..b588705 100644 --- a/src/modules/database/puzzle-heroes/puzzle-heroes.service.ts +++ b/src/modules/database/puzzle-heroes/puzzle-heroes.service.ts @@ -2,6 +2,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm import { InjectModel } from '@nestjs/mongoose'; import { BaseService } from '../../../services/base.service'; import { DateUtils } from '../../../utils/date.utils'; +import { Sponsors } from '../sponsors/sponsors.model'; import { PuzzleHeroes, PuzzleHeroesUtils } from './puzzle-heroes.model'; import * as mongoose from 'mongoose'; import { Model } from 'mongoose'; @@ -259,14 +260,26 @@ export class PuzzleHeroesService extends BaseService const team = await this.teamsModel.findOne({ event: eventId, _id: s.value - }); - - const school = await this.schoolsModel.findById(team.school); + }).populate([{ + model: 'teams', + path: 'team', + select: ['name'] + }, { + model: 'sponsors', + path: 'sponsor', + select: ['name'] + }]).exec(); + + if (user.role === 'sponsor' && !team.sponsor) { + return null; + } else if (!user.role.endsWith('admin') && !team.school) { + return null; + } return { teamId: team._id.toHexString(), teamName: team.name, - schoolName: school.name, + schoolName: team.school ? (team.school as Schools).name : (team.sponsor as Sponsors).name, score: s.score }; } catch (e) { diff --git a/src/modules/database/registrations/registration.exception.ts b/src/modules/database/registrations/registration.exception.ts index 8890992..99bc8b1 100644 --- a/src/modules/database/registrations/registration.exception.ts +++ b/src/modules/database/registrations/registration.exception.ts @@ -57,7 +57,7 @@ export class TeamAlreadyExistException extends CodeException { export class TeamDoesntExistException extends CodeException { constructor () { - super(Code.TEAM_ALREADY_EXIST); + super(Code.TEAM_DOESNT_EXIST); } } diff --git a/src/modules/database/registrations/registrations.dto.ts b/src/modules/database/registrations/registrations.dto.ts index 211fef7..3bb6f42 100644 --- a/src/modules/database/registrations/registrations.dto.ts +++ b/src/modules/database/registrations/registrations.dto.ts @@ -5,7 +5,7 @@ import { CreateAttendeeDto } from '../attendees/attendees.dto'; export class CreateRegistrationDto { @IsString() @IsNotEmpty() - @IsIn(['attendee', 'captain', 'godparent']) + @IsIn(['attendee', 'captain', 'godparent', 'sponsor']) @ApiModelProperty({ required: true }) role: string; @@ -36,6 +36,12 @@ export class CreateRegistrationDto { @ApiModelProperty({ required: true }) schoolId: string; + @IsMongoId() + @IsNotEmpty() + @ValidateIf(x => x.role === 'sponsor') + @ApiModelProperty({ required: true }) + sponsorId: string; + @IsNumber() @ValidateIf(x => x.role === 'captain') @IsNotEmpty() diff --git a/src/modules/database/registrations/registrations.service.ts b/src/modules/database/registrations/registrations.service.ts index 33177d4..550cdb6 100644 --- a/src/modules/database/registrations/registrations.service.ts +++ b/src/modules/database/registrations/registrations.service.ts @@ -21,6 +21,7 @@ import { Registrations } from './registrations.model'; export class RegistrationsService { private roleTemplate = { attendee: 'attendee_account_creation', + sponsor: 'attendee_account_creation', captain: 'captain_account_creation', godparent: 'godparent_account_creation' }; @@ -41,8 +42,9 @@ export class RegistrationsService { throw new AttendeeAlreadyExistException(); } - await this.validateTeam(dto.teamName, dto.role, eventId); - + if (dto.role !== 'sponsor') { + await this.validateTeam(dto.teamName, dto.role, eventId); + } const attendee = await this.attendeeService.create({ email: dto.email.toLowerCase(), firstName: dto.firstName, @@ -55,20 +57,10 @@ export class RegistrationsService { }); registration = await registration.save(); - if (dto.role === 'captain' && (role === 'admin' || role === 'super-admin')) { - await this.teamsService.createTeam({ - name: dto.teamName, - event: eventId, - school: dto.schoolId, - attendeeId: attendee._id, - maxMembersNumber: dto.maxMembersNumber - }); + if (dto.role === 'sponsor') { + await this.addSponsor(dto, role, eventId, attendee); } else { - await this.teamsService.update({name: dto.teamName, event: eventId}, { - $push: { - attendees: attendee._id - } - } as any); + await this.addAttendee(dto, role, eventId, attendee); } await this.eventService.addAttendee(eventId, attendee, dto.role); @@ -223,6 +215,48 @@ export class RegistrationsService { } } + private async addAttendee(dto: CreateRegistrationDto, role: string, eventId: string, attendee: Attendees) { + if (dto.role === 'captain' && (role === 'admin' || role === 'super-admin')) { + await this.teamsService.createTeam({ + name: dto.teamName, + event: eventId, + school: dto.schoolId, + sponsor: null, + attendeeId: attendee._id, + maxMembersNumber: dto.maxMembersNumber + }); + } else { + await this.teamsService.update({name: dto.teamName, event: eventId}, { + $push: { + attendees: attendee._id + } + } as any); + } + } + + private async addSponsor(dto: CreateRegistrationDto, role: string, eventId: string, attendee: Attendees) { + const team = await this.teamsService.findOne({ + sponsor: dto.sponsorId, + event: eventId + }); + if (!team) { + await this.teamsService.createTeam({ + name: dto.teamName, + event: eventId, + school: null, + sponsor: dto.sponsorId, + attendeeId: attendee._id, + maxMembersNumber: dto.maxMembersNumber + }); + } else { + await this.teamsService.update({sponsor: dto.sponsorId, event: eventId}, { + $push: { + attendees: attendee._id + } + } as any); + } + } + private async fetchRoles() { const roles = await this.stsService.getRoles().then(x => x.roles); if (!roles) { diff --git a/src/modules/database/teams/teams.dto.ts b/src/modules/database/teams/teams.dto.ts index cae7cc6..ea2a89b 100644 --- a/src/modules/database/teams/teams.dto.ts +++ b/src/modules/database/teams/teams.dto.ts @@ -1,5 +1,5 @@ import { ApiModelProperty } from "@nestjs/swagger"; -import { IsMongoId, IsNotEmpty, IsString, IsOptional, MaxLength, IsNumber } from "class-validator"; +import { IsMongoId, IsNotEmpty, IsString, MaxLength, IsNumber, ValidateIf } from 'class-validator'; export class CreateTeamDto { @IsString() @@ -15,9 +15,17 @@ export class CreateTeamDto { @IsMongoId() @IsNotEmpty() @IsString() + @ValidateIf(x => !x.sponsor) @ApiModelProperty({required: true}) school: string; + @IsMongoId() + @IsNotEmpty() + @IsString() + @ValidateIf(x => !x.school) + @ApiModelProperty({required: true}) + sponsor: string; + @IsMongoId() @IsNotEmpty() @IsString() diff --git a/src/modules/database/teams/teams.model.ts b/src/modules/database/teams/teams.model.ts index 875bbc5..a7d7a10 100644 --- a/src/modules/database/teams/teams.model.ts +++ b/src/modules/database/teams/teams.model.ts @@ -2,6 +2,7 @@ import * as mongoose from "mongoose"; import { Attendees } from "../attendees/attendees.model"; import { Events } from "../events/events.model"; import { Schools } from "../schools/schools.model"; +import { Sponsors } from '../sponsors/sponsors.model'; export interface Teams extends mongoose.Document { _id: mongoose.Types.ObjectId; @@ -9,6 +10,7 @@ export interface Teams extends mongoose.Document { attendees: (Attendees | mongoose.Types.ObjectId | string)[]; event: Events | mongoose.Types.ObjectId | string; school: Schools | mongoose.Types.ObjectId | string; + sponsor: Sponsors | mongoose.Types.ObjectId | string; present: boolean; maxMembersNumber: number; } @@ -30,9 +32,12 @@ export const TeamsSchema = new mongoose.Schema({ }, school: { type: mongoose.Schema.Types.ObjectId, - required: true, ref: 'schools' }, + sponsor: { + type: mongoose.Schema.Types.ObjectId, + ref: 'sponsors' + }, maxMembersNumber: { type: Number, required: true diff --git a/src/modules/database/teams/teams.service.ts b/src/modules/database/teams/teams.service.ts index 6a58695..eb6db71 100644 --- a/src/modules/database/teams/teams.service.ts +++ b/src/modules/database/teams/teams.service.ts @@ -25,6 +25,7 @@ export class TeamsService extends BaseService { event: createTeamDto.event, attendees: [Types.ObjectId(createTeamDto.attendeeId)], school: createTeamDto.school, + sponsor: createTeamDto.sponsor, maxMembersNumber: createTeamDto.maxMembersNumber }); } @@ -61,6 +62,9 @@ export class TeamsService extends BaseService { }, { model: 'schools', path: 'school' + }, { + model: 'sponsors', + path: 'sponsor' }]).exec() as Teams[]; for (let team of teams) { From fc7ea3b90a8638608d08cb1e8d98d99753cf6f7e Mon Sep 17 00:00:00 2001 From: JulienDuf Date: Thu, 4 Apr 2019 18:00:11 -0400 Subject: [PATCH 2/3] Update team update --- src/modules/database/teams/teams.dto.ts | 24 ++++++++++++++++++++- src/modules/database/teams/teams.service.ts | 7 ++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/modules/database/teams/teams.dto.ts b/src/modules/database/teams/teams.dto.ts index ea2a89b..0682c13 100644 --- a/src/modules/database/teams/teams.dto.ts +++ b/src/modules/database/teams/teams.dto.ts @@ -1,5 +1,5 @@ import { ApiModelProperty } from "@nestjs/swagger"; -import { IsMongoId, IsNotEmpty, IsString, MaxLength, IsNumber, ValidateIf } from 'class-validator'; +import { IsMongoId, IsNotEmpty, IsString, MaxLength, IsNumber, ValidateIf, IsOptional } from 'class-validator'; export class CreateTeamDto { @IsString() @@ -44,4 +44,26 @@ export class UpdateTeamDto { @MaxLength(30) @ApiModelProperty({required: true}) name: string; + + @IsOptional() + @IsNumber() + @IsNotEmpty() + @ApiModelProperty({ required: true }) + maxMembersNumber: number; + + @IsOptional() + @IsMongoId() + @IsNotEmpty() + @IsString() + @ValidateIf(x => !x.sponsor) + @ApiModelProperty({required: true}) + school: string; + + @IsOptional() + @IsMongoId() + @IsNotEmpty() + @IsString() + @ValidateIf(x => !x.school) + @ApiModelProperty({required: true}) + sponsor: string; } diff --git a/src/modules/database/teams/teams.service.ts b/src/modules/database/teams/teams.service.ts index eb6db71..c47a562 100644 --- a/src/modules/database/teams/teams.service.ts +++ b/src/modules/database/teams/teams.service.ts @@ -39,11 +39,14 @@ export class TeamsService extends BaseService { } } const team = await this.findOne({ name, event: eventId }); - if (team) { + if (team && !team._id.equals(id)) { throw new TeamAlreadyCreatedException(); } - return this.update({ _id: id }, { name }); + return this.update({ _id: id }, { + ...updateTeamDto, + name + }); } public async getTeamFromEvent(eventId: string): Promise { From c53d16485dfb063e9b86f6a1bd3c9988308b3f57 Mon Sep 17 00:00:00 2001 From: JulienDuf Date: Thu, 4 Apr 2019 18:01:59 -0400 Subject: [PATCH 3/3] Remove attendee from event when removing attendee form team --- src/modules/database/teams/teams.service.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/modules/database/teams/teams.service.ts b/src/modules/database/teams/teams.service.ts index c47a562..7b26a07 100644 --- a/src/modules/database/teams/teams.service.ts +++ b/src/modules/database/teams/teams.service.ts @@ -186,5 +186,15 @@ export class TeamsService extends BaseService { attendees: attendeeId } }).exec(); + + await this.eventsModel.updateOne({ + _id: eventId + }, { + $pull: { + attendees: { + attendee: attendeeId + } + } + }); } }