오늘은 도전기능에서 몇가지 API를 구현하였고 Joi를 이용하여 유효성 검사를 하고, 에러미들웨어를 따로 만들어 좀 더 에러처리를 관리하기 쉽게 작업하였습니다.
Joi를 이용하여 하나의 클래스에 여러 schema를 지정하여 관리하면 편할 것 같아 해보았습니다.
Joi를 이용한 shema 클래스
import Joi from 'joi';
export default class schema {
userSchema() {
const idPattern = /^[a-z0-9]+$/;
const userSchema = Joi.object({
id: Joi.string().regex(idPattern).required(),
password: Joi.string().min(6).required(),
verifyPassword: Joi.string().min(6),
name: Joi.string(),
});
return userSchema;
}
itemSchema() {
const itemSchema = Joi.object({
itemCode: Joi.number(),
itemName: Joi.string(),
health: Joi.number(),
power: Joi.number(),
itemPrice: Joi.number(),
});
return itemSchema;
}
inventorySchema() {
const inventorySchema = Joi.object({
characterId: Joi.number(),
itemCode: Joi.number(),
count: Joi.number(),
});
return inventorySchema;
}
}
에러핸들링 미들웨어
export default function (err, req, res, next) {
// if (err.isJoi) return res.status(400).json({ errorNmae: err.name, errorMessage: err.message });
if (err.name === 'TokenExpiredError')
return res.status(401).json({
errorNmae: err.name,
errorMessage: 'AccessToken이 만료되었습니다. 다시 로그인해주세요',
});
if (err.name === 'ValidationError') {
return res.status(400).json({
errorNmae: err.name,
errorMessage: '데이터 형식에 맞지 않습니다.',
});
}
if (err.name === 'SyntaxError') {
return res.status(400).json({
errorNmae: err.name,
errorMessage: '잘못된 입력값입니다. 데이터 형식에 맞지 않습니다.',
});
}
return res
.status(err.status === undefined ? 500 : err.status)
.json({ errorName: err.name, errorMessage: err.message });
}
아이템 구입 API
router.post('/inventories/:characterId', authMiddleware, async (req, res, next) => {
try {
const { characterId } = await joiSchema.inventorySchema().validateAsync(req.params);
const { itemCode, count } = await joiSchema.inventorySchema().validateAsync(req.body);
const character = await prisma.characters.findFirst({
where: {
characterId: +characterId, // 어차피 현재 characterId는 고유하다
},
});
// 캐릭터가 있는지 확인
if (!character) return res.status(404).json({ message: '캐릭터가 존재하지 않습니다.' });
const item = await prisma.items.findFirst({
where: {
itemCode: +itemCode,
},
});
// 아이템 목록에 있는 아이템인지 확인
if (!item) return res.status(404).json({ message: '아이템이 존재하지 않습니다.' });
// 트랜잭션
const renewalcharacter = await prisma.$transaction(
async (tx) => {
const totalPrice = item.itemPrice * count;
if (character.money < totalPrice) return res.status(409).json({ messgae: '캐릭터의 돈이 부족합니다.' });
// 캐릭터 머니 수정
const renewalcharacter = await tx.characters.update({
data: {
money: character.money - totalPrice,
},
where: {
characterId: +characterId,
},
});
// 인벤토리 추가 및 수정
// 인벤토리에 해당 캐릭터가 아이템을 이미 가지고 있으면
const isExistinventoryItem = await tx.inventories.findFirst({
where: {
characterId: +characterId,
itemCode: +itemCode,
},
});
if (isExistinventoryItem) {
// 인벤토리에 아이템 있으면 갯수 추가
await tx.inventories.update({
data: {
count: isExistinventoryItem.count + count,
},
where: {
inventoryId: isExistinventoryItem.inventoryId,
characterId: +characterId,
},
});
} else {
// 인벤토리에 아이템 생성
await tx.inventories.create({
data: {
characterId: +characterId,
itemCode: itemCode,
count: count,
},
});
}
return renewalcharacter;
},
{
isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted, // DB가 커밋된 후
},
);
return res.status(201).json({ message: '아이템을 구입했습니다.', money: renewalcharacter.money });
} catch (err) {
next(err);
}
});
아이템 판매 API
router.patch('/inventories/:characterId', authMiddleware, async (req, res, next) => {
try {
const { characterId } = await joiSchema.inventorySchema().validateAsync(req.params);
const { itemCode, count } = await joiSchema.inventorySchema().validateAsync(req.body);
const character = await prisma.characters.findFirst({
where: {
characterId: +characterId, // 어차피 현재 characterId는 고유하다
},
});
// 캐릭터가 있는지 확인
if (!character) return res.status(404).json({ message: '캐릭터가 존재하지 않습니다.' });
const item = await prisma.items.findFirst({
where: {
itemCode: +itemCode,
},
});
// 아이템 목록에 있는 아이템인지 확인
if (!item) return res.status(404).json({ message: '아이템이 존재하지 않습니다.' });
// 트랜잭션
const renewalcharacter = await prisma.$transaction(
async (tx) => {
const totalPrice = Math.floor(item.itemPrice * count * 0.6); // 판매금액의 60%만 갖게 기획을 가집니다.
// 캐릭터 머니 수정
const renewalcharacter = await tx.characters.update({
data: {
money: character.money + totalPrice,
},
where: {
characterId: +characterId,
},
});
const isExistinventoryItem = await tx.inventories.findFirst({
where: {
characterId: +characterId,
itemCode: +itemCode,
},
});
// 인벤토리에 해당 아이템이 없으면
if (!isExistinventoryItem) return res.status(404).json({ message: '현재 인벤토리에 해당 아이템이 없습니다.' });
if (isExistinventoryItem.count < count)
return res.status(409).json({ message: '판매할 아이템의 갯수가 많습니다.' });
const renewalInventory = await tx.inventories.update({
data: {
count: isExistinventoryItem.count - count,
},
where: {
inventoryId: isExistinventoryItem.inventoryId,
characterId: +characterId,
},
});
if (renewalInventory.count === 0) {
await tx.inventories.delete({
where: {
inventoryId: renewalInventory.inventoryId,
characterId: +characterId,
},
});
}
return renewalcharacter;
},
{
isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted, // DB가 커밋된 후
},
);
return res.status(201).json({ message: '아이템을 판매하였습니다.', money: renewalcharacter.money });
} catch (err) {
next(err);
}
});
내 아이템 목록 조회 API
router.get('/inventories/:characterId', authMiddleware, async (req, res, next) => {
try {
const { characterId } = await joiSchema.inventorySchema(req.params);
const inventories = await prisma.inventories.findMany({
select: {
itemCode: true,
count: true,
},
where: {
characterId: characterId,
},
});
// if (!inventories) return res.status(404).json({ message: '현재 캐릭터의 인벤토리에 아이템이 존재하지 않습니다.' });
for (let i = 0; i < inventories.length; i++) {
const item = await prisma.items.findFirst({
select: { itemName: true },
where: { itemCode: inventories[i].itemCode },
});
inventories[i].itemName = item.itemName;
}
return res.status(200).json({ inventories: inventories });
} catch (err) {
next(err);
}
});
오늘 약간의 문제발생
아이템 판매 API를 테스트하다가 밑에 문구로 에러가 나오더라구요
무작정 이게 뭐지? 왜 안되는거여 하면서 제가 작성한 코드부터 쳐다보았습니다.
코드의 문제가 될만한 요소가 없는것 같아 다시 에러문구를 자세히 읽어보니 헤더의 문제가 있다는것 같더라구요.
insomnia에서 제가 헤더 값으로 토큰 수정이 제대로 안되어 있더라구요 수정후 다시 실행하니 정상작동 되었습니다.
아오 그냥 에러문구부터 자세히 확인했으면 금방 해결할 문제였네요.. 물론 당연하게 먼저 에러문구부터 확인했어야 했는데 뭐가 급한건지.. 다음부턴 제대로 확인해야겠습니다!! 무조건!
후 오늘도 화이팅 내일도 화이팅!
즐거운 추석보내세요~
2024-09-16 수정 오류가 발생한 이유가 정확히는 아니지만 트랜잭션안에 임의로 특정조건이 없을때 바로 return res.... 해서 중간에 return을 하여 트랙잭션에 오류가 나오는 것 같더라구요. 이것도 기억하면 좋을 것 같아 추가 작성해봅니다.