본문 바로가기
TIL

Nodejs 뉴스피드 프로젝트 팀과제 (3)

by 황민도 2024. 6. 7.

내일배움캠프 스파르타 코딩클럽

< src.routers. index.js >

import express from 'express';
import { authRouter } from './auth.router.js';
import { userRouter } from './user.router.js';
import feedsRouter from './feeds.router.js';
import { likeRouter } from './like.router.js';
import { followRouter } from './follow.router.js';

const apiRouter = express.Router();

apiRouter.use('/users', userRouter);
apiRouter.use('/auth', authRouter);
apiRouter.use('/feeds', feedsRouter);
apiRouter.use('/feeds', likeRouter);
apiRouter.use('/follow', followRouter);

export { apiRouter };

 

< src.routers. auth.router.js >

import express from 'express';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { signupValidator } from '../middlewares/validators/sign-up-validator.middleware.js';
import { prisma } from '../utils/prisma.util.js';
import { Prisma } from '@prisma/client';
import bcrypt from 'bcrypt';
import {
  ACCESS_TOKEN_EXPIRES,
  HASH_SALT_ROUNDS,
  REFRESH_TOKEN_EXPIRES,
} from '../constants/auth.constant.js';
import jwt from 'jsonwebtoken';
import {
  ACCESS_TOKEN_SECRET,
  REFRESH_TOKEN_SECRET,
} from '../constants/env.constant.js';
import { signInValidator } from '../middlewares/validators/sign-in-validator.middleware.js';
import { requireRefreshToken } from '../middlewares/require-refresh-token.middleware.js';

const authRouter = express.Router();

authRouter.post('/sign-up', signupValidator, async (req, res, next) => {
  try {
    const {
      email,
      password,
      repeat_password,
      nickName,
      introduce,
      maxweight,
      weight,
      height,
      fat,
      showLog,
      metabolic,
      muscleweight,
      profile_img_url,
    } = req.body;

    const existedUser = await prisma.user.findUnique({ where: { email } });

    // 이메일 중복처리
    if (existedUser) {
      return res
        .status(HTTP_STATUS.CONFLICT)
        .json({ message: MESSAGES.AUTH.COMMON.EMAIL.DUPLICATED });
    }
    const hashedPassword = bcrypt.hashSync(password, HASH_SALT_ROUNDS);

    // 유저 테이블과 유저 정보 테이블을 한번에 생성하는 트랜잭션
    const [userData, profileData] = await prisma.$transaction(
      async (tx) => {
        const userData = await tx.user.create({
          data: { email, password: hashedPassword, nickName },
        });

        userData.password = undefined;

        const profileData = await tx.profile.create({
          data: {
            introduce,
            maxweight,
            weight,
            height,
            fat,
            metabolic,
            muscleweight,
            profile_img_url,
            showLog,
            nickName: userData.nickName,
            userId: userData.userId,
          },
        });
        return [userData, profileData];
      },
      { isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted },
    );
    const data = {
      userId: userData.userId,
      email: userData.email,
      nickName: userData.nickName,
      introduce: profileData.introduce,
      profile_img_url: profileData.profile_img_url,
      maxweight: profileData.maxweight,
      weight: profileData.weight,
      height: profileData.height,
      muscleweight: profileData.muscleweight,
      fat: profileData.fat,
      metabolic: profileData.metabolic,
      createdAt: userData.createdAt,
    };
    return res.status(HTTP_STATUS.CREATED).json({
      message: MESSAGES.AUTH.SIGN_UP.SUCCEED,
      data,
    });
  } catch (error) {
    next(error);
  }
});

// login API
authRouter.post('/sign-in', signInValidator, async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const user = await prisma.user.findUnique({ where: { email } });
    const passwordMatch = user && bcrypt.compareSync(password, user.password);

    if (!passwordMatch) {
      return res.status(HTTP_STATUS.UNAUTHORIZED).json({
        status: res.statusCode,
        message: MESSAGES.AUTH.SIGN_IN.UNAUTHORIZED,
      });
    }

    const payload = { id: user.userId };
    const data = await generateAuthTokens(payload);

    return res.status(HTTP_STATUS.OK).json({
      status: HTTP_STATUS.OK,
      message: MESSAGES.AUTH.SIGN_IN.SUCCEED,
      data,
    });
  } catch (error) {
    next(error);
  }
});

// 토큰 재발급 API
authRouter.post('/token', requireRefreshToken, async (req, res, next) => {
  try {
    const user = req.user;
    const payload = { id: user.userId };

    const data = await generateAuthTokens(payload);

    return res.status(HTTP_STATUS.OK).json({
      status: res.statusCode,
      message: MESSAGES.AUTH.SIGN_IN.TOKEN,
      data,
    });
  } catch (error) {
    next(error);
  }
});

//로그아웃 API
authRouter.post('/sign-out', requireRefreshToken, async (req, res, next) => {
  try {
    const user = req.user;

    await prisma.user.update({
      where: { userId: user.userId },
      data: {
        refreshToken: null,
      },
    });

    return res.status(HTTP_STATUS.OK).json({
      message: MESSAGES.AUTH.SIGN_OUT.SUCCEED,
      data: { id: user.userId },
    });
  } catch (error) {
    next(error);
  }
});

const generateAuthTokens = async (payload) => {
  const userId = payload.id;
  const accessToken = jwt.sign(payload, ACCESS_TOKEN_SECRET, {
    expiresIn: ACCESS_TOKEN_EXPIRES,
  });
  const refreshToken = jwt.sign(payload, REFRESH_TOKEN_SECRET, {
    expiresIn: REFRESH_TOKEN_EXPIRES,
  });

  const hashedRefreshToken = bcrypt.hashSync(refreshToken, HASH_SALT_ROUNDS);

  await prisma.user.update({
    where: {
      userId,
    },
    data: {
      refreshToken: hashedRefreshToken,
    },
  });

  return { accessToken, refreshToken };
};

export { authRouter };

 

< src.routers. user.router.js >

import express from 'express';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { prisma } from '../utils/prisma.util.js';
import { Prisma } from '@prisma/client';
import { requireAccessToken } from '../middlewares/require-access-token.middleware.js';
import bcrypt from 'bcrypt';
import { HASH_SALT_ROUNDS } from '../constants/auth.constant.js';
import {
  profileUpdateValidator,
  passwordUpdateValidator,
} from '../middlewares/validators/user-profile-validator.middleware.js';

const userRouter = express.Router();

// 유저 프로필 조회 API
userRouter.get('/:profileId', async (req, res, next) => {
  try {
    const { profileId } = req.params;
    let userProfile = await prisma.profile.findUnique({
      where: {
        profileId: +profileId,
      },
    });
    if (!userProfile.showLog) {
      userProfile = {
        nickName: userProfile.nickName,
        userId: userProfile.userId,
        createdAt: userProfile.createdAt,
      };
    }
    return res.status(HTTP_STATUS.OK).json({
      userProfile,
      status: res.statusCode,
      message: MESSAGES.USRES.READ.SUCCEED,
    });
  } catch (error) {
    next(error);
  }
});

//패스워드 수정 API
userRouter.patch(
  '/password',
  passwordUpdateValidator,
  requireAccessToken,
  async (req, res, next) => {
    try {
      const user = req.user;
      const { updatePassword, repeat_password, password } = req.body;
      const prevPassword = await prisma.user.findUnique({
        where: {
          userId: user.userId,
        },
        select: {
          password: true,
        },
      });

      const passwordMatched =
        prevPassword && bcrypt.compareSync(password, prevPassword.password);
      if (!passwordMatched) {
        return res.status(HTTP_STATUS.UNAUTHORIZED).json({
          status: res.statusCode,
          message: MESSAGES.AUTH.COMMON.REPEAT_PASSWORD.NOT_MATCHED,
        });
      }

      const hashedPassword = bcrypt.hashSync(updatePassword, HASH_SALT_ROUNDS);
      const newPassword = await prisma.user.update({
        where: {
          userId: user.userId,
        },
        data: {
          password: hashedPassword,
        },
      });
      return res.status(HTTP_STATUS.OK).json({
        status: res.statusCode,
        message: MESSAGES.USRES.PASSWORD.UPDATE.SUCCEED,
      });
    } catch (error) {
      next(error);
    }
  },
);

// 프로필 수정, 로그 기록 API
userRouter.patch(
  '/profile',
  requireAccessToken,
  profileUpdateValidator,
  async (req, res, next) => {
    try {
      const user = req.user;
      const {
        showLog,
        introduce,
        profile_img_url,
        maxweight,
        weight,
        height,
        muscleweight,
        fat,
        metabolic,
      } = req.body;

      const updateData = {
        ...(introduce && { introduce }),
        ...(profile_img_url && { profile_img_url }),
        ...(maxweight && { maxweight }),
        ...(weight && { weight }),
        ...(height && { height }),
        ...(muscleweight && { muscleweight }),
        ...(fat && { fat }),
        ...(metabolic && { metabolic }),
      };
      const prevProfile = await prisma.profile.findUnique({
        where: {
          userId: user.userId,
        },
      });
      await prisma.$transaction(
        async (tx) => {
          // 프로필 데이터 수정
          const profileData = await tx.profile.update({
            where: { userId: user.userId },
            data: {
              ...updateData,
              showLog,
            },
          });
          // 로그 데이터 추가
          for (let key in updateData) {
            if (prevProfile[key] !== updateData[key]) {
              await tx.log.create({
                data: {
                  profileId: profileData.profileId,
                  changeField: key,
                  oldValue: String(prevProfile[key]),
                  newValue: String(updateData[key]),
                },
              });
            }
          }
          return res.status(HTTP_STATUS.OK).json({
            status: res.statusCode,
            message: MESSAGES.USRES.UPDATE.SUCCEED,
            profileData,
          });
        },
        { isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted },
      );
    } catch (error) {
      next(error);
    }
  },
);

userRouter.get('/profile/log', requireAccessToken, async (req, res, next) => {
  try {
    const user = req.user;
    const userLog = await prisma.profile.findUnique({
      where: {
        userId: user.userId,
      },
      include: {
        log: {
          orderBy: {
            changedAt: 'desc',
          },
        },
      },
    });
    return res.status(HTTP_STATUS.OK).json({
      status: res.statusCode,
      message: MESSAGES.USRES.LOG.READ,
      userLog,
    });
  } catch (error) {
    next(error);
  }
});

export { userRouter };

 

< src.routers. feeds.router.js >

import express from 'express';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { prisma } from '../utils/prisma.util.js';
import { requireAccessToken } from '../middlewares/require-access-token.middleware.js';
import { feedCreateValidator } from '../middlewares/validators/feed-create-validator.middleware.js';
import { feedUpdateValidator } from '../middlewares/validators/feed-update-validator.middleware.js';
import { commentRouter } from './comments.router.js';
import { likeRouter } from './like.router.js';

const feedsRouter = express.Router();

// 게시물 작성 API

feedsRouter.post(
  '/',
  requireAccessToken,
  feedCreateValidator,
  async (req, res, next) => {
    try {
      const { userId } = req.user;
      const { title, content, feed_img_url } = req.body;

      const { nickName } = await prisma.user.findFirst({
        where: { userId },
        select: {
          nickName: true,
        },
      });

      const feed = await prisma.feed.create({
        data: {
          userId,
          nickName,
          title,
          content,
          feed_img_url,
        },
        select: {
          feedId: true,
          userId: true,
          title: true,
          nickName: true,
          content: true,
          feed_img_url: true,
          createdAt: true,
          updatedAt: true,
        },
      });
      return res.status(HTTP_STATUS.CREATED).json({
        status: HTTP_STATUS.CREATED,
        message: MESSAGES.FEED.COMMON.SUCCEED.CREATED,
        data: feed,
      });
    } catch (error) {
      next(error);
    }
  },
);

// 게시물 목록 조회 API

feedsRouter.get('/', async (req, res, next) => {
  const orderBy = req.query.sort ? req.query.sort.toLowerCase() : 'desc';

  const feeds = await prisma.feed.findMany({
    select: {
      feedId: true,
      nickName: true,
      title: true,
      content: true,
      feed_img_url: true,
      updatedAt: true,
    },
    orderBy: {
      updatedAt: orderBy,
    },
  });

  return res.status(HTTP_STATUS.OK).json({
    status: HTTP_STATUS.OK,
    message: MESSAGES.FEED.COMMON.SUCCEED.GET_ALL,
    data: feeds,
  });
});

//게시물 팔로우 우선 조회 API

feedsRouter.get('/follow', requireAccessToken, async (req, res, next) => {
  try {
    const user = req.user;
    const feeds = await prisma.feed.findMany({
      where: {
        user: {
          following: {
            some: {
              followedbyid: user.userId,
            },
          },
        },
      },
    });
    return res.status(HTTP_STATUS.OK).json({ feeds });
  } catch (error) {
    next(error);
  }
});

// 게시물 상세 조회 API

feedsRouter.get('/:feedId', async (req, res, next) => {
  try {
    const { feedId } = req.params;

    const feed = await prisma.feed.findFirst({
      where: { feedId: +feedId },
      select: {
        feedId: true,
        nickName: true,
        title: true,
        content: true,
        feed_img_url: true,
        likedUsersId: true,
        createdAt: true,
        updatedAt: true,
        comment: true,
      },
    });
    if (!feed) {
      return res.status(HTTP_STATUS.NOT_FOUND).json({
        status: HTTP_STATUS.NOT_FOUND,
        message: MESSAGES.FEED.COMMON.NO.FEED,
      });
    }
    return res.status(HTTP_STATUS.OK).json({
      status: HTTP_STATUS.OK,
      message: MESSAGES.FEED.COMMON.SUCCEED.GET,
      data: feed,
    });
  } catch (error) {
    next(error);
  }
});

// 게시물 수정 API

feedsRouter.patch(
  '/:feedId',
  requireAccessToken,
  feedUpdateValidator,
  async (req, res, next) => {
    try {
      const { userId } = req.user;
      const { feedId } = req.params;
      const { title, content, feed_img_url } = req.body;
      if (!title && !content && !feed_img_url) {
        return res.status(HTTP_STATUS.BAD_REQUEST).json({
          status: HTTP_STATUS.BAD_REQUEST,
          message: MESSAGES.FEED.COMMON.REQUIRED.UPDATE,
        });
      }
      const { nickName } = await prisma.user.findFirst({
        where: { userId },
        select: {
          nickName: true,
        },
      });

      const feed = await prisma.feed.findFirst({
        where: {
          userId,
          feedId: +feedId,
        },
      });
      if (!feed) {
        return res.status(HTTP_STATUS.NOT_FOUND).json({
          status: HTTP_STATUS.NOT_FOUND,
          message: MESSAGES.FEED.COMMON.NO.FEED,
        });
      }

      const updatedFeed = await prisma.feed.update({
        where: {
          userId,
          feedId: +feedId,
        },
        data: {
          nickName,
          title: title || undefined,
          content: content || undefined,
          feed_img_url: feed_img_url || undefined,
        },
        select: {
          feedId: true,
          userId: true,
          nickName: true,
          title: true,
          content: true,
          feed_img_url: true,
          createdAt: true,
          updatedAt: true,
        },
      });
      return res.status(HTTP_STATUS.OK).json({
        status: HTTP_STATUS.OK,
        message: MESSAGES.FEED.COMMON.SUCCEED.UPDATED,
        data: updatedFeed,
      });
    } catch (error) {
      next(error);
    }
  },
);

// 게시물 삭제 API

feedsRouter.delete('/:feedId', requireAccessToken, async (req, res, next) => {
  try {
    const { userId } = req.user;
    const { feedId } = req.params;
    const whereCondition = { feedId: +feedId };
    if (req.user.role == 'COMMON') {
      whereCondition.userId = userId;
    }
    const feed = await prisma.feed.findUnique({
      where: whereCondition,
    });
    if (!feed) {
      return res.status(HTTP_STATUS.NOT_FOUND).json({
        status: HTTP_STATUS.NOT_FOUND,
        message: MESSAGES.FEED.COMMON.NO.FEED,
      });
    }
    await prisma.feed.delete({
      where: {
        feedId: +feedId,
      },
    });
    return res.status(HTTP_STATUS.OK).json({
      status: HTTP_STATUS.OK,
      message: MESSAGES.FEED.COMMON.SUCCEED.DELETED,
      deletedFeedId: +feedId,
    });
  } catch (error) {
    next(error);
  }
});

feedsRouter.use('/:feedId/comments', commentRouter);

export default feedsRouter;

이때 알고가야 할 부분이 있다.
prisma를 사용하면서 다음과 같은 메서드를 사용할 때 차이점을 알아야 할 필요가 있다.   
findFirst 와 findUnique의 차이
findFirst는 모든 필드를 이용하여 필터링이 가능하고
findUnique는 unique필드만(@unique)을 이용하여 필터링이 가능하다.

 

 

< src.routers. comments.router.js >

import express from 'express';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { prisma } from '../utils/prisma.util.js';
import { requireAccessToken } from '../middlewares/require-access-token.middleware.js';
import { createCommentValidator } from '../middlewares/validators/create-comment-validator.middleware.js';
import { updateCommentValidator } from '../middlewares/validators/update-comment-validator.middleware.js';

const commentRouter = express.Router({ mergeParams: true });

// 댓글 작성
commentRouter.post(
  '/',
  requireAccessToken,
  createCommentValidator,
  async (req, res, next) => {
    try {
      const user = req.user;
      const userId = user.userId;
      const { feedId } = req.params;
      const { comment } = req.body;

      const feedIdInt = parseInt(feedId, 10);

      if (isNaN(feedIdInt)) {
        return res.status(HTTP_STATUS.BAD_REQUEST).json({
          status: HTTP_STATUS.BAD_REQUEST,
          message: 'Invalid feedId',
        });
      }

      const data = await prisma.comment.create({
        data: {
          userId: userId,
          feedId: feedIdInt,
          comment: comment,
        },
      });

      return res.status(HTTP_STATUS.CREATED).json({
        status: HTTP_STATUS.CREATED,
        message: MESSAGES.COMMENTS.CREATE.SUCCEED,
        data,
      });
    } catch (error) {
      next(error);
    }
  },
);

// 댓글 목록 조회
commentRouter.get('/', async (req, res, next) => {
  try {
    const { feedId } = req.params;

    let { sort } = req.query;

    sort = sort?.toLowerCase();

    if (sort !== 'desc' && sort !== 'asc') {
      sort = 'desc';
    }

    let data = await prisma.comment.findMany({
      where: { feedId: +feedId },
      orderBy: {
        createdAt: sort,
      },
    });

    if (!data.length) {
      return res.status(HTTP_STATUS.NOT_FOUND).json({
        status: HTTP_STATUS.NOT_FOUND,
        message: MESSAGES.COMMENTS.COMMON.NOT_FOUND,
      });
    }

    data = data.map((comment) => {
      return {
        userId: comment.userId,
        feedId: comment.feedId,
        commentId: comment.commentId,
        content: comment.comment,
        updatedAt: comment.updatedAt,
      };
    });

    return res.status(HTTP_STATUS.OK).json({
      status: HTTP_STATUS.OK,
      message: MESSAGES.COMMENTS.READ_LIST.SUCCEED,
      data,
    });
  } catch (error) {
    next(error);
  }
});

// 댓글 수정
commentRouter.patch(
  '/:commentId',
  requireAccessToken,
  updateCommentValidator,
  async (req, res, next) => {
    try {
      const user = req.user;
      const userId = user.userId;
      const { commentId } = req.params;
      const { comment } = req.body;

      let existedComment = await prisma.comment.findUnique({
        where: { userId, commentId: +commentId },
      });

      if (!existedComment) {
        return res.status(HTTP_STATUS.NOT_FOUND).json({
          status: HTTP_STATUS.NOT_FOUND,
          message: MESSAGES.COMMENTS.COMMON.NOT_FOUND,
        });
      }

      if (existedComment.userId !== userId) {
        return res.status(HTTP_STATUS.UNAUTHORIZED).json({
          status: HTTP_STATUS.UNAUTHORIZED,
          message: MESSAGES.COMMENTS.UPDATE.NO_AUTH,
        });
      }

      const data = await prisma.comment.update({
        where: { userId, commentId: +commentId },
        data: {
          ...(comment && { comment }),
        },
      });

      return res.status(HTTP_STATUS.OK).json({
        status: HTTP_STATUS.OK,
        message: MESSAGES.COMMENTS.UPDATE.SUCCEED,
        data,
      });
    } catch (error) {
      next(error);
    }
  },
);

// 댓글 삭제
commentRouter.delete(
  '/:commentId',
  requireAccessToken,
  async (req, res, next) => {
    try {
      const user = req.user;
      const userId = user.userId;
      const { commentId } = req.params;

      let existedComment = await prisma.comment.findUnique({
        where: { userId, commentId: +commentId },
      });

      if (!existedComment) {
        return res.status(HTTP_STATUS.NOT_FOUND).json({
          status: HTTP_STATUS.NOT_FOUND,
          message: MESSAGES.COMMENTS.COMMON.NOT_FOUND,
        });
      }

      if (existedComment.userId !== userId) {
        return res.status(HTTP_STATUS.UNAUTHORIZED).json({
          status: HTTP_STATUS.UNAUTHORIZED,
          message: MESSAGES.COMMENTS.DELETE.NO_AUTH,
        });
      }

      const data = await prisma.comment.delete({
        where: { userId, commentId: +commentId },
      });

      return res.status(HTTP_STATUS.OK).json({
        status: HTTP_STATUS.OK,
        message: MESSAGES.COMMENTS.DELETE.SUCCEED,
        data,
      });
    } catch (error) {
      next(error);
    }
  },
);

export { commentRouter };

 

< src.routers. follow.router.js >

import express from 'express';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { prisma } from '../utils/prisma.util.js';
import { requireAccessToken } from '../middlewares/require-access-token.middleware.js';

const followRouter = express.Router();

followRouter.post('/:followId', requireAccessToken, async (req, res, next) => {
  try {
    const user = req.user;
    const { followId } = req.params;

    // 자기 자신 팔로우시 정지
    if (user.userId == followId) {
      return res.status(HTTP_STATUS.BAD_REQUEST).json({
        status: res.statusCode,
        message: '자기 자신은 팔로우 할 수 없습니다.',
      });
    }

    // 중복 팔로우 검사
    const existedfollow = await prisma.follows.findUnique({
      where: {
        followingid_followedbyid: {
          followedbyid: user.userId,
          followingid: +followId,
        },
      },
    });

    // 팔로우하려는 유저가 존재하는지 확인
    const userfollowcheck = await prisma.user.findUnique({
      where: {
        userId: +followId,
      },
    });
    if (!userfollowcheck) {
      return res.status(HTTP_STATUS.BAD_REQUEST).json({
        status: res.statusCode,
        message: '존재하지 않는 유저입니다.',
      });
    }

    // 중복 팔로우 시 팔로우 데이터 삭제
    if (existedfollow) {
      const deleteFollow = await prisma.follows.delete({
        where: {
          followingid_followedbyid: {
            followedbyid: user.userId,
            followingid: +followId,
          },
        },
      });

      return res.status(HTTP_STATUS.OK).json({
        status: res.statusCode,
        message: '팔로우가 해제되었습니다.',
        deleteFollow,
      });
    }

    // 팔로잉 데이터 생성
    const follow = await prisma.follows.create({
      data: {
        followedbyid: user.userId,
        followingid: +followId,
      },
    });
    return res.status(HTTP_STATUS.OK).json({
      status: res.statusCode,
      message: '해당 유저를 팔로우 하였습니다.',
      follow,
    });
  } catch (error) {
    next(error);
  }
});

// 팔로우 목록조회
followRouter.get('/', requireAccessToken, async (req, res, next) => {
  try {
    const user = req.user;
    const followList = await prisma.user.findMany({
      where: {
        userId: user.userId,
      },
      select: {
        nickName: true,
        userId: true,
        followedby: true,
        following: true,
      },
    });
    return res
      .status(HTTP_STATUS.OK)
      .json({
        status: res.statusCode,
        message: '팔로우 목록 조회에 성공하였습니다.',
        followList,
      });
  } catch (error) {
    next(error);
  }
});

export { followRouter };

 

< src.routers. follow.router.js >

import express from 'express';
import { requireAccessToken } from '../middlewares/require-access-token.middleware.js';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { prisma } from '../utils/prisma.util.js';

const likeRouter = express.Router();

function likePost(a, b) {
  const checkData = JSON.parse(a);
  checkData.includes(+b)
    ? checkData.splice(checkData.indexOf(+b), 1)
    : checkData.push(+b);
  a = JSON.stringify(checkData);
  return a;
}

likeRouter.post(
  '/:feedId/feedlike',
  requireAccessToken,
  async (req, res, next) => {
    try {
      const { userId } = req.user;
      const { feedId } = req.params;

      const userData = await prisma.user.findUnique({
        where: { userId: userId },
      });
      const feedData = await prisma.feed.findUnique({
        where: { feedId: +feedId },
      });
      const userArr = userData.likedFeedsId;
      const feedArr = feedData.likedUsersId;

      const isLiked = JSON.parse(userArr)

      const changedUser = likePost(userArr, feedId);
      const changedFeed = likePost(feedArr, userId);

      await prisma.user.update({
        where: {
          userId: userId,
        },
        data: {
          likedFeedsId: changedUser,
        },
      });

      await prisma.feed.update({
        where: {
          feedId: +feedId,
        },
        data: {
          likedUsersId: changedFeed,
        },
      });

      const afterLiked = JSON.parse(changedUser)
      const likedNumber = afterLiked.length

      if(isLiked.length > afterLiked.length){
        return res.status(HTTP_STATUS.OK).json({
            message: '좋아요 취소 되었습니다',
        })
      }
      else{
        return res.status(HTTP_STATUS.OK).json({
            message: '좋아요 처리 되었습니다',
            data: { likedNumber },
          });
      }

     
    } catch (error) {
      next(error);
    }
  },
);

likeRouter.post('/:feedId/comments/:commentId/commentlike', requireAccessToken, async (req, res, next) => {
  try {
    const { userId } = req.user;
    const commentId = req.params.commentId;
   
    console.log(req.params)
    console.log(req.params.commentId)
    console.log(commentId)
    console.log(userId)

      const userData = await prisma.user.findUnique({
        where: { userId: userId },
      });
      const commentData = await prisma.comment.findUnique({
        where: { commentId: +commentId },
      });
      const userArr = userData.likedCommentsId;
      const commentArr = commentData.likedUsersId;

      const isLiked = JSON.parse(userArr)

      const changedUser = likePost(userArr, commentId);
      const changedComment = likePost(commentArr, userId);

      await prisma.user.update({
        where: {
          userId: userId,
        },
        data: {
          likedCommentsId: changedUser,
        },
      });

      await prisma.comment.update({
        where: {
          commentId: +commentId,
        },
        data: {
          likedUsersId: changedComment,
        },
      });

      const afterLiked = JSON.parse(changedUser)
      const likedNumber = afterLiked.length

      if(isLiked.length > afterLiked.length){
        return res.status(HTTP_STATUS.OK).json({
            message: '좋아요 취소 되었습니다',
        })
      }
      else{
        return res.status(HTTP_STATUS.OK).json({
            message: '좋아요 처리 되었습니다',
            data: { likedNumber },
          });
      }

  } catch (error) {
    next(error);
  }
});

export { likeRouter };

 

[ 추가 기능중 보안이 두려워 사용하지 못한 nodemailer ]

 

nodemailer를 사용하여 이메일을 보내기 위해 Gmail 계정의 주소와 비밀번호가 필요한 이유는 이메일 전송에 사용되는 SMTP(Simple Mail Transfer Protocol) 서버에 인증을 해야 하기 때문입니다. 이메일 서버는 보안을 위해 이메일을 보내는 사용자가 실제로 해당 이메일 계정의 소유자인지 확인할 필요가 있습니다.

여기서 간단히 nodemailer의 역할과 Gmail 계정의 필요성을 설명하겠습니다:

  1. SMTP 서버와 nodemailer:
    • nodemailer는 Node.js 애플리케이션에서 이메일을 전송하기 위해 SMTP 프로토콜을 사용하는 라이브러리입니다.
    • 이메일을 보내기 위해서는 이메일 서비스 제공자의 SMTP 서버에 연결하고 인증을 받아야 합니다. 이 과정에서 이메일 주소와 비밀번호가 필요합니다.
  2. Gmail 계정의 사용:
    • Gmail의 SMTP 서버를 이용하기 위해서는 Gmail 계정의 인증 정보(이메일 주소와 비밀번호)가 필요합니다.
    • Gmail의 SMTP 서버 설정:
      • SMTP 서버: smtp.gmail.com
      • 포트: 587 (TLS) 또는 465 (SSL)
      • 인증 방식: 이메일 주소와 비밀번호를 사용한 로그인
  3. 보안:
    • 이메일 서버는 스팸과 불법적인 사용을 방지하기 위해 인증을 요구합니다. 이는 이메일 서버가 사용자의 신원을 확인하고 권한이 있는 사용자만 이메일을 전송할 수 있게 하기 위함입니다.

예제 코드 (보안 정보 환경 변수 사용)

환경 변수 파일(.env)을 사용하여 민감한 정보를 보호할 수 있습니다.

  1. 환경 변수 파일 설정:
    • 프로젝트 루트 디렉토리에 .env 파일을 생성합니다.
    plaintext
    코드 복사
    NODEMAILER_USER=your-email@gmail.com NODEMAILER_PASS=your-email-password
  2. 환경 변수 로드 및 nodemailer 설정:
    • dotenv 패키지를 설치하고 환경 변수를 로드합니다.
    bash
    코드 복사
    npm install dotenv
    javascript
    코드 복사
    // index.js require('dotenv').config(); const nodemailer = require('nodemailer'); const transporter = nodemailer.createTransport({ service: 'gmail', host: 'smtp.gmail.com', port: 587, secure: false, auth: { user: process.env.NODEMAILER_USER, pass: process.env.NODEMAILER_PASS, }, }); // 이메일 전송 예제 const mailOptions = { from: process.env.NODEMAILER_USER, to: 'recipient@example.com', subject: 'Test Email', text: 'This is a test email sent using nodemailer', }; transporter.sendMail(mailOptions, (error, info) => { if (error) { console.error('Error sending email:', error); } else { console.log('Email sent:', info.response); } });

주의 사항

  • Gmail 보안 설정: 기본적으로 Gmail은 외부 애플리케이션의 접근을 차단합니다. 이를 허용하려면 Gmail 계정에서 "보안 수준이 낮은 앱의 액세스 허용"을 활성화해야 합니다. 그러나 이는 보안에 취약할 수 있으므로 권장하지 않습니다.
  • 앱 비밀번호 사용: Gmail 계정에서 2단계 인증을 설정한 경우, 애플리케이션 전용 비밀번호를 생성하여 사용하는 것이 더 안전합니다.
  • 환경 변수 사용: 민감한 정보는 코드에 직접 포함하지 말고 환경 변수를 사용하여 관리하십시오.

이 방법을 통해 이메일 전송에 필요한 인증 정보를 안전하게 관리하고 Gmail을 통해 이메일을 전송할 수 있습니다.