@yimingliao/cms 0.0.8 → 0.0.10
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/dist/base-DbGnfZr6.d.ts +340 -0
- package/dist/chunk-KEQXXUK2.js +207 -0
- package/dist/index.d.ts +3 -903
- package/dist/index.js +1 -1447
- package/dist/server/index.d.ts +566 -0
- package/dist/server/index.js +1242 -0
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,1447 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import argon2, { argon2id } from 'argon2';
|
|
3
|
-
import crypto, { timingSafeEqual } from 'crypto';
|
|
4
|
-
import 'next/headers';
|
|
5
|
-
import { extension, lookup } from 'mime-types';
|
|
6
|
-
import { ulid } from 'ulid';
|
|
7
|
-
|
|
8
|
-
// src/server/infrastructure/jwt/jwt.service.ts
|
|
9
|
-
function createJwtService(config) {
|
|
10
|
-
const { defaultSecret, ...options } = config;
|
|
11
|
-
function sign({
|
|
12
|
-
payload = {},
|
|
13
|
-
secret = defaultSecret,
|
|
14
|
-
expiresIn = 60 * 60
|
|
15
|
-
}) {
|
|
16
|
-
return jwt.sign(payload, secret, {
|
|
17
|
-
algorithm: "HS256",
|
|
18
|
-
expiresIn,
|
|
19
|
-
...options
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function verify({
|
|
23
|
-
token,
|
|
24
|
-
secret = defaultSecret
|
|
25
|
-
}) {
|
|
26
|
-
const payload = jwt.verify(token, secret, {
|
|
27
|
-
algorithms: ["HS256"],
|
|
28
|
-
...options
|
|
29
|
-
});
|
|
30
|
-
if (typeof payload === "string") {
|
|
31
|
-
throw new TypeError("Invalid JWT payload");
|
|
32
|
-
}
|
|
33
|
-
return payload;
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
sign,
|
|
37
|
-
verify
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
var OPTIONS = {
|
|
41
|
-
type: argon2id,
|
|
42
|
-
memoryCost: 19456,
|
|
43
|
-
// ~19MB
|
|
44
|
-
timeCost: 2,
|
|
45
|
-
parallelism: 1
|
|
46
|
-
};
|
|
47
|
-
function createArgon2Service() {
|
|
48
|
-
async function hash(password) {
|
|
49
|
-
return await argon2.hash(password, OPTIONS);
|
|
50
|
-
}
|
|
51
|
-
async function verify(hash2, plain) {
|
|
52
|
-
return await argon2.verify(hash2, plain);
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
hash,
|
|
56
|
-
verify
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function createCryptoService(config) {
|
|
60
|
-
const { defaultSecret } = config;
|
|
61
|
-
const SECRET = crypto.createHash("sha256").update(defaultSecret).digest();
|
|
62
|
-
const ALGORITHM = "aes-256-gcm";
|
|
63
|
-
const IV_LENGTH = 12;
|
|
64
|
-
function generateToken() {
|
|
65
|
-
return crypto.randomBytes(32).toString("base64url");
|
|
66
|
-
}
|
|
67
|
-
function hash(value) {
|
|
68
|
-
return crypto.createHash("sha256").update(value).digest("hex");
|
|
69
|
-
}
|
|
70
|
-
function hashBuffer(value) {
|
|
71
|
-
return crypto.createHash("sha256").update(value).digest();
|
|
72
|
-
}
|
|
73
|
-
function encrypt(data) {
|
|
74
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
75
|
-
const cipher = crypto.createCipheriv(ALGORITHM, SECRET, iv);
|
|
76
|
-
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
77
|
-
const tag = cipher.getAuthTag();
|
|
78
|
-
return [
|
|
79
|
-
iv.toString("hex"),
|
|
80
|
-
encrypted.toString("hex"),
|
|
81
|
-
tag.toString("hex")
|
|
82
|
-
].join(":");
|
|
83
|
-
}
|
|
84
|
-
function decrypt(data) {
|
|
85
|
-
const parts = data.split(":");
|
|
86
|
-
if (parts.length !== 3 || !parts.every(Boolean)) {
|
|
87
|
-
throw new Error("Invalid encrypted payload");
|
|
88
|
-
}
|
|
89
|
-
const [ivHex, encryptedHex, tagHex] = parts;
|
|
90
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
91
|
-
const encrypted = Buffer.from(encryptedHex, "hex");
|
|
92
|
-
const tag = Buffer.from(tagHex, "hex");
|
|
93
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, SECRET, iv);
|
|
94
|
-
decipher.setAuthTag(tag);
|
|
95
|
-
const decrypted = Buffer.concat([
|
|
96
|
-
decipher.update(encrypted),
|
|
97
|
-
decipher.final()
|
|
98
|
-
]);
|
|
99
|
-
return decrypted.toString("utf8");
|
|
100
|
-
}
|
|
101
|
-
function sign({
|
|
102
|
-
value,
|
|
103
|
-
expireSeconds,
|
|
104
|
-
createdAt
|
|
105
|
-
}) {
|
|
106
|
-
const payload = `${value}|${expireSeconds}|${createdAt ?? Date.now()}`;
|
|
107
|
-
const signature = crypto.createHmac("sha256", SECRET).update(payload).digest("hex");
|
|
108
|
-
const signed = `${payload}|${signature}`;
|
|
109
|
-
return { signed, signature };
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
generateToken,
|
|
113
|
-
hash,
|
|
114
|
-
hashBuffer,
|
|
115
|
-
encrypt,
|
|
116
|
-
decrypt,
|
|
117
|
-
sign
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
var DEFAULTS = {
|
|
121
|
-
httpOnly: true,
|
|
122
|
-
secure: process.env["NODE_ENV"] === "production",
|
|
123
|
-
sameSite: "strict",
|
|
124
|
-
path: "/"
|
|
125
|
-
};
|
|
126
|
-
function createCookieService(nextCookies, cryptoService) {
|
|
127
|
-
async function set({
|
|
128
|
-
name,
|
|
129
|
-
value,
|
|
130
|
-
expireSeconds
|
|
131
|
-
}) {
|
|
132
|
-
const cookieStore = await nextCookies();
|
|
133
|
-
cookieStore.set(name, value, { ...DEFAULTS, maxAge: expireSeconds });
|
|
134
|
-
}
|
|
135
|
-
async function setSignedCookie({
|
|
136
|
-
name,
|
|
137
|
-
value,
|
|
138
|
-
expireSeconds
|
|
139
|
-
}) {
|
|
140
|
-
const createdAt = Date.now();
|
|
141
|
-
const { signed } = cryptoService.sign({
|
|
142
|
-
value,
|
|
143
|
-
expireSeconds,
|
|
144
|
-
createdAt
|
|
145
|
-
});
|
|
146
|
-
await set({ name, value: signed, expireSeconds });
|
|
147
|
-
}
|
|
148
|
-
async function get({ name }) {
|
|
149
|
-
const cookieStore = await nextCookies();
|
|
150
|
-
const cookieValue = cookieStore.get(name)?.value;
|
|
151
|
-
return cookieValue;
|
|
152
|
-
}
|
|
153
|
-
async function getSignedCookie({ name }) {
|
|
154
|
-
const signed = await get({ name });
|
|
155
|
-
const value = verifySignedCookie({ signed });
|
|
156
|
-
return value;
|
|
157
|
-
}
|
|
158
|
-
function verifySignedCookie({ signed }) {
|
|
159
|
-
if (!signed) throw new Error("Invalid cookie");
|
|
160
|
-
const parts = signed.split("|");
|
|
161
|
-
if (parts.length !== 4) throw new Error("Invalid cookie");
|
|
162
|
-
const [value, expireSecondsStr, createdAtStr, signature] = parts;
|
|
163
|
-
const expireSeconds = Number.parseInt(expireSecondsStr, 10);
|
|
164
|
-
const createdAt = Number.parseInt(createdAtStr, 10);
|
|
165
|
-
if (Number.isNaN(expireSeconds) || Number.isNaN(createdAt)) {
|
|
166
|
-
throw new TypeError("Invalid cookie");
|
|
167
|
-
}
|
|
168
|
-
const now = Date.now();
|
|
169
|
-
if (now > createdAt + expireSeconds * 1e3) {
|
|
170
|
-
throw new Error("Cookie expired");
|
|
171
|
-
}
|
|
172
|
-
const { signature: generatedSignature } = cryptoService.sign({
|
|
173
|
-
value,
|
|
174
|
-
expireSeconds,
|
|
175
|
-
createdAt
|
|
176
|
-
});
|
|
177
|
-
const sig = Buffer.from(signature);
|
|
178
|
-
const gen = Buffer.from(generatedSignature);
|
|
179
|
-
if (sig.length !== gen.length || !timingSafeEqual(sig, gen)) {
|
|
180
|
-
throw new Error("Invalid cookie signature");
|
|
181
|
-
}
|
|
182
|
-
return value;
|
|
183
|
-
}
|
|
184
|
-
async function deleteCokkie({ name }) {
|
|
185
|
-
const cookieStore = await nextCookies();
|
|
186
|
-
cookieStore.delete(name);
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
set,
|
|
190
|
-
setSignedCookie,
|
|
191
|
-
get,
|
|
192
|
-
getSignedCookie,
|
|
193
|
-
verifySignedCookie,
|
|
194
|
-
delete: deleteCokkie
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// src/server/infrastructure/database/utils/connect.ts
|
|
199
|
-
var ids = (items) => items.map(({ id }) => ({ id }));
|
|
200
|
-
function connectOne(item) {
|
|
201
|
-
return item ? { connect: { id: item.id } } : {};
|
|
202
|
-
}
|
|
203
|
-
function connectMany(items) {
|
|
204
|
-
return { connect: ids(items) };
|
|
205
|
-
}
|
|
206
|
-
function updateOne(item) {
|
|
207
|
-
return item ? { connect: { id: item.id } } : { disconnect: true };
|
|
208
|
-
}
|
|
209
|
-
function updateMany(items) {
|
|
210
|
-
return { set: ids(items) };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// src/server/infrastructure/database/admin/command/create-admin-command-repository.ts
|
|
214
|
-
function createAdminCommandRepository(prisma) {
|
|
215
|
-
async function create({
|
|
216
|
-
// ------------------------------------
|
|
217
|
-
// relations
|
|
218
|
-
// ------------------------------------
|
|
219
|
-
// File
|
|
220
|
-
avatarImage,
|
|
221
|
-
// ------------------------------------
|
|
222
|
-
// translation
|
|
223
|
-
// ------------------------------------
|
|
224
|
-
translations,
|
|
225
|
-
// rest
|
|
226
|
-
...params
|
|
227
|
-
}) {
|
|
228
|
-
const created = await prisma.admin.create({
|
|
229
|
-
data: {
|
|
230
|
-
...params,
|
|
231
|
-
// ------------------------------------------------------------------------
|
|
232
|
-
// relations
|
|
233
|
-
// ------------------------------------------------------------------------
|
|
234
|
-
// File
|
|
235
|
-
avatarImage: connectOne(avatarImage),
|
|
236
|
-
// ------------------------------------------------------------------------
|
|
237
|
-
// translation
|
|
238
|
-
// ------------------------------------------------------------------------
|
|
239
|
-
translations: { create: translations }
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
return created;
|
|
243
|
-
}
|
|
244
|
-
async function update({
|
|
245
|
-
id,
|
|
246
|
-
// ------------------------------------
|
|
247
|
-
// relations
|
|
248
|
-
// ------------------------------------
|
|
249
|
-
// File
|
|
250
|
-
avatarImage,
|
|
251
|
-
// ------------------------------------
|
|
252
|
-
// translation
|
|
253
|
-
// ------------------------------------
|
|
254
|
-
translations,
|
|
255
|
-
// rest
|
|
256
|
-
...params
|
|
257
|
-
}) {
|
|
258
|
-
const updated = await prisma.admin.update({
|
|
259
|
-
where: { id },
|
|
260
|
-
data: {
|
|
261
|
-
...params,
|
|
262
|
-
// ------------------------------------------------------------------------
|
|
263
|
-
// relations
|
|
264
|
-
// ------------------------------------------------------------------------
|
|
265
|
-
// File
|
|
266
|
-
avatarImage: updateOne(avatarImage),
|
|
267
|
-
// ------------------------------------------------------------------------
|
|
268
|
-
// translation
|
|
269
|
-
// ------------------------------------------------------------------------
|
|
270
|
-
translations: {
|
|
271
|
-
upsert: translations.map((t) => ({
|
|
272
|
-
where: { adminId_locale: { adminId: id, locale: t.locale } },
|
|
273
|
-
update: t,
|
|
274
|
-
create: t
|
|
275
|
-
}))
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
return updated;
|
|
280
|
-
}
|
|
281
|
-
async function _delete({ id }) {
|
|
282
|
-
const deleted = await prisma.admin.delete({
|
|
283
|
-
where: { id }
|
|
284
|
-
});
|
|
285
|
-
return deleted;
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
create,
|
|
289
|
-
update,
|
|
290
|
-
delete: _delete
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// src/server/infrastructure/database/constants.ts
|
|
295
|
-
var ORDER_BY = [
|
|
296
|
-
{ updatedAt: "desc" },
|
|
297
|
-
{ createdAt: "desc" },
|
|
298
|
-
{ id: "asc" }
|
|
299
|
-
];
|
|
300
|
-
var ADMIN_ORDER_BY = [
|
|
301
|
-
{ role: "asc" },
|
|
302
|
-
...ORDER_BY
|
|
303
|
-
];
|
|
304
|
-
var POST_ORDER_BY = [
|
|
305
|
-
{ index: "asc" },
|
|
306
|
-
...ORDER_BY
|
|
307
|
-
];
|
|
308
|
-
|
|
309
|
-
// src/domain/resources/admin/props.ts
|
|
310
|
-
var ADMIN_ROLES = {
|
|
311
|
-
SUPER_ADMIN: "SUPER_ADMIN",
|
|
312
|
-
ADMIN: "ADMIN",
|
|
313
|
-
EDITOR: "EDITOR"
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// src/domain/resources/file/props.ts
|
|
317
|
-
var FILE_TYPES = {
|
|
318
|
-
IMAGE: "IMAGE",
|
|
319
|
-
AUDIO: "AUDIO",
|
|
320
|
-
VIDEO: "VIDEO",
|
|
321
|
-
DOCUMENT: "DOCUMENT",
|
|
322
|
-
ARCHIVE: "ARCHIVE",
|
|
323
|
-
OTHER: "OTHER"
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
// src/domain/resources/post/props.ts
|
|
327
|
-
var POST_TYPES = {
|
|
328
|
-
TOPIC: "TOPIC",
|
|
329
|
-
CATEGORY: "CATEGORY",
|
|
330
|
-
POST: "POST",
|
|
331
|
-
TAG: "TAG",
|
|
332
|
-
PAGE: "PAGE"
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
// src/domain/resources/constants.ts
|
|
336
|
-
var ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
|
|
337
|
-
var ROOT_FOLDER_NAME = "ROOT";
|
|
338
|
-
var ROOT_FOLDER = {
|
|
339
|
-
id: ROOT_FOLDER_ID,
|
|
340
|
-
// core
|
|
341
|
-
name: ROOT_FOLDER_NAME,
|
|
342
|
-
key: "",
|
|
343
|
-
// states
|
|
344
|
-
isLocked: true,
|
|
345
|
-
// ---------------------------
|
|
346
|
-
// relations: Folder
|
|
347
|
-
// ---------------------------
|
|
348
|
-
parentFolder: null,
|
|
349
|
-
parentFolderId: null,
|
|
350
|
-
subFolders: [],
|
|
351
|
-
// ---------------------------
|
|
352
|
-
// relations: Folder
|
|
353
|
-
// ---------------------------
|
|
354
|
-
files: [],
|
|
355
|
-
// ---------------------------
|
|
356
|
-
// timestamps
|
|
357
|
-
// ---------------------------
|
|
358
|
-
createdAt: "",
|
|
359
|
-
updatedAt: ""
|
|
360
|
-
};
|
|
361
|
-
var SIMPLE_UPLOAD_FOLDER_NAME = "simple-upload";
|
|
362
|
-
var SIMPLE_UPLOAD_FOLDER_KEY = `${SIMPLE_UPLOAD_FOLDER_NAME}`;
|
|
363
|
-
|
|
364
|
-
// src/server/infrastructure/database/admin/include.ts
|
|
365
|
-
var ADMIN_FULL_INCLUDE = {
|
|
366
|
-
// ---------------------------
|
|
367
|
-
// relations: AdminRefreshToken
|
|
368
|
-
// ---------------------------
|
|
369
|
-
adminRefreshTokens: true,
|
|
370
|
-
// ---------------------------
|
|
371
|
-
// relations: File
|
|
372
|
-
// ---------------------------
|
|
373
|
-
avatarImage: { include: { translations: true } },
|
|
374
|
-
// ---------------------------
|
|
375
|
-
// relations: Post
|
|
376
|
-
// ---------------------------
|
|
377
|
-
posts: true,
|
|
378
|
-
// ---------------------------
|
|
379
|
-
// translation
|
|
380
|
-
// ---------------------------
|
|
381
|
-
translations: true
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
// src/server/infrastructure/database/utils/create-search.ts
|
|
385
|
-
function buildContainsOr(fields, value) {
|
|
386
|
-
return fields.map((field) => ({
|
|
387
|
-
[field]: { contains: value, mode: "insensitive" }
|
|
388
|
-
}));
|
|
389
|
-
}
|
|
390
|
-
function buildTranslationSearch(locale, fields, value) {
|
|
391
|
-
return {
|
|
392
|
-
translations: {
|
|
393
|
-
some: { locale, OR: buildContainsOr(fields, value) }
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
function createSearch({
|
|
398
|
-
locale,
|
|
399
|
-
searchString,
|
|
400
|
-
rootFields = [],
|
|
401
|
-
translationFields = []
|
|
402
|
-
}) {
|
|
403
|
-
if (!searchString) return {};
|
|
404
|
-
const conditions = [];
|
|
405
|
-
if (rootFields.length > 0) {
|
|
406
|
-
conditions.push(...buildContainsOr(rootFields, searchString));
|
|
407
|
-
}
|
|
408
|
-
if (translationFields.length > 0 && locale) {
|
|
409
|
-
conditions.push(
|
|
410
|
-
buildTranslationSearch(locale, translationFields, searchString)
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
return conditions.length > 0 ? { OR: conditions } : {};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/server/infrastructure/database/utils/create-pagination.ts
|
|
417
|
-
function createPagination(page, pageSize) {
|
|
418
|
-
if (!page || !pageSize) return {};
|
|
419
|
-
return {
|
|
420
|
-
skip: (page - 1) * pageSize,
|
|
421
|
-
take: pageSize
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/server/infrastructure/database/admin/query/create-admin-query-repository.ts
|
|
426
|
-
var OMIT_PASSWORD = { omit: { passwordHash: true } };
|
|
427
|
-
function buildWhere(params) {
|
|
428
|
-
if (params.id) return { id: params.id };
|
|
429
|
-
if (params.email) return { email: params.email };
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
function createAdminQueryRepository(prisma) {
|
|
433
|
-
async function findListCards({
|
|
434
|
-
locale,
|
|
435
|
-
// search
|
|
436
|
-
searchString,
|
|
437
|
-
role,
|
|
438
|
-
adminIds,
|
|
439
|
-
// pagination
|
|
440
|
-
page,
|
|
441
|
-
pageSize
|
|
442
|
-
}) {
|
|
443
|
-
const where = {
|
|
444
|
-
// search
|
|
445
|
-
...createSearch({
|
|
446
|
-
...searchString !== void 0 ? { searchString } : {},
|
|
447
|
-
locale,
|
|
448
|
-
translationFields: ["name"]
|
|
449
|
-
}),
|
|
450
|
-
// role
|
|
451
|
-
...role !== ADMIN_ROLES.SUPER_ADMIN ? { role: { notIn: [ADMIN_ROLES.SUPER_ADMIN] } } : {},
|
|
452
|
-
// Specified ids
|
|
453
|
-
...adminIds?.length ? { id: { in: adminIds } } : {}
|
|
454
|
-
};
|
|
455
|
-
const [items, total] = await prisma.$transaction([
|
|
456
|
-
prisma.admin.findMany({
|
|
457
|
-
where,
|
|
458
|
-
orderBy: ADMIN_ORDER_BY,
|
|
459
|
-
include: ADMIN_FULL_INCLUDE,
|
|
460
|
-
...createPagination(page, pageSize),
|
|
461
|
-
...OMIT_PASSWORD
|
|
462
|
-
}),
|
|
463
|
-
prisma.admin.count({ where })
|
|
464
|
-
]);
|
|
465
|
-
return { items, total };
|
|
466
|
-
}
|
|
467
|
-
async function find(params) {
|
|
468
|
-
const where = buildWhere(params);
|
|
469
|
-
if (!where) return null;
|
|
470
|
-
return prisma.admin.findUnique({
|
|
471
|
-
where,
|
|
472
|
-
...OMIT_PASSWORD
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
async function findFull(params) {
|
|
476
|
-
const where = buildWhere(params);
|
|
477
|
-
if (!where) return null;
|
|
478
|
-
return await prisma.admin.findUnique({
|
|
479
|
-
where,
|
|
480
|
-
include: ADMIN_FULL_INCLUDE,
|
|
481
|
-
...OMIT_PASSWORD
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
async function findWithPasswordHash(params) {
|
|
485
|
-
const where = buildWhere(params);
|
|
486
|
-
if (!where) return null;
|
|
487
|
-
return prisma.admin.findUnique({
|
|
488
|
-
where
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
return {
|
|
492
|
-
findListCards,
|
|
493
|
-
find,
|
|
494
|
-
findFull,
|
|
495
|
-
findWithPasswordHash
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// src/server/infrastructure/database/admin-refresh-token/command/create-admin-refresh-token-command-repository.ts
|
|
500
|
-
function createAdminRefreshTokenCommandRepository(prisma) {
|
|
501
|
-
async function create({
|
|
502
|
-
adminId,
|
|
503
|
-
...params
|
|
504
|
-
}) {
|
|
505
|
-
const created = await prisma.adminRefreshToken.create({
|
|
506
|
-
data: {
|
|
507
|
-
...params,
|
|
508
|
-
admin: { connect: { id: adminId } },
|
|
509
|
-
deviceInfo: params.deviceInfo
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
return created;
|
|
513
|
-
}
|
|
514
|
-
async function _delete({ id, tokenHash }) {
|
|
515
|
-
const where = id ? { id } : tokenHash ? { tokenHash } : void 0;
|
|
516
|
-
if (where) {
|
|
517
|
-
await prisma.adminRefreshToken.delete({
|
|
518
|
-
where
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function deleteManyByExpired() {
|
|
523
|
-
const { count } = await prisma.adminRefreshToken.deleteMany({
|
|
524
|
-
where: { expiresAt: { lt: /* @__PURE__ */ new Date() } }
|
|
525
|
-
});
|
|
526
|
-
return count;
|
|
527
|
-
}
|
|
528
|
-
return {
|
|
529
|
-
create,
|
|
530
|
-
delete: _delete,
|
|
531
|
-
deleteManyByExpired
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// src/server/infrastructure/database/admin-refresh-token/query/create-admin-refresh-token-query-repository.ts
|
|
536
|
-
function createAdminRefreshTokenQueryRepository(prisma) {
|
|
537
|
-
async function findManyByAdminId({
|
|
538
|
-
adminId
|
|
539
|
-
}) {
|
|
540
|
-
return await prisma.adminRefreshToken.findMany({
|
|
541
|
-
where: { adminId },
|
|
542
|
-
orderBy: [{ createdAt: "desc" }, { id: "asc" }]
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
async function findByToken({
|
|
546
|
-
tokenHash
|
|
547
|
-
}) {
|
|
548
|
-
return await prisma.adminRefreshToken.findUnique({
|
|
549
|
-
where: { tokenHash }
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
return {
|
|
553
|
-
findManyByAdminId,
|
|
554
|
-
findByToken
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// src/server/infrastructure/database/file/include.ts
|
|
559
|
-
var FILE_FULL_INCLUDE = {
|
|
560
|
-
// ---------------------------
|
|
561
|
-
// relations: Folder
|
|
562
|
-
// ---------------------------
|
|
563
|
-
folder: true,
|
|
564
|
-
// ---------------------------
|
|
565
|
-
// relations: Admin
|
|
566
|
-
// ---------------------------
|
|
567
|
-
adminAsAvatarImage: true,
|
|
568
|
-
// ---------------------------
|
|
569
|
-
// relations: Post
|
|
570
|
-
// ---------------------------
|
|
571
|
-
postsAsCoverImage: true,
|
|
572
|
-
postsAsContentImage: true,
|
|
573
|
-
// ---------------------------
|
|
574
|
-
// --- custom fields
|
|
575
|
-
// ---------------------------
|
|
576
|
-
postsAsImages1: true,
|
|
577
|
-
postsAsImages2: true,
|
|
578
|
-
postsAsImage1: true,
|
|
579
|
-
postsAsImage2: true,
|
|
580
|
-
postsAsImage3: true,
|
|
581
|
-
postsAsImage4: true,
|
|
582
|
-
// ---------------------------
|
|
583
|
-
// translation
|
|
584
|
-
// ---------------------------
|
|
585
|
-
translations: true
|
|
586
|
-
};
|
|
587
|
-
var mimeToExtension = (mimeType) => {
|
|
588
|
-
if (!mimeType) return "unknown";
|
|
589
|
-
return (extension(mimeType) || "unknown").toLowerCase();
|
|
590
|
-
};
|
|
591
|
-
var ARCHIVE_MIME = /* @__PURE__ */ new Set([
|
|
592
|
-
"application/zip",
|
|
593
|
-
"application/x-rar-compressed",
|
|
594
|
-
"application/x-7z-compressed",
|
|
595
|
-
"application/gzip",
|
|
596
|
-
"application/x-tar"
|
|
597
|
-
]);
|
|
598
|
-
var classifyFileType = (mimeType, extension2) => {
|
|
599
|
-
if (!mimeType && extension2) {
|
|
600
|
-
mimeType = lookup(extension2) || void 0;
|
|
601
|
-
}
|
|
602
|
-
if (!mimeType) return FILE_TYPES.OTHER;
|
|
603
|
-
if (mimeType.startsWith("image/")) return FILE_TYPES.IMAGE;
|
|
604
|
-
if (mimeType.startsWith("video/")) return FILE_TYPES.VIDEO;
|
|
605
|
-
if (mimeType.startsWith("audio/")) return FILE_TYPES.AUDIO;
|
|
606
|
-
if (ARCHIVE_MIME.has(mimeType)) return FILE_TYPES.ARCHIVE;
|
|
607
|
-
if (mimeType.startsWith("text/") || mimeType === "application/pdf" || mimeType === "application/json" || mimeType === "application/xml") {
|
|
608
|
-
return FILE_TYPES.DOCUMENT;
|
|
609
|
-
}
|
|
610
|
-
if (mimeType.startsWith("application/")) return FILE_TYPES.DOCUMENT;
|
|
611
|
-
return FILE_TYPES.OTHER;
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
// src/shared/blob-file/get-media-info/get-audio-info.ts
|
|
615
|
-
function getAudioInfo(file) {
|
|
616
|
-
return new Promise((resolve, reject) => {
|
|
617
|
-
const audio = document.createElement("audio");
|
|
618
|
-
const objectUrl = URL.createObjectURL(file);
|
|
619
|
-
audio.preload = "metadata";
|
|
620
|
-
const cleanup = () => {
|
|
621
|
-
audio.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
622
|
-
audio.removeEventListener("error", onError);
|
|
623
|
-
URL.revokeObjectURL(objectUrl);
|
|
624
|
-
};
|
|
625
|
-
const onLoadedMetadata = () => {
|
|
626
|
-
const duration = audio.duration;
|
|
627
|
-
cleanup();
|
|
628
|
-
resolve({ duration });
|
|
629
|
-
};
|
|
630
|
-
const onError = () => {
|
|
631
|
-
cleanup();
|
|
632
|
-
reject(new Error("Failed to load audio metadata."));
|
|
633
|
-
};
|
|
634
|
-
audio.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
|
|
635
|
-
audio.addEventListener("error", onError, { once: true });
|
|
636
|
-
audio.src = objectUrl;
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// src/shared/blob-file/get-media-info/get-image-info.ts
|
|
641
|
-
function getImageInfo(file) {
|
|
642
|
-
return new Promise((resolve, reject) => {
|
|
643
|
-
const img = new Image();
|
|
644
|
-
const objectUrl = URL.createObjectURL(file);
|
|
645
|
-
const cleanup = () => {
|
|
646
|
-
img.removeEventListener("load", onLoad);
|
|
647
|
-
img.removeEventListener("error", onError);
|
|
648
|
-
URL.revokeObjectURL(objectUrl);
|
|
649
|
-
};
|
|
650
|
-
const onLoad = () => {
|
|
651
|
-
const width = img.naturalWidth;
|
|
652
|
-
const height = img.naturalHeight;
|
|
653
|
-
cleanup();
|
|
654
|
-
resolve({ width, height });
|
|
655
|
-
};
|
|
656
|
-
const onError = () => {
|
|
657
|
-
cleanup();
|
|
658
|
-
reject(new Error("Failed to load image for size detection."));
|
|
659
|
-
};
|
|
660
|
-
img.addEventListener("load", onLoad, { once: true });
|
|
661
|
-
img.addEventListener("error", onError, { once: true });
|
|
662
|
-
img.src = objectUrl;
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// src/shared/blob-file/get-media-info/get-video-info.ts
|
|
667
|
-
function getVideoInfo(file) {
|
|
668
|
-
return new Promise((resolve, reject) => {
|
|
669
|
-
const video = document.createElement("video");
|
|
670
|
-
const objectUrl = URL.createObjectURL(file);
|
|
671
|
-
video.preload = "metadata";
|
|
672
|
-
const cleanup = () => {
|
|
673
|
-
video.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
674
|
-
video.removeEventListener("error", onError);
|
|
675
|
-
video.src = "";
|
|
676
|
-
URL.revokeObjectURL(objectUrl);
|
|
677
|
-
};
|
|
678
|
-
const onLoadedMetadata = () => {
|
|
679
|
-
const info = {
|
|
680
|
-
width: video.videoWidth,
|
|
681
|
-
height: video.videoHeight,
|
|
682
|
-
duration: video.duration
|
|
683
|
-
};
|
|
684
|
-
cleanup();
|
|
685
|
-
resolve(info);
|
|
686
|
-
};
|
|
687
|
-
const onError = () => {
|
|
688
|
-
cleanup();
|
|
689
|
-
reject(new Error("Failed to load video metadata."));
|
|
690
|
-
};
|
|
691
|
-
video.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
|
|
692
|
-
video.addEventListener("error", onError, { once: true });
|
|
693
|
-
video.src = objectUrl;
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// src/shared/blob-file/get-media-info/get-media-info.ts
|
|
698
|
-
var IMAGE_EXT = /\.(jpe?g|png|gif|webp|bmp|avif)$/i;
|
|
699
|
-
var VIDEO_EXT = /\.(mp4|webm|mov|mkv)$/i;
|
|
700
|
-
var AUDIO_EXT = /\.(mp3|wav|ogg|m4a)$/i;
|
|
701
|
-
var getMediaInfo = async (file) => {
|
|
702
|
-
const fallback = { width: null, height: null, duration: null };
|
|
703
|
-
const mime = file.type || "";
|
|
704
|
-
const name = file instanceof File ? file.name.toLowerCase() : "";
|
|
705
|
-
try {
|
|
706
|
-
if (mime.startsWith("image/") || IMAGE_EXT.test(name)) {
|
|
707
|
-
const { width, height } = await getImageInfo(file);
|
|
708
|
-
return { width, height, duration: null };
|
|
709
|
-
}
|
|
710
|
-
if (mime.startsWith("video/") || VIDEO_EXT.test(name)) {
|
|
711
|
-
const { width, height, duration } = await getVideoInfo(file);
|
|
712
|
-
return { width, height, duration };
|
|
713
|
-
}
|
|
714
|
-
if (mime.startsWith("audio/") || AUDIO_EXT.test(name)) {
|
|
715
|
-
const { duration } = await getAudioInfo(file);
|
|
716
|
-
return { width: null, height: null, duration };
|
|
717
|
-
}
|
|
718
|
-
return fallback;
|
|
719
|
-
} catch {
|
|
720
|
-
return fallback;
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
|
|
724
|
-
// src/shared/blob-file/format-file-size.ts
|
|
725
|
-
var formatFileSize = (size, decimals = 2) => {
|
|
726
|
-
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
727
|
-
let value = size;
|
|
728
|
-
let unitIndex = 0;
|
|
729
|
-
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
730
|
-
value /= 1024;
|
|
731
|
-
unitIndex++;
|
|
732
|
-
}
|
|
733
|
-
const display = unitIndex === 0 ? value.toString() : value.toFixed(decimals);
|
|
734
|
-
return `${display} ${units[unitIndex]}`;
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
// src/server/infrastructure/database/file/command/create-file-command-repository.ts
|
|
738
|
-
function createFileCommandRepository(prisma) {
|
|
739
|
-
async function create({
|
|
740
|
-
// meta
|
|
741
|
-
fileMeta,
|
|
742
|
-
// ------------------------------------
|
|
743
|
-
// relations
|
|
744
|
-
// ------------------------------------
|
|
745
|
-
// Folder
|
|
746
|
-
folder,
|
|
747
|
-
// ------------------------------------
|
|
748
|
-
// translation
|
|
749
|
-
// ------------------------------------
|
|
750
|
-
translations,
|
|
751
|
-
// rest
|
|
752
|
-
...params
|
|
753
|
-
}) {
|
|
754
|
-
const extension2 = mimeToExtension(fileMeta.type);
|
|
755
|
-
const created = await prisma.file.create({
|
|
756
|
-
data: {
|
|
757
|
-
...params,
|
|
758
|
-
// derived
|
|
759
|
-
originalName: fileMeta.name || "unknown",
|
|
760
|
-
size: fileMeta.size ?? 0,
|
|
761
|
-
extension: extension2,
|
|
762
|
-
mimeType: fileMeta.type || "unknown",
|
|
763
|
-
type: classifyFileType(fileMeta.type, extension2),
|
|
764
|
-
// ------------------------------------------------------------------------
|
|
765
|
-
// relations
|
|
766
|
-
// ------------------------------------------------------------------------
|
|
767
|
-
// Folder
|
|
768
|
-
folder: connectOne(folder),
|
|
769
|
-
// ------------------------------------------------------------------------
|
|
770
|
-
// translation
|
|
771
|
-
// ------------------------------------------------------------------------
|
|
772
|
-
translations: {
|
|
773
|
-
create: translations.map((t) => ({
|
|
774
|
-
...t,
|
|
775
|
-
name: t.name ?? fileMeta.name ?? "unknown"
|
|
776
|
-
}))
|
|
777
|
-
}
|
|
778
|
-
},
|
|
779
|
-
include: FILE_FULL_INCLUDE
|
|
780
|
-
});
|
|
781
|
-
return created;
|
|
782
|
-
}
|
|
783
|
-
async function update({
|
|
784
|
-
file,
|
|
785
|
-
// Original file (File)
|
|
786
|
-
id,
|
|
787
|
-
// meta
|
|
788
|
-
fileMeta,
|
|
789
|
-
// ------------------------------------
|
|
790
|
-
// relations
|
|
791
|
-
// ------------------------------------
|
|
792
|
-
// Folder
|
|
793
|
-
folder,
|
|
794
|
-
// ------------------------------------
|
|
795
|
-
// translation
|
|
796
|
-
// ------------------------------------
|
|
797
|
-
translations,
|
|
798
|
-
// rest
|
|
799
|
-
...params
|
|
800
|
-
}) {
|
|
801
|
-
const extension2 = mimeToExtension(fileMeta.type);
|
|
802
|
-
const updated = await prisma.file.update({
|
|
803
|
-
where: { id: file.id },
|
|
804
|
-
data: {
|
|
805
|
-
...params,
|
|
806
|
-
// derived
|
|
807
|
-
size: fileMeta.size ?? file.size,
|
|
808
|
-
extension: fileMeta ? extension2 : file.extension,
|
|
809
|
-
mimeType: fileMeta.type || file.mimeType,
|
|
810
|
-
type: fileMeta.type ? classifyFileType(fileMeta.type, extension2) : file.type,
|
|
811
|
-
// ------------------------------------------------------------------------
|
|
812
|
-
// relations
|
|
813
|
-
// ------------------------------------------------------------------------
|
|
814
|
-
// Folder
|
|
815
|
-
folder: updateOne(folder),
|
|
816
|
-
// ------------------------------------------------------------------------
|
|
817
|
-
// translation
|
|
818
|
-
// ------------------------------------------------------------------------
|
|
819
|
-
translations: {
|
|
820
|
-
upsert: translations.map((t) => ({
|
|
821
|
-
where: { fileId_locale: { fileId: id, locale: t.locale } },
|
|
822
|
-
update: t,
|
|
823
|
-
create: t
|
|
824
|
-
}))
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
return updated;
|
|
829
|
-
}
|
|
830
|
-
async function softDelete({ id }) {
|
|
831
|
-
const updated = await prisma.file.update({
|
|
832
|
-
where: { id, isLocked: false },
|
|
833
|
-
data: { deletedAt: /* @__PURE__ */ new Date() }
|
|
834
|
-
});
|
|
835
|
-
return updated;
|
|
836
|
-
}
|
|
837
|
-
async function softDeleteMany({ ids: ids2 }) {
|
|
838
|
-
const { count } = await prisma.file.updateMany({
|
|
839
|
-
where: { id: { in: ids2 }, isLocked: false },
|
|
840
|
-
data: { deletedAt: /* @__PURE__ */ new Date() }
|
|
841
|
-
});
|
|
842
|
-
return count;
|
|
843
|
-
}
|
|
844
|
-
async function restoreMany({ ids: ids2 }) {
|
|
845
|
-
const { count } = await prisma.file.updateMany({
|
|
846
|
-
where: { id: { in: ids2 } },
|
|
847
|
-
data: { deletedAt: null }
|
|
848
|
-
});
|
|
849
|
-
return count;
|
|
850
|
-
}
|
|
851
|
-
async function _delete({ id }) {
|
|
852
|
-
const deleted = await prisma.file.delete({
|
|
853
|
-
where: { id }
|
|
854
|
-
});
|
|
855
|
-
return deleted;
|
|
856
|
-
}
|
|
857
|
-
return {
|
|
858
|
-
create,
|
|
859
|
-
update,
|
|
860
|
-
softDelete,
|
|
861
|
-
softDeleteMany,
|
|
862
|
-
restoreMany,
|
|
863
|
-
delete: _delete
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// src/server/infrastructure/database/utils/build-file-usage.ts
|
|
868
|
-
function buildFileUsage(isLocked) {
|
|
869
|
-
const relations = [
|
|
870
|
-
"adminAsAvatarImage",
|
|
871
|
-
"postsAsCoverImage",
|
|
872
|
-
"postsAsContentImage",
|
|
873
|
-
"postsAsImages1",
|
|
874
|
-
"postsAsImages2",
|
|
875
|
-
"postsAsImage1",
|
|
876
|
-
"postsAsImage2",
|
|
877
|
-
"postsAsImage3",
|
|
878
|
-
"postsAsImage4"
|
|
879
|
-
];
|
|
880
|
-
if (isLocked === void 0 || isLocked === null) return {};
|
|
881
|
-
const condition = relations.map((r) => ({
|
|
882
|
-
[r]: { [isLocked ? "some" : "none"]: {} }
|
|
883
|
-
}));
|
|
884
|
-
return isLocked ? { OR: condition } : { AND: condition };
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// src/server/infrastructure/database/file/query/create-file-query-repository.ts
|
|
888
|
-
function createFileQueryRepository(prisma) {
|
|
889
|
-
async function findListCards({
|
|
890
|
-
locale,
|
|
891
|
-
// pagination
|
|
892
|
-
page,
|
|
893
|
-
pageSize,
|
|
894
|
-
// search
|
|
895
|
-
searchString,
|
|
896
|
-
type,
|
|
897
|
-
folderId,
|
|
898
|
-
isLocked,
|
|
899
|
-
isDeleted = false,
|
|
900
|
-
fileIds
|
|
901
|
-
}) {
|
|
902
|
-
const where = {
|
|
903
|
-
// search
|
|
904
|
-
...createSearch({
|
|
905
|
-
...searchString !== void 0 ? { searchString } : {},
|
|
906
|
-
locale,
|
|
907
|
-
translationFields: ["name"]
|
|
908
|
-
}),
|
|
909
|
-
// type
|
|
910
|
-
...type ? { type } : {},
|
|
911
|
-
// state: isLocked
|
|
912
|
-
...buildFileUsage(isLocked),
|
|
913
|
-
// state: deleteAt
|
|
914
|
-
deletedAt: isDeleted ? { not: null } : null,
|
|
915
|
-
// Find deleted files in trash page
|
|
916
|
-
// relations: Folder
|
|
917
|
-
...folderId ? { folderId: folderId === ROOT_FOLDER_ID ? null : folderId } : {},
|
|
918
|
-
// Specified ids
|
|
919
|
-
...fileIds ? { id: { in: fileIds } } : {}
|
|
920
|
-
};
|
|
921
|
-
const [items, total] = await prisma.$transaction([
|
|
922
|
-
prisma.file.findMany({
|
|
923
|
-
where,
|
|
924
|
-
orderBy: [...ORDER_BY],
|
|
925
|
-
include: FILE_FULL_INCLUDE,
|
|
926
|
-
...createPagination(page, pageSize)
|
|
927
|
-
}),
|
|
928
|
-
prisma.file.count({ where })
|
|
929
|
-
]);
|
|
930
|
-
return { items, total };
|
|
931
|
-
}
|
|
932
|
-
async function findManyByIds({
|
|
933
|
-
ids: ids2
|
|
934
|
-
}) {
|
|
935
|
-
return await prisma.file.findMany({
|
|
936
|
-
where: { id: { in: ids2 } },
|
|
937
|
-
include: FILE_FULL_INCLUDE
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
async function findFull({ id }) {
|
|
941
|
-
return await prisma.file.findUnique({
|
|
942
|
-
where: { id },
|
|
943
|
-
include: FILE_FULL_INCLUDE
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
return {
|
|
947
|
-
findListCards,
|
|
948
|
-
findManyByIds,
|
|
949
|
-
findFull
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// src/server/infrastructure/database/folder/command/create-folder-command-repository.ts
|
|
954
|
-
function createFolderCommandRepository(prisma) {
|
|
955
|
-
async function create({
|
|
956
|
-
// ------------------------------------
|
|
957
|
-
// relations
|
|
958
|
-
// ------------------------------------
|
|
959
|
-
// Folder
|
|
960
|
-
parentFolder,
|
|
961
|
-
// rest
|
|
962
|
-
...params
|
|
963
|
-
}) {
|
|
964
|
-
const created = await prisma.folder.create({
|
|
965
|
-
data: {
|
|
966
|
-
...params,
|
|
967
|
-
// ------------------------------------------------------------------------
|
|
968
|
-
// relations
|
|
969
|
-
// ------------------------------------------------------------------------
|
|
970
|
-
// Folder
|
|
971
|
-
parentFolder: connectOne(parentFolder)
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
return created;
|
|
975
|
-
}
|
|
976
|
-
async function update({
|
|
977
|
-
id,
|
|
978
|
-
// ------------------------------------
|
|
979
|
-
// relations
|
|
980
|
-
// ------------------------------------
|
|
981
|
-
// Folder
|
|
982
|
-
parentFolder,
|
|
983
|
-
// rest
|
|
984
|
-
...params
|
|
985
|
-
}) {
|
|
986
|
-
const updated = await prisma.folder.update({
|
|
987
|
-
where: { id },
|
|
988
|
-
data: {
|
|
989
|
-
...params,
|
|
990
|
-
// ------------------------------------------------------------------------
|
|
991
|
-
// relations
|
|
992
|
-
// ------------------------------------------------------------------------
|
|
993
|
-
// Folder
|
|
994
|
-
parentFolder: updateOne(parentFolder)
|
|
995
|
-
}
|
|
996
|
-
});
|
|
997
|
-
return updated;
|
|
998
|
-
}
|
|
999
|
-
async function _delete({ id }) {
|
|
1000
|
-
const deleted = await prisma.folder.delete({
|
|
1001
|
-
where: { id }
|
|
1002
|
-
});
|
|
1003
|
-
return deleted;
|
|
1004
|
-
}
|
|
1005
|
-
return {
|
|
1006
|
-
create,
|
|
1007
|
-
update,
|
|
1008
|
-
delete: _delete
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// src/server/infrastructure/database/folder/include.ts
|
|
1013
|
-
var FOLDER_FULL_INCLUDE = {
|
|
1014
|
-
// ---------------------------
|
|
1015
|
-
// relations: Folder
|
|
1016
|
-
// ---------------------------
|
|
1017
|
-
parentFolder: { include: { subFolders: true, files: true } },
|
|
1018
|
-
subFolders: true,
|
|
1019
|
-
// ---------------------------
|
|
1020
|
-
// relations: File
|
|
1021
|
-
// ---------------------------
|
|
1022
|
-
files: true
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
// src/server/infrastructure/database/folder/query/create-folder-query-repository.ts
|
|
1026
|
-
function buildWhere2(params) {
|
|
1027
|
-
if (params.id) return { id: params.id };
|
|
1028
|
-
if (params.key) return { key: params.key };
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
function createFolderQueryRepository(prisma) {
|
|
1032
|
-
async function findListCards({
|
|
1033
|
-
// search
|
|
1034
|
-
searchString,
|
|
1035
|
-
parentFolderId,
|
|
1036
|
-
folderIds,
|
|
1037
|
-
// pagination
|
|
1038
|
-
page,
|
|
1039
|
-
pageSize
|
|
1040
|
-
}) {
|
|
1041
|
-
const where = {
|
|
1042
|
-
// search
|
|
1043
|
-
...searchString ? { name: { contains: searchString, mode: "insensitive" } } : {},
|
|
1044
|
-
// relations: Folder
|
|
1045
|
-
...parentFolderId ? parentFolderId === ROOT_FOLDER_ID ? { parentFolderId: null } : { parentFolderId } : {},
|
|
1046
|
-
// Specified ids
|
|
1047
|
-
...folderIds ? { id: { in: folderIds } } : {}
|
|
1048
|
-
};
|
|
1049
|
-
const [items, total] = await prisma.$transaction([
|
|
1050
|
-
prisma.folder.findMany({
|
|
1051
|
-
where,
|
|
1052
|
-
orderBy: [...ORDER_BY],
|
|
1053
|
-
include: FOLDER_FULL_INCLUDE,
|
|
1054
|
-
...createPagination(page, pageSize)
|
|
1055
|
-
}),
|
|
1056
|
-
prisma.folder.count({ where })
|
|
1057
|
-
]);
|
|
1058
|
-
return { items, total };
|
|
1059
|
-
}
|
|
1060
|
-
async function findFull(params) {
|
|
1061
|
-
const where = buildWhere2(params);
|
|
1062
|
-
if (!where) return null;
|
|
1063
|
-
return await prisma.folder.findUnique({
|
|
1064
|
-
where,
|
|
1065
|
-
include: FOLDER_FULL_INCLUDE
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
return {
|
|
1069
|
-
findListCards,
|
|
1070
|
-
findFull
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
function createPostCommandRepository(prisma) {
|
|
1074
|
-
async function create({
|
|
1075
|
-
slug,
|
|
1076
|
-
// ------------------------------------
|
|
1077
|
-
// relations
|
|
1078
|
-
// ------------------------------------
|
|
1079
|
-
// Admin
|
|
1080
|
-
author,
|
|
1081
|
-
// Post
|
|
1082
|
-
topicId,
|
|
1083
|
-
parents,
|
|
1084
|
-
tags,
|
|
1085
|
-
relatedPosts,
|
|
1086
|
-
// File
|
|
1087
|
-
coverImage,
|
|
1088
|
-
contentImageIds,
|
|
1089
|
-
// ------------------------------------
|
|
1090
|
-
// --- custom fields
|
|
1091
|
-
// ------------------------------------
|
|
1092
|
-
// multi images
|
|
1093
|
-
images1,
|
|
1094
|
-
images2,
|
|
1095
|
-
// single images
|
|
1096
|
-
image1,
|
|
1097
|
-
image2,
|
|
1098
|
-
image3,
|
|
1099
|
-
image4,
|
|
1100
|
-
// ------------------------------------
|
|
1101
|
-
// translation
|
|
1102
|
-
// ------------------------------------
|
|
1103
|
-
translations,
|
|
1104
|
-
// rest
|
|
1105
|
-
...params
|
|
1106
|
-
}) {
|
|
1107
|
-
const id = ulid();
|
|
1108
|
-
const created = await prisma.post.create({
|
|
1109
|
-
data: {
|
|
1110
|
-
...params,
|
|
1111
|
-
id,
|
|
1112
|
-
// ------------------------------------------------------------------------
|
|
1113
|
-
// states
|
|
1114
|
-
// ------------------------------------------------------------------------
|
|
1115
|
-
slug: slug?.trim() || id,
|
|
1116
|
-
// Use id when slug is null or empty string
|
|
1117
|
-
// ------------------------------------------------------------------------
|
|
1118
|
-
// relations
|
|
1119
|
-
// ------------------------------------------------------------------------
|
|
1120
|
-
// Admin
|
|
1121
|
-
author: connectOne(author),
|
|
1122
|
-
// Post
|
|
1123
|
-
topic: connectOne(topicId ? { id: topicId } : null),
|
|
1124
|
-
parents: connectMany(parents),
|
|
1125
|
-
tags: connectMany(tags),
|
|
1126
|
-
relatedPosts: connectMany(relatedPosts),
|
|
1127
|
-
// File
|
|
1128
|
-
coverImage: connectOne(coverImage),
|
|
1129
|
-
contentImages: connectMany(contentImageIds.map((id2) => ({ id: id2 }))),
|
|
1130
|
-
// ------------------------------------------------------------------------
|
|
1131
|
-
// --- custom fields
|
|
1132
|
-
// ------------------------------------------------------------------------
|
|
1133
|
-
// multi images
|
|
1134
|
-
images1: connectMany(images1),
|
|
1135
|
-
images2: connectMany(images2),
|
|
1136
|
-
// single images
|
|
1137
|
-
image1: connectOne(image1),
|
|
1138
|
-
image2: connectOne(image2),
|
|
1139
|
-
image3: connectOne(image3),
|
|
1140
|
-
image4: connectOne(image4),
|
|
1141
|
-
// ------------------------------------------------------------------------
|
|
1142
|
-
// translation
|
|
1143
|
-
// ------------------------------------------------------------------------
|
|
1144
|
-
translations: {
|
|
1145
|
-
create: translations.map((t) => ({
|
|
1146
|
-
...t,
|
|
1147
|
-
externalLinks: t.externalLinks,
|
|
1148
|
-
faq: t.faq,
|
|
1149
|
-
toc: t.toc
|
|
1150
|
-
}))
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
return created;
|
|
1155
|
-
}
|
|
1156
|
-
async function update({
|
|
1157
|
-
id,
|
|
1158
|
-
slug,
|
|
1159
|
-
// ------------------------------------
|
|
1160
|
-
// relations
|
|
1161
|
-
// ------------------------------------
|
|
1162
|
-
// Admin
|
|
1163
|
-
author,
|
|
1164
|
-
// Post
|
|
1165
|
-
topicId,
|
|
1166
|
-
parents,
|
|
1167
|
-
tags,
|
|
1168
|
-
relatedPosts,
|
|
1169
|
-
// File
|
|
1170
|
-
coverImage,
|
|
1171
|
-
contentImageIds,
|
|
1172
|
-
// ------------------------------------
|
|
1173
|
-
// --- custom fields
|
|
1174
|
-
// ------------------------------------
|
|
1175
|
-
// multi images
|
|
1176
|
-
images1,
|
|
1177
|
-
images2,
|
|
1178
|
-
// single images
|
|
1179
|
-
image1,
|
|
1180
|
-
image2,
|
|
1181
|
-
image3,
|
|
1182
|
-
image4,
|
|
1183
|
-
// ------------------------------------
|
|
1184
|
-
// translation
|
|
1185
|
-
// ------------------------------------
|
|
1186
|
-
translations,
|
|
1187
|
-
// rest
|
|
1188
|
-
...params
|
|
1189
|
-
}) {
|
|
1190
|
-
const updated = await prisma.post.update({
|
|
1191
|
-
where: { id },
|
|
1192
|
-
data: {
|
|
1193
|
-
...params,
|
|
1194
|
-
// ------------------------------------------------------------------------
|
|
1195
|
-
// states
|
|
1196
|
-
// ------------------------------------------------------------------------
|
|
1197
|
-
slug: slug?.trim() || id,
|
|
1198
|
-
// Use id when slug is null or empty string
|
|
1199
|
-
// ------------------------------------------------------------------------
|
|
1200
|
-
// relations
|
|
1201
|
-
// ------------------------------------------------------------------------
|
|
1202
|
-
// Admin
|
|
1203
|
-
author: updateOne(author),
|
|
1204
|
-
// Post
|
|
1205
|
-
topic: updateOne(topicId ? { id: topicId } : null),
|
|
1206
|
-
parents: updateMany(parents),
|
|
1207
|
-
tags: updateMany(tags),
|
|
1208
|
-
relatedPosts: updateMany(relatedPosts),
|
|
1209
|
-
// File
|
|
1210
|
-
coverImage: updateOne(coverImage),
|
|
1211
|
-
contentImages: updateMany(contentImageIds.map((id2) => ({ id: id2 }))),
|
|
1212
|
-
// ------------------------------------------------------------------------
|
|
1213
|
-
// --- custom fields
|
|
1214
|
-
// ------------------------------------------------------------------------
|
|
1215
|
-
// multi images
|
|
1216
|
-
images1: updateMany(images1),
|
|
1217
|
-
images2: updateMany(images2),
|
|
1218
|
-
// single images
|
|
1219
|
-
image1: updateOne(image1),
|
|
1220
|
-
image2: updateOne(image2),
|
|
1221
|
-
image3: updateOne(image3),
|
|
1222
|
-
image4: updateOne(image4),
|
|
1223
|
-
// ------------------------------------------------------------------------
|
|
1224
|
-
// translation
|
|
1225
|
-
// ------------------------------------------------------------------------
|
|
1226
|
-
translations: {
|
|
1227
|
-
upsert: translations.map((t) => ({
|
|
1228
|
-
where: { postId_locale: { postId: id, locale: t.locale } },
|
|
1229
|
-
update: {
|
|
1230
|
-
...t,
|
|
1231
|
-
externalLinks: t.externalLinks,
|
|
1232
|
-
faq: t.faq,
|
|
1233
|
-
toc: t.toc
|
|
1234
|
-
},
|
|
1235
|
-
create: {
|
|
1236
|
-
...t,
|
|
1237
|
-
externalLinks: t.externalLinks,
|
|
1238
|
-
faq: t.faq,
|
|
1239
|
-
toc: t.toc
|
|
1240
|
-
}
|
|
1241
|
-
}))
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
return updated;
|
|
1246
|
-
}
|
|
1247
|
-
async function _delete({ id }) {
|
|
1248
|
-
const deleted = await prisma.post.delete({
|
|
1249
|
-
where: { id }
|
|
1250
|
-
});
|
|
1251
|
-
return deleted;
|
|
1252
|
-
}
|
|
1253
|
-
return {
|
|
1254
|
-
create,
|
|
1255
|
-
update,
|
|
1256
|
-
delete: _delete
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// src/server/infrastructure/database/post/include.ts
|
|
1261
|
-
var POST_LIST_CARD_INCLUDE = {
|
|
1262
|
-
// ---------------------------
|
|
1263
|
-
// relations: Post
|
|
1264
|
-
// ---------------------------
|
|
1265
|
-
topic: { include: { translations: true } },
|
|
1266
|
-
// Use as post, category
|
|
1267
|
-
postsInTopic: true,
|
|
1268
|
-
// Use as topic
|
|
1269
|
-
children: true,
|
|
1270
|
-
// Use as category
|
|
1271
|
-
taggedPosts: true,
|
|
1272
|
-
// Use as tag
|
|
1273
|
-
// ---------------------------
|
|
1274
|
-
// relations: File
|
|
1275
|
-
// ---------------------------
|
|
1276
|
-
coverImage: true,
|
|
1277
|
-
// ---------------------------
|
|
1278
|
-
// translation
|
|
1279
|
-
// ---------------------------
|
|
1280
|
-
translations: true
|
|
1281
|
-
};
|
|
1282
|
-
var POST_FULL_INCLUDE = {
|
|
1283
|
-
// ---------------------------
|
|
1284
|
-
// relations: Admin
|
|
1285
|
-
// ---------------------------
|
|
1286
|
-
author: { include: { translations: true } },
|
|
1287
|
-
// ---------------------------
|
|
1288
|
-
// relations: Post
|
|
1289
|
-
// ---------------------------
|
|
1290
|
-
topic: { include: POST_LIST_CARD_INCLUDE },
|
|
1291
|
-
postsInTopic: { include: POST_LIST_CARD_INCLUDE },
|
|
1292
|
-
parents: { include: POST_LIST_CARD_INCLUDE },
|
|
1293
|
-
children: { include: POST_LIST_CARD_INCLUDE },
|
|
1294
|
-
tags: { include: POST_LIST_CARD_INCLUDE },
|
|
1295
|
-
taggedPosts: { include: POST_LIST_CARD_INCLUDE },
|
|
1296
|
-
relatedPosts: { include: POST_LIST_CARD_INCLUDE },
|
|
1297
|
-
referencingPosts: { include: POST_LIST_CARD_INCLUDE },
|
|
1298
|
-
// ---------------------------
|
|
1299
|
-
// relations: File
|
|
1300
|
-
// ---------------------------
|
|
1301
|
-
coverImage: { include: { translations: true } },
|
|
1302
|
-
// ---------------------------
|
|
1303
|
-
// --- custom fields
|
|
1304
|
-
// ---------------------------
|
|
1305
|
-
images1: { include: { translations: true } },
|
|
1306
|
-
images2: { include: { translations: true } },
|
|
1307
|
-
image1: { include: { translations: true } },
|
|
1308
|
-
image2: { include: { translations: true } },
|
|
1309
|
-
image3: { include: { translations: true } },
|
|
1310
|
-
image4: { include: { translations: true } },
|
|
1311
|
-
// ---------------------------
|
|
1312
|
-
// translation
|
|
1313
|
-
// ---------------------------
|
|
1314
|
-
translations: true
|
|
1315
|
-
};
|
|
1316
|
-
|
|
1317
|
-
// src/server/infrastructure/database/post/query/create-post-query-repository.ts
|
|
1318
|
-
function buildWhere3(params) {
|
|
1319
|
-
if (params.id) return { id: params.id };
|
|
1320
|
-
if (params.type && params.slug)
|
|
1321
|
-
return { type: params.type, slug: params.slug };
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
function createPostQueryRepository(prisma) {
|
|
1325
|
-
async function findListCards({
|
|
1326
|
-
locale,
|
|
1327
|
-
// search
|
|
1328
|
-
searchString,
|
|
1329
|
-
type,
|
|
1330
|
-
topicId,
|
|
1331
|
-
postIds,
|
|
1332
|
-
isActive,
|
|
1333
|
-
isIndexActive,
|
|
1334
|
-
isSlugActive,
|
|
1335
|
-
isFeatured,
|
|
1336
|
-
isShownOnHome,
|
|
1337
|
-
// pagination
|
|
1338
|
-
page,
|
|
1339
|
-
pageSize
|
|
1340
|
-
}) {
|
|
1341
|
-
const where = {
|
|
1342
|
-
// search
|
|
1343
|
-
...createSearch({
|
|
1344
|
-
...searchString ? { searchString } : {},
|
|
1345
|
-
locale,
|
|
1346
|
-
rootFields: ["slug"],
|
|
1347
|
-
translationFields: ["title", "summary", "description", "content"]
|
|
1348
|
-
}),
|
|
1349
|
-
// type
|
|
1350
|
-
...type ? { type } : {},
|
|
1351
|
-
// states
|
|
1352
|
-
...isActive ? { isActive: true } : {},
|
|
1353
|
-
...isIndexActive ? { isIndexActive: true } : {},
|
|
1354
|
-
...isSlugActive ? { isSlugActive: true } : {},
|
|
1355
|
-
...isFeatured ? { isFeatured: true } : {},
|
|
1356
|
-
...isShownOnHome ? { isShownOnHome: true } : {},
|
|
1357
|
-
// relations: Post
|
|
1358
|
-
...topicId ? { topicId } : {},
|
|
1359
|
-
// Specified ids
|
|
1360
|
-
...postIds ? { id: { in: postIds } } : {}
|
|
1361
|
-
};
|
|
1362
|
-
const [items, total] = await prisma.$transaction([
|
|
1363
|
-
prisma.post.findMany({
|
|
1364
|
-
where,
|
|
1365
|
-
orderBy: POST_ORDER_BY,
|
|
1366
|
-
include: POST_LIST_CARD_INCLUDE,
|
|
1367
|
-
...createPagination(page, pageSize)
|
|
1368
|
-
}),
|
|
1369
|
-
prisma.post.count({ where })
|
|
1370
|
-
]);
|
|
1371
|
-
return { items, total };
|
|
1372
|
-
}
|
|
1373
|
-
async function find(params) {
|
|
1374
|
-
const where = buildWhere3(params);
|
|
1375
|
-
if (!where) return null;
|
|
1376
|
-
return prisma.post.findFirst({
|
|
1377
|
-
where,
|
|
1378
|
-
include: { translations: true }
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
async function findMany({
|
|
1382
|
-
type,
|
|
1383
|
-
topicId
|
|
1384
|
-
}) {
|
|
1385
|
-
return await prisma.post.findMany({
|
|
1386
|
-
where: { type, ...topicId ? { topicId } : {} },
|
|
1387
|
-
orderBy: POST_ORDER_BY,
|
|
1388
|
-
include: { translations: true }
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
async function findFull(params) {
|
|
1392
|
-
const where = buildWhere3(params);
|
|
1393
|
-
if (!where) return null;
|
|
1394
|
-
return prisma.post.findFirst({
|
|
1395
|
-
where,
|
|
1396
|
-
include: POST_FULL_INCLUDE
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
async function findWithSeoMetadata(params) {
|
|
1400
|
-
const where = buildWhere3(params);
|
|
1401
|
-
if (!where) return null;
|
|
1402
|
-
return await prisma.post.findFirst({
|
|
1403
|
-
where,
|
|
1404
|
-
include: { translations: true, seoMetadatas: true }
|
|
1405
|
-
});
|
|
1406
|
-
}
|
|
1407
|
-
return {
|
|
1408
|
-
findListCards,
|
|
1409
|
-
findMany,
|
|
1410
|
-
find,
|
|
1411
|
-
findFull,
|
|
1412
|
-
findWithSeoMetadata
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
// src/server/infrastructure/database/seo-metadata/command/create-seo-metadata-command-repository.ts
|
|
1417
|
-
function createSeoMetadataCommandRepository(prisma) {
|
|
1418
|
-
async function upsert({
|
|
1419
|
-
// ------------------------------------
|
|
1420
|
-
// relations
|
|
1421
|
-
// ------------------------------------
|
|
1422
|
-
// Post
|
|
1423
|
-
postId,
|
|
1424
|
-
// ------------------------------------
|
|
1425
|
-
// translation
|
|
1426
|
-
// ------------------------------------
|
|
1427
|
-
translations
|
|
1428
|
-
}) {
|
|
1429
|
-
await prisma.post.update({
|
|
1430
|
-
where: { id: postId },
|
|
1431
|
-
data: {
|
|
1432
|
-
seoMetadatas: {
|
|
1433
|
-
upsert: translations.map((t) => ({
|
|
1434
|
-
where: { postId_locale: { postId, locale: t.locale } },
|
|
1435
|
-
update: t,
|
|
1436
|
-
create: t
|
|
1437
|
-
}))
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
return {
|
|
1443
|
-
upsert
|
|
1444
|
-
};
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
export { ADMIN_ORDER_BY, ADMIN_ROLES, FILE_TYPES, ORDER_BY, POST_ORDER_BY, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, classifyFileType, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository, formatFileSize, getMediaInfo, mimeToExtension };
|
|
1
|
+
export { ADMIN_ROLES, FILE_TYPES, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension } from './chunk-KEQXXUK2.js';
|