Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OV-2: FEAT: Sign-in flow #17

Merged
merged 19 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion backend/src/bundles/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { type UserSignUpRequestDto } from '~/bundles/users/users.js';
import {
type UserSignInRequestDto,
type UserSignUpRequestDto,
userSignInValidationSchema,
} from '~/bundles/users/users.js';
import { userSignUpValidationSchema } from '~/bundles/users/users.js';
import {
type ApiHandlerOptions,
Expand All @@ -20,6 +24,20 @@ class AuthController extends BaseController {

this.authService = authService;

this.addRoute({
path: AuthApiPath.SIGN_IN,
method: 'POST',
validation: {
body: userSignInValidationSchema,
},
handler: (options) =>
this.signIn(
options as ApiHandlerOptions<{
body: UserSignInRequestDto;
}>,
),
});

this.addRoute({
path: AuthApiPath.SIGN_UP,
method: 'POST',
Expand All @@ -35,6 +53,55 @@ class AuthController extends BaseController {
});
}

/**
* @swagger
* /auth/sign-in:
* post:
* description: Sign in user into the application
* requestBody:
* description: User auth data
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* format: email
* password:
* type: string
* responses:
* 200:
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: object
* $ref: '#/components/schemas/User'
* 400:
* description: Failed operation
* content:
* application/json:
* schema:
* type: object
* $ref: '#/components/schemas/Error'
*/

private async signIn(
options: ApiHandlerOptions<{
Copy link
Collaborator

Choose a reason for hiding this comment

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

you should add js doc for endpoint

body: UserSignInRequestDto;
}>,
): Promise<ApiHandlerResponse> {
return {
payload: await this.authService.signIn(options.body),
status: HttpCode.OK,
};
}

/**
* @swagger
* /auth/sign-up:
Expand Down Expand Up @@ -65,6 +132,7 @@ class AuthController extends BaseController {
* type: object
* $ref: '#/components/schemas/User'
*/

private async signUp(
options: ApiHandlerOptions<{
body: UserSignUpRequestDto;
Expand Down
38 changes: 38 additions & 0 deletions backend/src/bundles/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import {
type UserSignUpResponseDto,
} from '~/bundles/users/types/types.js';
import { type UserService } from '~/bundles/users/user.service.js';
import {
type UserSignInRequestDto,
type UserSignInResponseDto,
} from '~/bundles/users/users.js';
import { HttpCode, HttpError } from '~/common/http/http.js';
import { cryptService } from '~/common/services/services.js';

import { UserValidationMessage } from './enums/enums.js';

class AuthService {
private userService: UserService;
Expand All @@ -11,6 +19,36 @@ class AuthService {
this.userService = userService;
}

public async signIn(
userRequestDto: UserSignInRequestDto,
): Promise<UserSignInResponseDto> {
const { email, password } = userRequestDto;
const user = await this.userService.findByEmail(email);

if (!user) {
throw new HttpError({
message: UserValidationMessage.WRONG_CREDENTIALS,
status: HttpCode.BAD_REQUEST,
});
}

const { passwordHash } = user.toNewObject();

const isPwdCorrect = cryptService.compareSyncPassword(
password,
passwordHash,
);

if (!isPwdCorrect) {
throw new HttpError({
message: UserValidationMessage.WRONG_CREDENTIALS,
status: HttpCode.BAD_REQUEST,
});
}

return user.toObject();
}

public signUp(
userRequestDto: UserSignUpRequestDto,
): Promise<UserSignUpResponseDto> {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/bundles/auth/enums/enums.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { AuthApiPath } from 'shared';
export { AuthApiPath, UserValidationMessage } from 'shared';
2 changes: 2 additions & 0 deletions backend/src/bundles/users/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export {
type UserGetAllResponseDto,
type UserSignInRequestDto,
type UserSignInResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from 'shared';
6 changes: 6 additions & 0 deletions backend/src/bundles/users/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class UserRepository implements Repository {
return Promise.resolve(null);
}

public async findByEmail(email: string): Promise<UserEntity | null> {
const user = await this.userModel.query().findOne({ email }).execute();

return user ? UserEntity.initialize(user) : null;
}

public async findAll(): Promise<UserEntity[]> {
const users = await this.userModel.query().execute();

Expand Down
4 changes: 4 additions & 0 deletions backend/src/bundles/users/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class UserService implements Service {
return Promise.resolve(null);
}

public async findByEmail(email: string): Promise<UserEntity | null> {
return await this.userRepository.findByEmail(email);
}

public async findAll(): Promise<UserGetAllResponseDto> {
const items = await this.userRepository.findAll();

Expand Down
7 changes: 6 additions & 1 deletion backend/src/bundles/users/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ const userController = new UserController(logger, userService);

export { userController, userService };
export {
type UserSignInRequestDto,
type UserSignInResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from './types/types.js';
export { UserModel } from './user.model.js';
export { userSignUpValidationSchema } from './validation-schemas/validation-schemas.js';
export {
userSignInValidationSchema,
userSignUpValidationSchema,
} from './validation-schemas/validation-schemas.js';
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { userSignUpValidationSchema } from 'shared';
export { userSignInValidationSchema, userSignUpValidationSchema } from 'shared';
20 changes: 18 additions & 2 deletions backend/src/common/server-application/base-server-app-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,27 @@ class BaseServerAppApi implements ServerAppApi {
definition: {
openapi: '3.0.0',
info: {
title: 'Hello World',
title: 'OutreachVids API documentation',
version: `${this.version}.0.0`,
},
components: {
schemas: {
Error: {
type: 'object',
properties: {
errorType: {
type: 'string',
enum: ['COMMON', 'VALIDATION'],
},
message: {
type: 'string',
},
},
},
},
},
},
apis: [`src/packages/**/*.controller.${controllerExtension}`],
apis: [`src/bundles/**/*.controller.${controllerExtension}`],
});
}
}
Expand Down
1 change: 1 addition & 0 deletions shared/src/bundles/auth/enums/auth-api-path.enum.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const AuthApiPath = {
ROOT: '/',
SIGN_IN: '/sign-in',
SIGN_UP: '/sign-up',
} as const;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const UserValidationMessage = {
EMAIL_REQUIRE: 'Email is required',
EMAIL_WRONG: 'Email is wrong',
EMAIL_INVALID: 'Please enter a valid email',
FIELD_REQUIRE: 'Please fill out this field',
PASSWORD_LENGTH: 'Password must have from 6 to 12 characters',
INVALID_DATA: 'Incorrect email or password. Please try again.',
WRONG_CREDENTIALS: 'Email or password are incorrect',
} as const;

export { UserValidationMessage };
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const UserValidationRule = {
EMAIL_MINIMUM_LENGTH: 1,
EMAIL_MINIMUM_LENGTH: 6,
EMAIL_MAXIMUM_LENGTH: 320,
PASSWORD_MINIMUM_LENGTH: 6,
PASSWORD_MAXIMUM_LENGTH: 12,
} as const;

export { UserValidationRule };
2 changes: 2 additions & 0 deletions shared/src/bundles/users/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { type UserGetAllItemResponseDto } from './user-get-all-item-response-dto.type.js';
export { type UserGetAllResponseDto } from './user-get-all-response-dto.type.js';
export { type UserSignInRequestDto } from './user-sign-in-request-dto.type.js';
export { type UserSignInResponseDto } from './user-sign-in-response-dto.type.js';
export { type UserSignUpRequestDto } from './user-sign-up-request-dto.type.js';
export { type UserSignUpResponseDto } from './user-sign-up-response-dto.type.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { type UserSignUpRequestDto } from './types.js';

type UserSignInRequestDto = Pick<UserSignUpRequestDto, 'email' | 'password'>;

export { type UserSignInRequestDto };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { type UserSignUpResponseDto } from './user-sign-up-response-dto.type.js';

type UserSignInResponseDto = UserSignUpResponseDto;

export { type UserSignInResponseDto };
7 changes: 6 additions & 1 deletion shared/src/bundles/users/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ export { UsersApiPath, UserValidationMessage } from './enums/enums.js';
export {
type UserGetAllItemResponseDto,
type UserGetAllResponseDto,
type UserSignInRequestDto,
type UserSignInResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from './types/types.js';
export { userSignUp as userSignUpValidationSchema } from './validation-schemas/validation-schemas.js';
export {
userSignIn as userSignInValidationSchema,
userSignUp as userSignUpValidationSchema,
} from './validation-schemas/validation-schemas.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { z } from 'zod';

import { UserValidationMessage, UserValidationRule } from '../enums/enums.js';

type UserSignInRequestValidationDto = {
email: z.ZodString;
password: z.ZodString;
};

const userSignIn = z
.object<UserSignInRequestValidationDto>({
email: z
.string({ required_error: UserValidationMessage.FIELD_REQUIRE })
.trim()
.min(UserValidationRule.EMAIL_MINIMUM_LENGTH, {
message: UserValidationMessage.EMAIL_INVALID,
})
.max(UserValidationRule.EMAIL_MAXIMUM_LENGTH, {
message: UserValidationMessage.EMAIL_INVALID,
})
.email({
message: UserValidationMessage.EMAIL_INVALID,
}),
password: z
.string({ required_error: UserValidationMessage.FIELD_REQUIRE })
.trim()
.min(UserValidationRule.PASSWORD_MINIMUM_LENGTH, {
message: UserValidationMessage.PASSWORD_LENGTH,
})
.max(UserValidationRule.PASSWORD_MAXIMUM_LENGTH, {
message: UserValidationMessage.PASSWORD_LENGTH,
}),
})
.required();

export { userSignIn };
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { userSignIn } from './user-sig-in.validation-schema.js';
export { userSignUp } from './user-sign-up.validation-schema.js';
1 change: 1 addition & 0 deletions shared/src/framework/http/enums/http-code.enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const HttpCode = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNPROCESSED_ENTITY: 422,
INTERNAL_SERVER_ERROR: 500,
} as const;
Expand Down
4 changes: 4 additions & 0 deletions shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ export { AuthApiPath } from './bundles/auth/auth.js';
export {
type UserGetAllItemResponseDto,
type UserGetAllResponseDto,
type UserSignInRequestDto,
type UserSignInResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
UsersApiPath,
userSignInValidationSchema,
userSignUpValidationSchema,
UserValidationMessage,
} from './bundles/users/users.js';
export {
ApiPath,
Expand Down
Loading