diff --git a/backend/src/bundles/auth/auth.controller.ts b/backend/src/bundles/auth/auth.controller.ts index eaf4bb7a3..324870b15 100644 --- a/backend/src/bundles/auth/auth.controller.ts +++ b/backend/src/bundles/auth/auth.controller.ts @@ -120,6 +120,8 @@ class AuthController extends BaseController { * format: email * password: * type: string + * fullName: + * type: string * responses: * 201: * description: Successful operation @@ -131,6 +133,13 @@ class AuthController extends BaseController { * message: * type: object * $ref: '#/components/schemas/User' + * 400: + * description: Failed operation + * content: + * application/json: + * schema: + * type: object + * $ref: '#/components/schemas/Error' */ private async signUp( diff --git a/backend/src/bundles/auth/auth.service.ts b/backend/src/bundles/auth/auth.service.ts index fabf0d934..1372f1a8d 100644 --- a/backend/src/bundles/auth/auth.service.ts +++ b/backend/src/bundles/auth/auth.service.ts @@ -49,9 +49,17 @@ class AuthService { return user.toObject(); } - public signUp( + public async signUp( userRequestDto: UserSignUpRequestDto, ): Promise { + const { email } = userRequestDto; + const emailExists = await this.userService.findByEmail(email); + if (emailExists) { + throw new HttpError({ + message: UserValidationMessage.EMAIL_ALREADY_EXISTS, + status: HttpCode.BAD_REQUEST, + }); + } return this.userService.create(userRequestDto); } } diff --git a/backend/src/bundles/users/user.entity.ts b/backend/src/bundles/users/user.entity.ts index 5a6f4a929..0d2f01e9b 100644 --- a/backend/src/bundles/users/user.entity.ts +++ b/backend/src/bundles/users/user.entity.ts @@ -5,6 +5,8 @@ class UserEntity implements Entity { private 'email': string; + private 'fullName': string; + private 'passwordHash': string; private 'passwordSalt': string; @@ -12,16 +14,19 @@ class UserEntity implements Entity { private constructor({ id, email, + fullName, passwordHash, passwordSalt, }: { id: number | null; email: string; + fullName: string; passwordHash: string; passwordSalt: string; }) { this.id = id; this.email = email; + this.fullName = fullName; this.passwordHash = passwordHash; this.passwordSalt = passwordSalt; } @@ -29,17 +34,20 @@ class UserEntity implements Entity { public static initialize({ id, email, + fullName, passwordHash, passwordSalt, }: { id: number; email: string; + fullName: string; passwordHash: string; passwordSalt: string; }): UserEntity { return new UserEntity({ id, email, + fullName, passwordHash, passwordSalt, }); @@ -47,16 +55,19 @@ class UserEntity implements Entity { public static initializeNew({ email, + fullName, passwordHash, passwordSalt, }: { email: string; + fullName: string; passwordHash: string; passwordSalt: string; }): UserEntity { return new UserEntity({ id: null, email, + fullName, passwordHash, passwordSalt, }); @@ -65,20 +76,24 @@ class UserEntity implements Entity { public toObject(): { id: number; email: string; + fullName: string; } { return { id: this.id as number, email: this.email, + fullName: this.fullName, }; } public toNewObject(): { email: string; + fullName: string; passwordHash: string; passwordSalt: string; } { return { email: this.email, + fullName: this.fullName, passwordHash: this.passwordHash, passwordSalt: this.passwordSalt, }; diff --git a/backend/src/bundles/users/user.model.ts b/backend/src/bundles/users/user.model.ts index f46dd5f46..32d23b7dd 100644 --- a/backend/src/bundles/users/user.model.ts +++ b/backend/src/bundles/users/user.model.ts @@ -6,6 +6,8 @@ import { class UserModel extends AbstractModel { public 'email': string; + public 'fullName': string; + public 'passwordHash': string; public 'passwordSalt': string; diff --git a/backend/src/bundles/users/user.repository.ts b/backend/src/bundles/users/user.repository.ts index b4d6110d8..af68505e8 100644 --- a/backend/src/bundles/users/user.repository.ts +++ b/backend/src/bundles/users/user.repository.ts @@ -26,12 +26,14 @@ class UserRepository implements Repository { } public async create(entity: UserEntity): Promise { - const { email, passwordSalt, passwordHash } = entity.toNewObject(); + const { email, fullName, passwordSalt, passwordHash } = + entity.toNewObject(); const item = await this.userModel .query() .insert({ email, + fullName, passwordSalt, passwordHash, }) diff --git a/backend/src/bundles/users/user.service.ts b/backend/src/bundles/users/user.service.ts index 778c8f95f..b55b0c301 100644 --- a/backend/src/bundles/users/user.service.ts +++ b/backend/src/bundles/users/user.service.ts @@ -39,8 +39,9 @@ class UserService implements Service { const user = await this.userRepository.create( UserEntity.initializeNew({ email: payload.email, - passwordSalt: salt, // TODO - passwordHash: hash, // TODO + fullName: payload.fullName, + passwordSalt: salt, + passwordHash: hash, }), ); diff --git a/backend/src/migrations/20240820093010_add_name_field_to_users_table.ts b/backend/src/migrations/20240820093010_add_name_field_to_users_table.ts new file mode 100644 index 000000000..22749b59e --- /dev/null +++ b/backend/src/migrations/20240820093010_add_name_field_to_users_table.ts @@ -0,0 +1,17 @@ +import { type Knex } from 'knex'; + +const TABLE_NAME = 'users'; + +async function up(knex: Knex): Promise { + await knex.schema.table(TABLE_NAME, (table) => { + table.string('full_name').notNullable(); + }); +} + +async function down(knex: Knex): Promise { + await knex.schema.table(TABLE_NAME, (table) => { + table.dropColumn('full_name'); + }); +} + +export { down, up }; diff --git a/frontend/src/bundles/auth/components/sign-up-form/constants/constants.ts b/frontend/src/bundles/auth/components/sign-up-form/constants/constants.ts index bee02e2d4..d2168f79b 100644 --- a/frontend/src/bundles/auth/components/sign-up-form/constants/constants.ts +++ b/frontend/src/bundles/auth/components/sign-up-form/constants/constants.ts @@ -1,7 +1,7 @@ import { type UserSignUpRequestDto } from '~/bundles/users/users.js'; const DEFAULT_SIGN_UP_PAYLOAD: UserSignUpRequestDto = { - name: '', + fullName: '', email: '', password: '', confirmPassword: '', diff --git a/frontend/src/bundles/auth/components/sign-up-form/sign-up-form.tsx b/frontend/src/bundles/auth/components/sign-up-form/sign-up-form.tsx index 2d58981e3..d1d7b74ad 100644 --- a/frontend/src/bundles/auth/components/sign-up-form/sign-up-form.tsx +++ b/frontend/src/bundles/auth/components/sign-up-form/sign-up-form.tsx @@ -63,7 +63,7 @@ const SignUpForm: React.FC = ({ onSubmit }) => { type="text" label="Full Name" placeholder="Name" - name="name" + name="fullName" required /> ({ - name: z + fullName: z .string({ required_error: UserValidationMessage.FIELD_REQUIRE }) .trim(), email: z @@ -46,6 +46,10 @@ const userSignUp = z }), }) .required() + .refine((data) => data.fullName.split(/\s+/).length >= 2, { + message: UserValidationMessage.FULL_NAME_INVALID, + path: ['name'], + }) .refine((data) => data.password === data.confirmPassword, { message: UserValidationMessage.PASS_DONT_MATCH, path: ['confirmPassword'],