TIL
Node.js 백오피스 팀 프로젝트 (4)
황민도
2024. 6. 24. 05:09
내일배움캠프 스파르타 코딩클럽
< routers. index.js >
import express from 'express';
import { authRouter } from './auth.router.js';
import { bookingRouter } from './bookings.router.js';
import { bookmarkRouter } from './bookmarks.router.js';
import { petsitterRouter } from './petsitters.router.js';
import { reviewRouter } from './reviews.router.js';
import { usersRouter } from './users.router.js';
const apiRouter = express.Router();
apiRouter.use('/auth', authRouter);
apiRouter.use('/users', usersRouter);
apiRouter.use('/petsitters', petsitterRouter);
apiRouter.use('/bookings', bookingRouter);
apiRouter.use('/bookmarks', bookmarkRouter);
apiRouter.use('/reviews', reviewRouter);
export { apiRouter };
< routers. auth.router.js >
import express from 'express';
import { prisma } from '../utils/prisma.utils.js';
import { UsersRepository } from '../repositories/users.repository.js';
import { PetsitterRepository } from '../repositories/petsitters.repository.js';
import { AuthService } from '../services/auth.service.js';
import { AuthController } from '../controllers/auth.controller.js';
import { signUpValidator } from '../middlewares/validators/sign-up-validator.middleware.js';
import { signInValidator } from '../middlewares/validators/sign-in-validator.middleware.js';
import { requireRefreshToken } from '../middlewares/require-refresh-token.middleware.js';
const authRouter = express.Router();
const usersRepository = new UsersRepository(prisma);
const petsitterRepository = new PetsitterRepository(prisma);
const authService = new AuthService(usersRepository, petsitterRepository);
const authController = new AuthController(authService);
// 회원가입
authRouter.post('/sign-up', signUpValidator, authController.signUp);
// 로그인
authRouter.post('/sign-in', signInValidator, authController.signIn);
//펫시터 회원가입
authRouter.post(
'/petsitters/sign-up',
signUpValidator,
authController.signUpPetsitter
);
//펫시터 로그인
authRouter.post(
'/petsitters/sign-in',
signInValidator,
authController.signInPetsitter
);
// 로그아웃
authRouter.post('/sign-out', requireRefreshToken, authController.signOut);
// 토큰 재발급
authRouter.post('/renew-tokens', requireRefreshToken, authController.renewTokens);
export { authRouter };
< controllers. auth.controller.js >
import { MESSAGES } from '../constants/message.constant.js';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
export class AuthController {
constructor(authService) {
this.authService = authService;
}
// 회원가입
signUp = async (req, res, next) => {
try {
const { email, password, name, introduce, profileImage } = req.body;
const data = await this.authService.signUp({
email,
password,
name,
introduce,
profileImage,
});
return res.status(HTTP_STATUS.CREATED).json({
status: HTTP_STATUS.CREATED,
message: MESSAGES.AUTH.SIGN_UP.SUCCEED,
data,
});
} catch (error) {
next(error);
}
};
// 로그인
signIn = async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await this.authService.signIn({
email,
password,
});
const { accessToken, refreshToken } =
await this.authService.createAccessAndRefreshToken({
id: user.id,
role: 'user',
});
return res.status(HTTP_STATUS.OK).json({
status: HTTP_STATUS.OK,
message: MESSAGES.AUTH.SIGN_IN.SUCCEED,
accessToken,
refreshToken,
});
} catch (error) {
next(error);
}
};
//펫시터 회원가입
signUpPetsitter = async (req, res, next) => {
try {
const { email, password, name, experience, introduce, profileImage } =
req.body;
const data = await this.authService.createPetsitter({
email,
password,
name,
experience: experience ? Number(experience) : undefined,
introduce,
profileImage,
});
res
.status(HTTP_STATUS.CREATED)
.json({ message: '펫시터로 회원가입 했습니다.', data: data.id });
} catch (error) {
next(error);
}
};
//펫시터 로그인
signInPetsitter = async (req, res, next) => {
try {
const { email, password } = req.body;
const petsitter = await this.authService.signInPetsitter({
email,
password,
});
const { accessToken, refreshToken } =
await this.authService.createAccessAndRefreshToken({
id: petsitter.id,
role: 'petsitter',
});
res
.status(HTTP_STATUS.OK)
.json({ result: true, accessToken, refreshToken });
return;
} catch (error) {
next(error);
}
};
// 로그아웃
signOut = async (req, res, next) => {
try {
const user = req.user;
await this.authService.signOut({
id: user.id,
hashedRefreshToken: 'nodata',
});
return res.status(HTTP_STATUS.OK).json({
status: HTTP_STATUS.OK,
message: MESSAGES.AUTH.SIGN_OUT.SUCCEED,
data: { id: user.id },
});
} catch (error) {
next(error);
}
};
// 토큰 재발급
renewTokens = async (req, res, next) => {
try {
const user = req.user;
const id = user.id;
const role = user.role;
console.log(id, role)
const { accessToken, refreshToken } =
await this.authService.createAccessAndRefreshToken({
id,
role,
});
return res.status(HTTP_STATUS.OK).json({
status: HTTP_STATUS.OK,
message: MESSAGES.AUTH.SIGN_IN.TOKEN,
accessToken,
refreshToken,
});
} catch (error) {
next(error);
}
};
}
< services. auth.service.js >
import { MESSAGES } from '../constants/message.constant.js';
import { HttpError } from '../errors/http.error.js';
import { HASH_SALT_ROUNDS } from '../constants/auth.constant.js';
import bcrypt from 'bcrypt';
import { createAccessToken, createRefreshToken } from '../utils/tokens.js';
export class AuthService {
constructor(usersRepository, petsitterRepository) {
this.usersRepository = usersRepository;
this.petsitterRepository = petsitterRepository;
}
// 회원가입
signUp = async ({ email, password, name, introduce, profileImage }) => {
const findOneEmail = await this.usersRepository.findOneEmail(email);
if (findOneEmail) {
throw new HttpError.Conflict(MESSAGES.AUTH.COMMON.EMAIL.DUPLICATED);
}
const hashedPassword = bcrypt.hashSync(password, HASH_SALT_ROUNDS);
const { password: _pw, ...data } = await this.usersRepository.createUsers({
email,
hashedPassword,
name,
introduce,
profileImage,
});
return data;
};
// 로그인
signIn = async ({ email, password }) => {
const user = await this.usersRepository.findOneEmail(email);
const passwordRef = user && bcrypt.compareSync(password, user.password);
if (!passwordRef) {
throw new HttpError.Unauthorized(MESSAGES.AUTH.SIGN_IN.UNAUTHORIZED);
}
return user;
};
// 펫시터 회원가입
createPetsitter = async ({
email,
password,
name,
experience,
introduce,
profileImage,
}) => {
const existingPetsitter =
await this.petsitterRepository.findPetsitterByEmail({
email,
});
if (existingPetsitter) {
throw new HttpError.Conflict('이미 가입된 이메일입니다.');
}
const hashedPassword = await bcrypt.hash(password, HASH_SALT_ROUNDS);
const user = await this.petsitterRepository.createPetsitter({
email,
password: hashedPassword,
name,
experience,
introduce,
profileImage,
});
return user;
};
// 펫시터 로그인
signInPetsitter = async ({ email, password }) => {
const petsitter = await this.petsitterRepository.findPetsitterByEmail({
email,
});
const decodedPassword = petsitter
? await bcrypt.compare(password, petsitter.password)
: null;
if (!petsitter || !decodedPassword) {
throw new HttpError.Unauthorized('인증 정보가 유효하지 않습니다.');
}
return petsitter;
};
// 로그아웃
signOut = async ({ id, hashedRefreshToken }) => {
await this.usersRepository.refreshTokenUpdate({ id, hashedRefreshToken });
};
//액세스토큰, 리프레시 토큰 발급
createAccessAndRefreshToken = async ({ id, role }) => {
const accessToken = createAccessToken({ id, role });
const refreshToken = createRefreshToken({ id, role });
if (role === 'user') {
const hashedRefreshToken = bcrypt.hashSync(
refreshToken,
HASH_SALT_ROUNDS
);
await this.usersRepository.refreshTokenUpdate({ id, hashedRefreshToken });
}
return { accessToken, refreshToken };
};
}
< repositories. users.repository.js >
export class UsersRepository {
constructor(prisma) {
this.prisma = prisma;
}
createUsers = async ({
email,
hashedPassword,
name,
introduce,
profileImage,
}) => {
const user = await this.prisma.user.create({
data: {
email,
password: hashedPassword,
name,
introduce,
profileImage,
},
});
await this.prisma.refreshToken.create({
data: {
refreshToken: null,
user: {
connect: { id: user.id },
},
},
});
return user;
};
findOneEmail = async (email) => {
const data = await this.prisma.user.findUnique({
where: { email },
});
return data;
};
findOneId = async (id) => {
const data = await this.prisma.user.findUnique({
where: { id },
});
const result = {
id: data.id,
email: data.email,
name: data.name,
introduce: data.introduce,
profileImage: data.profileImage,
createdAt: data.createdAt,
updatedAt: data.updatedAt,
};
return result;
};
findOneRefreshTokenId = async (id) => {
const data = await this.prisma.refreshToken.findUnique({
where: { userId: id },
});
return data;
};
getProfile = async (id) => {
const data = await this.prisma.user.findUnique({
where: { id },
select: {
id: true,
email: true,
name: true,
introduce: true,
profileImage: true,
createdAt: true,
updatedAt: true,
},
});
return data;
};
// 비밀번호를 포함한 사용자 정보 가져오기
getProfileWithPassword = async (userId) => {
const userData = await this.prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
name: true,
introduce: true,
profileImage: true,
password: true,
createdAt: true,
updatedAt: true,
},
});
return userData;
};
updateUser = async (userId, newPassword, name, introduce, profileImage) => {
return await this.prisma.user.update({
where: { id: userId },
data: { password: newPassword, name, introduce, profileImage },
});
};
refreshTokenUpdate = async ({ id, hashedRefreshToken }) => {
if (hashedRefreshToken === 'nodata') {
await this.prisma.refreshToken.update({
where: {
userId: id,
},
data: {
refreshToken: null,
},
});
} else {
await this.prisma.refreshToken.update({
where: {
userId: id,
},
data: {
refreshToken: hashedRefreshToken,
},
});
}
};
}
< routers. users.router.js >
import express from 'express';
import UsersController from '../controllers/users.controllers.js';
import UsersService from '../services/users.service.js';
import { UsersRepository } from '../repositories/users.repository.js';
import { updateProfileValidator } from '../middlewares/validators/update-profile-validator.middleware.js';
import { requireAccessToken } from '../middlewares/require-access-token.middleware.js';
import { prisma } from '../utils/prisma.utils.js';
import { upload } from '../middlewares/upload-image.js';
import { requireRoles } from '../middlewares/require-roles.middleware.js';
const usersRouter = express.Router();
const usersRepository = new UsersRepository(prisma);
const usersService = new UsersService(usersRepository);
const usersController = new UsersController(usersService);
usersRouter.get(
'/mypage',
requireAccessToken,
requireRoles(['user']),
usersController.readMe
);
usersRouter.patch(
'/mypage',
requireAccessToken,
requireRoles(['user']),
upload,
updateProfileValidator,
usersController.updateUsers
);
export { usersRouter };
< controllers. users.controllers.js >
import { HTTP_STATUS } from '../constants/http-status.constant.js';
// User의 컨트롤러(Controller)역할을 하는 클래스
export default class UsersController {
constructor(usersService) {
this.usersService = usersService;
}
// 프로필 조회
readMe = async (req, res, next) => {
try {
const userId = req.user.id;
const userInfo = await this.usersService.getUserInfoByUserId(userId);
res.status(HTTP_STATUS.OK).json({
status: HTTP_STATUS.OK,
message: '해당 유저를 성공적으로 찾아 왔습니다.',
data: userInfo,
});
} catch (err) {
next(err);
}
};
//프로필 업데이트
updateUsers = async (req, res, next) => {
try {
const userId = req.user.id;
const {
password,
newPassword,
newPasswordConfirm,
name,
introduce,
profileImage,
} = req.body;
// 사용자 정보 업데이트 서비스 호출
const updatedUser = await this.usersService.updateUser(
userId,
password,
newPassword,
newPasswordConfirm,
name,
introduce,
profileImage
);
// 프로필 이미지 처리
if (req.file) {
userData.profileImage = req.file.path;
}
res.status(HTTP_STATUS.OK).json({
status: HTTP_STATUS.OK,
message: '사용자 정보를 성공적으로 업데이트했습니다.',
data: updatedUser,
});
} catch (err) {
next(err);
}
};
}
< services. users.service.js >
import bcrypt from 'bcrypt';
import { HttpError } from '../errors/http.error.js';
import { HASH_SALT_ROUNDS } from '../constants/auth.constant.js';
export default class UsersService {
constructor(usersRepository) {
this.usersRepository = usersRepository;
}
getUserInfoByUserId = async (userId) => {
const userInfo = await this.usersRepository.getProfile(userId);
if (!userInfo) {
throw new HttpError.NotFound('사용자를 찾을 수 없습니다.');
}
return userInfo;
};
updateUser = async (
userId,
password,
newPassword,
newPasswordConfirm,
name,
introduce,
profileImage
) => {
const existedUser =
await this.usersRepository.getProfileWithPassword(userId);
if (!existedUser) {
throw new HttpError.NotFound('사용자를 찾을 수 없습니다.');
}
// 비밀번호 확인 및 해싱
if (password) {
console.log(password);
console.log(existedUser.password);
console.log(password === existedUser.password);
const dbPasswordMatch = await bcrypt.compare(
password,
existedUser.password
);
if (!dbPasswordMatch) {
throw new HttpError.BadRequest('현재 비밀번호가 일치하지 않습니다.');
}
if (newPassword !== newPasswordConfirm) {
throw new HttpError.BadRequest(
'새 비밀번호와 비밀번호 확인이 일치하지 않습니다.'
);
}
}
const hashedPassword = await bcrypt.hash(newPassword, HASH_SALT_ROUNDS);
const updatedUser = await this.usersRepository.updateUser(
userId,
hashedPassword,
name,
introduce,
profileImage
);
return {
id: updatedUser.id,
email: updatedUser.email,
name: updatedUser.name,
introduce: updatedUser.introduce,
profileImage: updatedUser.profileImage,
createdAt: updatedUser.createdAt,
updatedAt: updatedUser.updatedAt,
};
};
}