Skip to content

Commit 459237c

Browse files
committed
Merge branch 'feat/teams' into chore
2 parents f87c1fc + 34ecbaf commit 459237c

44 files changed

Lines changed: 2217 additions & 257 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.DS_Store

0 Bytes
Binary file not shown.

backend/src/controllers/application-controller.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
JsonController,
99
NotAcceptableError,
1010
NotFoundError,
11+
Param,
1112
Post,
1213
Put,
1314
} from "routing-controllers";
@@ -39,7 +40,14 @@ import {
3940
IDsRequestDTO,
4041
QuestionDTO,
4142
StoreAnswersRequestDTO,
43+
SuccessResponseDTO,
44+
TeamDTO,
45+
TeamRequestDTO,
46+
TeamResponseDTO,
47+
TeamUpdateDTO,
4248
} from "./dto";
49+
import { ITeamService, TeamServiceToken } from "../services/team-service";
50+
import { Team } from "../entities/team";
4351

4452
@JsonController("/application")
4553
export class ApplicationController {
@@ -48,6 +56,8 @@ export class ApplicationController {
4856
private readonly _application: IApplicationService,
4957
@Inject(UserServiceToken)
5058
private readonly _users: IUserService,
59+
@Inject(TeamServiceToken)
60+
private readonly _teams: ITeamService,
5161
) {}
5262

5363
/**
@@ -232,4 +242,109 @@ export class ApplicationController {
232242

233243
await this._application.checkIn(user);
234244
}
245+
246+
/**
247+
* Gets all existing teams.
248+
*/
249+
@Get("/team")
250+
@Authorized(UserRole.User)
251+
public async getAllTeams(): Promise<readonly TeamDTO[]> {
252+
const teams = await this._teams.getAllTeams();
253+
return teams.map((team) => convertBetweenEntityAndDTO(team, TeamDTO));
254+
}
255+
256+
/**
257+
* Creates a team.
258+
*/
259+
@Post("/team")
260+
@Authorized(UserRole.User)
261+
public async createTeam(
262+
@Body() { data: teamDTO }: { data: TeamRequestDTO },
263+
): Promise<TeamDTO> {
264+
const team = convertBetweenEntityAndDTO(teamDTO, Team);
265+
const createdTeam = await this._teams.createTeam(team);
266+
return convertBetweenEntityAndDTO(createdTeam, TeamDTO);
267+
}
268+
269+
/**
270+
* Update a team.
271+
*/
272+
@Put("/team")
273+
@Authorized(UserRole.User)
274+
public async updateTeam(
275+
@Body() { data: teamDTO }: { data: TeamUpdateDTO },
276+
@CurrentUser() user: User,
277+
): Promise<TeamDTO> {
278+
const team = convertBetweenEntityAndDTO(teamDTO, Team);
279+
const updateTeam = await this._teams.updateTeam(team, user);
280+
return convertBetweenEntityAndDTO(updateTeam, TeamDTO);
281+
}
282+
283+
/**
284+
* Request to join a team.
285+
* @param teamId The id of the team
286+
*/
287+
@Post("/team/:id/request")
288+
@Authorized(UserRole.User)
289+
public async requestToJoinTeam(
290+
@Param("id") teamId: number,
291+
@CurrentUser() user: User,
292+
): Promise<SuccessResponseDTO> {
293+
await this._teams.requestToJoinTeam(teamId, user);
294+
const response = new SuccessResponseDTO();
295+
response.success = true;
296+
return response;
297+
}
298+
299+
/**
300+
* Accept a user to a team.
301+
* @param teamId The id of the team
302+
* @param userId The id of the user
303+
*/
304+
@Put("/team/:teamId/accept/:userId")
305+
@Authorized(UserRole.User)
306+
public async acceptUserToTeam(
307+
@Param("teamId") teamId: number,
308+
@Param("userId") userId: number,
309+
@CurrentUser() user: User,
310+
): Promise<SuccessResponseDTO> {
311+
await this._teams.acceptUserToTeam(teamId, userId, user);
312+
const response = new SuccessResponseDTO();
313+
response.success = true;
314+
return response;
315+
}
316+
317+
/**
318+
* Get team by id.
319+
* @param id The id of the team
320+
*/
321+
@Get("/team/:id")
322+
@Authorized(UserRole.User)
323+
public async getTeamByID(
324+
@Param("id") teamId: number,
325+
): Promise<TeamResponseDTO> {
326+
const team = await this._teams.getTeamByID(teamId);
327+
328+
if (team == null) {
329+
throw new NotFoundError(`no team with id ${teamId}`);
330+
}
331+
332+
return team;
333+
}
334+
335+
/**
336+
* Delete a team by id
337+
* @param id The id of the team
338+
*/
339+
@Delete("/team/:id")
340+
@Authorized(UserRole.User)
341+
public async deleteTeamByID(
342+
@Param("id") teamId: number,
343+
@CurrentUser() user: User,
344+
): Promise<SuccessResponseDTO> {
345+
await this._teams.deleteTeamByID(teamId, user);
346+
const response = new SuccessResponseDTO();
347+
response.success = true;
348+
return response;
349+
}
235350
}

backend/src/controllers/dto.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ export class EmailSettingsDTO implements DTO<EmailSettings> {
142142
@ValidateNested()
143143
@Expose()
144144
public admittedEmail!: EmailTemplateDTO;
145+
@Type(() => EmailTemplateDTO)
146+
@ValidateNested()
147+
@Expose()
148+
public submittedEmail!: EmailTemplateDTO;
145149
}
146150

147151
export class SettingsDTO implements DTO<Omit<Settings, "updatedAt">> {
@@ -418,6 +422,8 @@ export class UserDTO {
418422
public declined!: boolean;
419423
@Expose()
420424
public checkedIn!: boolean;
425+
@Expose()
426+
public profileSubmitted!: boolean;
421427
}
422428

423429
export class UserTokenResponseDTO {
@@ -467,11 +473,20 @@ export class StoreAnswersRequestDTO
467473
public data!: readonly AnswerDTO[];
468474
}
469475

476+
export class UserListDto {
477+
@Expose()
478+
public id!: number;
479+
@Expose()
480+
public name!: string;
481+
}
482+
470483
export class ApplicationDTO {
471484
@Expose()
472485
@Type(() => UserDTO)
473486
public user!: UserDTO;
474487
@Expose()
488+
public teams!: string[];
489+
@Expose()
475490
@Type(() => AnswerDTO)
476491
public answers!: AnswerDTO[];
477492
}
@@ -485,3 +500,64 @@ export class IDRequestDTO implements IApiRequest<number> {
485500
@IsInt()
486501
public data!: number;
487502
}
503+
504+
export class UserResponseDto {
505+
@Expose()
506+
public id!: number;
507+
@Expose()
508+
public name!: string;
509+
}
510+
511+
export class TeamDTO {
512+
@Expose()
513+
public id!: number;
514+
@Expose()
515+
public title!: string;
516+
@Expose()
517+
public users?: string[];
518+
@Expose()
519+
public teamImg!: string;
520+
@Expose()
521+
public description!: string;
522+
}
523+
524+
export class TeamResponseDTO {
525+
@Expose()
526+
public id!: number;
527+
@Expose()
528+
public title!: string;
529+
@Expose()
530+
@Type(() => UserResponseDto)
531+
public users?: UserResponseDto[];
532+
@Expose()
533+
public teamImg!: string;
534+
@Expose()
535+
public description!: string;
536+
@Expose()
537+
@Type(() => UserResponseDto)
538+
public requests?: UserResponseDto[];
539+
}
540+
541+
export class TeamRequestDTO {
542+
@Expose()
543+
public title!: string;
544+
@Expose()
545+
public users?: number[];
546+
@Expose()
547+
public teamImg!: string;
548+
@Expose()
549+
public description!: string;
550+
}
551+
552+
export class TeamUpdateDTO {
553+
@Expose()
554+
public id!: number;
555+
@Expose()
556+
public title!: string;
557+
@Expose()
558+
public users?: number[];
559+
@Expose()
560+
public teamImg!: string;
561+
@Expose()
562+
public description!: string;
563+
}

backend/src/controllers/users-controller.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
SignupResponseDTO,
3030
SuccessResponseDTO,
3131
UserDTO,
32+
UserListDto,
3233
UserTokenResponseDTO,
3334
} from "./dto";
3435

@@ -171,6 +172,15 @@ export class UsersController {
171172
return response;
172173
}
173174

175+
/**
176+
* Get user list only with names and ids
177+
*/
178+
@Get("/list")
179+
@Authorized(UserRole.User)
180+
public async getUserList(): Promise<UserListDto[]> {
181+
return await this._users.getAllUsers();
182+
}
183+
174184
/**
175185
* Deletes the user with the given id.
176186
* @param userID The id of the user to delete

backend/src/entities/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export class EmailSettings {
4343
@Column(() => EmailTemplate)
4444
public admittedEmail!: EmailTemplate;
4545
@Column(() => EmailTemplate)
46+
public submittedEmail!: EmailTemplate;
47+
@Column(() => EmailTemplate)
4648
public forgotPasswordEmail!: EmailTemplate;
4749
}
4850

backend/src/entities/team.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
2+
3+
@Entity()
4+
export class Team {
5+
@PrimaryGeneratedColumn()
6+
public readonly id!: number;
7+
@Column({ length: 1024 })
8+
public title!: string;
9+
@Column("simple-array")
10+
public users!: number[];
11+
@Column()
12+
public teamImg!: string;
13+
@Column("longtext")
14+
public description!: string;
15+
@Column("simple-array")
16+
public requests!: number[];
17+
}

backend/src/entities/user.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export class User {
3636
@Column({ default: null, type: "datetime" })
3737
public confirmationExpiresAt!: Date | null;
3838
@Column({ default: false })
39+
public profileSubmitted!: boolean;
40+
@Column({ default: false })
3941
public admitted!: boolean;
4042
@Column({ default: false })
4143
public confirmed!: boolean;

backend/src/services/application-service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from "./question-service";
1919
import { ISettingsService, SettingsServiceToken } from "./settings-service";
2020
import { IUserService, UserServiceToken } from "./user-service";
21+
import { Team } from "../entities/team";
2122

2223
/**
2324
* A form containing questions and given answers.
@@ -40,6 +41,7 @@ export interface IRawAnswer {
4041
*/
4142
export interface IApplication {
4243
user: User;
44+
teams: string[];
4345
answers: readonly Answer[];
4446
}
4547

@@ -119,6 +121,7 @@ export const ApplicationServiceToken = new Token<IApplicationService>();
119121
@Service(ApplicationServiceToken)
120122
export class ApplicationService implements IApplicationService {
121123
private _answers!: Repository<Answer>;
124+
private _teams!: Repository<Team>;
122125

123126
constructor(
124127
@Inject(QuestionGraphServiceToken)
@@ -135,6 +138,7 @@ export class ApplicationService implements IApplicationService {
135138
*/
136139
public async bootstrap(): Promise<void> {
137140
this._answers = this._database.getRepository(Answer);
141+
this._teams = this._database.getRepository(Team);
138142
}
139143

140144
/**
@@ -404,8 +408,12 @@ export class ApplicationService implements IApplicationService {
404408

405409
if (user.initialProfileFormSubmittedAt == null) {
406410
user.initialProfileFormSubmittedAt = new Date();
411+
// send mail to user about successful submission
412+
await this._email.sendSubmissionEmail(user);
407413
await this._users.updateUser(user);
408414
}
415+
user.profileSubmitted = true;
416+
await this._users.updateUser(user);
409417
}
410418

411419
/**
@@ -537,8 +545,13 @@ export class ApplicationService implements IApplicationService {
537545
}
538546
}
539547

548+
const allTeams = await this._teams.find();
549+
540550
const applications = allUsers.map<IApplication>((user) => ({
541551
answers: answersByUserID.get(user.id) ?? [],
552+
teams: allTeams
553+
.filter((team) => team.users.toString().includes(user.id.toString()))
554+
.map((team) => team.title),
542555
user,
543556
}));
544557

0 commit comments

Comments
 (0)