[TIL] 24.09.11 WED

GDORI·2024년 9월 11일
0

TIL

목록 보기
38/79
post-thumbnail

[ 오늘 푼 알고리즘 코드카타 ]

172928. 공원 산책
42840. 모의고사
12939. 최댓값과 최솟값
12951. JadenCase 문자열 만들기

어제 필수 기능 API를 구현했는데, 에러처리 부분과 데이터 가져오는데에 있어 몇가지 부분을 수정하였다.
[Node.js] CH3 개인과제 - 필수 API 구현

또 수정하다보니까 중복된 인증들이 눈에 밟힌다..
미들웨어를 뭘 쓸지 고민하고 코딩할 걸 그랬다. 완벽히 구현 끝나면 제출전까지 미들웨어 추가 구현해서
리팩토링 해봐야겠다.😎

밑에의 내용은 오늘 구현한 도전 기능 관련 API이다.

도전 기능 API 구현목록

  • 아이템 구입 API (JWT)
  • 아이템 판매 API (JWT)
  • 본인 캐릭터 인벤토리 조회 API (JWT)
  • 캐릭터 장비창 조회 API
  • 아이템 장착 API (JWT)
  • 아이템 탈착 API (JWT)
  • 돈벌기 API (JWT)

1. 아이템 구입 API (JWT)

요구조건

  1. 보유한 게임머니 한에서 구매 가능
  2. 구입 후 보유머니 결제
  3. 캐릭터 ID params 전달, 구입하고 싶은 아이템 코드 및 수량 request.body로 전달
  4. 존재하지 않는 아이템은 살 수 없습니다.
  5. 전체 가격이 내가 보유한 금액보다 작으면 구매할 수 없습니다.
  6. 구입 성공 시 변경된 잔액을 반환합니다.
  7. 구입된 아이템은 인벤토리에 기록되어야 합니다.

요청

POST /shop/buy/:characterId
Authorization: Bearer <your_token>
[
  {
	  "itemNo": 1,
		"count": 2
	},
	{
	  "itemNo": 2,
		"count": 1
	}
]

응답

  • 성공(200)
{
	"data": {
		"changedMoney": your_changed_money,
		"changedInventory": "{your_changed_inventory}"
	}
}
  • 실패(400)
{ errorMessage: `금액 부족. ${character.money} 원 보유중` }
  • 실패(401)
{ errorMessage: "본인 캐릭터만 구매 가능합니다." }
  • 실패(404)
{ errorMessage: "존재하지 않는 캐릭터입니다." }
{ errorMessage: "아이템이 없습니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

router.post(
  "/shop/buy/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const {
      params: { characterNo },
      user: { userNo },
      body: shoppingCart,
    } = req;

    try {
      const [character, inventory] = await Promise.all([
        prisma.characters.findFirst({ where: { characterNo: +characterNo } }),
        prisma.inventories.findFirst({ where: { inventoryNo: +characterNo } }),
      ]);

      if (!character)
        return res
          .status(404)
          .json({ errorMessage: "존재하지 않는 캐릭터입니다." });
      if (character.userNo !== userNo)
        return res
          .status(401)
          .json({ errorMessage: "본인 캐릭터만 구매 가능합니다." });

      const inventoryItems = JSON.parse(inventory.items);
      let price = 0;

      // 쇼핑카트 가격 계산
      for (const { itemNo, count } of shoppingCart) {
        const item = await prisma.items.findFirst({ where: { itemNo } });
        if (!item)
          return res.status(404).json({ errorMessage: "아이템이 없습니다." });
        price += item.itemPrice * count;
      }

      // 결제 처리
      if (character.money < price)
        return res
          .status(400)
          .json({ errorMessage: `금액 부족. ${character.money} 원 보유중` });

      // 인벤토리 및 캐릭터 업데이트

      for (const { itemNo, count } of shoppingCart) {
        inventoryItems[itemNo] = (inventoryItems[itemNo] || 0) + count;
      }

      const [updatedCharacter, updatedInventory] = await Promise.all([
        prisma.characters.update({
          where: { characterNo: +characterNo },
          data: { money: character.money - price },
        }),
        prisma.inventories.update({
          where: { inventoryNo: +characterNo },
          data: { items: JSON.stringify(inventoryItems) },
        }),
      ]);

      res.status(200).json({
        data: {
          changedMoney: updatedCharacter.money,
          changedInventory: JSON.parse(updatedInventory.items),
        },
      });
    } catch (err) {
      res.status(500).json({ errorMessage: "서버 오류" });
    }
  }
);

2. 아이템 판매 API (JWT)

요구조건

  1. 캐릭터 ID params 전달, 판매하고 싶은 아이템 코드 및 수량 request.body로 전달
  2. 유저가 판매하고 싶은 아이템을 처분할 경우 구매가의 60%를 돌려받습니다.
  3. 성공 시 변경된 잔액을 반환합니다.
  4. 인벤토리에 있는 아이템만 팔 수 있습니다.
  5. 장착중인 아이템은 판매할 수 없습니다.

요청

POST /shop/sell/:characterNo
Authorization: Bearer <your_token>
[
  {
	  "itemNo": 1,
		"count": 2
	}
]

응답

  • 성공(200)
{
	"data": {
		"changedMoney": your_changed_money,
		"changedInventory": {
			your_changed_inventory
		}
	}
}
  • 실패(400)
{errorMessage: "판매할 아이템보다 보유한 아이템이 적습니다."}
  • 실패(401)
{ errorMessage: "본인 캐릭터만 판매 가능합니다." }
  • 실패(404)
{ errorMessage: "존재하지 않는 캐릭터입니다." }
{ errorMessage: "아이템이 없습니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

router.post(
  "/shop/sell/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const {
      params: { characterNo },
      user: { userNo },
      body: sellingCart,
    } = req;

    try {
      const [character, inventory] = await Promise.all([
        prisma.characters.findFirst({ where: { characterNo: +characterNo } }),
        prisma.inventories.findFirst({ where: { inventoryNo: +characterNo } }),
      ]);

      if (!character)
        return res
          .status(404)
          .json({ errorMessage: "존재하지 않는 캐릭터입니다." });
      if (character.userNo !== userNo)
        return res
          .status(401)
          .json({ errorMessage: "본인 캐릭터만 판매 가능합니다." });

      const inventoryItems = JSON.parse(inventory.items);
      let price = 0;

      // 판매카트 가격 계산
      for (const { itemNo, count } of sellingCart) {
        const item = await prisma.items.findFirst({ where: { itemNo } });
        if (!item)
          return res.status(404).json({ errorMessage: "아이템이 없습니다." });
        if (!inventoryItems[itemNo] || inventoryItems[itemNo] < count)
          return res.status(400).json({
            errorMessage: "판매할 아이템보다 보유한 아이템이 적습니다.",
          });
        price += Math.ceil(item.itemPrice * count * 0.6);
      }

      // 인벤토리 업데이트

      for (const { itemNo, count } of sellingCart) {
        if (inventoryItems[itemNo] - count === 0) {
          delete inventoryItems[itemNo];
        } else {
          inventoryItems[itemNo] -= count;
        }
      }

      const [updatedCharacter, updatedInventory] = await Promise.all([
        prisma.characters.update({
          where: { characterNo: +characterNo },
          data: { money: character.money + price },
        }),
        prisma.inventories.update({
          where: { inventoryNo: +characterNo },
          data: { items: JSON.stringify(inventoryItems) },
        }),
      ]);

      res.status(200).json({
        data: {
          changedMoney: updatedCharacter.money,
          changedInventory: JSON.parse(updatedInventory.items),
        },
      });
    } catch (err) {
      res.status(500).json({ errorMessage: "서버 오류" });
    }
  }
);

3. 본인 캐릭터 인벤토리 조회 API (JWT)

요구조건

  1. 인벤토리 목록을 조회할 내 캐릭터 ID params로 전달받음
  2. 내 캐릭터만 조회 가능

요청

GET /inventory/:characterNo
Authorization: Bearer <your_token>

응답

  • 성공(200)
{
	"data": {
		your_inventory
	}
}
  • 실패(401)
{ errorMessage: "본인의 캐릭터만 조회 가능합니다." }
  • 실패(404)
{ errorMessage: "조회하려는 캐릭터가 없습니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

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

    try {
      const [inventory, character] = await Promise.all([
        prisma.inventories.findFirst({ where: { inventoryNo: +characterNo } }),
        prisma.characters.findFirst({ where: { characterNo: +characterNo } }),
      ]);
      if (!inventory || !character)
        return res
          .status(404)
          .json({ errorMessage: "조회하려는 캐릭터가 없습니다." });
      if (userNo !== character.userNo)
        return res
          .status(401)
          .json({ errorMessage: "본인의 캐릭터만 조회 가능합니다." });

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

4. 캐릭터 장비창 조회 API

요구조건

  1. 장착된 아이템이 없으면 빈 배열로 반환됩니다.
  2. 다른 유저도 볼 수 있기 때문에 인증은 거치지 않습니다.

요청

GET /equip/:characterNo

응답

  • 성공(200)
{
	"data": {your_equip}
}
  • 실패(404)
{ errorMessage: "조회하려는 장비창이 없습니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

router.get("/equip/:characterNo", async (req, res, next) => {
  const { characterNo } = req.params;

  try {
    const equip = await prisma.equips.findFirst({
      where: { equipNo: +characterNo },
    });
    if (!equip)
      return res
        .status(404)
        .json({ errorMessage: "조회하려는 장비창이 없습니다." });
    if (Object.keys(equip.items).length === 0)
      return res.status(200).json({ data: [] });

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

5. 아이템 장착 API (JWT)

요구조건

  1. 아이템 장착할 내 캐릭터의 ID를 params로 전달받음
  2. 장착할 아이템 코드를 request에서 전달 받음
  3. 인벤토리에 존재하지 않는 아이템이라면 없는 아이템이라고 거부
  4. 이미 장착한 아이템을 또 장착하려고 하면 이미 장착된 아이템이라고 거부
  5. 아이템 장착 시 스탯을 변경
  6. 캐릭터 조회 API 사용 시 변경된 스탯 확인
  7. 장착된 아이템 정보는 인벤토리에서 삭제

요청

POST /equipItem/:characterNo
Authorization: Bearer <your_token>
{
	"doEquipItem" : input_equip_item_number
}

응답

  • 성공(200)
{
	"data": {
		"character_health": 560,
		"character_power": 109,
		"updatedInventory": {
			"1": 3,
			"2": 1
		},
		"updatedEquip": {
			"1": 1,
			"2": 1
		}
	}
}
  • 실패(401)
{ errorMessage: " 장비 착용 권한이 없습니다. " }
  • 실패(404)
{errorMessage: " 장비를 착용할 캐릭터가 존재하지 않습니다. "}
{ errorMessage: "보유하지 않은 아이템입니다." }
  • 실패(409)
{ errorMessage: "이미 장착하신 아이템입니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

router.post(
  "/equipItem/:characterNo",
  authMiddleware,
  async (req, res, next) => {
    const {
      params: { characterNo },
      body: { doEquipItem },
      user: { userNo },
    } = req;

    try {
      const [character, inventory, equip, item] = await Promise.all([
        prisma.characters.findFirst({ where: { characterNo: +characterNo } }),
        prisma.inventories.findFirst({ where: { inventoryNo: +characterNo } }),
        prisma.equips.findFirst({ where: { equipNo: +characterNo } }),
        prisma.items.findFirst({ where: { itemNo: +doEquipItem } }),
      ]);

      if (!character)
        return res.status(404).json({
          errorMessage: " 장비를 착용할 캐릭터가 존재하지 않습니다. ",
        });
      if (character.userNo !== userNo)
        return res
          .status(401)
          .json({ errorMessage: " 장비 착용 권한이 없습니다. " });

      const inventoryItems = JSON.parse(inventory.items);
      const equipItems = JSON.parse(equip.items);

      if (equipItems[doEquipItem])
        return res
          .status(409)
          .json({ errorMessage: "이미 장착하신 아이템입니다." });

      if (!inventoryItems[doEquipItem])
        return res
          .status(404)
          .json({ errorMessage: "보유하지 않은 아이템입니다." });

      // 장착할 아이템 인벤토리 제거
      if (inventoryItems[doEquipItem] === 1) delete inventoryItems[doEquipItem];
      else inventoryItems[doEquipItem] -= 1;

      // 장착한 아이템 장비창에 등록
      equipItems[doEquipItem] = 1;

      // 캐릭터 스탯 조정
      for (const [key, value] of Object.entries(item.itemStat)) {
        character[key] += value;
      }

      const [updatedCharacter, updatedInventory, updatedEquip] =
        await Promise.all([
          prisma.characters.update({
            where: { characterNo: +characterNo },
            data: { health: character.health, power: character.power },
          }),
          prisma.inventories.update({
            where: { inventoryNo: +characterNo },
            data: { items: JSON.stringify(inventoryItems) },
          }),
          prisma.equips.update({
            where: { equipNo: +characterNo },
            data: { items: JSON.stringify(equipItems) },
          }),
        ]);

      return res.status(200).json({
        data: {
          character_health: updatedCharacter.health,
          character_power: updatedCharacter.power,
          updatedInventory: JSON.parse(updatedInventory.items),
          updatedEquip: JSON.parse(updatedEquip.items),
        },
      });
    } catch (err) {
      console.error(err);
      res.status(500).json({ errorMessage: "서버 오류" });
    }
  }
);

6. 아이템 탈착 API (JWT)

요구조건

  1. 아이템을 탈착할 내 캐릭터의 ID를 params로 전달 받음
  2. 탈착할 아이템 코드 request로 전달 받기
  3. 장착하지 않은 아이템 탈착 시도 시 거부
  4. 아이템 탈착 성공 시 스탯 변경
  5. 인벤토리에 탈착한 아이템 추가

요청

POST /unEquipItem/:characterNo
Authorization: Bearer <your_token>
{
	"unEquipItem" : input_equip_item_number
}

응답

  • 성공(200)
{
	"data": {
		"character_health": 500,
		"character_power": 100,
		"updatedInventory": {
			"1": 4,
			"2": 2
		},
		"updatedEquip": {}
	}
}
  • 실패(401)
{ errorMessage: " 장비 탈착 권한이 없습니다. " }
  • 실패(404)
{errorMessage: " 장비를 착용할 캐릭터가 존재하지 않습니다. "}
{ errorMessage: "장착하신 아이템이 아닙니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

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

    try {
      const [character, inventory, equip, item] = await Promise.all([
        prisma.characters.findFirst({ where: { characterNo: +characterNo } }),
        prisma.inventories.findFirst({ where: { inventoryNo: +characterNo } }),
        prisma.equips.findFirst({ where: { equipNo: +characterNo } }),
        prisma.items.findFirst({ where: { itemNo: +unEquipItem } }),
      ]);

      if (!character)
        return res.status(404).json({
          errorMessage: " 장비를 착용할 캐릭터가 존재하지 않습니다. ",
        });
      if (character.userNo !== userNo)
        return res
          .status(401)
          .json({ errorMessage: " 장비 탈착 권한이 없습니다. " });

      const inventoryItems = JSON.parse(inventory.items);
      const equipItems = JSON.parse(equip.items);

      if (!equipItems[unEquipItem])
        return res
          .status(404)
          .json({ errorMessage: "장착하신 아이템이 아닙니다." });

      // 장착한 아이템 장비창에서 제거
      delete equipItems[unEquipItem];

      // 탈착한 아이템 인벤토리 추가
      if (!inventoryItems[unEquipItem]) inventoryItems[unEquipItem] = 1;
      else inventoryItems[unEquipItem] += 1;

      // 캐릭터 스탯 조정
      for (const [key, value] of Object.entries(item.itemStat)) {
        character[key] -= value;
      }

      const [updatedCharacter, updatedInventory, updatedEquip] =
        await Promise.all([
          prisma.characters.update({
            where: { characterNo: +characterNo },
            data: { health: character.health, power: character.power },
          }),
          prisma.inventories.update({
            where: { inventoryNo: +characterNo },
            data: { items: JSON.stringify(inventoryItems) },
          }),
          prisma.equips.update({
            where: { equipNo: +characterNo },
            data: { items: JSON.stringify(equipItems) },
          }),
        ]);

      return res.status(200).json({
        data: {
          character_health: updatedCharacter.health,
          character_power: updatedCharacter.power,
          updatedInventory: JSON.parse(updatedInventory.items),
          updatedEquip: JSON.parse(updatedEquip.items),
        },
      });
    } catch (err) {
      console.error(err);
      res.status(500).json({ errorMessage: "서버 오류" });
    }
  }
);

7. 돈벌기 API (JWT)

요구조건

  1. 캐릭터 ID를 params로 전달 받음
  2. 캐릭터 잔액 100원씩 증가

요청

GET /showMeTheMoney/:characterNo
Authorization: Bearer <your_token>

응답

  • 성공(200)
{
	"message": "100원이 추가되어 잔액이 6200 원이 되었습니다."
}
  • 실패(401)
{ errorMessage: "남 캐릭터 돈벌어줘서 뭐합니까" }
  • 실패(404)
{ errorMessage: "캐릭터가 존재하지 않습니다." }
  • 실패(500)
{ errorMessage: "서버 오류" }

API CODE

router.get(
  "/showMeTheMoney/: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({ errorMessage: "캐릭터가 존재하지 않습니다." });

      if (character.userNo !== userNo)
        return res
          .status(401)
          .json({ errorMessage: "남 캐릭터 돈벌어줘서 뭐합니까" });

      character.money += 100;
      const changedCharacter = await prisma.characters.update({
        where: { characterNo: +characterNo },
        data: { money: character.money },
      });

      return res.status(200).json({
        message: `100원이 추가되어 잔액이 ${changedCharacter.money} 원이 되었습니다.`,
      });
    } catch (err) {
      console.error(err);
    }
  }
);
profile
하루 최소 1시간이라도 공부하자..

0개의 댓글