target-supabase-sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +110 -70
- package/package.json +57 -45
- package/src/auth/auth.api.ts +85 -85
- package/src/core.api.ts +338 -338
- package/src/core.interface.ts +74 -74
- package/src/core.utils.ts +98 -98
- package/src/feed/feed.interface.ts +33 -33
- package/src/file/file.api.ts +43 -43
- package/src/file/file.interface.ts +29 -29
- package/src/file-list/file-list.api.ts +64 -64
- package/src/file-list/file-list.interface.ts +24 -24
- package/src/idea/idea.interface.ts +27 -27
- package/src/index.ts +50 -50
- package/src/link/link.interface.ts +21 -21
- package/src/parcel/parcel-manager.ts +209 -209
- package/src/parcel/parcel.interface.ts +40 -40
- package/src/question-list/question-list.api.ts +42 -42
- package/src/question-list/question-list.interface.ts +21 -21
- package/src/review/review.api.ts +145 -145
- package/src/review/review.interface.ts +26 -26
- package/src/review-v2/apis/post-review-setting.api.ts +35 -35
- package/src/review-v2/apis/post-review.api.ts +59 -59
- package/src/review-v2/review-setting.interface.ts +26 -26
- package/src/review-v2/review.interface.ts +23 -23
- package/src/service/api.interface.ts +47 -47
- package/src/service/base.interface.ts +97 -97
- package/src/service/service.interface.ts +27 -27
- package/src/supabase.ts +143 -143
package/src/core.api.ts
CHANGED
|
@@ -1,338 +1,338 @@
|
|
|
1
|
-
import { pick } from "lodash-es";
|
|
2
|
-
import { createResponse, Target } from "./core.interface";
|
|
3
|
-
import { supabase } from ".";
|
|
4
|
-
import { BaseValidator, handleSupabaseError } from "./core.utils";
|
|
5
|
-
import { PostgrestFilterBuilder } from "@supabase/postgrest-js";
|
|
6
|
-
|
|
7
|
-
export interface QueryFilter {
|
|
8
|
-
field: string;
|
|
9
|
-
operator: "eq";
|
|
10
|
-
value: unknown;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface QueryOrderBy {
|
|
14
|
-
field: string;
|
|
15
|
-
ascending: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface BaseQueryParams {
|
|
19
|
-
filterList?: QueryFilter[];
|
|
20
|
-
orderBy?: QueryOrderBy;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const getTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
|
|
24
|
-
const query =
|
|
25
|
-
filterList != null && filterList.length > 0
|
|
26
|
-
? filterList.reduce((query, filter) => {
|
|
27
|
-
switch (filter.operator) {
|
|
28
|
-
case "eq":
|
|
29
|
-
return query.eq(filter.field, filter.value);
|
|
30
|
-
default:
|
|
31
|
-
return query;
|
|
32
|
-
}
|
|
33
|
-
}, supabase.client.from("target").select().eq("id", id))
|
|
34
|
-
: supabase.client.from("target").select().eq("id", id);
|
|
35
|
-
|
|
36
|
-
const { data, error } = await query.single();
|
|
37
|
-
if (error) {
|
|
38
|
-
handleSupabaseError("getTarget", error, "Failed to fetch target.");
|
|
39
|
-
}
|
|
40
|
-
return createResponse.success<Target>(data as Target);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const getPossibleTarget = async ({ filterList }: { filterList: QueryFilter[] }) => {
|
|
44
|
-
const query = filterList.reduce((query, filter) => {
|
|
45
|
-
switch (filter.operator) {
|
|
46
|
-
case "eq":
|
|
47
|
-
return query.eq(filter.field, filter.value);
|
|
48
|
-
default:
|
|
49
|
-
return query;
|
|
50
|
-
}
|
|
51
|
-
}, supabase.client.from("target").select());
|
|
52
|
-
|
|
53
|
-
const { data, error } = await query.maybeSingle();
|
|
54
|
-
if (error) {
|
|
55
|
-
handleSupabaseError("getPossibleTarget", error, "Failed to fetch target.");
|
|
56
|
-
}
|
|
57
|
-
return createResponse.success<Target | null>(data);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export interface GetTargetListParams extends BaseQueryParams {
|
|
61
|
-
pageNum: number;
|
|
62
|
-
pageSize: number;
|
|
63
|
-
category: Target["category"];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const getTargetList = async ({
|
|
67
|
-
pageNum,
|
|
68
|
-
pageSize,
|
|
69
|
-
category,
|
|
70
|
-
filterList = [],
|
|
71
|
-
orderBy = { field: "created_at", ascending: false },
|
|
72
|
-
filterBuilder,
|
|
73
|
-
}: GetTargetListParams & {
|
|
74
|
-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
75
|
-
filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
|
|
76
|
-
}) => {
|
|
77
|
-
const query =
|
|
78
|
-
filterBuilder ??
|
|
79
|
-
filterList.reduce((query, filter) => {
|
|
80
|
-
switch (filter.operator) {
|
|
81
|
-
case "eq":
|
|
82
|
-
return query.eq(filter.field, filter.value);
|
|
83
|
-
default:
|
|
84
|
-
return query;
|
|
85
|
-
}
|
|
86
|
-
}, supabase.client.from("target").select("*").eq("category", category));
|
|
87
|
-
|
|
88
|
-
const { data, error } = await query
|
|
89
|
-
.order(orderBy.field, { ascending: orderBy.ascending })
|
|
90
|
-
.range(pageNum * pageSize, (pageNum + 1) * pageSize - 1);
|
|
91
|
-
|
|
92
|
-
if (error) {
|
|
93
|
-
handleSupabaseError("getTargetList", error, "Failed to fetch target list.");
|
|
94
|
-
}
|
|
95
|
-
return createResponse.success<Target[]>(data);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export interface GetTargetTotalCountParams extends BaseQueryParams {
|
|
99
|
-
category: Target["category"];
|
|
100
|
-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
101
|
-
filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export const getTargetTotalCount = async ({
|
|
105
|
-
category,
|
|
106
|
-
filterList = [],
|
|
107
|
-
filterBuilder,
|
|
108
|
-
}: GetTargetTotalCountParams) => {
|
|
109
|
-
const query =
|
|
110
|
-
filterBuilder ??
|
|
111
|
-
filterList.reduce(
|
|
112
|
-
(query, filter) => {
|
|
113
|
-
switch (filter.operator) {
|
|
114
|
-
case "eq":
|
|
115
|
-
return query.eq(filter.field, filter.value);
|
|
116
|
-
default:
|
|
117
|
-
return query;
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
supabase.client
|
|
121
|
-
.from("target")
|
|
122
|
-
.select("id", { count: "exact", head: true }) // head: true means no row payload
|
|
123
|
-
.eq("category", category)
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const { count, error } = await query;
|
|
127
|
-
|
|
128
|
-
if (error) {
|
|
129
|
-
handleSupabaseError("getTargetTotalCount", error, "Failed to fetch target count.");
|
|
130
|
-
}
|
|
131
|
-
return createResponse.success<number>(count ?? 0);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export const deleteTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
|
|
135
|
-
// Check whether the target exists given filterList
|
|
136
|
-
if (filterList != null && filterList.length > 0) {
|
|
137
|
-
const query = filterList.reduce((query, filter) => {
|
|
138
|
-
switch (filter.operator) {
|
|
139
|
-
case "eq":
|
|
140
|
-
return query.eq(filter.field, filter.value);
|
|
141
|
-
default:
|
|
142
|
-
return query;
|
|
143
|
-
}
|
|
144
|
-
}, supabase.client.from("target").select().eq("id", id));
|
|
145
|
-
const { data: existingTarget, error: existingError } = await query.single();
|
|
146
|
-
if (existingError || existingTarget == null) {
|
|
147
|
-
const msg = `[deleteTarget] Cannot find the target ${id}`;
|
|
148
|
-
console.error(msg, { filterList }, existingError);
|
|
149
|
-
throw new Error(msg);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const { error } = await supabase.client.from("target").delete().eq("id", id);
|
|
153
|
-
if (error) {
|
|
154
|
-
handleSupabaseError("deleteTarget", error, `Failed to delete target ${id}.`);
|
|
155
|
-
}
|
|
156
|
-
return createResponse.success();
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export interface PostTargetPayload {
|
|
160
|
-
name: Target["name"];
|
|
161
|
-
category: Target["category"];
|
|
162
|
-
value: Target["value"];
|
|
163
|
-
tagList: Target["tagList"];
|
|
164
|
-
extra?: Target["extra"];
|
|
165
|
-
details?: Target["details"];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
class PostTargetPayloadValidator extends BaseValidator<PostTargetPayload> {
|
|
169
|
-
protected requiredFields: (keyof PostTargetPayload)[] = ["category", "name", "value", "tagList"];
|
|
170
|
-
protected optionalFields: (keyof PostTargetPayload)[] = ["extra", "details"];
|
|
171
|
-
|
|
172
|
-
constructor() {
|
|
173
|
-
super();
|
|
174
|
-
// Add custom
|
|
175
|
-
this.addValidator((val) => {
|
|
176
|
-
return true;
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export const postTarget = async (payload: PostTargetPayload) => {
|
|
182
|
-
const validPayload = new PostTargetPayloadValidator().validate(payload);
|
|
183
|
-
|
|
184
|
-
const { data, error } = await supabase.client
|
|
185
|
-
.from("target")
|
|
186
|
-
.insert([{ ...validPayload }])
|
|
187
|
-
.select()
|
|
188
|
-
.single();
|
|
189
|
-
|
|
190
|
-
if (error) {
|
|
191
|
-
handleSupabaseError("postTarget", error, "Failed to create target.");
|
|
192
|
-
}
|
|
193
|
-
return createResponse.success<Target>(data as Target);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
export interface PatchTargetPayload extends PostTargetPayload {
|
|
197
|
-
id: string;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) => {
|
|
201
|
-
const { data: currentData, error: fetchError } = await supabase.client.from("target").select().eq("id", id).single();
|
|
202
|
-
if (!currentData) {
|
|
203
|
-
throw new Error("Target NOT exists");
|
|
204
|
-
}
|
|
205
|
-
if (fetchError) handleSupabaseError("patchTarget", fetchError, "Failed to fetch target.");
|
|
206
|
-
|
|
207
|
-
const updatedTarget = new PostTargetPayloadValidator().validate(restPayload);
|
|
208
|
-
|
|
209
|
-
const { data, error } = await supabase.client
|
|
210
|
-
.from("target")
|
|
211
|
-
.update({
|
|
212
|
-
...updatedTarget,
|
|
213
|
-
})
|
|
214
|
-
.eq("id", id)
|
|
215
|
-
.select()
|
|
216
|
-
.single();
|
|
217
|
-
|
|
218
|
-
if (error) {
|
|
219
|
-
handleSupabaseError("patchTarget", error, "Failed to update target.");
|
|
220
|
-
}
|
|
221
|
-
return createResponse.success<Target>(data as Target);
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
export const updateTargetDetails = async <T, D>({
|
|
225
|
-
id,
|
|
226
|
-
updateFn,
|
|
227
|
-
beforeUpdateValidator = () => true,
|
|
228
|
-
updateExtraFn,
|
|
229
|
-
}: {
|
|
230
|
-
id: string;
|
|
231
|
-
updateFn: (existing: D) => D;
|
|
232
|
-
beforeUpdateValidator?: (existing: D) => true | string;
|
|
233
|
-
updateExtraFn?: (existing: D) => string;
|
|
234
|
-
}) => {
|
|
235
|
-
const { data: currentData, error: fetchError } = await supabase.client
|
|
236
|
-
.from("target")
|
|
237
|
-
.select("details")
|
|
238
|
-
.eq("id", id)
|
|
239
|
-
.single();
|
|
240
|
-
if (!currentData) {
|
|
241
|
-
const msg = "[updateTargetDetails] Target NOT exists: " + id;
|
|
242
|
-
console.error(msg);
|
|
243
|
-
throw new Error(msg);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const currentDetails = currentData.details as D;
|
|
247
|
-
|
|
248
|
-
// Check before update
|
|
249
|
-
const validatorResult = beforeUpdateValidator(currentDetails);
|
|
250
|
-
if (validatorResult !== true) {
|
|
251
|
-
throw new Error("[updateTargetDetails] validation failed. " + validatorResult);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const updatedDetails: D = updateFn(currentDetails);
|
|
255
|
-
const updated =
|
|
256
|
-
updateExtraFn == null
|
|
257
|
-
? {
|
|
258
|
-
details: updatedDetails,
|
|
259
|
-
}
|
|
260
|
-
: {
|
|
261
|
-
details: updatedDetails,
|
|
262
|
-
extra: updateExtraFn(currentDetails),
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const { data, error } = await supabase.client.from("target").update(updated).eq("id", id).select().single();
|
|
266
|
-
|
|
267
|
-
if (error) handleSupabaseError("updateTargetDetails", error, "Failed to update target details.");
|
|
268
|
-
return data as T;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
export const createTarget = async <T extends Target, P extends object>({
|
|
272
|
-
payload,
|
|
273
|
-
validator,
|
|
274
|
-
createFn,
|
|
275
|
-
checkRedundancyFilterList,
|
|
276
|
-
upsert = false,
|
|
277
|
-
}: {
|
|
278
|
-
payload: P;
|
|
279
|
-
validator?: new () => BaseValidator<P>;
|
|
280
|
-
createFn: (validPayload: P) => Omit<T, "id" | "created_at">;
|
|
281
|
-
checkRedundancyFilterList?: QueryFilter[];
|
|
282
|
-
upsert?: boolean;
|
|
283
|
-
}) => {
|
|
284
|
-
// (Optional)Step 1: validate payload
|
|
285
|
-
const validPayload = validator != null ? new validator().validate(payload) : payload;
|
|
286
|
-
|
|
287
|
-
// (Optional)Step 2: check redundancy
|
|
288
|
-
if (checkRedundancyFilterList != null && checkRedundancyFilterList.length > 0) {
|
|
289
|
-
const query = checkRedundancyFilterList.reduce((query, filter) => {
|
|
290
|
-
switch (filter.operator) {
|
|
291
|
-
case "eq":
|
|
292
|
-
return query.eq(filter.field, filter.value);
|
|
293
|
-
default:
|
|
294
|
-
return query;
|
|
295
|
-
}
|
|
296
|
-
}, supabase.client.from("target").select("id"));
|
|
297
|
-
const { data: existingTarget, error: existingError } = await query.maybeSingle();
|
|
298
|
-
if (existingError) {
|
|
299
|
-
handleSupabaseError("createTarget", existingError, "Failed to check target existence.");
|
|
300
|
-
}
|
|
301
|
-
if (existingTarget) {
|
|
302
|
-
if (upsert) {
|
|
303
|
-
const newTarget = createFn(validPayload);
|
|
304
|
-
const updated = pick(newTarget, ["category", "name", "value", "tagList", "extra", "details"]);
|
|
305
|
-
const { data, error } = await supabase.client
|
|
306
|
-
.from("target")
|
|
307
|
-
.update(updated)
|
|
308
|
-
.eq("id", existingTarget.id)
|
|
309
|
-
.select()
|
|
310
|
-
.single();
|
|
311
|
-
if (error) {
|
|
312
|
-
handleSupabaseError("createTarget", error, "Failed to update existing target.");
|
|
313
|
-
}
|
|
314
|
-
return createResponse.success<T>(data);
|
|
315
|
-
}
|
|
316
|
-
const msg = "[createTarget] Target already exists";
|
|
317
|
-
console.error(msg, checkRedundancyFilterList);
|
|
318
|
-
throw new Error(msg);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Step 3: create target
|
|
323
|
-
const newTarget = createFn(validPayload);
|
|
324
|
-
const { data, error } = await supabase.client.from("target").insert([newTarget]).select().single();
|
|
325
|
-
if (error) {
|
|
326
|
-
handleSupabaseError("createTarget", error, "Failed to create target.");
|
|
327
|
-
}
|
|
328
|
-
return createResponse.success<T>(data);
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
export function validateWith<P extends object, V extends BaseValidator<P>>(ValidatorClass: new () => V) {
|
|
332
|
-
return <R>(fn: (validPayload: P) => R) => {
|
|
333
|
-
return (payload: P): R => {
|
|
334
|
-
const validPayload = new ValidatorClass().validate(payload);
|
|
335
|
-
return fn(validPayload);
|
|
336
|
-
};
|
|
337
|
-
};
|
|
338
|
-
}
|
|
1
|
+
import { pick } from "lodash-es";
|
|
2
|
+
import { createResponse, Target } from "./core.interface";
|
|
3
|
+
import { supabase } from ".";
|
|
4
|
+
import { BaseValidator, handleSupabaseError } from "./core.utils";
|
|
5
|
+
import { PostgrestFilterBuilder } from "@supabase/postgrest-js";
|
|
6
|
+
|
|
7
|
+
export interface QueryFilter {
|
|
8
|
+
field: string;
|
|
9
|
+
operator: "eq";
|
|
10
|
+
value: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface QueryOrderBy {
|
|
14
|
+
field: string;
|
|
15
|
+
ascending: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BaseQueryParams {
|
|
19
|
+
filterList?: QueryFilter[];
|
|
20
|
+
orderBy?: QueryOrderBy;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const getTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
|
|
24
|
+
const query =
|
|
25
|
+
filterList != null && filterList.length > 0
|
|
26
|
+
? filterList.reduce((query, filter) => {
|
|
27
|
+
switch (filter.operator) {
|
|
28
|
+
case "eq":
|
|
29
|
+
return query.eq(filter.field, filter.value);
|
|
30
|
+
default:
|
|
31
|
+
return query;
|
|
32
|
+
}
|
|
33
|
+
}, supabase.client.from("target").select().eq("id", id))
|
|
34
|
+
: supabase.client.from("target").select().eq("id", id);
|
|
35
|
+
|
|
36
|
+
const { data, error } = await query.single();
|
|
37
|
+
if (error) {
|
|
38
|
+
handleSupabaseError("getTarget", error, "Failed to fetch target.");
|
|
39
|
+
}
|
|
40
|
+
return createResponse.success<Target>(data as Target);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getPossibleTarget = async ({ filterList }: { filterList: QueryFilter[] }) => {
|
|
44
|
+
const query = filterList.reduce((query, filter) => {
|
|
45
|
+
switch (filter.operator) {
|
|
46
|
+
case "eq":
|
|
47
|
+
return query.eq(filter.field, filter.value);
|
|
48
|
+
default:
|
|
49
|
+
return query;
|
|
50
|
+
}
|
|
51
|
+
}, supabase.client.from("target").select());
|
|
52
|
+
|
|
53
|
+
const { data, error } = await query.maybeSingle();
|
|
54
|
+
if (error) {
|
|
55
|
+
handleSupabaseError("getPossibleTarget", error, "Failed to fetch target.");
|
|
56
|
+
}
|
|
57
|
+
return createResponse.success<Target | null>(data);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export interface GetTargetListParams extends BaseQueryParams {
|
|
61
|
+
pageNum: number;
|
|
62
|
+
pageSize: number;
|
|
63
|
+
category: Target["category"];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const getTargetList = async ({
|
|
67
|
+
pageNum,
|
|
68
|
+
pageSize,
|
|
69
|
+
category,
|
|
70
|
+
filterList = [],
|
|
71
|
+
orderBy = { field: "created_at", ascending: false },
|
|
72
|
+
filterBuilder,
|
|
73
|
+
}: GetTargetListParams & {
|
|
74
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
75
|
+
filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
|
|
76
|
+
}) => {
|
|
77
|
+
const query =
|
|
78
|
+
filterBuilder ??
|
|
79
|
+
filterList.reduce((query, filter) => {
|
|
80
|
+
switch (filter.operator) {
|
|
81
|
+
case "eq":
|
|
82
|
+
return query.eq(filter.field, filter.value);
|
|
83
|
+
default:
|
|
84
|
+
return query;
|
|
85
|
+
}
|
|
86
|
+
}, supabase.client.from("target").select("*").eq("category", category));
|
|
87
|
+
|
|
88
|
+
const { data, error } = await query
|
|
89
|
+
.order(orderBy.field, { ascending: orderBy.ascending })
|
|
90
|
+
.range(pageNum * pageSize, (pageNum + 1) * pageSize - 1);
|
|
91
|
+
|
|
92
|
+
if (error) {
|
|
93
|
+
handleSupabaseError("getTargetList", error, "Failed to fetch target list.");
|
|
94
|
+
}
|
|
95
|
+
return createResponse.success<Target[]>(data);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export interface GetTargetTotalCountParams extends BaseQueryParams {
|
|
99
|
+
category: Target["category"];
|
|
100
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
101
|
+
filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const getTargetTotalCount = async ({
|
|
105
|
+
category,
|
|
106
|
+
filterList = [],
|
|
107
|
+
filterBuilder,
|
|
108
|
+
}: GetTargetTotalCountParams) => {
|
|
109
|
+
const query =
|
|
110
|
+
filterBuilder ??
|
|
111
|
+
filterList.reduce(
|
|
112
|
+
(query, filter) => {
|
|
113
|
+
switch (filter.operator) {
|
|
114
|
+
case "eq":
|
|
115
|
+
return query.eq(filter.field, filter.value);
|
|
116
|
+
default:
|
|
117
|
+
return query;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
supabase.client
|
|
121
|
+
.from("target")
|
|
122
|
+
.select("id", { count: "exact", head: true }) // head: true means no row payload
|
|
123
|
+
.eq("category", category)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const { count, error } = await query;
|
|
127
|
+
|
|
128
|
+
if (error) {
|
|
129
|
+
handleSupabaseError("getTargetTotalCount", error, "Failed to fetch target count.");
|
|
130
|
+
}
|
|
131
|
+
return createResponse.success<number>(count ?? 0);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const deleteTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
|
|
135
|
+
// Check whether the target exists given filterList
|
|
136
|
+
if (filterList != null && filterList.length > 0) {
|
|
137
|
+
const query = filterList.reduce((query, filter) => {
|
|
138
|
+
switch (filter.operator) {
|
|
139
|
+
case "eq":
|
|
140
|
+
return query.eq(filter.field, filter.value);
|
|
141
|
+
default:
|
|
142
|
+
return query;
|
|
143
|
+
}
|
|
144
|
+
}, supabase.client.from("target").select().eq("id", id));
|
|
145
|
+
const { data: existingTarget, error: existingError } = await query.single();
|
|
146
|
+
if (existingError || existingTarget == null) {
|
|
147
|
+
const msg = `[deleteTarget] Cannot find the target ${id}`;
|
|
148
|
+
console.error(msg, { filterList }, existingError);
|
|
149
|
+
throw new Error(msg);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const { error } = await supabase.client.from("target").delete().eq("id", id);
|
|
153
|
+
if (error) {
|
|
154
|
+
handleSupabaseError("deleteTarget", error, `Failed to delete target ${id}.`);
|
|
155
|
+
}
|
|
156
|
+
return createResponse.success();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export interface PostTargetPayload {
|
|
160
|
+
name: Target["name"];
|
|
161
|
+
category: Target["category"];
|
|
162
|
+
value: Target["value"];
|
|
163
|
+
tagList: Target["tagList"];
|
|
164
|
+
extra?: Target["extra"];
|
|
165
|
+
details?: Target["details"];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class PostTargetPayloadValidator extends BaseValidator<PostTargetPayload> {
|
|
169
|
+
protected requiredFields: (keyof PostTargetPayload)[] = ["category", "name", "value", "tagList"];
|
|
170
|
+
protected optionalFields: (keyof PostTargetPayload)[] = ["extra", "details"];
|
|
171
|
+
|
|
172
|
+
constructor() {
|
|
173
|
+
super();
|
|
174
|
+
// Add custom
|
|
175
|
+
this.addValidator((val) => {
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const postTarget = async (payload: PostTargetPayload) => {
|
|
182
|
+
const validPayload = new PostTargetPayloadValidator().validate(payload);
|
|
183
|
+
|
|
184
|
+
const { data, error } = await supabase.client
|
|
185
|
+
.from("target")
|
|
186
|
+
.insert([{ ...validPayload }])
|
|
187
|
+
.select()
|
|
188
|
+
.single();
|
|
189
|
+
|
|
190
|
+
if (error) {
|
|
191
|
+
handleSupabaseError("postTarget", error, "Failed to create target.");
|
|
192
|
+
}
|
|
193
|
+
return createResponse.success<Target>(data as Target);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export interface PatchTargetPayload extends PostTargetPayload {
|
|
197
|
+
id: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) => {
|
|
201
|
+
const { data: currentData, error: fetchError } = await supabase.client.from("target").select().eq("id", id).single();
|
|
202
|
+
if (!currentData) {
|
|
203
|
+
throw new Error("Target NOT exists");
|
|
204
|
+
}
|
|
205
|
+
if (fetchError) handleSupabaseError("patchTarget", fetchError, "Failed to fetch target.");
|
|
206
|
+
|
|
207
|
+
const updatedTarget = new PostTargetPayloadValidator().validate(restPayload);
|
|
208
|
+
|
|
209
|
+
const { data, error } = await supabase.client
|
|
210
|
+
.from("target")
|
|
211
|
+
.update({
|
|
212
|
+
...updatedTarget,
|
|
213
|
+
})
|
|
214
|
+
.eq("id", id)
|
|
215
|
+
.select()
|
|
216
|
+
.single();
|
|
217
|
+
|
|
218
|
+
if (error) {
|
|
219
|
+
handleSupabaseError("patchTarget", error, "Failed to update target.");
|
|
220
|
+
}
|
|
221
|
+
return createResponse.success<Target>(data as Target);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const updateTargetDetails = async <T, D>({
|
|
225
|
+
id,
|
|
226
|
+
updateFn,
|
|
227
|
+
beforeUpdateValidator = () => true,
|
|
228
|
+
updateExtraFn,
|
|
229
|
+
}: {
|
|
230
|
+
id: string;
|
|
231
|
+
updateFn: (existing: D) => D;
|
|
232
|
+
beforeUpdateValidator?: (existing: D) => true | string;
|
|
233
|
+
updateExtraFn?: (existing: D) => string;
|
|
234
|
+
}) => {
|
|
235
|
+
const { data: currentData, error: fetchError } = await supabase.client
|
|
236
|
+
.from("target")
|
|
237
|
+
.select("details")
|
|
238
|
+
.eq("id", id)
|
|
239
|
+
.single();
|
|
240
|
+
if (!currentData) {
|
|
241
|
+
const msg = "[updateTargetDetails] Target NOT exists: " + id;
|
|
242
|
+
console.error(msg);
|
|
243
|
+
throw new Error(msg);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const currentDetails = currentData.details as D;
|
|
247
|
+
|
|
248
|
+
// Check before update
|
|
249
|
+
const validatorResult = beforeUpdateValidator(currentDetails);
|
|
250
|
+
if (validatorResult !== true) {
|
|
251
|
+
throw new Error("[updateTargetDetails] validation failed. " + validatorResult);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const updatedDetails: D = updateFn(currentDetails);
|
|
255
|
+
const updated =
|
|
256
|
+
updateExtraFn == null
|
|
257
|
+
? {
|
|
258
|
+
details: updatedDetails,
|
|
259
|
+
}
|
|
260
|
+
: {
|
|
261
|
+
details: updatedDetails,
|
|
262
|
+
extra: updateExtraFn(currentDetails),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const { data, error } = await supabase.client.from("target").update(updated).eq("id", id).select().single();
|
|
266
|
+
|
|
267
|
+
if (error) handleSupabaseError("updateTargetDetails", error, "Failed to update target details.");
|
|
268
|
+
return data as T;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const createTarget = async <T extends Target, P extends object>({
|
|
272
|
+
payload,
|
|
273
|
+
validator,
|
|
274
|
+
createFn,
|
|
275
|
+
checkRedundancyFilterList,
|
|
276
|
+
upsert = false,
|
|
277
|
+
}: {
|
|
278
|
+
payload: P;
|
|
279
|
+
validator?: new () => BaseValidator<P>;
|
|
280
|
+
createFn: (validPayload: P) => Omit<T, "id" | "created_at">;
|
|
281
|
+
checkRedundancyFilterList?: QueryFilter[];
|
|
282
|
+
upsert?: boolean;
|
|
283
|
+
}) => {
|
|
284
|
+
// (Optional)Step 1: validate payload
|
|
285
|
+
const validPayload = validator != null ? new validator().validate(payload) : payload;
|
|
286
|
+
|
|
287
|
+
// (Optional)Step 2: check redundancy
|
|
288
|
+
if (checkRedundancyFilterList != null && checkRedundancyFilterList.length > 0) {
|
|
289
|
+
const query = checkRedundancyFilterList.reduce((query, filter) => {
|
|
290
|
+
switch (filter.operator) {
|
|
291
|
+
case "eq":
|
|
292
|
+
return query.eq(filter.field, filter.value);
|
|
293
|
+
default:
|
|
294
|
+
return query;
|
|
295
|
+
}
|
|
296
|
+
}, supabase.client.from("target").select("id"));
|
|
297
|
+
const { data: existingTarget, error: existingError } = await query.maybeSingle();
|
|
298
|
+
if (existingError) {
|
|
299
|
+
handleSupabaseError("createTarget", existingError, "Failed to check target existence.");
|
|
300
|
+
}
|
|
301
|
+
if (existingTarget) {
|
|
302
|
+
if (upsert) {
|
|
303
|
+
const newTarget = createFn(validPayload);
|
|
304
|
+
const updated = pick(newTarget, ["category", "name", "value", "tagList", "extra", "details"]);
|
|
305
|
+
const { data, error } = await supabase.client
|
|
306
|
+
.from("target")
|
|
307
|
+
.update(updated)
|
|
308
|
+
.eq("id", existingTarget.id)
|
|
309
|
+
.select()
|
|
310
|
+
.single();
|
|
311
|
+
if (error) {
|
|
312
|
+
handleSupabaseError("createTarget", error, "Failed to update existing target.");
|
|
313
|
+
}
|
|
314
|
+
return createResponse.success<T>(data);
|
|
315
|
+
}
|
|
316
|
+
const msg = "[createTarget] Target already exists";
|
|
317
|
+
console.error(msg, checkRedundancyFilterList);
|
|
318
|
+
throw new Error(msg);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Step 3: create target
|
|
323
|
+
const newTarget = createFn(validPayload);
|
|
324
|
+
const { data, error } = await supabase.client.from("target").insert([newTarget]).select().single();
|
|
325
|
+
if (error) {
|
|
326
|
+
handleSupabaseError("createTarget", error, "Failed to create target.");
|
|
327
|
+
}
|
|
328
|
+
return createResponse.success<T>(data);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export function validateWith<P extends object, V extends BaseValidator<P>>(ValidatorClass: new () => V) {
|
|
332
|
+
return <R>(fn: (validPayload: P) => R) => {
|
|
333
|
+
return (payload: P): R => {
|
|
334
|
+
const validPayload = new ValidatorClass().validate(payload);
|
|
335
|
+
return fn(validPayload);
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
}
|