Skip to content

Commit dbe9e8e

Browse files
authored
[hotfix]: 카테고리 어미 추가 및 로직 수정 (#79)
2 parents 4a98351 + 88a034e commit dbe9e8e

File tree

4 files changed

+82
-49
lines changed

4 files changed

+82
-49
lines changed

prisma/seed-categories.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ async function main() {
1515
const defaultCategoryNames = [
1616
"최근사용",
1717
"즐겨찾기",
18+
"어미",
1819
"기본",
1920
"사람",
2021
"행동",

src/category/category.service.js

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BaseError } from "../errors/app.error.js";
22
import { PrismaClient } from "@prisma/client";
33
const prisma = new PrismaClient();
4+
45
import {
56
countDuplicateCategoryName,
67
createUserCategory,
@@ -11,8 +12,7 @@ import {
1112
listUserCategories,
1213
} from "./category.repository.js";
1314

14-
// 카테고리 생성
15-
15+
/// 카테고리 생성
1616
export const createCategoryService = async ({
1717
userId,
1818
name,
@@ -27,19 +27,11 @@ export const createCategoryService = async ({
2727
if (dup > 0)
2828
throw new BaseError("이미 존재하는 카테고리명입니다", 409, "CAT_DUP");
2929

30-
const lastCategory = await prisma.userCategory.findFirst({
31-
where: { userId },
32-
orderBy: { displayOrder: "desc" },
33-
});
34-
35-
const nextOrder = lastCategory ? lastCategory.displayOrder + 1 : 0;
36-
3730
const created = await createUserCategory({
3831
userId,
3932
categoryName: name,
4033
iconKey,
4134
iconUrl,
42-
displayOrder: nextOrder,
4335
});
4436

4537
const wordCount = await countUserWordsInCategory({
@@ -51,20 +43,16 @@ export const createCategoryService = async ({
5143
};
5244

5345
// 카테고리 목록 조회
54-
// - 기본 + 사용자 모두 userCategory에서 조회
55-
// - isDefault 여부만 다름
56-
5746
export const getCategoryListService = async ({ userId }) => {
58-
// 현재 유저 카테고리 조회
5947
let categories = await listUserCategories({ userId });
6048

61-
// 기본 카테고리가 하나도 없다면 자동 생성
6249
const hasDefault = categories.some((c) => c.isDefault);
6350

6451
if (!hasDefault) {
6552
const DEFAULT_ICON_MAP = {
6653
최근사용: "ICON_RECENT",
6754
즐겨찾기: "ICON_FAVORITE",
55+
어미: "ICON_ENDING",
6856
기본: "ICON_BASIC",
6957
사람: "ICON_PERSON",
7058
행동: "ICON_ACTION",
@@ -86,11 +74,9 @@ export const getCategoryListService = async ({ userId }) => {
8674
})),
8775
});
8876

89-
// 다시 조회
9077
categories = await listUserCategories({ userId });
9178
}
9279

93-
// wordCount 계산
9480
return Promise.all(
9581
categories.map(async (c) => {
9682
let wordCount = 0;
@@ -120,8 +106,6 @@ export const getCategoryListService = async ({ userId }) => {
120106
};
121107

122108
// 카테고리 수정
123-
// - 기본 카테고리는 이름 수정 금지
124-
125109
export const patchCategoryService = async ({
126110
userId,
127111
id,
@@ -136,20 +120,17 @@ export const patchCategoryService = async ({
136120

137121
const LOCKED_DEFAULT_NAMES = ["최근사용", "즐겨찾기", "어미"];
138122

139-
// 일부 기본 카테고리만 수정 금지
140123
if (
141124
existing.isDefault &&
142-
name !== undefined &&
143125
LOCKED_DEFAULT_NAMES.includes(existing.categoryName)
144126
) {
145127
throw new BaseError(
146-
"해당 기본 카테고리는 이름을 수정할 수 없습니다",
128+
"해당 기본 카테고리는 수정할 수 없습니다",
147129
400,
148130
"CAT_DEFAULT_EDIT",
149131
);
150132
}
151133

152-
// 이름 중복 검사
153134
if (name !== undefined) {
154135
const dup = await countDuplicateCategoryName({
155136
userId,
@@ -170,7 +151,12 @@ export const patchCategoryService = async ({
170151
const updated = await updateUserCategory({ userId, id, data });
171152

172153
const wordCount = existing.isDefault
173-
? 0
154+
? await prisma.word.count({
155+
where: {
156+
category: { categoryName: existing.categoryName },
157+
isDefault: true,
158+
},
159+
})
174160
: await countUserWordsInCategory({
175161
userId,
176162
userCategoryId: id,
@@ -180,17 +166,20 @@ export const patchCategoryService = async ({
180166
};
181167

182168
// 카테고리 삭제
183-
// - 기본 카테고리 삭제 금지
184-
185169
export const removeCategoryService = async ({ userId, id }) => {
186170
const existing = await findUserCategoryById({ userId, id });
187171

188172
if (!existing)
189173
throw new BaseError("카테고리를 찾을 수 없습니다", 404, "CAT404");
190174

191-
if (existing.isDefault) {
175+
const LOCKED_DEFAULT_NAMES = ["최근사용", "즐겨찾기", "어미"];
176+
177+
if (
178+
existing.isDefault &&
179+
LOCKED_DEFAULT_NAMES.includes(existing.categoryName)
180+
) {
192181
throw new BaseError(
193-
"기본 카테고리는 삭제할 수 없습니다",
182+
"해당 기본 카테고리는 삭제할 수 없습니다",
194183
400,
195184
"CAT_DEFAULT_DELETE",
196185
);

src/category/defaultCategory.constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const DEFAULT_CATEGORY_MAP = {
22
최근사용: "ICON_RECENT",
33
즐겨찾기: "ICON_FAVORITE",
4+
어미: "ICON_ENDING",
45
기본: "ICON_BASIC",
56
사람: "ICON_PERSON",
67
행동: "ICON_ACTION",

src/order/order.repository.js

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,83 @@
11
import prisma from "../config/prisma.config.js";
22
import { BaseError } from "../errors/app.error.js";
33

4+
const LOCKED_DEFAULTS = {
5+
최근사용: 0,
6+
즐겨찾기: 1,
7+
어미: 2,
8+
};
9+
410
export const patchCategoryOrdersRepo = async ({ userId, orders }) => {
511
return await prisma.$transaction(async (tx) => {
12+
// 전체 카테고리 조회
13+
const allCategories = await tx.userCategory.findMany({
14+
where: { userId },
15+
});
16+
17+
if (allCategories.length !== orders.length) {
18+
throw new BaseError(
19+
"카테고리 전체 배열을 보내야 합니다.",
20+
400,
21+
"ORDER_FULL_REQUIRED",
22+
);
23+
}
24+
25+
// ID 검증
626
const ids = orders.map((o) => o.categoryId);
727

8-
// 내 카테고리인지 검증
9-
const categories = await tx.userCategory.findMany({
10-
where: {
11-
id: { in: ids },
12-
userId,
13-
},
14-
});
28+
const idSet = new Set(ids);
29+
if (idSet.size !== ids.length) {
30+
throw new BaseError("중복 ID가 포함되어 있습니다.", 400, "ORDER_DUP_ID");
31+
}
32+
33+
const dbIdSet = new Set(allCategories.map((c) => c.id));
34+
35+
for (const id of ids) {
36+
if (!dbIdSet.has(id)) {
37+
throw new BaseError(
38+
"잘못된 카테고리 ID입니다.",
39+
400,
40+
"ORDER_INVALID_ID",
41+
);
42+
}
43+
}
1544

16-
if (categories.length !== orders.length) {
45+
// displayOrder 중복 검사
46+
const orderValues = orders.map((o) => o.displayOrder);
47+
const orderSet = new Set(orderValues);
48+
49+
if (orderSet.size !== orderValues.length) {
1750
throw new BaseError(
18-
"잘못된 카테고리 ID가 포함되어 있습니다.",
51+
"displayOrder 값이 중복될 수 없습니다.",
1952
400,
20-
"ORDER002",
53+
"ORDER_DUP_VALUE",
2154
);
2255
}
2356

24-
// 기존 displayOrder 초기화 (임시값으로)
25-
for (const item of categories) {
26-
await tx.userCategory.update({
27-
where: { id: item.id },
28-
data: { displayOrder: -1 },
29-
});
57+
// 고정 카테고리 보호
58+
for (const category of allCategories) {
59+
if (LOCKED_DEFAULTS[category.categoryName] !== undefined) {
60+
const incoming = orders.find((o) => o.categoryId === category.id);
61+
62+
if (incoming.displayOrder !== LOCKED_DEFAULTS[category.categoryName]) {
63+
throw new BaseError(
64+
"고정 카테고리의 순서는 변경할 수 없습니다.",
65+
400,
66+
"ORDER_LOCKED",
67+
);
68+
}
69+
}
3070
}
3171

32-
// 새 displayOrder 반영
33-
for (const item of orders) {
34-
await tx.userCategory.update({
72+
// 한 번에 업데이트
73+
const updatePromises = orders.map((item) =>
74+
tx.userCategory.update({
3575
where: { id: item.categoryId },
3676
data: { displayOrder: item.displayOrder },
37-
});
38-
}
77+
}),
78+
);
79+
80+
await Promise.all(updatePromises);
3981

4082
return {
4183
updatedCount: orders.length,

0 commit comments

Comments
 (0)