[Node.js] CH3 개인과제 - 필수 API 구현

GDORI·2024년 9월 10일
0

Node.js

목록 보기
9/11

※ 24.09.11 코드 리팩토링 - 부분수정 및 try-catch구문 추가 ※

기본적으로 구현 해야하는 API

  • 회원가입 API
  • 로그인 API
  • 캐릭터 생성 API
  • 캐릭터 삭제 API
  • 캐릭터 상세조회 API
  • 아이템 생성 API
  • 아이템 수정 API
  • 아이템 목록조회 API
  • 아이템 상세조회 API

1. 회원가입 API

요구조건

  1. 아이디, 비밀번호, 비밀번호 확인, 이름 데이터로 회원가입 요청
  2. 보안을 위하여 비밀번호는 해싱처리
  3. 아이디는 중복되지 않으며, 오로지 영어 소문자 + 숫자 조합으로 구성
  4. 비밀번호는 최소 6자 이상이며, 비밀번호 확인과 일치
  5. 실패 시 Status Code 및 메시지 반환
  6. 성공 시 비밀번호를 제외한 사용자 정보 반환
  7. 성공 시 Authorization Header에 토큰값 반환

요청

{
  "userId": "your_Id",
  "userPw": "your_Password",
  "confirmPw" : "one_more_your_Password",
  "userName" : "your_name"
}

응답

  • 성공 (201)
data: {
      userNo: cretead_id_No,
      userId: cretead_id,
      userName: cretead_id_userName,
    }
  • 실패 (400)
{errorMessage: "아이디는 소문자+숫자 형식만 가능합니다."}
{errorMessage: "비밀번호는 최소 6자리 이상만 가능합니다."}
  • 실패 (401)
{errorMessage: "비밀번호와 확인이 일치하지 않습니다."}
  • 실패 (409)
{errorMessage: "이미 존재하는 아이디 입니다."}

API CODE

router.post("/sign-up", async (req, res, next) => {
  const { userId, userPw, confirmPw, userName } = req.body;

  const validUserId = /^[a-z0-9]+$/;

  const isUser = await prisma.users.findFirst({
    where: { userId },
  });
  if (isUser)
    return res.status(409).json({
      errorMessage: "이미 존재하는 아이디 입니다.",
    });
  if (!validUserId.test(userId))
    return res.status(400).json({
      errorMessage: "아이디는 소문자 + 숫자 형식만 가능합니다.",
    });
  if (userPw.length < 6)
    return res.status(400).json({
      errorMessage: "비밀번호는 최소 6자리 이상만 가능합니다.",
    });
  if (userPw !== confirmPw)
    return res.status(401).json({
      errorMessage: "비밀번호와 확인이 일치하지 않습니다.",
    });

  const hashedPw = await bcrypt.hash(userPw, 10);
  const user = await prisma.users.create({
    data: { userId, userPw: hashedPw, userName },
  });

  return res.status(201).json({
    data: {
      userNo: user.userNo,
      userId: user.userId,
      userName: user.userName,
    },
  });
});

API CODE (리팩토링 버전)

router.post("/sign-up", async (req, res, next) => {
  const {
    body: { userId, userPw, confirmPw, userName },
  } = req;
  const validUserId = /^[a-z0-9]+$/;

  try {
    const isUser = await prisma.users.findFirst({
      where: { userId },
    });

    if (isUser)
      return res.status(409).json({
        errorMessage: "이미 존재하는 아이디 입니다.",
      });
    if (!validUserId.test(userId))
      return res.status(400).json({
        errorMessage: "아이디는 소문자 + 숫자 형식만 가능합니다.",
      });
    if (userPw.length < 6)
      return res.status(400).json({
        errorMessage: "비밀번호는 최소 6자리 이상만 가능합니다.",
      });
    if (userPw !== confirmPw)
      return res.status(401).json({
        errorMessage: "비밀번호와 확인이 일치하지 않습니다.",
      });

    const hashedPw = await bcrypt.hash(userPw, 10);
    const user = await prisma.users.create({
      data: { userId, userPw: hashedPw, userName },
    });

    return res.status(201).json({
      data: {
        userNo: user.userNo,
        userId: user.userId,
        userName: user.userName,
      },
    });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버 오류" });
  }
});

2. 로그인 API

요구조건

  • 아이디, 비밀번호로 로그인 요청
  • 계정정보 불일치 시 Status Code 및 메시지 반환(아이디 존재하지 않는 경우, 비밀번호 틀린 경우)
  • 로그인 성공 시 액세스 토큰 반환( 페이로드에 계정 ID 삽입 )

요청

{
  "userId": "your_Id",
  "userPw": "your_Password",
}

응답

  • 성공(200)
{ message: "로그인 성공, 헤더에 토큰값이 반환되었습니다." }
  • 실패(400)
{errorMessage: "없는 아이디 입니다."}
  • 실패(401)
{errorMessage: "틀린 비밀번호 입니다."}

API CODE

router.post("/sign-in", async (req, res, next) => {
  const { userId, userPw } = req.body;
  const isUser = await prisma.users.findFirst({
    where: { userId },
  });
  if (!isUser)
    return res.status(400).json({
      errorMessage: "없는 아이디 입니다.",
    });
  if (!(await bcrypt.compare(userPw, isUser.userPw)))
    return res.status(401).json({
      errorMessage: "틀린 비밀번호 입니다.",
    });
  const token = jwt.sign({ userId: userId }, SECRET_CODE);
  res.setHeader("Authorization", `Bearer ${token}`);
  return res
    .status(200)
    .json({ message: "로그인 성공, 헤더에 토큰값이 반환되었습니다." });
});

API CODE (리팩토링 버전)

router.post("/sign-in", async (req, res, next) => {
  const { userId, userPw } = req.body;
  try {
    const isUser = await prisma.users.findFirst({
      where: { userId },
    });
    if (!isUser)
      return res.status(400).json({
        errorMessage: "없는 아이디 입니다.",
      });
    if (!(await bcrypt.compare(userPw, isUser.userPw)))
      return res.status(401).json({
        errorMessage: "틀린 비밀번호 입니다.",
      });
    const token = jwt.sign({ userId: userId }, SECRET_CODE);
    res.setHeader("Authorization", `Bearer ${token}`);
    return res
      .status(200)
      .json({ message: "로그인 성공, 헤더에 토큰값이 반환되었습니다." });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버 오류" });
  }
});

3. 캐릭터 생성 API (JWT 인증)

요구조건

  • 캐릭터 명 request 전달, 캐릭터 ID response 반환
  • 캐릭터 스탯 ( health:500, power:100, money:10000 )

요청

Authorization: Bearer <your_token>
{
	"characterName" : "your_characterName"
}

응답

  • 성공(201)
{
	"characterNo : your_created_characterNo"
}
  • 실패(409)
{ message: "이미 있는 캐릭터명입니다. " }

API CODE

router.post("/character", authMiddleware, async (req, res, next) => {
  const { characterName } = req.body;
  const { userNo } = req.user;
  console.log(userNo);
  const isCharacter = await prisma.characters.findFirst({
    where: { characterName },
  });
  if (isCharacter)
    return res.status(409).json({ message: "이미 있는 캐릭터명입니다. " });

  const character = await prisma.characters.create({
    data: {
      characterName,
      userNo,
      inventory: {
        create: {
          items: JSON.stringify([]),
        },
      },
      equip: {
        create: {
          items: JSON.stringify([]),
        },
      },
    },
  });

  const characterNo = character.characterNo;
  return res.status(201).json({
    data: { characterNo },
  });
});

API CODE (리팩토링 버전)

router.post("/character", authMiddleware, async (req, res, next) => {
  const {
    body: { characterName },
    user: { userNo },
  } = req;

  try {
    const isCharacter = await prisma.characters.findFirst({
      where: { characterName },
    });
    if (isCharacter)
      return res.status(409).json({ message: "이미 있는 캐릭터명입니다. " });

    const character = await prisma.characters.create({
      data: {
        characterName,
        userNo,
        inventory: {
          create: { items: JSON.stringify({}) },
        },
        equip: { create: { items: JSON.stringify({}) } },
      },
    });

    return res.status(201).json({
      data: { characterNo: character.characterNo },
    });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버오류" });
  }
});

4. 캐릭터 삭제 API (JWT 인증)

요구조건

  • 삭제할 캐릭터 ID params로 전달
  • 내 계정의 캐릭터가 아니면 삭제 불가

요청

GET /characters/delete/:characterId
Authorization: Bearer <your_token>

응답

  • 성공(200)
{ data: { deleteCharacter } }
  • 실패(401)
{ message: "계정 주인이 아닙니다." }
  • 실패(404)
{ message: "삭제하려는 계정이 없습니다. " }

API CODE

router.delete(
  "/character/delete/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const { characterNo } = req.params;
    const { userNo } = req.user;
    const character = await prisma.characters.findFirst({
      where: { characterNo: +characterNo },
    });
    if (!character)
      return res.status(404).json({ message: "삭제하려는 계정이 없습니다. " });

    if (character.userNo !== userNo)
      return res.status(401).json({ message: "계정 주인이 아닙니다." });

    const deleteCharacter = await prisma.characters.delete({
      where: { characterNo: +characterNo },
    });

    return res.status(200).json({ data: { deleteCharacter } });
  }
);

API CODE (리팩토링 버전)

router.delete(
  "/character/delete/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const {
      params: { characterNo },
      user: { userNo },
    } = req;

    try {
      const character = await prisma.characters.findFirst({
        where: { characterNo: +characterNo },
      });
      if (!character)
        return res
          .status(404)
          .json({ message: "삭제하려는 계정이 없습니다. " });

      if (character.userNo !== userNo)
        return res.status(401).json({ message: "계정 주인이 아닙니다." });

      const deleteCharacter = await prisma.characters.delete({
        where: { characterNo: +characterNo },
      });

      return res.status(200).json({ data: { deleteCharacter } });
    } catch (err) {
      res.status(500).json({ errorMessage: "서버오류" });
    }
  }
);

5. 캐릭터 상세 조회 API

요구조건

  • 조회할 캐릭터 ID params로 전달
  • 캐릭터 이름, HP, 힘 스탯 전달
  • 내 캐릭터 조회시 게임머니까지 조회
  • 다른 유저가 내 캐릭을 조회할 때와 내가 보유한 캐릭을 조회할 때가 달라야한다는 점.

요청

GET /characters/info/:characterId

응답

  • 성공(200)
// 본인의 캐릭터일 때
{
        data: {
          characterName: character.characterName,
          money: character.money,
          health: character.health,
          power: character.power,
        },
      }
// 본인의 캐릭터가 아닐 때
{
        data: {
          characterName: character.characterName,
          health: character.health,
          power: character.power,
        },
      }
  • 실패(404)
{ message: "삭제하려는 계정이 없습니다. " }

API CODE

router.get(
  "/character/info/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const { characterNo } = req.params;
    const { userNo } = req.user;
    const character = await prisma.characters.findFirst({
      where: { characterNo: +characterNo },
    });
    if (!character)
      return res.status(404).json({ message: "삭제하려는 계정이 없습니다. " });

    if (character.userNo === userNo) {
      return res.status(200).json({
        data: {
          characterName: character.characterName,
          money: character.money,
          health: character.health,
          power: character.power,
        },
      });
    } else {
      return res.status(200).json({
        data: {
          characterName: character.characterName,
          health: character.health,
          power: character.power,
        },
      });
    }
  }
);

API CODE (리팩토링 버전)

router.get(
  "/character/info/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const {
      params: { characterNo },
      user: { userNo },
    } = req;

    try {
      const character = await prisma.characters.findFirst({
        where: { characterNo: +characterNo },
      });
      if (!character)
        return res
          .status(404)
          .json({ message: "조회하려는 계정이 없습니다. " });

      const returnData = {
        characterName: character.characterName,
        health: character.health,
        power: character.power,
      };
      if (character.userNo === userNo) returnData.money = character.money;

      return res.status(200).json({
        data: returnData,
      });
    } catch (err) {
      res.status(500).json({ errorMessage: "서버오류" });
    }
  }
);

6. 아이템 생성 API

요구조건

  • 아이템 코드, 아이템 명, 아이템 능력, 아이템 가격 request.body 전달
  • 아이템 능력 JSON 포맷 전달

요청

{
	"itemNo" : your_itemNo,
    "itemName" : "your_itemName",
    "itemPrice" : "your_itemPrice",
    "itemStat" : "your_itemStat",
}

응답

  • 성공(201)
{ data: item }
  • 실패(409)
{ message: "이미 있는 아이템 넘버입니다." }

API CODE

router.post("/item", async (req, res, next) => {
  const { itemNo, itemName, itemStat, itemPrice } = req.body;
  const isItem = await prisma.items.findFirst({
    where: { itemNo },
  });
  if (isItem)
    return res.status(409).json({ message: "이미 있는 아이템 넘버입니다." });

  const item = await prisma.items.create({
    data: {
      itemNo,
      itemName,
      itemPrice,
      itemStat,
    },
  });

  return res.status(201).json({ data: item });
});

API CODE (리팩토링 버전)

router.post("/item", async (req, res, next) => {
  const { itemNo, itemName, itemStat, itemPrice } = req.body;
  try {
    const isItem = await prisma.items.findFirst({
      where: { itemNo },
    });
    if (isItem)
      return res.status(409).json({ message: "이미 있는 아이템 넘버입니다." });

    const item = await prisma.items.create({
      data: {
        itemNo,
        itemName,
        itemPrice,
        itemStat,
      },
    });

    return res.status(201).json({ data: item });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버 오류" });
  }
});

7. 아이템 수정 API

요구조건

  • 아이템 코드 params 전달
  • 아이템 명, 아이템 능력 request 전달

요청

PATCH /updateItem/:itemNo
{
	"itemName" : "change_itemName",
    "itemStat" : {
    	wanted_stat_info
    }
}

응답

  • 성공(200)
{ data: updateItem }
  • 실패(404)
{ message: "없는 아이템입니다." }

API CODE

router.patch("/updateItem/:itemNo", async (req, res, next) => {
  const { itemNo } = req.params;
  const { itemName, itemStat } = req.body;

  const Item = await prisma.items.findFirst({
    where: { itemNo: +itemNo },
  });

  if (!Item) return res.status(404).json({ message: "없는 아이템입니다." });

  const updateItem = await prisma.items.update({
    where: { itemNo: +itemNo },
    data: {
      itemName,
      itemStat,
    },
  });

  return res.status(200).json({ data: updateItem });
});

API CODE (리팩토링 버전)

router.patch("/updateItem/:itemNo", async (req, res, next) => {
  const {
    params: { itemNo },
    body: { itemName, itemStat },
  } = req;

  try {
    const Item = await prisma.items.findFirst({
      where: { itemNo: +itemNo },
    });

    if (!Item) return res.status(404).json({ message: "없는 아이템입니다." });

    const updateItem = await prisma.items.update({
      where: { itemNo: +itemNo },
      data: {
        itemName,
        itemStat,
      },
    });

    return res.status(200).json({ data: updateItem });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버 오류" });
  }
});

8. 아이템 목록 API

요구조건

  • 아이템 코드, 아이템 명, 아이템 가격 내용만 조회
  • 아이템 생성 API를 통해 생성된 모든 아이템들이 목록으로 조회

요청

NULL

응답

  • 성공(200)
{ data: items }
  • 실패(404)
{ message: "등록된 아이템이 없습니다." }

API CODE

router.get("/item", async (req, res, next) => {
  const items = await prisma.items.findMany();
  if (!items)
    return res.status(404).json({ message: "등록된 아이템이 없습니다." });

  return res.status(200).json({ data: items });
});

API CODE (리팩토링 버전)

router.get("/item", async (req, res, next) => {
  const items = await prisma.items.findMany();
  try {
    if (!items)
      return res.status(404).json({ message: "등록된 아이템이 없습니다." });
    return res.status(200).json({ data: items });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버 오류" });
  }
});

9. 아이템 상세 조회 API

요구조건

  • 아이템 코드를 params 로 전달받아 아이템 코드, 아이템 명, 아이템 능력, 아이템 가격 조회

요청

GET /characters/:characterId

응답

  • 성공(200)
{ data: items }
  • 실패(404)
{ message: "등록된 아이템이 없습니다." }

API CODE

router.get("/item/:itemNo", async (req, res, next) => {
  const { itemNo } = req.params;
  const items = await prisma.items.findFirst({
    where: { itemNo: +itemNo },
  });
  if (!items)
    return res.status(404).json({ message: "등록된 아이템이 없습니다." });

  return res.status(200).json({ data: items });
});

API CODE (리팩토링 버전)

router.get("/item/:itemNo", async (req, res, next) => {
  const { itemNo } = req.params;
  try {
    const items = await prisma.items.findFirst({
      where: { itemNo: +itemNo },
    });
    if (!items)
      return res.status(404).json({ message: "등록된 아이템이 없습니다." });

    return res.status(200).json({ data: items });
  } catch (err) {
    res.status(500).json({ errorMessage: "서버 오류" });
  }
});
profile
하루 최소 1시간이라도 공부하자..

0개의 댓글