1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
// src/lib/items/service.ts
"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택)
import { revalidateTag, unstable_noStore } from "next/cache";
import db from "@/db/db";
import { customAlphabet } from "nanoid";
import { filterColumns } from "@/lib/filter-columns";
import { unstable_cache } from "@/lib/unstable-cache";
import { getErrorMessage } from "@/lib/handle-error";
import { asc, desc, ilike, inArray, and, gte, lte, not, or ,eq} from "drizzle-orm";
import { CreateItemSchema, GetItemsSchema, UpdateItemSchema } from "./validations";
import { Item, items } from "@/db/schema/items";
import { countItems, deleteItemById, deleteItemsByIds, findAllItems, insertItem, selectItems, updateItem } from "./repository";
/* -----------------------------------------------------
1) 조회 관련
----------------------------------------------------- */
/**
* 복잡한 조건으로 Item 목록을 조회 (+ pagination) 하고,
* 총 개수에 따라 pageCount를 계산해서 리턴.
* Next.js의 unstable_cache를 사용해 일정 시간 캐시.
*/
export async function getItems(input: GetItemsSchema) {
return unstable_cache(
async () => {
try {
const offset = (input.page - 1) * input.perPage;
// const advancedTable = input.flags.includes("advancedTable");
const advancedTable = true;
// advancedTable 모드면 filterColumns()로 where 절 구성
const advancedWhere = filterColumns({
table: items,
filters: input.filters,
joinOperator: input.joinOperator,
});
let globalWhere
if (input.search) {
const s = `%${input.search}%`
globalWhere = or(ilike(items.itemCode, s), ilike(items.itemName, s)
, ilike(items.description, s)
)
// 필요시 여러 칼럼 OR조건 (status, priority, etc)
}
const finalWhere = and(
// advancedWhere or your existing conditions
advancedWhere,
globalWhere // and()함수로 결합 or or() 등으로 결합
)
// 아니면 ilike, inArray, gte 등으로 where 절 구성
const where = finalWhere
const orderBy =
input.sort.length > 0
? input.sort.map((item) =>
item.desc ? desc(items[item.id]) : asc(items[item.id])
)
: [asc(items.createdAt)];
// 트랜잭션 내부에서 Repository 호출
const { data, total } = await db.transaction(async (tx) => {
const data = await selectItems(tx, {
where,
orderBy,
offset,
limit: input.perPage,
});
const total = await countItems(tx, where);
return { data, total };
});
const pageCount = Math.ceil(total / input.perPage);
return { data, pageCount };
} catch (err) {
// 에러 발생 시 디폴트
return { data: [], pageCount: 0 };
}
},
[JSON.stringify(input)], // 캐싱 키
{
revalidate: 3600,
tags: ["items"], // revalidateTag("items") 호출 시 무효화
}
)();
}
/* -----------------------------------------------------
2) 생성(Create)
----------------------------------------------------- */
export interface ItemCreateData {
itemCode: string
itemName: string
description: string | null
}
/**
* Item 생성 후, (가장 오래된 Item 1개) 삭제로
* 전체 Item 개수를 고정
*/
export async function createItem(input: ItemCreateData) {
unstable_noStore() // Next.js 서버 액션 캐싱 방지
try {
if (!input.itemCode || !input.itemName) {
return {
success: false,
message: "아이템 코드와 아이템 명은 필수입니다",
data: null,
error: "필수 필드 누락"
}
}
// result 변수에 명시적으로 타입과 초기값 할당
let result: any[] = []
// 트랜잭션 결과를 result에 할당
result = await db.transaction(async (tx) => {
// 기존 아이템 확인 (itemCode는 unique)
const existingItem = await tx.query.items.findFirst({
where: eq(items.itemCode, input.itemCode),
})
let txResult
if (existingItem) {
// 기존 아이템 업데이트
txResult = await updateItem(tx, existingItem.id, {
itemName: input.itemName,
description: input.description,
})
} else {
// 새 아이템 생성
txResult = await insertItem(tx, {
itemCode: input.itemCode,
itemName: input.itemName,
description: input.description,
})
}
return txResult
})
// 캐시 무효화
revalidateTag("items")
return {
success: true,
data: result[0] || null,
error: null
}
} catch (err) {
console.error("아이템 생성/업데이트 오류:", err)
// 중복 키 오류 처리
if (err instanceof Error && err.message.includes("unique constraint")) {
return {
success: false,
message: "이미 존재하는 아이템 코드입니다",
data: null,
error: "중복 키 오류"
}
}
return {
success: false,
message: getErrorMessage(err),
data: null,
error: getErrorMessage(err)
}
}
}
/* -----------------------------------------------------
3) 업데이트
----------------------------------------------------- */
/** 단건 업데이트 */
export async function modifyItem(input: UpdateItemSchema & { id: number }) {
unstable_noStore();
try {
const data = await db.transaction(async (tx) => {
const [res] = await updateItem(tx, input.id, {
itemCode: input.itemCode,
itemName: input.itemName,
description: input.description,
});
return res;
});
revalidateTag("items");
return { data: null, error: null };
} catch (err) {
return { data: null, error: getErrorMessage(err) };
}
}
/** 단건 삭제 */
export async function removeItem(input: { id: number }) {
unstable_noStore();
try {
await db.transaction(async (tx) => {
// 삭제
await deleteItemById(tx, input.id);
// 바로 새 Item 생성
});
revalidateTag("items");
return { data: null, error: null };
} catch (err) {
return { data: null, error: getErrorMessage(err) };
}
}
/** 복수 삭제 */
export async function removeItems(input: { ids: number[] }) {
unstable_noStore();
try {
await db.transaction(async (tx) => {
// 삭제
await deleteItemsByIds(tx, input.ids);
});
revalidateTag("items");
return { data: null, error: null };
} catch (err) {
return { data: null, error: getErrorMessage(err) };
}
}
export async function getAllItems(): Promise<Item[]> {
try {
return await findAllItems();
} catch (err) {
throw new Error("Failed to get roles");
}
}
|