From c575df84537240e74af2b2f38a8c17651fb99567 Mon Sep 17 00:00:00 2001 From: Glemen Neo Date: Tue, 17 Sep 2024 02:29:08 +0800 Subject: [PATCH] [#7] feat: add new models and dto for user --- backend/user-service/src/models/user.model.ts | 47 ++++++++++++++++ .../user-service/src/types/CreateUserDto.ts | 56 +++++++++++++++++++ backend/user-service/src/types/IUser.ts | 14 +++++ backend/user-service/src/types/Proficiency.ts | 6 ++ backend/user-service/src/types/Role.ts | 4 ++ .../user-service/src/types/TypedRequest.ts | 5 ++ backend/user-service/src/types/UserDto.ts | 53 ++++++++++++++++++ backend/user-service/tsconfig.json | 2 +- 8 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 backend/user-service/src/models/user.model.ts create mode 100644 backend/user-service/src/types/CreateUserDto.ts create mode 100644 backend/user-service/src/types/IUser.ts create mode 100644 backend/user-service/src/types/Proficiency.ts create mode 100644 backend/user-service/src/types/Role.ts create mode 100644 backend/user-service/src/types/TypedRequest.ts create mode 100644 backend/user-service/src/types/UserDto.ts diff --git a/backend/user-service/src/models/user.model.ts b/backend/user-service/src/models/user.model.ts new file mode 100644 index 0000000000..d72e28c6bf --- /dev/null +++ b/backend/user-service/src/models/user.model.ts @@ -0,0 +1,47 @@ +import { Schema, model } from 'mongoose' +import { IUser } from '../types/IUser' +import { Proficiency } from '../types/Proficiency' +import { Role } from '../types/Role' + +const UserSchema = new Schema({ + username: { + type: String, + required: true, + unique: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + role: { + type: String, + enum: Object.values(Role), + required: true, + }, + proficiency: { + type: String, + enum: Object.values(Proficiency), + required: false, + }, + createdAt: { + type: Date, + required: false, + }, + updatedAt: { + type: Date, + required: false, + default: null, + }, + deletedAt: { + type: Date, + required: false, + default: null, + }, +}) + +export default model('User', UserSchema) diff --git a/backend/user-service/src/types/CreateUserDto.ts b/backend/user-service/src/types/CreateUserDto.ts new file mode 100644 index 0000000000..96d4c737f7 --- /dev/null +++ b/backend/user-service/src/types/CreateUserDto.ts @@ -0,0 +1,56 @@ +import { + IsEmail, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, + IsStrongPassword, + validate, + ValidationError, +} from 'class-validator' +import { Proficiency } from './Proficiency' +import { Role } from './Role' +import { TypedRequest } from './TypedRequest' + +export class CreateUserDto { + @IsString() + @IsNotEmpty() + username: string + + @IsStrongPassword({ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1 }) + password: string + + @IsEmail() + email: string + + @IsEnum(Role) + role: Role + + @IsOptional() + @IsEnum(Proficiency) + proficiency?: Proficiency + + constructor(username: string, email: string, password: string, role: Role, proficiency?: Proficiency) { + this.username = username + this.email = email + this.password = password + this.role = role + this.proficiency = proficiency + } + + static fromRequest({ + body: { username, email, password, role, proficiency }, + }: TypedRequest<{ + username: string + password: string + email: string + role: Role + proficiency: Proficiency | undefined + }>): CreateUserDto { + return new CreateUserDto(username, email, password, role, proficiency) + } + + async validate(): Promise { + return validate(this) + } +} diff --git a/backend/user-service/src/types/IUser.ts b/backend/user-service/src/types/IUser.ts new file mode 100644 index 0000000000..3243e267a5 --- /dev/null +++ b/backend/user-service/src/types/IUser.ts @@ -0,0 +1,14 @@ +import { Proficiency } from './Proficiency' +import { Role } from './Role' + +export interface IUser { + id: string + username: string + email: string + password: string + role: Role + proficiency?: Proficiency + createdAt?: Date + updatedAt?: Date + deletedAt?: Date +} diff --git a/backend/user-service/src/types/Proficiency.ts b/backend/user-service/src/types/Proficiency.ts new file mode 100644 index 0000000000..96c93ed714 --- /dev/null +++ b/backend/user-service/src/types/Proficiency.ts @@ -0,0 +1,6 @@ +export enum Proficiency { + BEGINNER = 'BEGINNER', + INTERMEDIATE = 'INTERMEDIATE', + ADVANCED = 'ADVANCED', + EXPERT = 'EXPERT', +} diff --git a/backend/user-service/src/types/Role.ts b/backend/user-service/src/types/Role.ts new file mode 100644 index 0000000000..0df48ac089 --- /dev/null +++ b/backend/user-service/src/types/Role.ts @@ -0,0 +1,4 @@ +export enum Role { + USER = 'USER', + ADMIN = 'ADMIN', +} diff --git a/backend/user-service/src/types/TypedRequest.ts b/backend/user-service/src/types/TypedRequest.ts new file mode 100644 index 0000000000..f891810f40 --- /dev/null +++ b/backend/user-service/src/types/TypedRequest.ts @@ -0,0 +1,5 @@ +import { Request } from 'express' + +export interface TypedRequest extends Request { + body: T +} diff --git a/backend/user-service/src/types/UserDto.ts b/backend/user-service/src/types/UserDto.ts new file mode 100644 index 0000000000..3b6b3ef973 --- /dev/null +++ b/backend/user-service/src/types/UserDto.ts @@ -0,0 +1,53 @@ +import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString, validate, ValidationError } from 'class-validator' +import { IUser } from './IUser' +import { Proficiency } from './Proficiency' +import { Role } from './Role' +import { TypedRequest } from './TypedRequest' + +export class UserDto { + @IsString() + @IsNotEmpty() + id: string + + @IsString() + @IsNotEmpty() + username: string + + @IsEmail() + email: string + + @IsEnum(Role) + role: Role + + @IsOptional() + @IsEnum(Proficiency) + proficiency?: Proficiency + + constructor(id: string, username: string, email: string, role: Role, proficiency?: Proficiency) { + this.id = id + this.username = username + this.email = email + this.role = role + this.proficiency = proficiency + } + + static fromModel({ id, username, email, role, proficiency }: IUser): UserDto { + return new UserDto(id, username, email, role, proficiency) + } + + static fromRequest({ + body: { id, username, email, role, proficiency }, + }: TypedRequest<{ + id: string + username: string + email: string + role: Role + proficiency: Proficiency | undefined + }>): UserDto { + return new UserDto(id, username, email, role, proficiency) + } + + async validate(): Promise { + return validate(this) + } +} diff --git a/backend/user-service/tsconfig.json b/backend/user-service/tsconfig.json index 1c7a975811..17f8f67dd5 100644 --- a/backend/user-service/tsconfig.json +++ b/backend/user-service/tsconfig.json @@ -14,7 +14,7 @@ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + "experimentalDecorators": true /* Enable experimental support for legacy experimental decorators. */, // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */