Skip to content
7 changes: 6 additions & 1 deletion apps/backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* This is only a minimal backend to get started.
*/

import { Logger } from '@nestjs/common';
import { Logger, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

import { AppModule } from './app/app.module';
Expand All @@ -14,6 +14,11 @@ async function bootstrap() {

const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
app.useGlobalPipes(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

new ValidationPipe({
whitelist: true,
}),
);

const port = process.env.PORT || 3000;
await app.listen(port);
Expand Down
36 changes: 31 additions & 5 deletions apps/backend/src/users/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
// TODO: Probably want these types to be available to both the frontend and backend in a "common" folder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

export enum Status {
MEMBER = 'MEMBER',
RECRUITER = 'RECRUITER',
ADMIN = 'ADMIN',
ALUMNI = 'ALUMNI',
APPLICANT = 'APPLICANT',
MEMBER = 'Member',
RECRUITER = 'Recruiter',
ADMIN = 'Admin',
ALUMNI = 'Alumni',
APPLICANT = 'Applicant',
}

export enum Team {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 should we add EBOARD as one of the possible values for this enum since the roles also contain all of the eboard positions? I think this will depend on what the purpose of the team property is (which we should follow up with product about)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not too familiar with how the org's structure should be represented here but this does sound sensible. I think once we get clearer specifications, we can make a quick change here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added EBOARD to the types. We can see what the purpose of the team property is and remove it if necessary

SFTT = 'Speak For The Trees',
CONSTELLATION = 'Constellation',
JPAL = 'J-PAL',
BREAKTIME = 'Breaktime',
GI = 'Green Infrastructure',
CI = 'Core Infrastructure',
EBOARD = 'E-Board',
}

export enum Role {
DIRECTOR_OF_ENGINEERING = 'Director of Engineering',
DIRECTOR_OF_PRODUCT = 'Director of Product',
DIRECTOR_OF_FINANCE = 'Director of Finance',
DIRECTOR_OF_MARKETING = 'Director of Marketing',
DIRECTOR_OF_RECRUITMENT = 'Director of Recruitment',
DIRECTOR_OF_OPERATIONS = 'Director of Operations',
DIRECTOR_OF_EVENTS = 'Director of Events',
DIRECTOR_OF_DESIGN = 'Director of Design',
PRODUCT_MANAGER = 'Product Manager',
PRODUCT_DESIGNER = 'Product Designer',
TECH_LEAD = 'Technical Lead',
DEVELOPER = 'Developer',
}
50 changes: 50 additions & 0 deletions apps/backend/src/users/update-user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Status, Role, Team } from './types';
import {
IsEmail,
IsOptional,
IsEnum,
IsArray,
ArrayMinSize,
ArrayUnique,
IsUrl,
} from 'class-validator';

export class UpdateUserDTO {
@IsOptional()
@IsEnum(Status)
status?: Status;

@IsOptional()
@IsEmail()
email?: string;

@IsOptional()
profilePicture?: string;

@IsOptional()
@IsUrl({
protocols: ['https'],
require_protocol: true,
host_whitelist: ['www.linkedin.com'],
})
linkedin?: string;

@IsOptional()
@IsUrl({
protocols: ['https'],
require_protocol: true,
host_whitelist: ['github.com'],
})
github?: string;

@IsOptional()
@IsEnum(Team)
team?: Team;

@IsOptional()
@IsArray()
@ArrayMinSize(1)
@ArrayUnique()
@IsEnum(Role, { each: true })
role?: Role[];
}
15 changes: 8 additions & 7 deletions apps/backend/src/users/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Entity, Column, ObjectIdColumn, ObjectId } from 'typeorm';
import { Status } from './types';
import { Entity, Column } from 'typeorm';
import { Status, Role, Team } from './types';

@Entity()
export class User {
@ObjectIdColumn() // https://github.com/typeorm/typeorm/issues/1584
userId: ObjectId;
@Column({ primary: true })
userId: number;

@Column()
status: Status;
Expand All @@ -18,7 +19,7 @@ export class User {
email: string;

@Column()
profilePicture: string;
profilePicture: string | null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 maybe instead of having a null profilePicture, we could have a placeholder picture? Just an idea, maybe for a future enhancement


@Column()
linkedin: string | null;
Expand All @@ -27,8 +28,8 @@ export class User {
github: string | null;

@Column()
team: string | null;
team: Team | null;

@Column()
role: string | null;
role: Role[] | null;
}
15 changes: 14 additions & 1 deletion apps/backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import {
DefaultValuePipe,
ParseBoolPipe,
Query,
Body,
Controller,
Get,
Param,
Patch,
ParseIntPipe,
} from '@nestjs/common';

import { UpdateUserDTO } from './update-user.dto';
import { UsersService } from './users.service';
import { User } from './user.entity';

@Controller('users')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ can we put a newline before this line (just to more visually separate the imports from the actual controller code)?

export class UsersController {
Expand All @@ -19,4 +24,12 @@ export class UsersController {
) {
return this.usersService.findAll(getAllMembers);
}

@Patch(':userId')
async updateUser(
@Body() updateUserDTO: UpdateUserDTO,
@Param('userId', ParseIntPipe) userId: number,
): Promise<User> {
return this.usersService.updateUser(updateUserDTO, userId);
}
}
55 changes: 38 additions & 17 deletions apps/backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { UnauthorizedException, Injectable } from '@nestjs/common';
import {
Injectable,
BadRequestException,
UnauthorizedException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MongoRepository } from 'typeorm';

import { User } from './user.entity';
import { UpdateUserDTO } from './update-user.dto';
import { Status } from './types';
import { ObjectId } from 'mongodb';
import { getCurrentUser } from './utils';

@Injectable()
export class UsersService {
Expand All @@ -16,20 +20,9 @@ export class UsersService {
async findAll(getAllMembers: boolean): Promise<User[]> {
if (!getAllMembers) return [];

const exampleUser: User = {
userId: new ObjectId('a0f3efa0f3efa0f3efa0f3ef'),
status: Status.ADMIN,
firstName: 'jimmy',
lastName: 'jimmy2',
email: 'jimmy.jimmy2@mail.com',
profilePicture: null,
linkedin: null,
github: null,
team: null,
role: null,
};

if (exampleUser.status == Status.APPLICANT) {
const currentUser = getCurrentUser();

if (currentUser.status === Status.APPLICANT) {
throw new UnauthorizedException();
}

Expand All @@ -41,4 +34,32 @@ export class UsersService {

return users;
}

async updateUser(
updateUserDTO: UpdateUserDTO,
userId: number,
): Promise<User> {
const user: User = await this.usersRepository.findOne({
where: {
userId: { $eq: userId },
},
});

if (!user) {
throw new BadRequestException(`User ${userId} not found.`);
}

const currentUser = getCurrentUser();

if (currentUser.status !== Status.ADMIN && userId !== currentUser.userId) {
throw new UnauthorizedException();
}

await this.usersRepository.update({ userId }, updateUserDTO);
return await this.usersRepository.findOne({
where: {
userId: { $eq: userId },
},
});
}
}
15 changes: 15 additions & 0 deletions apps/backend/src/users/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Status } from './types';
import { User } from './user.entity';

export const getCurrentUser = (): User => ({
userId: 999,
status: Status.ADMIN,
firstName: 'jimmy',
lastName: 'jimmy2',
email: 'jimmy.jimmy2@mail.com',
profilePicture: null,
linkedin: null,
github: null,
team: null,
role: null,
});