@yimingliao/cms 0.0.64 → 0.0.66

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.
@@ -1 +1,4318 @@
1
- export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, ServerError, createAdminCommandRepository, createAdminCreateAction, createAdminDeleteAction, createAdminFindFullAction, createAdminFindListCardsAction, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenDeleteAction, createAdminRefreshTokenFindManyAction, createAdminRefreshTokenQueryRepository, createAdminUpdateAction, createArgon2Service, createAuthMiddleware, createAuthUseCases, createCache, createCacheResult, createChangePasswordAction, createCookieService, createCryptoService, createEmailUnverifiedAction, createEmailVerificationEmail, createExecuteAction, createExecuteApi, createExist, createFileCommandRepository, createFileCreateAction, createFileCreateManyAction, createFileFindFullAction, createFileFindListCardsAction, createFilePurgeManyAction, createFileQueryRepository, createFileRestoreManyAction, createFileSchema, createFileSoftDeleteAction, createFileSoftDeleteManyAction, createFileUpdateAction, createFolderCommandRepository, createFolderCreateAction, createFolderDeleteAction, createFolderFindFullAction, createFolderFindListCardsAction, createFolderQueryRepository, createFolderUpdateAction, createForgotPasswordAction, createForgotPasswordEmail, createIpRateLimiter, createJwtService, createMultiFileSchema, createPostCommandRepository, createPostCreateAction, createPostDeleteAction, createPostFindAction, createPostFindFullAction, createPostFindListCardsAction, createPostFindManyAction, createPostQueryRepository, createPostUpdateAction, createRenderEmailTemplate, createResetPasswordAction, createSchemas, createSendEmail, createSeoMetadataCommandRepository, createSeoMetadataUpsertAction, createSignInAction, createSignOutAction, createTocItemSchema, createTransporter, createUnique, createVerifyAccessToken, createVerifyAction, createVerifyEmailAction, createVerifyRefreshToken, createZod, normalizeCacheKey } from '../chunk-AOAWCZ4L.js';
1
+ import { ADMIN_ROLES, isFileLocked, isFolderLocked, ROOT_FOLDER_ID, ROOT_FOLDER, POST_TYPES } from '../chunk-FUAJWL2N.js';
2
+ import { SIZE, result, datetimeToDb, jsonArrayToDb, mimeToExtension, classifyFileType, TWITTER_CARD_ARRAY, OG_TYPE_ARRAY } from '../chunk-N2PRNBP2.js';
3
+ import jwt from 'jsonwebtoken';
4
+ import argon2 from 'argon2';
5
+ import crypto, { timingSafeEqual } from 'crypto';
6
+ import KeyvRedis from '@keyv/redis';
7
+ import Keyv from 'keyv';
8
+ import { z, ZodError } from 'zod';
9
+ import nodemailer from 'nodemailer';
10
+ import { readFile } from 'fs/promises';
11
+ import path from 'path';
12
+ import { ulid } from 'ulid';
13
+ import { NextResponse } from 'next/server';
14
+ import path2 from 'path/posix';
15
+
16
+ function createJwtService({
17
+ defaultSecret,
18
+ ...options
19
+ }) {
20
+ function sign({
21
+ payload = {},
22
+ secret = defaultSecret,
23
+ expiresIn = 60 * 60
24
+ }) {
25
+ return jwt.sign(payload, secret, {
26
+ algorithm: "HS256",
27
+ expiresIn,
28
+ ...options
29
+ });
30
+ }
31
+ function verify({
32
+ token,
33
+ secret = defaultSecret
34
+ }) {
35
+ const payload = jwt.verify(token, secret, {
36
+ algorithms: ["HS256"],
37
+ ...options
38
+ });
39
+ if (typeof payload === "string") {
40
+ throw new TypeError("Invalid JWT payload");
41
+ }
42
+ return payload;
43
+ }
44
+ return {
45
+ sign,
46
+ verify
47
+ };
48
+ }
49
+ var DEFAULT_OPTIONS = {
50
+ type: argon2.argon2id,
51
+ memoryCost: 19456,
52
+ // ~19MB
53
+ timeCost: 2,
54
+ parallelism: 1
55
+ };
56
+ function createArgon2Service() {
57
+ async function hash(password) {
58
+ return await argon2.hash(password, DEFAULT_OPTIONS);
59
+ }
60
+ async function verify(digest, password) {
61
+ return await argon2.verify(digest, password);
62
+ }
63
+ return {
64
+ hash,
65
+ verify
66
+ };
67
+ }
68
+ function createCryptoService({
69
+ defaultSecret
70
+ }) {
71
+ const SECRET = crypto.createHash("sha256").update(defaultSecret).digest();
72
+ const ALGORITHM = "aes-256-gcm";
73
+ const IV_LENGTH = 12;
74
+ function generateToken() {
75
+ return crypto.randomBytes(32).toString("base64url");
76
+ }
77
+ function hash(value) {
78
+ return crypto.createHash("sha256").update(value).digest("hex");
79
+ }
80
+ function hashBuffer(value) {
81
+ return crypto.createHash("sha256").update(value).digest();
82
+ }
83
+ function encrypt(data) {
84
+ const iv = crypto.randomBytes(IV_LENGTH);
85
+ const cipher = crypto.createCipheriv(ALGORITHM, SECRET, iv);
86
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
87
+ const tag = cipher.getAuthTag();
88
+ return [
89
+ iv.toString("hex"),
90
+ encrypted.toString("hex"),
91
+ tag.toString("hex")
92
+ ].join(":");
93
+ }
94
+ function decrypt(data) {
95
+ const parts = data.split(":");
96
+ if (parts.length !== 3 || !parts.every(Boolean)) {
97
+ throw new Error("Invalid encrypted payload");
98
+ }
99
+ const [ivHex, encryptedHex, tagHex] = parts;
100
+ const iv = Buffer.from(ivHex, "hex");
101
+ const encrypted = Buffer.from(encryptedHex, "hex");
102
+ const tag = Buffer.from(tagHex, "hex");
103
+ const decipher = crypto.createDecipheriv(ALGORITHM, SECRET, iv);
104
+ decipher.setAuthTag(tag);
105
+ const decrypted = Buffer.concat([
106
+ decipher.update(encrypted),
107
+ decipher.final()
108
+ ]);
109
+ return decrypted.toString("utf8");
110
+ }
111
+ function sign({
112
+ value,
113
+ expireSeconds,
114
+ createdAt
115
+ }) {
116
+ const payload = `${value}|${expireSeconds}|${createdAt ?? Date.now()}`;
117
+ const signature = crypto.createHmac("sha256", SECRET).update(payload).digest("hex");
118
+ const signed = `${payload}|${signature}`;
119
+ return { signed, signature };
120
+ }
121
+ return {
122
+ generateToken,
123
+ hash,
124
+ hashBuffer,
125
+ encrypt,
126
+ decrypt,
127
+ sign
128
+ };
129
+ }
130
+ var DEFAULT_OPTIONS2 = {
131
+ httpOnly: true,
132
+ secure: process.env["NODE_ENV"] === "production",
133
+ sameSite: "strict",
134
+ path: "/"
135
+ };
136
+ function createCookieService(nextCookies, cryptoService) {
137
+ async function set({
138
+ name,
139
+ value,
140
+ expireSeconds
141
+ }) {
142
+ const cookieStore = await nextCookies();
143
+ cookieStore.set(name, value, { ...DEFAULT_OPTIONS2, maxAge: expireSeconds });
144
+ }
145
+ async function setSignedCookie({
146
+ name,
147
+ value,
148
+ expireSeconds
149
+ }) {
150
+ const createdAt = Date.now();
151
+ const { signed } = cryptoService.sign({
152
+ value,
153
+ expireSeconds,
154
+ createdAt
155
+ });
156
+ await set({ name, value: signed, expireSeconds });
157
+ }
158
+ async function get({ name }) {
159
+ const cookieStore = await nextCookies();
160
+ const cookieValue = cookieStore.get(name)?.value;
161
+ return cookieValue;
162
+ }
163
+ async function getSignedCookie({ name }) {
164
+ const signed = await get({ name });
165
+ const value = verifySignedCookie({ signed });
166
+ return value;
167
+ }
168
+ function verifySignedCookie({ signed }) {
169
+ if (!signed) throw new Error("Invalid cookie");
170
+ const parts = signed.split("|");
171
+ if (parts.length !== 4) throw new Error("Invalid cookie");
172
+ const [value, expireSecondsStr, createdAtStr, signature] = parts;
173
+ const expireSeconds = Number.parseInt(expireSecondsStr, 10);
174
+ const createdAt = Number.parseInt(createdAtStr, 10);
175
+ if (Number.isNaN(expireSeconds) || Number.isNaN(createdAt)) {
176
+ throw new TypeError("Invalid cookie");
177
+ }
178
+ const now = Date.now();
179
+ if (now > createdAt + expireSeconds * 1e3) {
180
+ throw new Error("Cookie expired");
181
+ }
182
+ const { signature: generatedSignature } = cryptoService.sign({
183
+ value,
184
+ expireSeconds,
185
+ createdAt
186
+ });
187
+ const sig = Buffer.from(signature);
188
+ const gen = Buffer.from(generatedSignature);
189
+ if (sig.length !== gen.length || !timingSafeEqual(sig, gen)) {
190
+ throw new Error("Invalid cookie signature");
191
+ }
192
+ return value;
193
+ }
194
+ async function deleteCokkie({ name }) {
195
+ const cookieStore = await nextCookies();
196
+ cookieStore.delete(name);
197
+ }
198
+ return {
199
+ set,
200
+ setSignedCookie,
201
+ get,
202
+ getSignedCookie,
203
+ verifySignedCookie,
204
+ delete: deleteCokkie
205
+ };
206
+ }
207
+
208
+ // src/server/infrastructure/cache/cache-key-delimiter.ts
209
+ var CACHE_KEY_DELIMITER = "|";
210
+
211
+ // src/server/infrastructure/cache/create-cache.ts
212
+ function createCache({
213
+ redisUrl,
214
+ namespace,
215
+ keyDelimiter = CACHE_KEY_DELIMITER
216
+ }) {
217
+ const redisStore = new KeyvRedis(redisUrl, {
218
+ keyPrefixSeparator: keyDelimiter
219
+ });
220
+ return new Keyv({
221
+ store: redisStore,
222
+ namespace,
223
+ useKeyPrefix: false
224
+ });
225
+ }
226
+
227
+ // src/server/infrastructure/cache/normalize-cache-key.ts
228
+ var sanitize = (k) => k.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/[\r\n]/g, "").trim();
229
+ var normalizeCacheKey = (key, delimiter = CACHE_KEY_DELIMITER) => {
230
+ if (!key) return null;
231
+ if (Array.isArray(key)) {
232
+ if (key.length === 0) return null;
233
+ const normalized = key.map((k) => {
234
+ if (k === null) return "__null";
235
+ if (k === void 0) return "__undefined";
236
+ if (typeof k === "boolean") return k ? "__true" : "__false";
237
+ return sanitize(String(k));
238
+ });
239
+ return normalized.join(delimiter);
240
+ }
241
+ if (typeof key === "boolean") return key ? "__true" : "__false";
242
+ return String(key);
243
+ };
244
+
245
+ // src/server/infrastructure/cache/create-cache-result.ts
246
+ var DAY = 1e3 * 60 * 60 * 24;
247
+ function createCacheResult(cache2, logger) {
248
+ return async function cacheResult({
249
+ key: rawKey,
250
+ ttl = DAY,
251
+ load
252
+ }) {
253
+ const key = normalizeCacheKey(rawKey);
254
+ if (!key) {
255
+ logger.error("Cache skipped due to invalid key", {
256
+ rawKey,
257
+ scope: "cacheResult"
258
+ });
259
+ return load();
260
+ }
261
+ try {
262
+ const cached = await cache2.get(key);
263
+ if (cached !== void 0) {
264
+ return cached;
265
+ }
266
+ const data = await load();
267
+ if (data !== void 0) {
268
+ await cache2.set(key, data, ttl);
269
+ }
270
+ return data;
271
+ } catch (error) {
272
+ logger.error("Cache failure, falling back to loader", {
273
+ key,
274
+ error,
275
+ scope: "cacheResult"
276
+ });
277
+ return load();
278
+ }
279
+ };
280
+ }
281
+
282
+ // src/server/infrastructure/cache/create-ip-rate-limiter.ts
283
+ var DEFAULT_MAX_ATTEMPTS = 10;
284
+ var DEFAULT_TIME_WINDOW = 60;
285
+ var lua = `
286
+ local current = redis.call('INCR', KEYS[1])
287
+ if current == 1 then
288
+ redis.call('EXPIRE', KEYS[1], ARGV[1])
289
+ end
290
+ return current
291
+ `;
292
+ function createIpRateLimiter({
293
+ appName,
294
+ cache: cache2,
295
+ headers
296
+ }) {
297
+ return async function ipRateLimiter({
298
+ key: rawKey,
299
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
300
+ timeWindow = DEFAULT_TIME_WINDOW
301
+ }) {
302
+ const secondaryStore = cache2.store;
303
+ const redis = await secondaryStore.getClient();
304
+ if (!redis) return true;
305
+ const headersStore = await headers();
306
+ const ip = headersStore.get("cf-connecting-ip")?.trim() || headersStore.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
307
+ const key = normalizeCacheKey([
308
+ appName,
309
+ "ip-rate-limiter",
310
+ ...Array.isArray(rawKey) ? rawKey : [rawKey],
311
+ ip
312
+ ]);
313
+ const currentRaw = await redis.eval(lua, {
314
+ keys: [key],
315
+ arguments: [timeWindow.toString()]
316
+ });
317
+ const current = typeof currentRaw === "number" ? currentRaw : Number(currentRaw);
318
+ return current <= maxAttempts;
319
+ };
320
+ }
321
+
322
+ // src/server/infrastructure/zod/rules/bcp47.ts
323
+ function bcp47(locale) {
324
+ if (typeof locale !== "string") return false;
325
+ const BCP47_REGEX = new RegExp(
326
+ "^[a-zA-Z]{2,3}(?:-[A-Z][a-z]{3})?" + // optional region
327
+ String.raw`(?:-(?:[A-Z]{2}|\d{3}))?` + // optional variants
328
+ String.raw`(?:-(?:[a-zA-Z0-9]{5,8}|\d[a-zA-Z0-9]{3}))*` + // optional extensions
329
+ "(?:-(?:[0-9A-WY-Za-wy-z]-[a-zA-Z0-9]{2,8}(?:-[a-zA-Z0-9]{2,8})*))*(?:-x(?:-[a-zA-Z0-9]{1,8})+)?$"
330
+ );
331
+ return BCP47_REGEX.test(locale);
332
+ }
333
+
334
+ // src/server/infrastructure/zod/rules/exist.ts
335
+ function createExist(prisma) {
336
+ return async function exist(value, options) {
337
+ if (!value) return false;
338
+ const column = options.column || "id";
339
+ const query = `
340
+ SELECT COUNT(*) AS count
341
+ FROM ${options.table}
342
+ WHERE ${column} = $1
343
+ `;
344
+ try {
345
+ const result2 = await prisma.$queryRawUnsafe(
346
+ query,
347
+ value
348
+ );
349
+ const count = Number(result2[0]?.count || 0);
350
+ return count > 0;
351
+ } catch (error) {
352
+ console.error("Exist check failed:", error);
353
+ return false;
354
+ }
355
+ };
356
+ }
357
+
358
+ // src/server/infrastructure/zod/rules/og-locale.ts
359
+ function ogLocale(locale) {
360
+ if (typeof locale !== "string") return false;
361
+ const OG_LOCALE_REGEX = /^[a-z]{2}_[A-Z]{2}$/;
362
+ return OG_LOCALE_REGEX.test(locale);
363
+ }
364
+
365
+ // src/server/infrastructure/zod/rules/unique.ts
366
+ function createUnique(prisma) {
367
+ return async function unique(value, options) {
368
+ if (!value) return true;
369
+ let query = `
370
+ SELECT COUNT(*) AS count
371
+ FROM ${options.table}
372
+ WHERE ${options.column} = $1
373
+ `;
374
+ const params = [value];
375
+ const appendCondition = (op, sc) => {
376
+ const paramIndex = params.length + 1;
377
+ const cast = sc.cast ? `::"${sc.cast}"` : "";
378
+ query += ` AND ${sc.name} ${op} $${paramIndex}${cast}`;
379
+ params.push(sc.value);
380
+ };
381
+ if (options.scope) {
382
+ options.scope.forEach((sc) => appendCondition("=", sc));
383
+ }
384
+ if (options.excludeSelf) {
385
+ appendCondition("!=", options.excludeSelf);
386
+ }
387
+ try {
388
+ const result2 = await prisma.$queryRawUnsafe(
389
+ query,
390
+ ...params
391
+ );
392
+ return Number(result2[0]?.count || 0) === 0;
393
+ } catch (error) {
394
+ console.error("Unique check failed:", error);
395
+ return false;
396
+ }
397
+ };
398
+ }
399
+
400
+ // src/server/infrastructure/zod/create-zod.ts
401
+ function createZod({
402
+ unique,
403
+ exist
404
+ }) {
405
+ const stringProto = z.string().constructor.prototype;
406
+ const emailProto = z.email().constructor.prototype;
407
+ if (!stringProto.unique) {
408
+ Object.defineProperty(stringProto, "unique", {
409
+ configurable: true,
410
+ writable: false,
411
+ value: function(options) {
412
+ return this.refine(async (v) => unique(v, options), {
413
+ params: { i18nKey: "validator.unique" }
414
+ });
415
+ }
416
+ });
417
+ }
418
+ if (!stringProto.exist) {
419
+ Object.defineProperty(stringProto, "exist", {
420
+ configurable: true,
421
+ writable: false,
422
+ value: function(options) {
423
+ return this.refine(async (v) => exist(v, options), {
424
+ params: { i18nKey: "validator.exist" }
425
+ });
426
+ }
427
+ });
428
+ }
429
+ if (!stringProto.bcp47) {
430
+ Object.defineProperty(stringProto, "bcp47", {
431
+ configurable: true,
432
+ writable: false,
433
+ value: function() {
434
+ return this.refine((v) => bcp47(v), {
435
+ params: { i18nKey: "validator.bcp47" }
436
+ });
437
+ }
438
+ });
439
+ }
440
+ if (!stringProto.ogLocale) {
441
+ Object.defineProperty(stringProto, "ogLocale", {
442
+ configurable: true,
443
+ writable: false,
444
+ value: function() {
445
+ return this.refine((v) => ogLocale(v), {
446
+ params: { i18nKey: "validator.og-locale" }
447
+ });
448
+ }
449
+ });
450
+ }
451
+ if (!emailProto.unique) {
452
+ Object.defineProperty(emailProto, "unique", {
453
+ configurable: true,
454
+ writable: false,
455
+ value: function(options) {
456
+ return this.refine(async (v) => unique(v, options), {
457
+ params: { i18nKey: "validator.unique" }
458
+ });
459
+ }
460
+ });
461
+ }
462
+ return z;
463
+ }
464
+
465
+ // src/server/infrastructure/zod/schemas/schemas.ts
466
+ function createSchemas({
467
+ z: z2,
468
+ localeArray,
469
+ exist
470
+ }) {
471
+ const trimmedString = () => z2.string().trim();
472
+ const MAX_NUMBER = 2147483647;
473
+ const MAX_STRING = 1e5;
474
+ const localeSet = new Set(localeArray);
475
+ function text() {
476
+ return trimmedString().max(MAX_STRING);
477
+ }
478
+ function positiveNumber() {
479
+ return z2.preprocess((val) => {
480
+ if (val == null || val === "") return;
481
+ const num = Number(typeof val === "string" ? val.trim() : val);
482
+ return Number.isNaN(num) ? void 0 : num;
483
+ }, z2.number().min(0).max(MAX_NUMBER));
484
+ }
485
+ function url() {
486
+ return z2.preprocess((val) => {
487
+ if (typeof val === "string" && val.trim() === "" || val === null)
488
+ return null;
489
+ return val;
490
+ }, z2.url().max(2048).nullable());
491
+ }
492
+ function email() {
493
+ return z2.email().max(254).toLowerCase();
494
+ }
495
+ function password() {
496
+ return trimmedString().min(6).max(255);
497
+ }
498
+ function isoString() {
499
+ return trimmedString().regex(
500
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
501
+ );
502
+ }
503
+ function array(schema) {
504
+ return z2.array(schema).max(100).transform(
505
+ (arr) => arr.filter((v) => v != null)
506
+ );
507
+ }
508
+ function id() {
509
+ return trimmedString().length(26).regex(/^[0-9A-Z]{26}$/);
510
+ }
511
+ function key() {
512
+ return trimmedString().max(1024);
513
+ }
514
+ function sha256Hash() {
515
+ return trimmedString().length(64).regex(/^[a-f0-9]{64}$/);
516
+ }
517
+ function slug() {
518
+ return trimmedString().regex(/^$|^[\p{L}0-9-_]+$/u).max(100);
519
+ }
520
+ function pathSegment() {
521
+ return trimmedString().min(1).regex(/^(?!\.\.?$)[^\u0000-\u001F/\\]+$/u).max(255);
522
+ }
523
+ function locale() {
524
+ return trimmedString().refine((val) => localeSet.has(val), {
525
+ error: "Invalid locale"
526
+ });
527
+ }
528
+ function singleItem(options) {
529
+ return z2.object({
530
+ id: id().refine((v) => exist(v, options), {
531
+ error: "Resource does not exist"
532
+ })
533
+ }).nullable();
534
+ }
535
+ function multiItems(options) {
536
+ return array(
537
+ z2.object({
538
+ id: id().refine((v) => exist(v, options), {
539
+ error: "Resource does not exist"
540
+ })
541
+ })
542
+ );
543
+ }
544
+ return {
545
+ z: z2,
546
+ // base
547
+ text,
548
+ positiveNumber,
549
+ url,
550
+ email,
551
+ password,
552
+ isoString,
553
+ array,
554
+ // resource related
555
+ id,
556
+ key,
557
+ sha256Hash,
558
+ slug,
559
+ pathSegment,
560
+ locale,
561
+ // item
562
+ singleItem,
563
+ multiItems
564
+ };
565
+ }
566
+
567
+ // src/server/infrastructure/zod/schemas/file.ts
568
+ function createFileSchema({
569
+ z: z2,
570
+ maxSizeInMb
571
+ }) {
572
+ return function fileSchema({
573
+ size = maxSizeInMb * SIZE.MB,
574
+ extensions = []
575
+ } = {}) {
576
+ return z2.instanceof(File, { error: "Invalid file" }).refine((file) => file.size <= size, {
577
+ error: `File is too large, max ${size / SIZE.MB}MB`
578
+ }).refine(
579
+ (file) => {
580
+ if (extensions.length === 0) return true;
581
+ return extensions.some(
582
+ (ext) => file.name.toLocaleLowerCase().endsWith(ext)
583
+ );
584
+ },
585
+ { error: `Only ${extensions.join(", ")} files are allowed` }
586
+ ).transform((file) => file);
587
+ };
588
+ }
589
+ function createMultiFileSchema({
590
+ z: z2,
591
+ fileSchema,
592
+ maxSizeInMb
593
+ }) {
594
+ return function multiFilesSchema({
595
+ size = maxSizeInMb * SIZE.MB,
596
+ extensions = []
597
+ } = {}) {
598
+ return z2.array(fileSchema({ size, extensions }));
599
+ };
600
+ }
601
+
602
+ // src/server/infrastructure/zod/schemas/toc-item.ts
603
+ function createTocItemSchema({
604
+ z: z2,
605
+ schemas
606
+ }) {
607
+ const tocItem = z2.lazy(
608
+ () => z2.object({
609
+ text: schemas.text(),
610
+ id: schemas.text(),
611
+ level: z2.union([
612
+ z2.literal(2),
613
+ z2.literal(3),
614
+ z2.literal(4),
615
+ z2.literal(5),
616
+ z2.literal(6)
617
+ ]),
618
+ children: z2.array(tocItem).default([])
619
+ })
620
+ );
621
+ return tocItem;
622
+ }
623
+ function createTransporter({
624
+ host,
625
+ port,
626
+ user,
627
+ pass
628
+ }) {
629
+ return nodemailer.createTransport({
630
+ host,
631
+ port,
632
+ auth: {
633
+ user,
634
+ pass
635
+ }
636
+ });
637
+ }
638
+ function createSendEmail({
639
+ transporter,
640
+ config
641
+ }) {
642
+ return async function sendEmail(options) {
643
+ return await transporter.sendMail({
644
+ from: `${config.fromName} <${config.fromAddress}>`,
645
+ replyTo: `${config.replyToName} <${config.replyToaddress}>`,
646
+ ...options
647
+ });
648
+ };
649
+ }
650
+ var TEMPLATE_DIR = path.resolve(process.cwd(), "emails");
651
+ var LAYOUT_PATH = path.join(TEMPLATE_DIR, "layout.html");
652
+ var isDev = process.env.NODE_ENV !== "production";
653
+ var cache = /* @__PURE__ */ new Map();
654
+ async function readTemplate(filePath) {
655
+ if (!isDev && cache.has(filePath)) {
656
+ return cache.get(filePath);
657
+ }
658
+ try {
659
+ const html = await readFile(filePath, "utf8");
660
+ if (!isDev) cache.set(filePath, html);
661
+ return html;
662
+ } catch (error) {
663
+ throw new Error(`Email template file not found: ${filePath}`, {
664
+ cause: error
665
+ });
666
+ }
667
+ }
668
+ function applyReplacements(html, replacements, logger) {
669
+ return html.replace(/\{\{\{(.*?)\}\}\}/g, (_, raw) => {
670
+ const key = raw.trim();
671
+ const value = replacements[key];
672
+ if (value == null) {
673
+ logger.warn({ msg: "Email template variable missing", variable: key });
674
+ return "";
675
+ }
676
+ return value;
677
+ });
678
+ }
679
+ function createRenderEmailTemplate({
680
+ siteName,
681
+ webUrl,
682
+ logoUrl,
683
+ logger
684
+ }) {
685
+ return async function renderEmailTemplate(templateKey, replacements = {}) {
686
+ try {
687
+ const contentPath = path.join(TEMPLATE_DIR, `${templateKey}.html`);
688
+ const vars = {
689
+ SITE_NAME: siteName,
690
+ WEB_URL: webUrl,
691
+ LOGO_URL: logoUrl,
692
+ ...replacements
693
+ };
694
+ const layoutHtml = await readTemplate(LAYOUT_PATH);
695
+ const contentHtml = await readTemplate(contentPath);
696
+ const merged = layoutHtml.replaceAll("{{{content}}}", contentHtml);
697
+ return applyReplacements(merged, vars, logger);
698
+ } catch (error) {
699
+ logger.error({
700
+ msg: "Email template render failed",
701
+ templateKey,
702
+ templateDir: TEMPLATE_DIR,
703
+ error
704
+ });
705
+ throw new Error(`Email template error: ${templateKey}`, { cause: error });
706
+ }
707
+ };
708
+ }
709
+
710
+ // src/server/infrastructure/database/utils/connect.ts
711
+ var ids = (items) => items.map(({ id }) => ({ id }));
712
+ function connectOne(item) {
713
+ return item ? { connect: { id: item.id } } : {};
714
+ }
715
+ function connectMany(items) {
716
+ return { connect: ids(items) };
717
+ }
718
+ function updateOne(item) {
719
+ return item ? { connect: { id: item.id } } : { disconnect: true };
720
+ }
721
+ function updateMany(items) {
722
+ return { set: ids(items) };
723
+ }
724
+
725
+ // src/server/infrastructure/database/admin/command/create-admin-command-repository.ts
726
+ function createAdminCommandRepository(prisma) {
727
+ async function create({
728
+ // core
729
+ role,
730
+ email,
731
+ passwordHash,
732
+ // better seo
733
+ socialLinks,
734
+ // ------------------------------------
735
+ // relations
736
+ // ------------------------------------
737
+ // File
738
+ avatarImage,
739
+ // ------------------------------------
740
+ // translation
741
+ // ------------------------------------
742
+ translations
743
+ }) {
744
+ const created = await prisma.admin.create({
745
+ data: {
746
+ // core
747
+ role,
748
+ email,
749
+ passwordHash,
750
+ // better seo
751
+ socialLinks,
752
+ // ------------------------------------------------------------------------
753
+ // relations
754
+ // ------------------------------------------------------------------------
755
+ // File
756
+ avatarImage: connectOne(avatarImage),
757
+ // ------------------------------------------------------------------------
758
+ // translation
759
+ // ------------------------------------------------------------------------
760
+ translations: { create: translations }
761
+ }
762
+ });
763
+ return created;
764
+ }
765
+ async function update({
766
+ id,
767
+ // core
768
+ role,
769
+ email,
770
+ // better seo
771
+ socialLinks,
772
+ // ------------------------------------
773
+ // relations
774
+ // ------------------------------------
775
+ // File
776
+ avatarImage,
777
+ // ------------------------------------
778
+ // translation
779
+ // ------------------------------------
780
+ translations,
781
+ // ------------------------------------
782
+ // timestamps
783
+ // ------------------------------------
784
+ emailVerifiedAt
785
+ }) {
786
+ const updated = await prisma.admin.update({
787
+ where: { id },
788
+ data: {
789
+ // core
790
+ role,
791
+ ...email !== void 0 ? { email } : {},
792
+ // better seo
793
+ socialLinks,
794
+ // ------------------------------------------------------------------------
795
+ // relations
796
+ // ------------------------------------------------------------------------
797
+ // File
798
+ avatarImage: updateOne(avatarImage),
799
+ // ------------------------------------------------------------------------
800
+ // translation
801
+ // ------------------------------------------------------------------------
802
+ translations: {
803
+ upsert: translations.map((t) => ({
804
+ where: { adminId_locale: { adminId: id, locale: t.locale } },
805
+ update: t,
806
+ create: t
807
+ }))
808
+ },
809
+ // ------------------------------------------------------------------------
810
+ // timestamps
811
+ // ------------------------------------------------------------------------
812
+ emailVerifiedAt
813
+ }
814
+ });
815
+ return updated;
816
+ }
817
+ async function _delete({ id }) {
818
+ const deleted = await prisma.admin.delete({
819
+ where: { id }
820
+ });
821
+ return deleted;
822
+ }
823
+ return {
824
+ create,
825
+ update,
826
+ delete: _delete
827
+ };
828
+ }
829
+
830
+ // src/server/infrastructure/database/constants.ts
831
+ var ORDER_BY = [
832
+ { updatedAt: "desc" },
833
+ { createdAt: "desc" },
834
+ { id: "asc" }
835
+ ];
836
+ var ADMIN_ORDER_BY = [
837
+ { role: "asc" },
838
+ ...ORDER_BY
839
+ ];
840
+ var POST_ORDER_BY = [
841
+ { index: "asc" },
842
+ ...ORDER_BY
843
+ ];
844
+
845
+ // src/server/infrastructure/database/utils/create-pagination.ts
846
+ function createPagination(page, pageSize) {
847
+ if (!page || !pageSize) return {};
848
+ return {
849
+ skip: (page - 1) * pageSize,
850
+ take: pageSize
851
+ };
852
+ }
853
+
854
+ // src/server/infrastructure/database/utils/create-search.ts
855
+ function buildContainsOr(fields, value) {
856
+ return fields.map((field) => ({
857
+ [field]: { contains: value, mode: "insensitive" }
858
+ }));
859
+ }
860
+ function buildTranslationSearch(locale, fields, value) {
861
+ return {
862
+ translations: {
863
+ some: { locale, OR: buildContainsOr(fields, value) }
864
+ }
865
+ };
866
+ }
867
+ function createSearch({
868
+ locale,
869
+ searchString,
870
+ rootFields = [],
871
+ translationFields = []
872
+ }) {
873
+ if (!searchString) return {};
874
+ const conditions = [];
875
+ if (rootFields.length > 0) {
876
+ conditions.push(...buildContainsOr(rootFields, searchString));
877
+ }
878
+ if (translationFields.length > 0 && locale) {
879
+ conditions.push(
880
+ buildTranslationSearch(locale, translationFields, searchString)
881
+ );
882
+ }
883
+ return conditions.length > 0 ? { OR: conditions } : {};
884
+ }
885
+
886
+ // src/server/infrastructure/database/admin/include.ts
887
+ var ADMIN_FULL_INCLUDE = {
888
+ // ---------------------------
889
+ // relations: AdminRefreshToken
890
+ // ---------------------------
891
+ adminRefreshTokens: true,
892
+ // ---------------------------
893
+ // relations: File
894
+ // ---------------------------
895
+ avatarImage: { include: { translations: true } },
896
+ // ---------------------------
897
+ // relations: Post
898
+ // ---------------------------
899
+ posts: true,
900
+ // ---------------------------
901
+ // translation
902
+ // ---------------------------
903
+ translations: true
904
+ };
905
+
906
+ // src/server/infrastructure/database/admin/query/create-admin-query-repository.ts
907
+ var OMIT_PASSWORD = { omit: { passwordHash: true } };
908
+ function buildWhere(params) {
909
+ if (params.id) return { id: params.id };
910
+ if (params.email) return { email: params.email };
911
+ return;
912
+ }
913
+ function createAdminQueryRepository(prisma) {
914
+ async function findListCards({
915
+ locale,
916
+ // search
917
+ searchString,
918
+ role,
919
+ adminIds,
920
+ // pagination
921
+ page,
922
+ pageSize
923
+ }) {
924
+ const where = {
925
+ // search
926
+ ...createSearch({
927
+ ...searchString !== void 0 ? { searchString } : {},
928
+ locale,
929
+ translationFields: ["name"]
930
+ }),
931
+ // role
932
+ ...role !== ADMIN_ROLES.SUPER_ADMIN ? { role: { notIn: [ADMIN_ROLES.SUPER_ADMIN] } } : {},
933
+ // Specified ids
934
+ ...adminIds?.length ? { id: { in: adminIds } } : {}
935
+ };
936
+ const [items, total] = await prisma.$transaction([
937
+ prisma.admin.findMany({
938
+ where,
939
+ orderBy: ADMIN_ORDER_BY,
940
+ include: ADMIN_FULL_INCLUDE,
941
+ ...createPagination(page, pageSize),
942
+ ...OMIT_PASSWORD
943
+ }),
944
+ prisma.admin.count({ where })
945
+ ]);
946
+ return { items, total };
947
+ }
948
+ async function find(params) {
949
+ const where = buildWhere(params);
950
+ if (!where) return null;
951
+ return prisma.admin.findUnique({
952
+ where,
953
+ ...OMIT_PASSWORD
954
+ });
955
+ }
956
+ async function findFull(params) {
957
+ const where = buildWhere(params);
958
+ if (!where) return null;
959
+ return prisma.admin.findUnique({
960
+ where,
961
+ include: ADMIN_FULL_INCLUDE,
962
+ ...OMIT_PASSWORD
963
+ });
964
+ }
965
+ async function findWithPasswordHash(params) {
966
+ const where = buildWhere(params);
967
+ if (!where) return null;
968
+ return prisma.admin.findUnique({
969
+ where
970
+ });
971
+ }
972
+ return {
973
+ findListCards,
974
+ find,
975
+ findFull,
976
+ findWithPasswordHash
977
+ };
978
+ }
979
+
980
+ // src/server/infrastructure/database/admin-refresh-token/command/create-admin-refresh-token-command-repository.ts
981
+ function createAdminRefreshTokenCommandRepository(prisma) {
982
+ async function create({
983
+ adminId,
984
+ ...params
985
+ }) {
986
+ const created = await prisma.adminRefreshToken.create({
987
+ data: {
988
+ ...params,
989
+ admin: { connect: { id: adminId } },
990
+ deviceInfo: params.deviceInfo
991
+ }
992
+ });
993
+ return created;
994
+ }
995
+ async function _delete({ id, tokenHash }) {
996
+ const where = id ? { id } : tokenHash ? { tokenHash } : void 0;
997
+ if (where) {
998
+ await prisma.adminRefreshToken.delete({
999
+ where
1000
+ });
1001
+ }
1002
+ }
1003
+ async function deleteManyByExpired() {
1004
+ const { count } = await prisma.adminRefreshToken.deleteMany({
1005
+ where: { expiresAt: { lt: /* @__PURE__ */ new Date() } }
1006
+ });
1007
+ return count;
1008
+ }
1009
+ return {
1010
+ create,
1011
+ delete: _delete,
1012
+ deleteManyByExpired
1013
+ };
1014
+ }
1015
+
1016
+ // src/server/infrastructure/database/admin-refresh-token/query/create-admin-refresh-token-query-repository.ts
1017
+ function createAdminRefreshTokenQueryRepository(prisma) {
1018
+ async function findManyByAdminId({
1019
+ adminId
1020
+ }) {
1021
+ return prisma.adminRefreshToken.findMany({
1022
+ where: { adminId },
1023
+ orderBy: [{ createdAt: "desc" }, { id: "asc" }]
1024
+ });
1025
+ }
1026
+ async function findByToken({
1027
+ tokenHash
1028
+ }) {
1029
+ return prisma.adminRefreshToken.findUnique({
1030
+ where: { tokenHash }
1031
+ });
1032
+ }
1033
+ return {
1034
+ findManyByAdminId,
1035
+ findByToken
1036
+ };
1037
+ }
1038
+
1039
+ // src/server/infrastructure/database/file/include.ts
1040
+ var FILE_FULL_INCLUDE = {
1041
+ // ---------------------------
1042
+ // relations: Folder
1043
+ // ---------------------------
1044
+ folder: true,
1045
+ // ---------------------------
1046
+ // relations: Admin
1047
+ // ---------------------------
1048
+ adminAsAvatarImage: true,
1049
+ // ---------------------------
1050
+ // relations: Post
1051
+ // ---------------------------
1052
+ postsAsCoverImage: true,
1053
+ postsAsContentImage: true,
1054
+ // ---------------------------
1055
+ // --- custom fields
1056
+ // ---------------------------
1057
+ postsAsImages1: true,
1058
+ postsAsImages2: true,
1059
+ postsAsImage1: true,
1060
+ postsAsImage2: true,
1061
+ postsAsImage3: true,
1062
+ postsAsImage4: true,
1063
+ // ---------------------------
1064
+ // translation
1065
+ // ---------------------------
1066
+ translations: true
1067
+ };
1068
+
1069
+ // src/server/infrastructure/database/file/command/create-file-command-repository.ts
1070
+ function createFileCommandRepository(prisma) {
1071
+ async function create({
1072
+ // core
1073
+ key,
1074
+ checksum,
1075
+ // meta
1076
+ fileMeta,
1077
+ // media info
1078
+ width,
1079
+ height,
1080
+ duration,
1081
+ // ------------------------------------
1082
+ // relations
1083
+ // ------------------------------------
1084
+ // Folder
1085
+ folder,
1086
+ // ------------------------------------
1087
+ // translation
1088
+ // ------------------------------------
1089
+ translations
1090
+ }) {
1091
+ const extension = mimeToExtension(fileMeta.type);
1092
+ const created = await prisma.file.create({
1093
+ data: {
1094
+ // core
1095
+ key,
1096
+ checksum,
1097
+ // media info
1098
+ ...width !== void 0 ? { width } : {},
1099
+ ...height !== void 0 ? { height } : {},
1100
+ ...duration !== void 0 ? { duration } : {},
1101
+ // derived
1102
+ originalName: fileMeta.name || "unknown",
1103
+ size: fileMeta.size ?? 0,
1104
+ extension,
1105
+ mimeType: fileMeta.type || "unknown",
1106
+ type: classifyFileType(fileMeta.type, extension),
1107
+ // ------------------------------------------------------------------------
1108
+ // relations
1109
+ // ------------------------------------------------------------------------
1110
+ // Folder
1111
+ folder: connectOne(folder),
1112
+ // ------------------------------------------------------------------------
1113
+ // translation
1114
+ // ------------------------------------------------------------------------
1115
+ translations: {
1116
+ create: translations.map((t) => ({
1117
+ ...t,
1118
+ name: t.name ?? fileMeta.name ?? "unknown"
1119
+ }))
1120
+ }
1121
+ },
1122
+ include: FILE_FULL_INCLUDE
1123
+ });
1124
+ return created;
1125
+ }
1126
+ async function update({
1127
+ file,
1128
+ // Original file (File)
1129
+ id,
1130
+ // core
1131
+ key,
1132
+ checksum,
1133
+ // meta
1134
+ fileMeta,
1135
+ // media info
1136
+ width,
1137
+ height,
1138
+ duration,
1139
+ // ------------------------------------
1140
+ // relations
1141
+ // ------------------------------------
1142
+ // Folder
1143
+ folder,
1144
+ // ------------------------------------
1145
+ // translation
1146
+ // ------------------------------------
1147
+ translations
1148
+ }) {
1149
+ const extension = mimeToExtension(fileMeta.type);
1150
+ const updated = await prisma.file.update({
1151
+ where: { id: file.id },
1152
+ data: {
1153
+ // core
1154
+ key,
1155
+ checksum,
1156
+ // media info
1157
+ ...width !== void 0 ? { width } : {},
1158
+ ...height !== void 0 ? { height } : {},
1159
+ ...duration !== void 0 ? { duration } : {},
1160
+ // derived
1161
+ size: fileMeta.size ?? file.size,
1162
+ extension: fileMeta ? extension : file.extension,
1163
+ mimeType: fileMeta.type || file.mimeType,
1164
+ type: fileMeta.type ? classifyFileType(fileMeta.type, extension) : file.type,
1165
+ // ------------------------------------------------------------------------
1166
+ // relations
1167
+ // ------------------------------------------------------------------------
1168
+ // Folder
1169
+ folder: updateOne(folder),
1170
+ // ------------------------------------------------------------------------
1171
+ // translation
1172
+ // ------------------------------------------------------------------------
1173
+ translations: {
1174
+ upsert: translations.map((t) => ({
1175
+ where: { fileId_locale: { fileId: id, locale: t.locale } },
1176
+ update: t,
1177
+ create: t
1178
+ }))
1179
+ }
1180
+ }
1181
+ });
1182
+ return updated;
1183
+ }
1184
+ async function softDelete({ id }) {
1185
+ const updated = await prisma.file.update({
1186
+ where: { id, isLocked: false },
1187
+ data: { deletedAt: /* @__PURE__ */ new Date() }
1188
+ });
1189
+ return updated;
1190
+ }
1191
+ async function softDeleteMany({ ids: ids2 }) {
1192
+ const { count } = await prisma.file.updateMany({
1193
+ where: { id: { in: ids2 }, isLocked: false },
1194
+ data: { deletedAt: /* @__PURE__ */ new Date() }
1195
+ });
1196
+ return count;
1197
+ }
1198
+ async function restoreMany({ ids: ids2 }) {
1199
+ const { count } = await prisma.file.updateMany({
1200
+ where: { id: { in: ids2 } },
1201
+ data: { deletedAt: null }
1202
+ });
1203
+ return count;
1204
+ }
1205
+ async function _delete({ id }) {
1206
+ const deleted = await prisma.file.delete({
1207
+ where: { id }
1208
+ });
1209
+ return deleted;
1210
+ }
1211
+ return {
1212
+ create,
1213
+ update,
1214
+ softDelete,
1215
+ softDeleteMany,
1216
+ restoreMany,
1217
+ delete: _delete
1218
+ };
1219
+ }
1220
+
1221
+ // src/server/infrastructure/database/utils/build-file-usage.ts
1222
+ function buildFileUsage(isLocked) {
1223
+ const relations = [
1224
+ "adminAsAvatarImage",
1225
+ "postsAsCoverImage",
1226
+ "postsAsContentImage",
1227
+ "postsAsImages1",
1228
+ "postsAsImages2",
1229
+ "postsAsImage1",
1230
+ "postsAsImage2",
1231
+ "postsAsImage3",
1232
+ "postsAsImage4"
1233
+ ];
1234
+ if (isLocked === void 0 || isLocked === null) return {};
1235
+ const condition = relations.map((r) => ({
1236
+ [r]: { [isLocked ? "some" : "none"]: {} }
1237
+ }));
1238
+ return isLocked ? { OR: condition } : { AND: condition };
1239
+ }
1240
+
1241
+ // src/server/infrastructure/database/file/query/create-file-query-repository.ts
1242
+ function createFileQueryRepository(prisma) {
1243
+ async function findListCards({
1244
+ locale,
1245
+ // pagination
1246
+ page,
1247
+ pageSize,
1248
+ // search
1249
+ searchString,
1250
+ type,
1251
+ folderId,
1252
+ isLocked,
1253
+ isDeleted = false,
1254
+ fileIds
1255
+ }) {
1256
+ const where = {
1257
+ // search
1258
+ ...createSearch({
1259
+ ...searchString !== void 0 ? { searchString } : {},
1260
+ locale,
1261
+ translationFields: ["name"]
1262
+ }),
1263
+ // type
1264
+ ...type ? { type } : {},
1265
+ // state: isLocked
1266
+ ...buildFileUsage(isLocked),
1267
+ // state: deleteAt
1268
+ deletedAt: isDeleted ? { not: null } : null,
1269
+ // Find deleted files in trash page
1270
+ // relations: Folder
1271
+ ...folderId ? { folderId: folderId === ROOT_FOLDER_ID ? null : folderId } : {},
1272
+ // Specified ids
1273
+ ...fileIds ? { id: { in: fileIds } } : {}
1274
+ };
1275
+ const [items, total] = await prisma.$transaction([
1276
+ prisma.file.findMany({
1277
+ where,
1278
+ orderBy: [...ORDER_BY],
1279
+ include: FILE_FULL_INCLUDE,
1280
+ ...createPagination(page, pageSize)
1281
+ }),
1282
+ prisma.file.count({ where })
1283
+ ]);
1284
+ return { items, total };
1285
+ }
1286
+ async function findManyByIds({
1287
+ ids: ids2
1288
+ }) {
1289
+ return prisma.file.findMany({
1290
+ where: { id: { in: ids2 } },
1291
+ include: FILE_FULL_INCLUDE
1292
+ });
1293
+ }
1294
+ async function findFull({ id }) {
1295
+ return prisma.file.findUnique({
1296
+ where: { id },
1297
+ include: FILE_FULL_INCLUDE
1298
+ });
1299
+ }
1300
+ return {
1301
+ findListCards,
1302
+ findManyByIds,
1303
+ findFull
1304
+ };
1305
+ }
1306
+
1307
+ // src/server/infrastructure/database/folder/command/create-folder-command-repository.ts
1308
+ function createFolderCommandRepository(prisma) {
1309
+ async function create({
1310
+ // core
1311
+ key,
1312
+ name,
1313
+ // ------------------------------------
1314
+ // relations
1315
+ // ------------------------------------
1316
+ // Folder
1317
+ parentFolder
1318
+ }) {
1319
+ const created = await prisma.folder.create({
1320
+ data: {
1321
+ // core
1322
+ key,
1323
+ name,
1324
+ // ------------------------------------------------------------------------
1325
+ // relations
1326
+ // ------------------------------------------------------------------------
1327
+ // Folder
1328
+ parentFolder: connectOne(parentFolder)
1329
+ }
1330
+ });
1331
+ return created;
1332
+ }
1333
+ async function update({
1334
+ id,
1335
+ // core
1336
+ key,
1337
+ name,
1338
+ // ------------------------------------
1339
+ // relations
1340
+ // ------------------------------------
1341
+ // Folder
1342
+ parentFolder
1343
+ }) {
1344
+ const updated = await prisma.folder.update({
1345
+ where: { id },
1346
+ data: {
1347
+ // core
1348
+ key,
1349
+ name,
1350
+ // ------------------------------------------------------------------------
1351
+ // relations
1352
+ // ------------------------------------------------------------------------
1353
+ // Folder
1354
+ parentFolder: updateOne(parentFolder)
1355
+ }
1356
+ });
1357
+ return updated;
1358
+ }
1359
+ async function _delete({ id }) {
1360
+ const deleted = await prisma.folder.delete({
1361
+ where: { id }
1362
+ });
1363
+ return deleted;
1364
+ }
1365
+ return {
1366
+ create,
1367
+ update,
1368
+ delete: _delete
1369
+ };
1370
+ }
1371
+
1372
+ // src/server/infrastructure/database/folder/include.ts
1373
+ var FOLDER_FULL_INCLUDE = {
1374
+ // ---------------------------
1375
+ // relations: Folder
1376
+ // ---------------------------
1377
+ parentFolder: { include: { subFolders: true, files: true } },
1378
+ subFolders: true,
1379
+ // ---------------------------
1380
+ // relations: File
1381
+ // ---------------------------
1382
+ files: true
1383
+ };
1384
+
1385
+ // src/server/infrastructure/database/folder/query/create-folder-query-repository.ts
1386
+ function buildWhere2(params) {
1387
+ if (params.id) return { id: params.id };
1388
+ if (params.key) return { key: params.key };
1389
+ return;
1390
+ }
1391
+ function createFolderQueryRepository(prisma) {
1392
+ async function findListCards({
1393
+ // search
1394
+ searchString,
1395
+ parentFolderId,
1396
+ folderIds,
1397
+ // pagination
1398
+ page,
1399
+ pageSize
1400
+ }) {
1401
+ const where = {
1402
+ // search
1403
+ ...searchString ? { name: { contains: searchString, mode: "insensitive" } } : {},
1404
+ // relations: Folder
1405
+ ...parentFolderId ? parentFolderId === ROOT_FOLDER_ID ? { parentFolderId: null } : { parentFolderId } : {},
1406
+ // Specified ids
1407
+ ...folderIds ? { id: { in: folderIds } } : {}
1408
+ };
1409
+ const [items, total] = await prisma.$transaction([
1410
+ prisma.folder.findMany({
1411
+ where,
1412
+ orderBy: [...ORDER_BY],
1413
+ include: FOLDER_FULL_INCLUDE,
1414
+ ...createPagination(page, pageSize)
1415
+ }),
1416
+ prisma.folder.count({ where })
1417
+ ]);
1418
+ return { items, total };
1419
+ }
1420
+ async function findFull(params) {
1421
+ const where = buildWhere2(params);
1422
+ if (!where) return null;
1423
+ return prisma.folder.findUnique({
1424
+ where,
1425
+ include: FOLDER_FULL_INCLUDE
1426
+ });
1427
+ }
1428
+ return {
1429
+ findListCards,
1430
+ findFull
1431
+ };
1432
+ }
1433
+ function createPostCommandRepository(prisma) {
1434
+ async function create({
1435
+ slug,
1436
+ // ------------------------------------
1437
+ // relations
1438
+ // ------------------------------------
1439
+ // Admin
1440
+ author,
1441
+ // Post
1442
+ topicId,
1443
+ parents,
1444
+ tags,
1445
+ relatedPosts,
1446
+ // File
1447
+ coverImage,
1448
+ contentImageIds,
1449
+ // ------------------------------------
1450
+ // --- custom fields
1451
+ // ------------------------------------
1452
+ // multi images
1453
+ images1,
1454
+ images2,
1455
+ // single images
1456
+ image1,
1457
+ image2,
1458
+ image3,
1459
+ image4,
1460
+ // ------------------------------------
1461
+ // translation
1462
+ // ------------------------------------
1463
+ translations,
1464
+ // rest
1465
+ ...params
1466
+ }) {
1467
+ const id = ulid();
1468
+ const created = await prisma.post.create({
1469
+ data: {
1470
+ ...params,
1471
+ id,
1472
+ // ------------------------------------------------------------------------
1473
+ // states
1474
+ // ------------------------------------------------------------------------
1475
+ slug: slug?.trim() || id,
1476
+ // Use id when slug is null or empty string
1477
+ // ------------------------------------------------------------------------
1478
+ // relations
1479
+ // ------------------------------------------------------------------------
1480
+ // Admin
1481
+ author: connectOne(author),
1482
+ // Post
1483
+ topic: connectOne(topicId ? { id: topicId } : null),
1484
+ parents: connectMany(parents),
1485
+ tags: connectMany(tags),
1486
+ relatedPosts: connectMany(relatedPosts),
1487
+ // File
1488
+ coverImage: connectOne(coverImage),
1489
+ contentImages: connectMany(contentImageIds.map((id2) => ({ id: id2 }))),
1490
+ // ------------------------------------------------------------------------
1491
+ // --- custom fields
1492
+ // ------------------------------------------------------------------------
1493
+ // multi images
1494
+ images1: connectMany(images1),
1495
+ images2: connectMany(images2),
1496
+ // single images
1497
+ image1: connectOne(image1),
1498
+ image2: connectOne(image2),
1499
+ image3: connectOne(image3),
1500
+ image4: connectOne(image4),
1501
+ // ------------------------------------------------------------------------
1502
+ // translation
1503
+ // ------------------------------------------------------------------------
1504
+ translations: {
1505
+ create: translations.map((t) => ({
1506
+ ...t,
1507
+ externalLinks: t.externalLinks,
1508
+ faq: t.faq,
1509
+ toc: t.toc
1510
+ }))
1511
+ }
1512
+ }
1513
+ });
1514
+ return created;
1515
+ }
1516
+ async function update({
1517
+ id,
1518
+ slug,
1519
+ // ------------------------------------
1520
+ // relations
1521
+ // ------------------------------------
1522
+ // Admin
1523
+ author,
1524
+ // Post
1525
+ topicId,
1526
+ parents,
1527
+ tags,
1528
+ relatedPosts,
1529
+ // File
1530
+ coverImage,
1531
+ contentImageIds,
1532
+ // ------------------------------------
1533
+ // --- custom fields
1534
+ // ------------------------------------
1535
+ // multi images
1536
+ images1,
1537
+ images2,
1538
+ // single images
1539
+ image1,
1540
+ image2,
1541
+ image3,
1542
+ image4,
1543
+ // ------------------------------------
1544
+ // translation
1545
+ // ------------------------------------
1546
+ translations,
1547
+ // rest
1548
+ ...params
1549
+ }) {
1550
+ const updated = await prisma.post.update({
1551
+ where: { id },
1552
+ data: {
1553
+ ...params,
1554
+ // ------------------------------------------------------------------------
1555
+ // states
1556
+ // ------------------------------------------------------------------------
1557
+ slug: slug?.trim() || id,
1558
+ // Use id when slug is null or empty string
1559
+ // ------------------------------------------------------------------------
1560
+ // relations
1561
+ // ------------------------------------------------------------------------
1562
+ // Admin
1563
+ author: updateOne(author),
1564
+ // Post
1565
+ topic: updateOne(topicId ? { id: topicId } : null),
1566
+ parents: updateMany(parents),
1567
+ tags: updateMany(tags),
1568
+ relatedPosts: updateMany(relatedPosts),
1569
+ // File
1570
+ coverImage: updateOne(coverImage),
1571
+ contentImages: updateMany(contentImageIds.map((id2) => ({ id: id2 }))),
1572
+ // ------------------------------------------------------------------------
1573
+ // --- custom fields
1574
+ // ------------------------------------------------------------------------
1575
+ // multi images
1576
+ images1: updateMany(images1),
1577
+ images2: updateMany(images2),
1578
+ // single images
1579
+ image1: updateOne(image1),
1580
+ image2: updateOne(image2),
1581
+ image3: updateOne(image3),
1582
+ image4: updateOne(image4),
1583
+ // ------------------------------------------------------------------------
1584
+ // translation
1585
+ // ------------------------------------------------------------------------
1586
+ translations: {
1587
+ upsert: translations.map((t) => ({
1588
+ where: { postId_locale: { postId: id, locale: t.locale } },
1589
+ update: {
1590
+ ...t,
1591
+ externalLinks: t.externalLinks,
1592
+ faq: t.faq,
1593
+ toc: t.toc
1594
+ },
1595
+ create: {
1596
+ ...t,
1597
+ externalLinks: t.externalLinks,
1598
+ faq: t.faq,
1599
+ toc: t.toc
1600
+ }
1601
+ }))
1602
+ }
1603
+ }
1604
+ });
1605
+ return updated;
1606
+ }
1607
+ async function _delete({ id }) {
1608
+ const deleted = await prisma.post.delete({
1609
+ where: { id }
1610
+ });
1611
+ return deleted;
1612
+ }
1613
+ return {
1614
+ create,
1615
+ update,
1616
+ delete: _delete
1617
+ };
1618
+ }
1619
+
1620
+ // src/server/infrastructure/database/post/include.ts
1621
+ var POST_LIST_CARD_INCLUDE = {
1622
+ // ---------------------------
1623
+ // relations: Post
1624
+ // ---------------------------
1625
+ topic: { include: { translations: true } },
1626
+ // Use as post, category
1627
+ postsInTopic: true,
1628
+ // Use as topic
1629
+ children: true,
1630
+ // Use as category
1631
+ taggedPosts: true,
1632
+ // Use as tag
1633
+ // ---------------------------
1634
+ // relations: File
1635
+ // ---------------------------
1636
+ coverImage: true,
1637
+ // ---------------------------
1638
+ // translation
1639
+ // ---------------------------
1640
+ translations: true
1641
+ };
1642
+ var POST_FULL_INCLUDE = {
1643
+ // ---------------------------
1644
+ // relations: Admin
1645
+ // ---------------------------
1646
+ author: { include: { translations: true }, omit: { passwordHash: true } },
1647
+ // ---------------------------
1648
+ // relations: Post
1649
+ // ---------------------------
1650
+ topic: { include: POST_LIST_CARD_INCLUDE },
1651
+ postsInTopic: { include: POST_LIST_CARD_INCLUDE },
1652
+ parents: { include: POST_LIST_CARD_INCLUDE },
1653
+ children: { include: POST_LIST_CARD_INCLUDE },
1654
+ tags: { include: POST_LIST_CARD_INCLUDE },
1655
+ taggedPosts: { include: POST_LIST_CARD_INCLUDE },
1656
+ relatedPosts: { include: POST_LIST_CARD_INCLUDE },
1657
+ referencingPosts: { include: POST_LIST_CARD_INCLUDE },
1658
+ // ---------------------------
1659
+ // relations: File
1660
+ // ---------------------------
1661
+ coverImage: { include: { translations: true } },
1662
+ // ---------------------------
1663
+ // relations: File
1664
+ // ---------------------------
1665
+ seoMetadatas: true,
1666
+ // ---------------------------
1667
+ // --- custom fields
1668
+ // ---------------------------
1669
+ images1: { include: { translations: true } },
1670
+ images2: { include: { translations: true } },
1671
+ image1: { include: { translations: true } },
1672
+ image2: { include: { translations: true } },
1673
+ image3: { include: { translations: true } },
1674
+ image4: { include: { translations: true } },
1675
+ // ---------------------------
1676
+ // translation
1677
+ // ---------------------------
1678
+ translations: true
1679
+ };
1680
+
1681
+ // src/server/infrastructure/database/post/query/create-post-query-repository.ts
1682
+ function buildWhere3(params) {
1683
+ if (params.id) return { id: params.id };
1684
+ if (params.type && params.slug)
1685
+ return { type: params.type, slug: params.slug };
1686
+ return;
1687
+ }
1688
+ function createPostQueryRepository(prisma) {
1689
+ async function findListCards({
1690
+ locale,
1691
+ // search
1692
+ searchString,
1693
+ // type
1694
+ type,
1695
+ // states
1696
+ isActive,
1697
+ isIndexActive,
1698
+ isSlugActive,
1699
+ isFeatured,
1700
+ isShownOnHome,
1701
+ state1,
1702
+ state2,
1703
+ state3,
1704
+ state4,
1705
+ state5,
1706
+ state6,
1707
+ state7,
1708
+ state8,
1709
+ state9,
1710
+ state10,
1711
+ // relations
1712
+ topicId,
1713
+ topicSlug,
1714
+ categoryId,
1715
+ categorySlug,
1716
+ // specified ids
1717
+ postIds,
1718
+ excludeIds,
1719
+ // pagination
1720
+ page,
1721
+ pageSize
1722
+ }) {
1723
+ const where = {
1724
+ // search
1725
+ ...createSearch({
1726
+ ...searchString ? { searchString } : {},
1727
+ locale,
1728
+ rootFields: ["slug"],
1729
+ translationFields: [
1730
+ "title",
1731
+ "subtitle",
1732
+ "summary",
1733
+ "description",
1734
+ "content"
1735
+ ]
1736
+ }),
1737
+ // type
1738
+ ...type ? { type } : {},
1739
+ // states
1740
+ ...isActive ? { isActive: true } : {},
1741
+ ...isIndexActive ? { isIndexActive: true } : {},
1742
+ ...isSlugActive ? { isSlugActive: true } : {},
1743
+ ...isFeatured ? { isFeatured: true } : {},
1744
+ ...isShownOnHome ? { isShownOnHome: true } : {},
1745
+ ...state1 ? { state1: true } : {},
1746
+ ...state2 ? { state2: true } : {},
1747
+ ...state3 ? { state3: true } : {},
1748
+ ...state4 ? { state4: true } : {},
1749
+ ...state5 ? { state5: true } : {},
1750
+ ...state6 ? { state6: true } : {},
1751
+ ...state7 ? { state7: true } : {},
1752
+ ...state8 ? { state8: true } : {},
1753
+ ...state9 ? { state9: true } : {},
1754
+ ...state10 ? { state10: true } : {},
1755
+ // relations
1756
+ ...topicId ? { topicId } : {},
1757
+ ...topicSlug ? { topic: { slug: topicSlug } } : {},
1758
+ ...categoryId ? { parents: { some: { id: categoryId } } } : {},
1759
+ ...categorySlug ? { parents: { some: { slug: categorySlug } } } : {},
1760
+ // Specified ids
1761
+ ...postIds?.length || excludeIds?.length ? {
1762
+ id: {
1763
+ ...postIds ? { in: postIds } : {},
1764
+ ...excludeIds ? { notIn: excludeIds } : {}
1765
+ }
1766
+ } : {}
1767
+ };
1768
+ const [items, total] = await prisma.$transaction([
1769
+ prisma.post.findMany({
1770
+ where,
1771
+ orderBy: POST_ORDER_BY,
1772
+ include: POST_LIST_CARD_INCLUDE,
1773
+ ...createPagination(page, pageSize)
1774
+ }),
1775
+ prisma.post.count({ where })
1776
+ ]);
1777
+ return { items, total };
1778
+ }
1779
+ async function find({
1780
+ isActive,
1781
+ ...rest
1782
+ }) {
1783
+ const where = buildWhere3(rest);
1784
+ if (!where) return null;
1785
+ return prisma.post.findFirst({
1786
+ where: { ...where, ...isActive ? { isActive } : {} },
1787
+ include: { translations: true }
1788
+ });
1789
+ }
1790
+ async function findMany({
1791
+ type,
1792
+ topicId
1793
+ }) {
1794
+ return prisma.post.findMany({
1795
+ where: { type, ...topicId ? { topicId } : {} },
1796
+ orderBy: POST_ORDER_BY,
1797
+ include: { translations: true }
1798
+ });
1799
+ }
1800
+ async function findFull({
1801
+ // states
1802
+ isActive,
1803
+ // relations
1804
+ topicSlug,
1805
+ ...rest
1806
+ }) {
1807
+ const where = buildWhere3(rest);
1808
+ if (!where) return null;
1809
+ return prisma.post.findFirst({
1810
+ where: {
1811
+ ...where,
1812
+ // states
1813
+ ...isActive ? { isActive } : {},
1814
+ // relations
1815
+ ...topicSlug ? { topic: { slug: topicSlug } } : {}
1816
+ },
1817
+ include: POST_FULL_INCLUDE
1818
+ });
1819
+ }
1820
+ return {
1821
+ findListCards,
1822
+ findMany,
1823
+ find,
1824
+ findFull
1825
+ };
1826
+ }
1827
+
1828
+ // src/server/infrastructure/database/seo-metadata/command/create-seo-metadata-command-repository.ts
1829
+ function createSeoMetadataCommandRepository(prisma) {
1830
+ async function upsert({
1831
+ // ------------------------------------
1832
+ // relations
1833
+ // ------------------------------------
1834
+ // Post
1835
+ postId,
1836
+ // ------------------------------------
1837
+ // translation
1838
+ // ------------------------------------
1839
+ translations
1840
+ }) {
1841
+ await prisma.post.update({
1842
+ where: { id: postId },
1843
+ data: {
1844
+ seoMetadatas: {
1845
+ upsert: translations.map((t) => ({
1846
+ where: { postId_locale: { postId, locale: t.locale } },
1847
+ update: { ...t, alternate: t.alternate },
1848
+ create: { ...t, alternate: t.alternate }
1849
+ }))
1850
+ }
1851
+ }
1852
+ });
1853
+ }
1854
+ return {
1855
+ upsert
1856
+ };
1857
+ }
1858
+
1859
+ // src/server/server-error.ts
1860
+ var ServerError = class _ServerError extends Error {
1861
+ i18nKey;
1862
+ statusCode;
1863
+ constructor({
1864
+ message,
1865
+ i18nKey,
1866
+ statusCode
1867
+ }) {
1868
+ super(message);
1869
+ this.name = this.constructor.name;
1870
+ if (i18nKey) this.i18nKey = i18nKey;
1871
+ if (statusCode) this.statusCode = statusCode;
1872
+ }
1873
+ /** 401 Unauthorized */
1874
+ static unauthorized() {
1875
+ return new _ServerError({ i18nKey: "error.unauthorized", statusCode: 401 });
1876
+ }
1877
+ /** 403 Forbidden */
1878
+ static forbidden() {
1879
+ return new _ServerError({ i18nKey: "error.forbidden", statusCode: 403 });
1880
+ }
1881
+ /** 404 Not found */
1882
+ static notFound() {
1883
+ return new _ServerError({ i18nKey: "error.not-found", statusCode: 404 });
1884
+ }
1885
+ /** 500 Internal Server Error */
1886
+ static internalServerError() {
1887
+ return new _ServerError({
1888
+ i18nKey: "error.internal-server-error",
1889
+ statusCode: 500
1890
+ });
1891
+ }
1892
+ };
1893
+
1894
+ // src/server/interfaces/execution/normalize-error.ts
1895
+ var normalizeError = (error, translator) => {
1896
+ if (error instanceof ZodError) {
1897
+ const errors = error.issues.map((issue) => {
1898
+ let message = issue.message;
1899
+ if (issue.code === "custom" && issue.params?.["i18nKey"]) {
1900
+ message = translator.t(issue.params["i18nKey"]);
1901
+ }
1902
+ return {
1903
+ field: issue.path.join("."),
1904
+ // e.path: string[] e.g. ["name", "email"]
1905
+ message,
1906
+ code: issue.code
1907
+ };
1908
+ });
1909
+ return { message: "Validation faild", errors, statusCode: 422 };
1910
+ }
1911
+ if (error instanceof ServerError) {
1912
+ const message = translator.t(
1913
+ error.i18nKey ?? "error.internal-server-error"
1914
+ );
1915
+ return { message, statusCode: error.statusCode ?? 500 };
1916
+ }
1917
+ return {
1918
+ message: error instanceof Error ? error.message : JSON.stringify(error),
1919
+ statusCode: 500,
1920
+ isInternal: true
1921
+ };
1922
+ };
1923
+
1924
+ // src/server/interfaces/execution/execute-action/create-execute-action.ts
1925
+ function createExecuteAction({
1926
+ initI18n,
1927
+ cacheResult,
1928
+ cache: cache2,
1929
+ logger
1930
+ }) {
1931
+ return async function executeAction(fn, options = {}) {
1932
+ const translator = await initI18n();
1933
+ const withCache = options.type === "query" && options.key;
1934
+ try {
1935
+ const { data, i18nKey, message, meta } = withCache ? await cacheResult({
1936
+ key: options.key,
1937
+ ...options.ttl ? { ttl: options.ttl } : {},
1938
+ load: async () => fn(translator)
1939
+ }) : await fn(translator);
1940
+ if (options.type === "command") await cache2.clear();
1941
+ const finalMessage = i18nKey ? translator.t(i18nKey) : message;
1942
+ return result.success({
1943
+ ...finalMessage ? { message: finalMessage } : {},
1944
+ ...data !== void 0 ? { data } : {},
1945
+ ...meta ? { meta } : {}
1946
+ });
1947
+ } catch (error) {
1948
+ const { message, errors, isInternal } = normalizeError(error, translator);
1949
+ logger.error({ message, errors });
1950
+ return result.error({
1951
+ message: isInternal ? "Internal server error" : message,
1952
+ ...errors !== void 0 ? { errors } : {}
1953
+ });
1954
+ }
1955
+ };
1956
+ }
1957
+ function createExecuteApi({ initI18n, logger }) {
1958
+ return async function serverApi(fn) {
1959
+ const translator = await initI18n();
1960
+ try {
1961
+ return await fn(translator);
1962
+ } catch (error) {
1963
+ const { message, errors, statusCode, isInternal } = normalizeError(
1964
+ error,
1965
+ translator
1966
+ );
1967
+ logger.error({ message, errors });
1968
+ return NextResponse.json(
1969
+ result.error({
1970
+ message: isInternal ? "Internal server error" : message,
1971
+ ...errors ? { errors } : {}
1972
+ }),
1973
+ { status: statusCode }
1974
+ );
1975
+ }
1976
+ };
1977
+ }
1978
+
1979
+ // src/server/interfaces/middlewares/auth/create-auth-middleware.ts
1980
+ function createAuthMiddleware({
1981
+ adminRefreshTokenCommandRepository,
1982
+ authUseCases,
1983
+ verifyAccessToken,
1984
+ verifyRefreshToken,
1985
+ headers
1986
+ }) {
1987
+ const authMiddleware = {
1988
+ async authenticate() {
1989
+ const verifiedAccessToken = await verifyAccessToken();
1990
+ if (verifiedAccessToken) return verifiedAccessToken.admin;
1991
+ const verifiedRefreshToken = await verifyRefreshToken();
1992
+ if (!verifiedRefreshToken) throw ServerError.unauthorized();
1993
+ const { adminRefreshToken, admin } = verifiedRefreshToken;
1994
+ await adminRefreshTokenCommandRepository.delete({
1995
+ id: adminRefreshToken.id
1996
+ });
1997
+ await authUseCases.refreshTokens({
1998
+ admin,
1999
+ deviceInfo: adminRefreshToken.deviceInfo,
2000
+ ip: (await headers()).get("x-forwarded-for") || "unknown"
2001
+ });
2002
+ return admin;
2003
+ }
2004
+ };
2005
+ return authMiddleware;
2006
+ }
2007
+
2008
+ // src/server/interfaces/middlewares/auth/create-verify-access-token.ts
2009
+ function createVerifyAccessToken({
2010
+ adminQueryRepository,
2011
+ jwtService,
2012
+ cryptoService,
2013
+ cookieService,
2014
+ config
2015
+ }) {
2016
+ return async function verifyAccessToken() {
2017
+ try {
2018
+ const token = await cookieService.getSignedCookie({
2019
+ name: config.accessTokenName
2020
+ });
2021
+ const payload = jwtService.verify({
2022
+ token,
2023
+ secret: cryptoService.hash(config.accessTokenSecret)
2024
+ });
2025
+ const admin = await adminQueryRepository.findFull({
2026
+ id: payload["id"]
2027
+ });
2028
+ return admin ? { admin } : null;
2029
+ } catch {
2030
+ return null;
2031
+ }
2032
+ };
2033
+ }
2034
+
2035
+ // src/server/interfaces/middlewares/auth/create-verify-refresh-token.ts
2036
+ function createVerifyRefreshToken({
2037
+ adminQueryRepository,
2038
+ adminRefreshTokenQueryRepository,
2039
+ cryptoService,
2040
+ cookieService,
2041
+ config
2042
+ }) {
2043
+ return async function verifyRefreshToken() {
2044
+ try {
2045
+ const token = await cookieService.getSignedCookie({
2046
+ name: config.refreshTokenName
2047
+ });
2048
+ const adminRefreshToken = await adminRefreshTokenQueryRepository.findByToken({
2049
+ tokenHash: cryptoService.hash(token)
2050
+ });
2051
+ if (!adminRefreshToken) return null;
2052
+ const admin = await adminQueryRepository.findFull({
2053
+ id: adminRefreshToken.adminId
2054
+ });
2055
+ return admin ? { adminRefreshToken, admin } : null;
2056
+ } catch {
2057
+ return null;
2058
+ }
2059
+ };
2060
+ }
2061
+
2062
+ // src/server/interfaces/actions/auth/sign-in/sign-in-validator.ts
2063
+ var signInValidator = (schemas) => schemas.z.object({
2064
+ email: schemas.email(),
2065
+ password: schemas.password()
2066
+ });
2067
+
2068
+ // src/server/interfaces/actions/auth/sign-in/create-sign-in-action.ts
2069
+ function createSignInAction(ctx) {
2070
+ return async function signInAction({
2071
+ formData,
2072
+ deviceInfo
2073
+ }) {
2074
+ const {
2075
+ repositories: {
2076
+ adminQueryRepository,
2077
+ adminRefreshTokenCommandRepository
2078
+ },
2079
+ useCases: { authUseCases },
2080
+ action: { executeAction, ipRateLimiter },
2081
+ http: { headers },
2082
+ schemas: { schemas }
2083
+ } = ctx;
2084
+ return executeAction(
2085
+ async () => {
2086
+ await ipRateLimiter({ key: ["sign-in"] });
2087
+ const { email, password } = await signInValidator(schemas).parseAsync(formData);
2088
+ const verified = await authUseCases.verifyCredentials({
2089
+ email,
2090
+ password
2091
+ });
2092
+ const admin = await adminQueryRepository.findFull({
2093
+ id: verified.id
2094
+ });
2095
+ if (!admin) throw ServerError.notFound();
2096
+ await authUseCases.refreshTokens({
2097
+ admin,
2098
+ deviceInfo,
2099
+ ip: (await headers()).get("x-forwarded-for") || "unknown"
2100
+ });
2101
+ await adminRefreshTokenCommandRepository.deleteManyByExpired();
2102
+ return {
2103
+ i18nKey: "ok.sign-in-ok",
2104
+ data: { admin }
2105
+ };
2106
+ },
2107
+ { type: "command" }
2108
+ );
2109
+ };
2110
+ }
2111
+
2112
+ // src/server/interfaces/actions/auth/sign-out/create-sign-out-action.ts
2113
+ function createSignOutAction(ctx) {
2114
+ const {
2115
+ services: { cryptoService, cookieService },
2116
+ repositories: { adminRefreshTokenCommandRepository },
2117
+ action: { executeAction },
2118
+ config: { accessTokenName, refreshTokenName }
2119
+ } = ctx;
2120
+ return async function signOutAction() {
2121
+ return executeAction(
2122
+ async () => {
2123
+ let token;
2124
+ try {
2125
+ token = await cookieService.getSignedCookie({
2126
+ name: refreshTokenName
2127
+ });
2128
+ } catch {
2129
+ }
2130
+ if (token) {
2131
+ await adminRefreshTokenCommandRepository.delete({
2132
+ tokenHash: cryptoService.hash(token)
2133
+ });
2134
+ }
2135
+ await cookieService.delete({ name: accessTokenName });
2136
+ await cookieService.delete({ name: refreshTokenName });
2137
+ return {
2138
+ i18nKey: "ok.sign-out-ok"
2139
+ };
2140
+ },
2141
+ { type: "command" }
2142
+ );
2143
+ };
2144
+ }
2145
+
2146
+ // src/server/interfaces/actions/auth/verify/create-verify-action.ts
2147
+ function createVerifyAction(ctx) {
2148
+ const {
2149
+ middlewares: { authMiddleware },
2150
+ action: { executeAction, ipRateLimiter }
2151
+ } = ctx;
2152
+ return async function verifyAction() {
2153
+ return executeAction(async () => {
2154
+ await ipRateLimiter({ key: ["verify"], maxAttempts: 60 });
2155
+ const admin = await authMiddleware.authenticate();
2156
+ return {
2157
+ data: { admin }
2158
+ };
2159
+ });
2160
+ };
2161
+ }
2162
+
2163
+ // src/server/interfaces/actions/auth/change-password/change-password-validator.ts
2164
+ var changePasswordValidator = (schemas) => schemas.z.object({
2165
+ password: schemas.password(),
2166
+ newPassword: schemas.password(),
2167
+ newPasswordConfirm: schemas.password()
2168
+ }).superRefine((data, ctx) => {
2169
+ if (data.newPassword !== data.newPasswordConfirm) {
2170
+ ctx.addIssue({
2171
+ code: "custom",
2172
+ params: { i18nKey: "validator.password-confirm" },
2173
+ path: ["newPassword"]
2174
+ });
2175
+ ctx.addIssue({
2176
+ code: "custom",
2177
+ params: { i18nKey: "validator.password-confirm" },
2178
+ path: ["newPasswordConfirm"]
2179
+ });
2180
+ }
2181
+ });
2182
+
2183
+ // src/server/interfaces/actions/auth/change-password/create-change-password-action.ts
2184
+ function createChangePasswordAction(ctx) {
2185
+ return async function changePasswordAction({
2186
+ formData
2187
+ }) {
2188
+ const {
2189
+ action: { executeAction, ipRateLimiter },
2190
+ useCases: { authUseCases },
2191
+ middlewares: { authMiddleware },
2192
+ schemas: { schemas }
2193
+ } = ctx;
2194
+ return executeAction(
2195
+ async () => {
2196
+ await ipRateLimiter({ key: ["change-password"] });
2197
+ const { email } = await authMiddleware.authenticate();
2198
+ const { password, newPassword } = await changePasswordValidator(schemas).parseAsync(formData);
2199
+ await authUseCases.verifyCredentials({ email, password });
2200
+ await authUseCases.updatePassword({ email, password: newPassword });
2201
+ return {
2202
+ i18nKey: "ok.change-password-ok"
2203
+ };
2204
+ },
2205
+ { type: "command" }
2206
+ );
2207
+ };
2208
+ }
2209
+
2210
+ // src/server/interfaces/actions/auth/verify-email/verify-email-validator.ts
2211
+ var verifyEmailValidator = (schemas) => schemas.z.object({
2212
+ email: schemas.email(),
2213
+ emailVerificationToken: schemas.z.string().trim().max(1e3)
2214
+ });
2215
+
2216
+ // src/server/interfaces/actions/auth/verify-email/create-verify-email-action.ts
2217
+ function createVerifyEmailAction(ctx) {
2218
+ return async function verifyEmailAction({
2219
+ formData
2220
+ }) {
2221
+ const {
2222
+ repositories: { adminQueryRepository },
2223
+ useCases: { authUseCases },
2224
+ action: { executeAction, ipRateLimiter },
2225
+ schemas: { schemas }
2226
+ } = ctx;
2227
+ return executeAction(
2228
+ async () => {
2229
+ await ipRateLimiter({ key: ["verify-email"] });
2230
+ const { email, emailVerificationToken } = await verifyEmailValidator(schemas).parseAsync(formData);
2231
+ const admin = await adminQueryRepository.find({ email });
2232
+ if (!admin) throw new ServerError({ i18nKey: "error.email-not-found" });
2233
+ await authUseCases.verifyEmailAndUpdate({
2234
+ token: emailVerificationToken || "",
2235
+ admin
2236
+ });
2237
+ return {
2238
+ i18nKey: "ok.verify-email-ok",
2239
+ data: { admin }
2240
+ };
2241
+ },
2242
+ { type: "command" }
2243
+ );
2244
+ };
2245
+ }
2246
+
2247
+ // src/server/interfaces/actions/auth/email-unverifired/email-unverifired-validator.ts
2248
+ var emailUnverifiedValidator = (schemas) => schemas.z.object({
2249
+ email: schemas.email()
2250
+ });
2251
+
2252
+ // src/server/interfaces/actions/auth/email-unverifired/create-email-unverifired-action.ts
2253
+ function createEmailUnverifiedAction(ctx) {
2254
+ return async function emailUnverifiedAction({
2255
+ formData
2256
+ }) {
2257
+ const {
2258
+ repositories: { adminQueryRepository },
2259
+ action: { executeAction, ipRateLimiter },
2260
+ emails: { emailVerificationEmail },
2261
+ schemas: { schemas }
2262
+ } = ctx;
2263
+ return executeAction(
2264
+ async (translator) => {
2265
+ await ipRateLimiter({
2266
+ key: ["email-unverified"],
2267
+ maxAttempts: 2,
2268
+ timeWindow: 60
2269
+ });
2270
+ const { email } = await emailUnverifiedValidator(schemas).parseAsync(formData);
2271
+ const admin = await adminQueryRepository.find({ email });
2272
+ if (!admin) throw new ServerError({ i18nKey: "error.email-not-found" });
2273
+ void emailVerificationEmail.send({ translator, admin });
2274
+ return {
2275
+ i18nKey: "ok.email-unverified-ok"
2276
+ };
2277
+ },
2278
+ { type: "command" }
2279
+ );
2280
+ };
2281
+ }
2282
+
2283
+ // src/server/interfaces/actions/auth/forgot-password/forgot-password-validator.ts
2284
+ var forgetPasswordValidator = (schemas) => schemas.z.object({
2285
+ email: schemas.email()
2286
+ });
2287
+
2288
+ // src/server/interfaces/actions/auth/forgot-password/create-forgot-password-action.ts
2289
+ function createForgotPasswordAction(ctx) {
2290
+ return async function forgotPasswordAction({
2291
+ formData
2292
+ }) {
2293
+ const {
2294
+ repositories: { adminQueryRepository },
2295
+ action: { executeAction, ipRateLimiter },
2296
+ emails: { forgotPasswordEmail },
2297
+ schemas: { schemas }
2298
+ } = ctx;
2299
+ return executeAction(
2300
+ async (translator) => {
2301
+ await ipRateLimiter({
2302
+ key: ["forget-password"],
2303
+ maxAttempts: 2,
2304
+ timeWindow: 60
2305
+ });
2306
+ const { email } = await forgetPasswordValidator(schemas).parseAsync(formData);
2307
+ const admin = await adminQueryRepository.find({ email });
2308
+ if (!admin) throw new ServerError({ i18nKey: "error.email-not-found" });
2309
+ void forgotPasswordEmail.send({ translator, admin });
2310
+ return {
2311
+ i18nKey: "ok.forgot-password-ok"
2312
+ };
2313
+ },
2314
+ { type: "command" }
2315
+ );
2316
+ };
2317
+ }
2318
+
2319
+ // src/server/interfaces/actions/auth/reset-password/reset-password-validator.ts
2320
+ var resetPasswordValidator = (schemas) => schemas.z.object({
2321
+ passwordResetToken: schemas.z.string().trim().max(1e3),
2322
+ newPassword: schemas.password(),
2323
+ newPasswordConfirm: schemas.password()
2324
+ }).superRefine((data, ctx) => {
2325
+ if (data.newPassword !== data.newPasswordConfirm) {
2326
+ ctx.addIssue({
2327
+ code: "custom",
2328
+ message: "custom.password-confirm",
2329
+ path: ["newPassword"]
2330
+ });
2331
+ ctx.addIssue({
2332
+ code: "custom",
2333
+ message: "custom.password-confirm",
2334
+ path: ["newPasswordConfirm"]
2335
+ });
2336
+ }
2337
+ });
2338
+
2339
+ // src/server/interfaces/actions/auth/reset-password/create-reset-password-action.ts
2340
+ function createResetPasswordAction(ctx) {
2341
+ return async function ResetPasswordAction({
2342
+ formData
2343
+ }) {
2344
+ const {
2345
+ useCases: { authUseCases },
2346
+ action: { executeAction, ipRateLimiter },
2347
+ schemas: { schemas }
2348
+ } = ctx;
2349
+ return executeAction(
2350
+ async () => {
2351
+ await ipRateLimiter({ key: ["reset-password"] });
2352
+ const { newPassword, passwordResetToken } = await resetPasswordValidator(schemas).parseAsync(formData);
2353
+ const decoded = authUseCases.verifyPasswordResetToken({
2354
+ token: passwordResetToken
2355
+ });
2356
+ await authUseCases.updatePassword({
2357
+ email: decoded.email,
2358
+ password: newPassword
2359
+ });
2360
+ return {
2361
+ i18nKey: "ok.reset-password-ok"
2362
+ };
2363
+ },
2364
+ { type: "command" }
2365
+ );
2366
+ };
2367
+ }
2368
+
2369
+ // src/server/interfaces/actions/resources/admin/commands/create/admin-create-validator.ts
2370
+ var adminCreateValidator = (schemas) => schemas.z.object({
2371
+ // core
2372
+ role: schemas.z.enum(Object.values(ADMIN_ROLES)),
2373
+ email: schemas.email().unique({ table: "admins", column: "email" }),
2374
+ password: schemas.password(),
2375
+ // better seo
2376
+ socialLinks: schemas.array(schemas.url()),
2377
+ // ----------------------------------------------------------------------------
2378
+ // relations
2379
+ // ----------------------------------------------------------------------------
2380
+ // File
2381
+ avatarImage: schemas.z.object({ id: schemas.id().exist({ table: "files" }) }).nullable(),
2382
+ // ----------------------------------------------------------------------------
2383
+ // translation
2384
+ // ----------------------------------------------------------------------------
2385
+ translations: schemas.array(
2386
+ schemas.z.object({
2387
+ // core
2388
+ locale: schemas.locale(),
2389
+ // text
2390
+ name: schemas.text().nullable(),
2391
+ // better seo
2392
+ authorName: schemas.text().nullable(),
2393
+ description: schemas.text().nullable(),
2394
+ jobTitle: schemas.text().nullable(),
2395
+ url: schemas.url().nullable(),
2396
+ worksFor: schemas.text().nullable(),
2397
+ knowsAbout: schemas.array(schemas.text()),
2398
+ homeLocation: schemas.text().nullable(),
2399
+ nationality: schemas.text().nullable()
2400
+ })
2401
+ )
2402
+ });
2403
+
2404
+ // src/server/interfaces/actions/resources/admin/commands/create/create-admin-create-action.ts
2405
+ function createAdminCreateAction(ctx) {
2406
+ const {
2407
+ services: { argon2Service },
2408
+ repositories: { adminCommandRepository },
2409
+ middlewares: { authMiddleware },
2410
+ action: { executeAction },
2411
+ emails: { emailVerificationEmail },
2412
+ schemas: { schemas }
2413
+ } = ctx;
2414
+ return async function adminCreateAction({
2415
+ formData
2416
+ }) {
2417
+ return executeAction(
2418
+ async (translator) => {
2419
+ await authMiddleware.authenticate();
2420
+ const {
2421
+ role,
2422
+ email,
2423
+ password,
2424
+ socialLinks,
2425
+ avatarImage,
2426
+ translations
2427
+ } = await adminCreateValidator(schemas).parseAsync(formData);
2428
+ const passwordHash = await argon2Service.hash(password);
2429
+ const created = await adminCommandRepository.create({
2430
+ role,
2431
+ email,
2432
+ passwordHash,
2433
+ socialLinks,
2434
+ avatarImage,
2435
+ translations
2436
+ });
2437
+ void emailVerificationEmail.send({ translator, admin: created });
2438
+ return {
2439
+ i18nKey: "ok.admins-store-ok",
2440
+ data: { admin: created }
2441
+ };
2442
+ },
2443
+ { type: "command" }
2444
+ );
2445
+ };
2446
+ }
2447
+
2448
+ // src/server/interfaces/actions/resources/admin/commands/update/admin-update-validator.ts
2449
+ var adminUpdateValidator = (schemas, id) => schemas.z.object({
2450
+ // core
2451
+ role: schemas.z.enum(Object.values(ADMIN_ROLES)),
2452
+ email: schemas.email().unique({
2453
+ table: "admins",
2454
+ column: "email",
2455
+ excludeSelf: { name: "id", value: id }
2456
+ }).optional(),
2457
+ // better seo
2458
+ socialLinks: schemas.array(schemas.url()),
2459
+ // ----------------------------------------------------------------------------
2460
+ // relations
2461
+ // ----------------------------------------------------------------------------
2462
+ // File
2463
+ avatarImage: schemas.z.object({ id: schemas.id().exist({ table: "files" }) }).nullable(),
2464
+ // ----------------------------------------------------------------------------
2465
+ // translation
2466
+ // ----------------------------------------------------------------------------
2467
+ translations: schemas.array(
2468
+ schemas.z.object({
2469
+ // core
2470
+ locale: schemas.locale(),
2471
+ // text
2472
+ name: schemas.text().nullable(),
2473
+ // better seo
2474
+ authorName: schemas.text().nullable(),
2475
+ description: schemas.text().nullable(),
2476
+ jobTitle: schemas.text().nullable(),
2477
+ url: schemas.url().nullable(),
2478
+ worksFor: schemas.text().nullable(),
2479
+ knowsAbout: schemas.array(schemas.text()),
2480
+ homeLocation: schemas.text().nullable(),
2481
+ nationality: schemas.text().nullable()
2482
+ })
2483
+ )
2484
+ });
2485
+
2486
+ // src/server/interfaces/actions/resources/admin/commands/update/create-admin-update-action.ts
2487
+ function createAdminUpdateAction(ctx) {
2488
+ const {
2489
+ repositories: { adminQueryRepository, adminCommandRepository },
2490
+ middlewares: { authMiddleware },
2491
+ action: { executeAction },
2492
+ emails: { emailVerificationEmail },
2493
+ schemas: { schemas }
2494
+ } = ctx;
2495
+ return async function adminUpdateAction({
2496
+ id,
2497
+ formData
2498
+ }) {
2499
+ return executeAction(
2500
+ async (translator) => {
2501
+ const currentAdmin = await authMiddleware.authenticate();
2502
+ const targetAdmin = await adminQueryRepository.findFull({ id });
2503
+ if (!targetAdmin) throw ServerError.notFound();
2504
+ const isSelf = currentAdmin.id === targetAdmin.id;
2505
+ const canModifyOthers = currentAdmin.role === ADMIN_ROLES.SUPER_ADMIN;
2506
+ if (!isSelf && !canModifyOthers) throw ServerError.forbidden();
2507
+ const { role, email, socialLinks, avatarImage, translations } = await adminUpdateValidator(schemas, targetAdmin.id).parseAsync(
2508
+ formData
2509
+ );
2510
+ const isUpdatingEmail = email !== targetAdmin.email;
2511
+ if (isUpdatingEmail) {
2512
+ void emailVerificationEmail.send({
2513
+ translator,
2514
+ admin: targetAdmin,
2515
+ ...email ? { newEmail: email } : {}
2516
+ });
2517
+ }
2518
+ const updatedAdmin = await adminCommandRepository.update({
2519
+ id: targetAdmin.id,
2520
+ role,
2521
+ ...email !== void 0 ? { email } : {},
2522
+ socialLinks,
2523
+ avatarImage,
2524
+ translations,
2525
+ emailVerifiedAt: !isUpdatingEmail ? targetAdmin.emailVerifiedAt : null
2526
+ // Clear emailVerifiedAt if updating a new email
2527
+ });
2528
+ return {
2529
+ i18nKey: !isUpdatingEmail ? "ok.update-ok" : "ok.admins-update-email-ok",
2530
+ data: { admin: updatedAdmin }
2531
+ };
2532
+ },
2533
+ { type: "command" }
2534
+ );
2535
+ };
2536
+ }
2537
+
2538
+ // src/server/interfaces/actions/resources/admin/commands/delete/create-admin-delete-action.ts
2539
+ function createAdminDeleteAction(ctx) {
2540
+ const {
2541
+ repositories: { adminQueryRepository, adminCommandRepository },
2542
+ middlewares: { authMiddleware },
2543
+ action: { executeAction }
2544
+ } = ctx;
2545
+ return async function adminDeleteAction({ targetId }) {
2546
+ return executeAction(
2547
+ async () => {
2548
+ await authMiddleware.authenticate();
2549
+ const targetAdmin = await adminQueryRepository.findFull({
2550
+ id: targetId
2551
+ });
2552
+ if (!targetAdmin) throw ServerError.notFound();
2553
+ if (targetAdmin.role === ADMIN_ROLES.SUPER_ADMIN) {
2554
+ throw new ServerError({ i18nKey: "error.admins-destroy-forbidden" });
2555
+ }
2556
+ await adminCommandRepository.delete({ id: targetId });
2557
+ return {
2558
+ i18nKey: "ok.destroy-ok"
2559
+ };
2560
+ },
2561
+ { type: "command" }
2562
+ );
2563
+ };
2564
+ }
2565
+
2566
+ // src/server/interfaces/actions/resources/admin/queries/create-admin-find-full-action.ts
2567
+ function createAdminFindFullAction(ctx) {
2568
+ const {
2569
+ repositories: { adminQueryRepository },
2570
+ middlewares: { authMiddleware },
2571
+ action: { executeAction }
2572
+ } = ctx;
2573
+ return async function adminFindFullAction(params) {
2574
+ return executeAction(
2575
+ async () => {
2576
+ await authMiddleware.authenticate();
2577
+ const admin = await adminQueryRepository.findFull(params);
2578
+ if (!admin) throw ServerError.notFound();
2579
+ return {
2580
+ data: { admin }
2581
+ };
2582
+ },
2583
+ {
2584
+ type: "query",
2585
+ key: ["admin", "findFullAction", params.id, params.email]
2586
+ }
2587
+ );
2588
+ };
2589
+ }
2590
+
2591
+ // src/server/interfaces/actions/resources/admin/queries/create-admin-find-list-cards-action.ts
2592
+ function createAdminFindListCardsAction(ctx) {
2593
+ const {
2594
+ repositories: { adminQueryRepository },
2595
+ middlewares: { authMiddleware },
2596
+ action: { executeAction }
2597
+ } = ctx;
2598
+ return async function adminFindFullAction(params) {
2599
+ return executeAction(
2600
+ async ({ locale }) => {
2601
+ const { role } = await authMiddleware.authenticate();
2602
+ const { items, total } = await adminQueryRepository.findListCards({
2603
+ ...params,
2604
+ locale,
2605
+ role
2606
+ });
2607
+ return {
2608
+ data: { items, total }
2609
+ };
2610
+ },
2611
+ {
2612
+ type: "query",
2613
+ key: [
2614
+ "admin",
2615
+ "findListCardsAction",
2616
+ params.searchString,
2617
+ ...params.adminIds ?? [],
2618
+ params.page,
2619
+ params.pageSize
2620
+ ]
2621
+ }
2622
+ );
2623
+ };
2624
+ }
2625
+
2626
+ // src/server/interfaces/actions/resources/admin-refresh-token/commands/delete/create-admin-refresh-token-delete-action.ts
2627
+ function createAdminRefreshTokenDeleteAction(ctx) {
2628
+ const {
2629
+ repositories: {
2630
+ adminRefreshTokenQueryRepository,
2631
+ adminRefreshTokenCommandRepository
2632
+ },
2633
+ middlewares: { authMiddleware },
2634
+ action: { executeAction }
2635
+ } = ctx;
2636
+ return async function adminRefreshTokenDeleteAction(tokenHash) {
2637
+ return executeAction(
2638
+ async () => {
2639
+ const currentAdmin = await authMiddleware.authenticate();
2640
+ const targetAdminRefreshToken = await adminRefreshTokenQueryRepository.findByToken({
2641
+ tokenHash
2642
+ });
2643
+ if (!targetAdminRefreshToken) throw ServerError.notFound();
2644
+ const isSelf = currentAdmin.id === targetAdminRefreshToken.adminId;
2645
+ const canModifyOthers = currentAdmin.role === ADMIN_ROLES.SUPER_ADMIN;
2646
+ if (!isSelf && !canModifyOthers) throw ServerError.forbidden();
2647
+ await adminRefreshTokenCommandRepository.delete({
2648
+ id: targetAdminRefreshToken.id
2649
+ });
2650
+ return {
2651
+ i18nKey: "ok.destroy-ok"
2652
+ };
2653
+ },
2654
+ { type: "command" }
2655
+ );
2656
+ };
2657
+ }
2658
+
2659
+ // src/server/interfaces/actions/resources/admin-refresh-token/queries/create-admin-refresh-token-find-full-action.ts
2660
+ function createAdminRefreshTokenFindManyAction(ctx) {
2661
+ const {
2662
+ services: { cryptoService, cookieService },
2663
+ repositories: { adminRefreshTokenQueryRepository },
2664
+ middlewares: { authMiddleware },
2665
+ action: { executeAction },
2666
+ config: { refreshTokenName }
2667
+ } = ctx;
2668
+ return async function adminRefreshTokenFindManyAction({
2669
+ adminId
2670
+ }) {
2671
+ return executeAction(
2672
+ async () => {
2673
+ await authMiddleware.authenticate();
2674
+ const token = await cookieService.getSignedCookie({
2675
+ name: refreshTokenName
2676
+ });
2677
+ const currentToken = await adminRefreshTokenQueryRepository.findByToken(
2678
+ { tokenHash: cryptoService.hash(token) }
2679
+ );
2680
+ if (!currentToken) throw ServerError.notFound();
2681
+ const adminRefreshTokens = await adminRefreshTokenQueryRepository.findManyByAdminId({
2682
+ adminId
2683
+ });
2684
+ return {
2685
+ data: {
2686
+ adminRefreshTokens,
2687
+ currentToken
2688
+ }
2689
+ };
2690
+ },
2691
+ {
2692
+ type: "query",
2693
+ key: ["admin-refresh-token", "findManyAction", adminId]
2694
+ }
2695
+ );
2696
+ };
2697
+ }
2698
+
2699
+ // src/server/interfaces/actions/resources/file/commands/create/file-create-validator.ts
2700
+ var fileCreateValidator = (schemas) => schemas.z.object({
2701
+ key: schemas.key(),
2702
+ checksum: schemas.sha256Hash(),
2703
+ // file meta
2704
+ fileMeta: schemas.z.object({
2705
+ type: schemas.text(),
2706
+ size: schemas.positiveNumber(),
2707
+ name: schemas.text()
2708
+ }),
2709
+ // media info
2710
+ width: schemas.positiveNumber().nullable().optional(),
2711
+ height: schemas.positiveNumber().nullable().optional(),
2712
+ duration: schemas.positiveNumber().nullable().optional(),
2713
+ // ----------------------------------------------------------------------------
2714
+ // relations
2715
+ // ----------------------------------------------------------------------------
2716
+ folder: schemas.z.object({
2717
+ id: schemas.id().exist({ table: "folders", column: "id" })
2718
+ }).nullable().optional(),
2719
+ // Use in [file] pages
2720
+ folderKey: schemas.key().exist({ table: "folders", column: "key" }).optional(),
2721
+ // Use in Simple Upload
2722
+ // ----------------------------------------------------------------------------
2723
+ // translation
2724
+ // ----------------------------------------------------------------------------
2725
+ translations: schemas.array(
2726
+ schemas.z.object({
2727
+ // core
2728
+ locale: schemas.locale(),
2729
+ // text
2730
+ name: schemas.text().nullable(),
2731
+ alt: schemas.text().nullable()
2732
+ })
2733
+ )
2734
+ });
2735
+
2736
+ // src/server/interfaces/actions/resources/file/commands/create/create-file-create-action.ts
2737
+ function createFileCreateAction(ctx) {
2738
+ const {
2739
+ repositories: { folderQueryRepository, fileCommandRepository },
2740
+ middlewares: { authMiddleware },
2741
+ action: { executeAction },
2742
+ schemas: { schemas }
2743
+ } = ctx;
2744
+ return async function fileCreateAction({
2745
+ formData
2746
+ }) {
2747
+ return executeAction(
2748
+ async () => {
2749
+ await authMiddleware.authenticate();
2750
+ const {
2751
+ key,
2752
+ checksum,
2753
+ fileMeta,
2754
+ width,
2755
+ height,
2756
+ duration,
2757
+ folder,
2758
+ folderKey,
2759
+ translations
2760
+ } = await fileCreateValidator(schemas).parseAsync(formData);
2761
+ let finalFolder = folder;
2762
+ if (folderKey && !folder) {
2763
+ finalFolder = await folderQueryRepository.findFull({
2764
+ key: folderKey
2765
+ });
2766
+ }
2767
+ const createdFile = await fileCommandRepository.create({
2768
+ key,
2769
+ checksum,
2770
+ fileMeta,
2771
+ ...width ? { width } : {},
2772
+ ...height ? { height } : {},
2773
+ ...duration ? { duration } : {},
2774
+ ...finalFolder ? { folder: finalFolder } : {},
2775
+ translations
2776
+ });
2777
+ return {
2778
+ i18nKey: "ok.store-ok",
2779
+ data: { file: createdFile }
2780
+ };
2781
+ },
2782
+ { type: "command" }
2783
+ );
2784
+ };
2785
+ }
2786
+
2787
+ // src/server/interfaces/actions/resources/file/commands/update/file-update-validator.ts
2788
+ var fileUpdateValidator = (schemas) => schemas.z.object({
2789
+ // core
2790
+ key: schemas.key().optional(),
2791
+ checksum: schemas.sha256Hash().optional(),
2792
+ // file meta
2793
+ fileMeta: schemas.z.object({
2794
+ type: schemas.text(),
2795
+ size: schemas.positiveNumber(),
2796
+ name: schemas.text()
2797
+ }),
2798
+ // media info
2799
+ width: schemas.positiveNumber().nullable(),
2800
+ height: schemas.positiveNumber().nullable(),
2801
+ duration: schemas.positiveNumber().nullable(),
2802
+ // ----------------------------------------------------------------------------
2803
+ // relations
2804
+ // ----------------------------------------------------------------------------
2805
+ folder: schemas.z.object({
2806
+ id: schemas.id().exist({ table: "folders", column: "id" })
2807
+ }).nullable().optional(),
2808
+ // ----------------------------------------------------------------------------
2809
+ // translation
2810
+ // ----------------------------------------------------------------------------
2811
+ translations: schemas.array(
2812
+ schemas.z.object({
2813
+ // core
2814
+ locale: schemas.locale(),
2815
+ // text
2816
+ name: schemas.text().nullable(),
2817
+ alt: schemas.text().nullable()
2818
+ })
2819
+ )
2820
+ });
2821
+
2822
+ // src/server/interfaces/actions/resources/file/commands/update/create-file-update-action.ts
2823
+ function createFileUpdateAction(ctx) {
2824
+ const {
2825
+ services: { storageService },
2826
+ repositories: { fileQueryRepository, fileCommandRepository },
2827
+ middlewares: { authMiddleware },
2828
+ action: { executeAction },
2829
+ schemas: { schemas }
2830
+ } = ctx;
2831
+ return async function fileUpdateAction({
2832
+ id,
2833
+ formData
2834
+ }) {
2835
+ return executeAction(
2836
+ async () => {
2837
+ await authMiddleware.authenticate();
2838
+ const { checksum, width, height, duration, translations } = await fileUpdateValidator(schemas).parseAsync(formData);
2839
+ const { folder, fileMeta, key: uploadedKey } = formData;
2840
+ const foundFile = await fileQueryRepository.findFull({ id });
2841
+ if (!foundFile) throw ServerError.notFound();
2842
+ const oldKey = foundFile.key;
2843
+ let newKey = oldKey;
2844
+ const folderChanged = folder?.key !== foundFile.folder?.key;
2845
+ if (uploadedKey) {
2846
+ newKey = uploadedKey;
2847
+ } else if (folderChanged) {
2848
+ const folderKey = folder?.key ?? "";
2849
+ newKey = path2.join(folderKey, path2.basename(oldKey));
2850
+ await storageService.move({ fromKey: oldKey, toKey: newKey });
2851
+ }
2852
+ const updatedFile = await fileCommandRepository.update({
2853
+ file: foundFile,
2854
+ id,
2855
+ key: newKey,
2856
+ checksum: checksum ?? foundFile.checksum,
2857
+ fileMeta,
2858
+ width,
2859
+ height,
2860
+ duration,
2861
+ folder,
2862
+ translations
2863
+ });
2864
+ if (uploadedKey) {
2865
+ await storageService.remove({ key: oldKey });
2866
+ }
2867
+ return {
2868
+ i18nKey: "ok.update-ok",
2869
+ data: { file: updatedFile }
2870
+ };
2871
+ },
2872
+ { type: "command" }
2873
+ );
2874
+ };
2875
+ }
2876
+
2877
+ // node_modules/yocto-queue/index.js
2878
+ var Node = class {
2879
+ value;
2880
+ next;
2881
+ constructor(value) {
2882
+ this.value = value;
2883
+ }
2884
+ };
2885
+ var Queue = class {
2886
+ #head;
2887
+ #tail;
2888
+ #size;
2889
+ constructor() {
2890
+ this.clear();
2891
+ }
2892
+ enqueue(value) {
2893
+ const node = new Node(value);
2894
+ if (this.#head) {
2895
+ this.#tail.next = node;
2896
+ this.#tail = node;
2897
+ } else {
2898
+ this.#head = node;
2899
+ this.#tail = node;
2900
+ }
2901
+ this.#size++;
2902
+ }
2903
+ dequeue() {
2904
+ const current = this.#head;
2905
+ if (!current) {
2906
+ return;
2907
+ }
2908
+ this.#head = this.#head.next;
2909
+ this.#size--;
2910
+ if (!this.#head) {
2911
+ this.#tail = void 0;
2912
+ }
2913
+ return current.value;
2914
+ }
2915
+ peek() {
2916
+ if (!this.#head) {
2917
+ return;
2918
+ }
2919
+ return this.#head.value;
2920
+ }
2921
+ clear() {
2922
+ this.#head = void 0;
2923
+ this.#tail = void 0;
2924
+ this.#size = 0;
2925
+ }
2926
+ get size() {
2927
+ return this.#size;
2928
+ }
2929
+ *[Symbol.iterator]() {
2930
+ let current = this.#head;
2931
+ while (current) {
2932
+ yield current.value;
2933
+ current = current.next;
2934
+ }
2935
+ }
2936
+ *drain() {
2937
+ while (this.#head) {
2938
+ yield this.dequeue();
2939
+ }
2940
+ }
2941
+ };
2942
+
2943
+ // node_modules/p-limit/index.js
2944
+ function pLimit(concurrency) {
2945
+ validateConcurrency(concurrency);
2946
+ const queue = new Queue();
2947
+ let activeCount = 0;
2948
+ const resumeNext = () => {
2949
+ if (activeCount < concurrency && queue.size > 0) {
2950
+ activeCount++;
2951
+ queue.dequeue()();
2952
+ }
2953
+ };
2954
+ const next = () => {
2955
+ activeCount--;
2956
+ resumeNext();
2957
+ };
2958
+ const run = async (function_, resolve, arguments_) => {
2959
+ const result2 = (async () => function_(...arguments_))();
2960
+ resolve(result2);
2961
+ try {
2962
+ await result2;
2963
+ } catch {
2964
+ }
2965
+ next();
2966
+ };
2967
+ const enqueue = (function_, resolve, arguments_) => {
2968
+ new Promise((internalResolve) => {
2969
+ queue.enqueue(internalResolve);
2970
+ }).then(run.bind(void 0, function_, resolve, arguments_));
2971
+ if (activeCount < concurrency) {
2972
+ resumeNext();
2973
+ }
2974
+ };
2975
+ const generator = (function_, ...arguments_) => new Promise((resolve) => {
2976
+ enqueue(function_, resolve, arguments_);
2977
+ });
2978
+ Object.defineProperties(generator, {
2979
+ activeCount: {
2980
+ get: () => activeCount
2981
+ },
2982
+ pendingCount: {
2983
+ get: () => queue.size
2984
+ },
2985
+ clearQueue: {
2986
+ value() {
2987
+ queue.clear();
2988
+ }
2989
+ },
2990
+ concurrency: {
2991
+ get: () => concurrency,
2992
+ set(newConcurrency) {
2993
+ validateConcurrency(newConcurrency);
2994
+ concurrency = newConcurrency;
2995
+ queueMicrotask(() => {
2996
+ while (activeCount < concurrency && queue.size > 0) {
2997
+ resumeNext();
2998
+ }
2999
+ });
3000
+ }
3001
+ },
3002
+ map: {
3003
+ async value(iterable, function_) {
3004
+ const promises = Array.from(iterable, (value, index) => this(function_, value, index));
3005
+ return Promise.all(promises);
3006
+ }
3007
+ }
3008
+ });
3009
+ return generator;
3010
+ }
3011
+ function validateConcurrency(concurrency) {
3012
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
3013
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
3014
+ }
3015
+ }
3016
+
3017
+ // src/server/interfaces/actions/resources/file/commands/create-many/file-create-many-validator.ts
3018
+ var fileCreateManyValidator = (schemas) => schemas.z.object({
3019
+ uploadResults: schemas.array(
3020
+ schemas.z.object({
3021
+ // core
3022
+ key: schemas.key(),
3023
+ checksum: schemas.sha256Hash(),
3024
+ // file meta
3025
+ fileMeta: schemas.z.object({
3026
+ type: schemas.text(),
3027
+ size: schemas.positiveNumber(),
3028
+ name: schemas.text()
3029
+ }),
3030
+ // media info
3031
+ width: schemas.positiveNumber().nullable(),
3032
+ height: schemas.positiveNumber().nullable(),
3033
+ duration: schemas.positiveNumber().nullable(),
3034
+ // ----------------------------------------------------------------------------
3035
+ // translation
3036
+ // ----------------------------------------------------------------------------
3037
+ translations: schemas.array(
3038
+ schemas.z.object({
3039
+ // core
3040
+ locale: schemas.locale(),
3041
+ // text
3042
+ name: schemas.text().nullable(),
3043
+ alt: schemas.text().nullable()
3044
+ })
3045
+ )
3046
+ })
3047
+ ),
3048
+ // ----------------------------------------------------------------------------
3049
+ // relations
3050
+ // ----------------------------------------------------------------------------
3051
+ folder: schemas.z.object({ id: schemas.id().exist({ table: "folders", column: "id" }) }).nullable().optional()
3052
+ });
3053
+
3054
+ // src/server/interfaces/actions/resources/file/commands/create-many/create-file-create-many-action.ts
3055
+ function createFileCreateManyAction(ctx) {
3056
+ const {
3057
+ repositories: { fileCommandRepository },
3058
+ middlewares: { authMiddleware },
3059
+ action: { executeAction },
3060
+ schemas: { schemas }
3061
+ } = ctx;
3062
+ return async function fileCreateManyAction({
3063
+ formData
3064
+ }) {
3065
+ return executeAction(
3066
+ async () => {
3067
+ await authMiddleware.authenticate();
3068
+ const { uploadResults, folder } = await fileCreateManyValidator(schemas).parseAsync(formData);
3069
+ const limit = pLimit(5);
3070
+ await Promise.all(
3071
+ uploadResults.map(
3072
+ (file) => limit(async () => {
3073
+ await fileCommandRepository.create({
3074
+ key: file.key,
3075
+ fileMeta: file.fileMeta,
3076
+ checksum: file.checksum,
3077
+ width: file.width,
3078
+ height: file.height,
3079
+ duration: file.duration,
3080
+ ...folder !== void 0 ? { folder } : {},
3081
+ translations: file.translations
3082
+ });
3083
+ })
3084
+ )
3085
+ );
3086
+ return {
3087
+ i18nKey: "ok.store-ok",
3088
+ data: { count: uploadResults.length }
3089
+ };
3090
+ },
3091
+ { type: "command" }
3092
+ );
3093
+ };
3094
+ }
3095
+
3096
+ // src/server/interfaces/actions/resources/file/commands/purge-many/file-purge-many-validator.ts
3097
+ var filePurgeManyValidator = (schemas) => schemas.z.object({
3098
+ ids: schemas.array(schemas.id().exist({ table: "files", column: "id" }))
3099
+ });
3100
+
3101
+ // src/server/interfaces/actions/resources/file/commands/purge-many/create-file-purge-many-action.ts
3102
+ function createFilePurgeManyAction(ctx) {
3103
+ const {
3104
+ services: { storageService },
3105
+ repositories: { fileQueryRepository, fileCommandRepository },
3106
+ middlewares: { authMiddleware },
3107
+ action: { executeAction },
3108
+ schemas: { schemas }
3109
+ } = ctx;
3110
+ return async function filePurgeManyAction({
3111
+ formData
3112
+ }) {
3113
+ return executeAction(
3114
+ async () => {
3115
+ await authMiddleware.authenticate();
3116
+ const { ids: ids2 } = await filePurgeManyValidator(schemas).parseAsync(formData);
3117
+ const foundFiles = await fileQueryRepository.findManyByIds({ ids: ids2 });
3118
+ if (foundFiles.length === 0) throw ServerError.notFound();
3119
+ if (foundFiles.some((file) => isFileLocked(file))) {
3120
+ throw new ServerError({ i18nKey: "error.files-destroy-is-locked" });
3121
+ }
3122
+ const keys = foundFiles.map((file) => file.key);
3123
+ await Promise.all(
3124
+ keys.map(async (key) => await storageService.remove({ key }))
3125
+ );
3126
+ await Promise.all(
3127
+ foundFiles.map(
3128
+ async (file) => await fileCommandRepository.delete({ id: file.id })
3129
+ )
3130
+ );
3131
+ return {
3132
+ i18nKey: "ok.destroy-ok"
3133
+ };
3134
+ },
3135
+ { type: "command" }
3136
+ );
3137
+ };
3138
+ }
3139
+
3140
+ // src/server/interfaces/actions/resources/file/commands/restore-many/file-create-restore-validator.ts
3141
+ var fileRestoreManyValidator = (schemas) => schemas.z.object({
3142
+ ids: schemas.array(schemas.id().exist({ table: "files", column: "id" }))
3143
+ });
3144
+
3145
+ // src/server/interfaces/actions/resources/file/commands/restore-many/create-file-restore-many-action.ts
3146
+ function createFileRestoreManyAction(ctx) {
3147
+ const {
3148
+ repositories: { fileCommandRepository },
3149
+ middlewares: { authMiddleware },
3150
+ action: { executeAction },
3151
+ schemas: { schemas }
3152
+ } = ctx;
3153
+ return async function fileRestoreManyAction({
3154
+ formData
3155
+ }) {
3156
+ return executeAction(
3157
+ async () => {
3158
+ await authMiddleware.authenticate();
3159
+ const { ids: ids2 } = await fileRestoreManyValidator(schemas).parseAsync(formData);
3160
+ await fileCommandRepository.restoreMany({ ids: ids2 });
3161
+ return {
3162
+ i18nKey: "ok.restore-ok"
3163
+ };
3164
+ },
3165
+ { type: "command" }
3166
+ );
3167
+ };
3168
+ }
3169
+
3170
+ // src/server/interfaces/actions/resources/file/commands/soft-delete/create-file-soft-delete-action.ts
3171
+ function createFileSoftDeleteAction(ctx) {
3172
+ const {
3173
+ repositories: { fileQueryRepository, fileCommandRepository },
3174
+ middlewares: { authMiddleware },
3175
+ action: { executeAction }
3176
+ } = ctx;
3177
+ return async function fileSoftDeleteAction({ id }) {
3178
+ return executeAction(
3179
+ async () => {
3180
+ await authMiddleware.authenticate();
3181
+ const foundFile = await fileQueryRepository.findFull({ id });
3182
+ if (!foundFile) throw ServerError.notFound();
3183
+ if (isFileLocked(foundFile)) {
3184
+ throw new ServerError({ i18nKey: "error.files-destroy-is-locked" });
3185
+ }
3186
+ await fileCommandRepository.softDelete({ id });
3187
+ return {
3188
+ i18nKey: "ok.destroy-ok"
3189
+ };
3190
+ },
3191
+ { type: "command" }
3192
+ );
3193
+ };
3194
+ }
3195
+
3196
+ // src/server/interfaces/actions/resources/file/commands/soft-delete-many/file-soft-delete-many-validator.ts
3197
+ var fileSoftDeleteManyValidator = (schemas) => schemas.z.object({
3198
+ ids: schemas.array(schemas.id().exist({ table: "files", column: "id" }))
3199
+ });
3200
+
3201
+ // src/server/interfaces/actions/resources/file/commands/soft-delete-many/create-file-soft-delete-many-action.ts
3202
+ function createFileSoftDeleteManyAction(ctx) {
3203
+ const {
3204
+ repositories: { fileQueryRepository, fileCommandRepository },
3205
+ middlewares: { authMiddleware },
3206
+ action: { executeAction },
3207
+ schemas: { schemas }
3208
+ } = ctx;
3209
+ return async function fileSoftDeleteManyAction({
3210
+ formData
3211
+ }) {
3212
+ return executeAction(
3213
+ async () => {
3214
+ await authMiddleware.authenticate();
3215
+ const { ids: ids2 } = await fileSoftDeleteManyValidator(schemas).parseAsync(formData);
3216
+ const foundFiles = await fileQueryRepository.findManyByIds({ ids: ids2 });
3217
+ if (foundFiles.length === 0) throw ServerError.notFound();
3218
+ const targetIds = foundFiles.filter((f) => !f.isLocked).map((f) => f.id);
3219
+ const hasLockedFile = ids2.length !== targetIds.length;
3220
+ const count = await fileCommandRepository.softDeleteMany({
3221
+ ids: targetIds
3222
+ });
3223
+ if (count === 0) {
3224
+ throw new ServerError({ i18nKey: "error.files-batch-destroy-no" });
3225
+ }
3226
+ return {
3227
+ i18nKey: hasLockedFile ? "ok.destroy-ok" : "ok.files-batch-destroy-has-locked"
3228
+ };
3229
+ },
3230
+ { type: "command" }
3231
+ );
3232
+ };
3233
+ }
3234
+
3235
+ // src/server/interfaces/actions/resources/file/querires/create-file-find-full-action.ts
3236
+ function createFileFindFullAction(ctx) {
3237
+ const {
3238
+ repositories: { fileQueryRepository },
3239
+ action: { executeAction }
3240
+ } = ctx;
3241
+ return async function FileFindFullAction(params) {
3242
+ return executeAction(
3243
+ async () => {
3244
+ const file = await fileQueryRepository.findFull(params);
3245
+ if (!file) throw ServerError.notFound();
3246
+ return {
3247
+ data: { file }
3248
+ };
3249
+ },
3250
+ {
3251
+ type: "query",
3252
+ key: ["file", "findFullAction", params.id]
3253
+ }
3254
+ );
3255
+ };
3256
+ }
3257
+
3258
+ // src/server/interfaces/actions/resources/file/querires/create-file-find-list-cards-action.ts
3259
+ function createFileFindListCardsAction(ctx) {
3260
+ const {
3261
+ repositories: { fileQueryRepository },
3262
+ middlewares: { authMiddleware },
3263
+ action: { executeAction }
3264
+ } = ctx;
3265
+ return async function fileFindFullAction(params) {
3266
+ return executeAction(
3267
+ async ({ locale }) => {
3268
+ await authMiddleware.authenticate();
3269
+ const { items, total } = await fileQueryRepository.findListCards({
3270
+ ...params,
3271
+ locale
3272
+ });
3273
+ return {
3274
+ data: { items, total }
3275
+ };
3276
+ },
3277
+ {
3278
+ type: "query",
3279
+ key: [
3280
+ "file",
3281
+ "findListCardsAction",
3282
+ params.searchString,
3283
+ params.type,
3284
+ params.isDeleted,
3285
+ params.isLocked,
3286
+ params.folderId,
3287
+ ...params.fileIds ?? [],
3288
+ params.page,
3289
+ params.pageSize
3290
+ ]
3291
+ }
3292
+ );
3293
+ };
3294
+ }
3295
+
3296
+ // src/server/interfaces/actions/resources/folder/commands/create/folder-create-validator.ts
3297
+ var folderCreateValidator = (schemas) => schemas.z.object({
3298
+ // core
3299
+ key: schemas.key().unique({ table: "folders", column: "key" }),
3300
+ name: schemas.pathSegment(),
3301
+ // ----------------------------------------------------------------------------
3302
+ // relations
3303
+ // ----------------------------------------------------------------------------
3304
+ // Folder
3305
+ parentFolder: schemas.z.object({ id: schemas.id().exist({ table: "folders", column: "id" }) }).nullable()
3306
+ });
3307
+
3308
+ // src/server/interfaces/actions/resources/folder/commands/create/create-folder-create-action.ts
3309
+ function createFolderCreateAction(ctx) {
3310
+ const {
3311
+ repositories: { folderCommandRepository },
3312
+ middlewares: { authMiddleware },
3313
+ action: { executeAction },
3314
+ schemas: { schemas }
3315
+ } = ctx;
3316
+ return async function folderCreateAction({
3317
+ formData
3318
+ }) {
3319
+ return executeAction(
3320
+ async () => {
3321
+ await authMiddleware.authenticate();
3322
+ const combinedKey = path2.join(
3323
+ formData.parentFolder?.key ?? "",
3324
+ formData.name
3325
+ );
3326
+ const { name } = await folderCreateValidator(schemas).parseAsync({
3327
+ ...formData,
3328
+ key: combinedKey
3329
+ });
3330
+ const createdFolder = await folderCommandRepository.create({
3331
+ key: combinedKey,
3332
+ name,
3333
+ parentFolder: formData.parentFolder
3334
+ });
3335
+ return {
3336
+ i18nKey: "ok.store-ok",
3337
+ data: { folder: createdFolder }
3338
+ };
3339
+ },
3340
+ { type: "command" }
3341
+ );
3342
+ };
3343
+ }
3344
+
3345
+ // src/server/interfaces/actions/resources/folder/commands/update/folder-update-validator.ts
3346
+ var folderUpdateValidator = (schemas, id) => schemas.z.object({
3347
+ // core
3348
+ key: schemas.key().unique({
3349
+ table: "folders",
3350
+ column: "key",
3351
+ excludeSelf: { name: "id", value: id }
3352
+ }),
3353
+ name: schemas.pathSegment(),
3354
+ // ----------------------------------------------------------------------------
3355
+ // relations
3356
+ // ----------------------------------------------------------------------------
3357
+ // Folder
3358
+ parentFolder: schemas.z.object({ id: schemas.id().exist({ table: "folders", column: "id" }) }).nullable()
3359
+ });
3360
+
3361
+ // src/server/interfaces/actions/resources/folder/commands/update/create-folder-update-action.ts
3362
+ function createFolderUpdateAction(ctx) {
3363
+ const {
3364
+ repositories: { folderCommandRepository },
3365
+ middlewares: { authMiddleware },
3366
+ action: { executeAction },
3367
+ schemas: { schemas }
3368
+ } = ctx;
3369
+ return async function folderUpdateAction({
3370
+ id,
3371
+ formData
3372
+ }) {
3373
+ return executeAction(
3374
+ async () => {
3375
+ await authMiddleware.authenticate();
3376
+ const combinedKey = path2.join(
3377
+ formData.parentFolder?.key ?? "",
3378
+ formData.name
3379
+ );
3380
+ const { name } = await folderUpdateValidator(schemas, id).parseAsync({
3381
+ ...formData,
3382
+ key: combinedKey
3383
+ });
3384
+ const updateddFolder = await folderCommandRepository.update({
3385
+ id,
3386
+ key: combinedKey,
3387
+ name,
3388
+ parentFolder: formData.parentFolder
3389
+ });
3390
+ return {
3391
+ i18nKey: "ok.update-ok",
3392
+ data: { folder: updateddFolder }
3393
+ };
3394
+ },
3395
+ { type: "command" }
3396
+ );
3397
+ };
3398
+ }
3399
+
3400
+ // src/server/interfaces/actions/resources/folder/commands/delete/create-folder-delete-action.ts
3401
+ function createFolderDeleteAction(ctx) {
3402
+ const {
3403
+ repositories: { folderQueryRepository, folderCommandRepository },
3404
+ middlewares: { authMiddleware },
3405
+ action: { executeAction }
3406
+ } = ctx;
3407
+ return async function folderDeleteAction({ id }) {
3408
+ return executeAction(
3409
+ async () => {
3410
+ await authMiddleware.authenticate();
3411
+ const foundFolder = await folderQueryRepository.findFull({ id });
3412
+ if (!foundFolder) throw ServerError.notFound();
3413
+ if (isFolderLocked(foundFolder)) {
3414
+ throw ServerError.forbidden();
3415
+ }
3416
+ await folderCommandRepository.delete({ id });
3417
+ return {
3418
+ i18nKey: "ok.destroy-ok"
3419
+ };
3420
+ },
3421
+ { type: "command" }
3422
+ );
3423
+ };
3424
+ }
3425
+
3426
+ // src/server/interfaces/actions/resources/folder/queries/create-folder-find-full-action.ts
3427
+ function createFolderFindFullAction(ctx) {
3428
+ const {
3429
+ repositories: { folderQueryRepository, fileQueryRepository },
3430
+ middlewares: { authMiddleware },
3431
+ action: { executeAction }
3432
+ } = ctx;
3433
+ return async function folderFindFullAction(params) {
3434
+ return executeAction(
3435
+ async (translator) => {
3436
+ await authMiddleware.authenticate();
3437
+ const resolvedKey = params.key || ROOT_FOLDER_ID;
3438
+ const identity = "id" in params ? params.id : resolvedKey;
3439
+ const isAtRoot = identity === ROOT_FOLDER_ID;
3440
+ let folder = ROOT_FOLDER;
3441
+ if (isAtRoot) {
3442
+ const { items: subFolders } = await folderQueryRepository.findListCards({
3443
+ parentFolderId: ROOT_FOLDER_ID
3444
+ });
3445
+ folder.subFolders = subFolders;
3446
+ const { items: files } = await fileQueryRepository.findListCards({
3447
+ locale: translator.locale,
3448
+ folderId: ROOT_FOLDER_ID
3449
+ });
3450
+ folder.files = files;
3451
+ } else {
3452
+ const found = await folderQueryRepository.findFull(params);
3453
+ if (!found) throw ServerError.notFound();
3454
+ folder = found;
3455
+ }
3456
+ return { data: { folder } };
3457
+ },
3458
+ {
3459
+ type: "query",
3460
+ key: ["file", "findFullAction", params.id, params.key]
3461
+ }
3462
+ );
3463
+ };
3464
+ }
3465
+
3466
+ // src/server/interfaces/actions/resources/folder/queries/create-folder-find-list-cards-action.ts
3467
+ function createFolderFindListCardsAction(ctx) {
3468
+ const {
3469
+ repositories: { folderQueryRepository },
3470
+ middlewares: { authMiddleware },
3471
+ action: { executeAction }
3472
+ } = ctx;
3473
+ return async function folderFindFullAction(params) {
3474
+ return executeAction(
3475
+ async () => {
3476
+ await authMiddleware.authenticate();
3477
+ const { items, total } = await folderQueryRepository.findListCards(params);
3478
+ return {
3479
+ data: { items, total }
3480
+ };
3481
+ },
3482
+ {
3483
+ type: "query",
3484
+ key: [
3485
+ "folder",
3486
+ "findListCardsAction",
3487
+ params.searchString,
3488
+ params.parentFolderId,
3489
+ ...params.folderIds ?? [],
3490
+ params.page,
3491
+ params.pageSize
3492
+ ]
3493
+ }
3494
+ );
3495
+ };
3496
+ }
3497
+
3498
+ // src/server/interfaces/actions/resources/post/commands/create/post-create-validator.ts
3499
+ var postCreateValidator = ({
3500
+ type,
3501
+ topicId,
3502
+ schemas,
3503
+ tocItemSchema
3504
+ }) => schemas.z.object({
3505
+ type: schemas.z.enum(POST_TYPES),
3506
+ // ----------------------------------------------------------------------------
3507
+ // states
3508
+ // ----------------------------------------------------------------------------
3509
+ isLocked: schemas.z.boolean(),
3510
+ isActive: schemas.z.boolean(),
3511
+ isIndexActive: schemas.z.boolean(),
3512
+ index: schemas.positiveNumber().nullable(),
3513
+ isSlugActive: schemas.z.boolean(),
3514
+ slug: schemas.slug().unique({
3515
+ table: "posts",
3516
+ column: "slug",
3517
+ scope: [
3518
+ { name: "type", value: type, cast: "PostType" },
3519
+ ...topicId ? [{ name: "topic_id", value: topicId }] : []
3520
+ // Use for: [post], [post:category]
3521
+ ]
3522
+ }).nullable(),
3523
+ isFeatured: schemas.z.boolean(),
3524
+ isShownOnHome: schemas.z.boolean(),
3525
+ // ----------------------------------------------------------------------------
3526
+ // relations
3527
+ // ----------------------------------------------------------------------------
3528
+ // Admin
3529
+ author: schemas.singleItem({ table: "admins", column: "id" }).nullable(),
3530
+ // Picked by modal
3531
+ // Post
3532
+ topicId: schemas.id().exist({ table: "posts", column: "id" }).nullable(),
3533
+ parents: schemas.multiItems({ table: "posts", column: "id" }),
3534
+ // Picked by modal
3535
+ tags: schemas.multiItems({ table: "posts", column: "id" }),
3536
+ // Picked by modal
3537
+ relatedPosts: schemas.multiItems({ table: "posts", column: "id" }),
3538
+ // Picked by modal
3539
+ // File
3540
+ coverImage: schemas.singleItem({ table: "files", column: "id" }),
3541
+ // Picked by modal
3542
+ contentImageIds: schemas.array(
3543
+ schemas.id().exist({ table: "files", column: "id" })
3544
+ ),
3545
+ // -----------------------------------------------------------------------
3546
+ // --- custom fields
3547
+ // -----------------------------------------------------------------------
3548
+ // states
3549
+ state1: schemas.z.boolean(),
3550
+ state2: schemas.z.boolean(),
3551
+ state3: schemas.z.boolean(),
3552
+ state4: schemas.z.boolean(),
3553
+ state5: schemas.z.boolean(),
3554
+ state6: schemas.z.boolean(),
3555
+ state7: schemas.z.boolean(),
3556
+ state8: schemas.z.boolean(),
3557
+ state9: schemas.z.boolean(),
3558
+ state10: schemas.z.boolean(),
3559
+ // multi images
3560
+ images1: schemas.multiItems({ table: "files", column: "id" }),
3561
+ images2: schemas.multiItems({ table: "files", column: "id" }),
3562
+ // single images
3563
+ image1: schemas.singleItem({ table: "files", column: "id" }),
3564
+ image2: schemas.singleItem({ table: "files", column: "id" }),
3565
+ image3: schemas.singleItem({ table: "files", column: "id" }),
3566
+ image4: schemas.singleItem({ table: "files", column: "id" }),
3567
+ // text
3568
+ text1: schemas.text().nullable(),
3569
+ text2: schemas.text().nullable(),
3570
+ text3: schemas.text().nullable(),
3571
+ text4: schemas.text().nullable(),
3572
+ text5: schemas.text().nullable(),
3573
+ text6: schemas.text().nullable(),
3574
+ text7: schemas.text().nullable(),
3575
+ text8: schemas.text().nullable(),
3576
+ text9: schemas.text().nullable(),
3577
+ text10: schemas.text().nullable(),
3578
+ // json array
3579
+ data1: schemas.array(schemas.z.any()),
3580
+ data2: schemas.array(schemas.z.any()),
3581
+ data3: schemas.array(schemas.z.any()),
3582
+ data4: schemas.array(schemas.z.any()),
3583
+ // ----------------------------------------------------------------------------
3584
+ // translation
3585
+ // ----------------------------------------------------------------------------
3586
+ translations: schemas.array(
3587
+ schemas.z.object({
3588
+ locale: schemas.locale(),
3589
+ // text
3590
+ title: schemas.text().nullable(),
3591
+ subtitle: schemas.text().nullable(),
3592
+ summary: schemas.text().nullable(),
3593
+ description: schemas.text().nullable(),
3594
+ content: schemas.text().nullable(),
3595
+ // better seo
3596
+ externalLinks: schemas.array(
3597
+ schemas.z.object({
3598
+ name: schemas.text(),
3599
+ href: schemas.text()
3600
+ })
3601
+ ),
3602
+ faq: schemas.array(
3603
+ schemas.z.object({
3604
+ question: schemas.text(),
3605
+ answer: schemas.text()
3606
+ })
3607
+ ),
3608
+ toc: schemas.array(tocItemSchema),
3609
+ // extra
3610
+ readTime: schemas.positiveNumber().nullable(),
3611
+ wordCount: schemas.positiveNumber(),
3612
+ // -------------------------------------------
3613
+ // --- custom fields
3614
+ // -------------------------------------------
3615
+ // text
3616
+ text1: schemas.text().nullable(),
3617
+ text2: schemas.text().nullable(),
3618
+ text3: schemas.text().nullable(),
3619
+ text4: schemas.text().nullable(),
3620
+ text5: schemas.text().nullable(),
3621
+ text6: schemas.text().nullable(),
3622
+ text7: schemas.text().nullable(),
3623
+ text8: schemas.text().nullable(),
3624
+ text9: schemas.text().nullable(),
3625
+ text10: schemas.text().nullable(),
3626
+ // json array
3627
+ data1: schemas.array(schemas.z.any()),
3628
+ data2: schemas.array(schemas.z.any()),
3629
+ data3: schemas.array(schemas.z.any()),
3630
+ data4: schemas.array(schemas.z.any())
3631
+ })
3632
+ )
3633
+ }).transform((obj) => ({
3634
+ ...obj,
3635
+ // states
3636
+ index: obj.isIndexActive ? obj.index : null,
3637
+ slug: obj.isSlugActive ? obj.slug : null,
3638
+ // translation
3639
+ translations: obj.translations.map((t) => ({
3640
+ ...t,
3641
+ readTime: t.readTime !== 0 ? t.readTime : null
3642
+ }))
3643
+ }));
3644
+
3645
+ // src/server/interfaces/actions/resources/post/commands/create/create-post-create-action.ts
3646
+ function createPostCreateAction(ctx) {
3647
+ const {
3648
+ repositories: { postCommandRepository },
3649
+ middlewares: { authMiddleware },
3650
+ action: { executeAction },
3651
+ schemas: { schemas, tocItemSchema }
3652
+ } = ctx;
3653
+ return async function postCreateAction({
3654
+ formData
3655
+ }) {
3656
+ return executeAction(
3657
+ async () => {
3658
+ await authMiddleware.authenticate();
3659
+ const validatedPayload = await postCreateValidator({
3660
+ schemas,
3661
+ tocItemSchema,
3662
+ type: formData.type,
3663
+ topicId: formData.topicId
3664
+ }).parseAsync(formData);
3665
+ const created = await postCommandRepository.create(validatedPayload);
3666
+ return {
3667
+ i18nKey: "ok.store-ok",
3668
+ data: { post: created }
3669
+ };
3670
+ },
3671
+ { type: "command" }
3672
+ );
3673
+ };
3674
+ }
3675
+
3676
+ // src/server/interfaces/actions/resources/post/commands/update/post-update-validator.ts
3677
+ var postUpdateValidator = ({
3678
+ id,
3679
+ type,
3680
+ topicId,
3681
+ schemas,
3682
+ tocItemSchema
3683
+ }) => schemas.z.object({
3684
+ type: schemas.z.enum(POST_TYPES),
3685
+ // depth: schemas.z.number().nullable(),
3686
+ // ----------------------------------------------------------------------------
3687
+ // states
3688
+ // ----------------------------------------------------------------------------
3689
+ isLocked: schemas.z.boolean(),
3690
+ isActive: schemas.z.boolean(),
3691
+ isIndexActive: schemas.z.boolean(),
3692
+ index: schemas.positiveNumber().nullable(),
3693
+ isSlugActive: schemas.z.boolean(),
3694
+ slug: schemas.slug().unique({
3695
+ table: "posts",
3696
+ column: "slug",
3697
+ scope: [
3698
+ { name: "type", value: type, cast: "PostType" },
3699
+ ...topicId ? [{ name: "topic_id", value: topicId }] : []
3700
+ // Use for: [post], [post:category]
3701
+ ],
3702
+ excludeSelf: { name: "id", value: id }
3703
+ }).nullable(),
3704
+ isFeatured: schemas.z.boolean(),
3705
+ isShownOnHome: schemas.z.boolean(),
3706
+ // ----------------------------------------------------------------------------
3707
+ // relations
3708
+ // ----------------------------------------------------------------------------
3709
+ // Admin
3710
+ author: schemas.singleItem({ table: "admins", column: "id" }),
3711
+ // Picked by modal
3712
+ // Post
3713
+ topicId: schemas.id().exist({ table: "posts", column: "id" }).nullable(),
3714
+ parents: schemas.multiItems({ table: "posts", column: "id" }),
3715
+ // Picked by modal
3716
+ tags: schemas.multiItems({ table: "posts", column: "id" }),
3717
+ // Picked by modal
3718
+ relatedPosts: schemas.multiItems({ table: "posts", column: "id" }),
3719
+ // Picked by modal
3720
+ // File
3721
+ coverImage: schemas.singleItem({ table: "files", column: "id" }),
3722
+ // Picked by modal
3723
+ contentImageIds: schemas.array(
3724
+ schemas.id().exist({ table: "files", column: "id" })
3725
+ ),
3726
+ // -----------------------------------------------------------------------
3727
+ // --- custom fields
3728
+ // -----------------------------------------------------------------------
3729
+ // states
3730
+ state1: schemas.z.boolean(),
3731
+ state2: schemas.z.boolean(),
3732
+ state3: schemas.z.boolean(),
3733
+ state4: schemas.z.boolean(),
3734
+ state5: schemas.z.boolean(),
3735
+ state6: schemas.z.boolean(),
3736
+ state7: schemas.z.boolean(),
3737
+ state8: schemas.z.boolean(),
3738
+ state9: schemas.z.boolean(),
3739
+ state10: schemas.z.boolean(),
3740
+ // multi images
3741
+ images1: schemas.multiItems({ table: "files", column: "id" }),
3742
+ images2: schemas.multiItems({ table: "files", column: "id" }),
3743
+ // single images
3744
+ image1: schemas.singleItem({ table: "files", column: "id" }),
3745
+ image2: schemas.singleItem({ table: "files", column: "id" }),
3746
+ image3: schemas.singleItem({ table: "files", column: "id" }),
3747
+ image4: schemas.singleItem({ table: "files", column: "id" }),
3748
+ // text
3749
+ text1: schemas.text().nullable(),
3750
+ text2: schemas.text().nullable(),
3751
+ text3: schemas.text().nullable(),
3752
+ text4: schemas.text().nullable(),
3753
+ text5: schemas.text().nullable(),
3754
+ text6: schemas.text().nullable(),
3755
+ text7: schemas.text().nullable(),
3756
+ text8: schemas.text().nullable(),
3757
+ text9: schemas.text().nullable(),
3758
+ text10: schemas.text().nullable(),
3759
+ // json array
3760
+ data1: schemas.array(schemas.z.any()),
3761
+ data2: schemas.array(schemas.z.any()),
3762
+ data3: schemas.array(schemas.z.any()),
3763
+ data4: schemas.array(schemas.z.any()),
3764
+ // ----------------------------------------------------------------------------
3765
+ // translation
3766
+ // ----------------------------------------------------------------------------
3767
+ translations: schemas.array(
3768
+ schemas.z.object({
3769
+ locale: schemas.locale(),
3770
+ // text
3771
+ title: schemas.text().nullable(),
3772
+ subtitle: schemas.text().nullable(),
3773
+ summary: schemas.text().nullable(),
3774
+ description: schemas.text().nullable(),
3775
+ content: schemas.text().nullable(),
3776
+ // better seo
3777
+ externalLinks: schemas.array(
3778
+ schemas.z.object({
3779
+ name: schemas.text(),
3780
+ href: schemas.text()
3781
+ })
3782
+ ),
3783
+ faq: schemas.array(
3784
+ schemas.z.object({
3785
+ question: schemas.text(),
3786
+ answer: schemas.text()
3787
+ })
3788
+ ),
3789
+ toc: schemas.array(tocItemSchema),
3790
+ // extra
3791
+ readTime: schemas.positiveNumber().nullable(),
3792
+ wordCount: schemas.positiveNumber(),
3793
+ // -------------------------------------------
3794
+ // --- custom fields
3795
+ // -------------------------------------------
3796
+ // text
3797
+ text1: schemas.text().nullable(),
3798
+ text2: schemas.text().nullable(),
3799
+ text3: schemas.text().nullable(),
3800
+ text4: schemas.text().nullable(),
3801
+ text5: schemas.text().nullable(),
3802
+ text6: schemas.text().nullable(),
3803
+ text7: schemas.text().nullable(),
3804
+ text8: schemas.text().nullable(),
3805
+ text9: schemas.text().nullable(),
3806
+ text10: schemas.text().nullable(),
3807
+ // json array
3808
+ data1: schemas.array(schemas.z.any()),
3809
+ data2: schemas.array(schemas.z.any()),
3810
+ data3: schemas.array(schemas.z.any()),
3811
+ data4: schemas.array(schemas.z.any())
3812
+ })
3813
+ )
3814
+ }).transform((obj) => ({
3815
+ ...obj,
3816
+ // states
3817
+ index: obj.isIndexActive ? obj.index : null,
3818
+ slug: obj.isSlugActive ? obj.slug : null,
3819
+ // translation
3820
+ translations: obj.translations.map((t) => ({
3821
+ ...t,
3822
+ readTime: t.readTime !== 0 ? t.readTime : null
3823
+ }))
3824
+ }));
3825
+
3826
+ // src/server/interfaces/actions/resources/post/commands/update/create-post-update-action.ts
3827
+ function createPostUpdateAction(ctx) {
3828
+ const {
3829
+ repositories: { postCommandRepository },
3830
+ middlewares: { authMiddleware },
3831
+ action: { executeAction },
3832
+ schemas: { schemas, tocItemSchema }
3833
+ } = ctx;
3834
+ return async function postUpdateAction({
3835
+ id,
3836
+ formData
3837
+ }) {
3838
+ return executeAction(
3839
+ async () => {
3840
+ await authMiddleware.authenticate();
3841
+ const validatedPayload = await postUpdateValidator({
3842
+ id,
3843
+ type: formData.type,
3844
+ topicId: formData.topicId,
3845
+ schemas,
3846
+ tocItemSchema
3847
+ }).parseAsync(formData);
3848
+ const updatedPost = await postCommandRepository.update({
3849
+ id,
3850
+ ...validatedPayload
3851
+ });
3852
+ return {
3853
+ i18nKey: "ok.update-ok",
3854
+ data: { post: updatedPost }
3855
+ };
3856
+ },
3857
+ { type: "command" }
3858
+ );
3859
+ };
3860
+ }
3861
+
3862
+ // src/server/interfaces/actions/resources/post/commands/delete/create-post-delete-action.ts
3863
+ function createPostDeleteAction(ctx) {
3864
+ const {
3865
+ repositories: { postQueryRepository, postCommandRepository },
3866
+ middlewares: { authMiddleware },
3867
+ action: { executeAction }
3868
+ } = ctx;
3869
+ return async function postDeleteAction({ id }) {
3870
+ return executeAction(
3871
+ async () => {
3872
+ await authMiddleware.authenticate();
3873
+ const post = await postQueryRepository.find({ id });
3874
+ if (!post) throw ServerError.notFound();
3875
+ await postCommandRepository.delete({ id: post.id });
3876
+ return {
3877
+ i18nKey: "ok.destroy-ok"
3878
+ };
3879
+ },
3880
+ { type: "command" }
3881
+ );
3882
+ };
3883
+ }
3884
+
3885
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-action.ts
3886
+ function createPostFindAction(ctx) {
3887
+ const {
3888
+ repositories: { postQueryRepository },
3889
+ action: { executeAction }
3890
+ } = ctx;
3891
+ return async function postFindAction(params) {
3892
+ return executeAction(
3893
+ async () => {
3894
+ const found = await postQueryRepository.find(params);
3895
+ if (!found) throw ServerError.notFound();
3896
+ return {
3897
+ data: { post: found }
3898
+ };
3899
+ },
3900
+ {
3901
+ type: "query",
3902
+ key: [
3903
+ "post",
3904
+ "findAction",
3905
+ params.id,
3906
+ params.type,
3907
+ params.slug,
3908
+ params.isActive,
3909
+ params.topicSlug
3910
+ ]
3911
+ }
3912
+ );
3913
+ };
3914
+ }
3915
+
3916
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-full-action.ts
3917
+ function createPostFindFullAction(ctx) {
3918
+ const {
3919
+ repositories: { postQueryRepository },
3920
+ action: { executeAction }
3921
+ } = ctx;
3922
+ return async function postFindFullAction(params) {
3923
+ return executeAction(
3924
+ async () => {
3925
+ const found = await postQueryRepository.findFull(params);
3926
+ if (!found) throw ServerError.notFound();
3927
+ return {
3928
+ data: { post: found }
3929
+ };
3930
+ },
3931
+ {
3932
+ type: "query",
3933
+ key: [
3934
+ "post",
3935
+ "findFullAction",
3936
+ params.id,
3937
+ params.type,
3938
+ params.slug,
3939
+ params.isActive,
3940
+ params.topicSlug
3941
+ ]
3942
+ }
3943
+ );
3944
+ };
3945
+ }
3946
+
3947
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-list-cards-action.ts
3948
+ function createPostFindListCardsAction(ctx) {
3949
+ const {
3950
+ repositories: { postQueryRepository },
3951
+ middlewares: { authMiddleware },
3952
+ action: { executeAction }
3953
+ } = ctx;
3954
+ return async function postFindFullAction(params) {
3955
+ return executeAction(
3956
+ async ({ locale }) => {
3957
+ await authMiddleware.authenticate();
3958
+ const { items, total } = await postQueryRepository.findListCards({
3959
+ ...params,
3960
+ locale
3961
+ });
3962
+ return {
3963
+ data: { items, total }
3964
+ };
3965
+ },
3966
+ {
3967
+ type: "query",
3968
+ key: [
3969
+ "post",
3970
+ "findListCardsAction",
3971
+ params.searchString,
3972
+ params.type,
3973
+ params.isActive,
3974
+ params.isIndexActive,
3975
+ params.isSlugActive,
3976
+ params.isFeatured,
3977
+ params.isShownOnHome,
3978
+ params.state1,
3979
+ params.state2,
3980
+ params.state3,
3981
+ params.state4,
3982
+ params.state5,
3983
+ params.state6,
3984
+ params.state7,
3985
+ params.state8,
3986
+ params.state9,
3987
+ params.state10,
3988
+ params.topicId,
3989
+ params.topicSlug,
3990
+ params.categoryId,
3991
+ params.categorySlug,
3992
+ ...params.postIds ?? [],
3993
+ ...params.excludeIds ?? [],
3994
+ params.page,
3995
+ params.pageSize
3996
+ ]
3997
+ }
3998
+ );
3999
+ };
4000
+ }
4001
+
4002
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-many-action.ts
4003
+ function createPostFindManyAction(ctx) {
4004
+ const {
4005
+ repositories: { postQueryRepository },
4006
+ action: { executeAction }
4007
+ } = ctx;
4008
+ return async function postFindManyAction(params) {
4009
+ return executeAction(
4010
+ async () => {
4011
+ const found = await postQueryRepository.findMany(params);
4012
+ return {
4013
+ data: { posts: found }
4014
+ };
4015
+ },
4016
+ {
4017
+ type: "query",
4018
+ key: ["post", "findManyAction", params.type, params.topicId]
4019
+ }
4020
+ );
4021
+ };
4022
+ }
4023
+
4024
+ // src/server/interfaces/actions/resources/seo-metadata/commands/upsert/seo-metadata-upsert-validator.ts
4025
+ var seoMetadataUpsertValidator = (schemas) => schemas.z.object({
4026
+ // -----------------------------------------------------------------------
4027
+ // relations
4028
+ // -----------------------------------------------------------------------
4029
+ // Post
4030
+ postId: schemas.id().exist({ table: "posts", column: "id" }),
4031
+ // ----------------------------------------------------------------------------
4032
+ // translation
4033
+ // ----------------------------------------------------------------------------
4034
+ translations: schemas.array(
4035
+ schemas.z.object({
4036
+ // core
4037
+ locale: schemas.locale(),
4038
+ // ---------------------------------------------------
4039
+ // Basic
4040
+ // ---------------------------------------------------
4041
+ title: schemas.text().nullable(),
4042
+ description: schemas.text().nullable(),
4043
+ // identity
4044
+ author: schemas.text().nullable(),
4045
+ // url
4046
+ canonical: schemas.url().nullable(),
4047
+ // URL
4048
+ alternate: schemas.array(
4049
+ schemas.z.object({
4050
+ hreflang: schemas.text().bcp47(),
4051
+ // BCP47
4052
+ href: schemas.url()
4053
+ // URL
4054
+ })
4055
+ ),
4056
+ // robots
4057
+ robots: schemas.text().nullable(),
4058
+ // ---------------------------------------------------
4059
+ // Basic
4060
+ // ---------------------------------------------------
4061
+ ogTitle: schemas.text().nullable(),
4062
+ ogDescription: schemas.text().nullable(),
4063
+ // url
4064
+ ogUrl: schemas.url().nullable(),
4065
+ // URL
4066
+ // identity
4067
+ ogType: schemas.z.enum(OG_TYPE_ARRAY).nullable(),
4068
+ ogSiteName: schemas.text().nullable(),
4069
+ // image
4070
+ ogImage: schemas.url().nullable(),
4071
+ // URL
4072
+ ogImageAlt: schemas.text().nullable(),
4073
+ ogImageType: schemas.text().nullable(),
4074
+ ogImageWidth: schemas.z.number().nullable(),
4075
+ ogImageHeight: schemas.z.number().nullable(),
4076
+ // locale
4077
+ ogLocale: schemas.text().ogLocale().nullable(),
4078
+ // OG Locale
4079
+ ogLocaleAlternate: schemas.array(schemas.text().ogLocale()),
4080
+ // OG Locale
4081
+ // article
4082
+ ogArticlePublishedTime: schemas.isoString().nullable(),
4083
+ // ISOString
4084
+ ogArticleModifiedTime: schemas.isoString().nullable(),
4085
+ // ISOString
4086
+ ogArticleAuthor: schemas.text().nullable(),
4087
+ ogArticleSection: schemas.text().nullable(),
4088
+ ogArticleTag: schemas.array(schemas.text()),
4089
+ // ---------------------------------------------------
4090
+ // Twitter
4091
+ // ---------------------------------------------------
4092
+ twitterCard: schemas.z.enum(TWITTER_CARD_ARRAY).nullable(),
4093
+ // identity
4094
+ twitterSite: schemas.text().nullable(),
4095
+ twitterCreator: schemas.text().nullable(),
4096
+ // ---------------------------------------------------
4097
+ // JSON-LD
4098
+ // ---------------------------------------------------
4099
+ jsonLd: schemas.array(schemas.z.any())
4100
+ })
4101
+ )
4102
+ });
4103
+
4104
+ // src/server/interfaces/actions/resources/seo-metadata/commands/upsert/create-seo-metadata-upsert-action.ts
4105
+ function createSeoMetadataUpsertAction(ctx) {
4106
+ const {
4107
+ repositories: { seoMetadataCommandRepository },
4108
+ middlewares: { authMiddleware },
4109
+ action: { executeAction },
4110
+ schemas: { schemas }
4111
+ } = ctx;
4112
+ return async function seoMetadataCreateAction({
4113
+ formData
4114
+ }) {
4115
+ return executeAction(
4116
+ async () => {
4117
+ await authMiddleware.authenticate();
4118
+ const normalizedPayload = {
4119
+ ...formData,
4120
+ translations: formData.translations.map((t) => {
4121
+ t.ogArticlePublishedTime = datetimeToDb(t.ogArticlePublishedTime);
4122
+ t.ogArticleModifiedTime = datetimeToDb(t.ogArticleModifiedTime);
4123
+ t.jsonLd = jsonArrayToDb(t.jsonLd);
4124
+ return t;
4125
+ })
4126
+ };
4127
+ const validatedPayload = await seoMetadataUpsertValidator(schemas).parseAsync(
4128
+ normalizedPayload
4129
+ );
4130
+ await seoMetadataCommandRepository.upsert(validatedPayload);
4131
+ return {
4132
+ i18nKey: "ok.update-ok"
4133
+ };
4134
+ },
4135
+ { type: "command" }
4136
+ );
4137
+ };
4138
+ }
4139
+
4140
+ // src/server/applications/auth/create-auth-use-cases.ts
4141
+ function createAuthUseCases({
4142
+ prisma,
4143
+ adminQueryRepository,
4144
+ adminRefreshTokenCommandRepository,
4145
+ jwtService,
4146
+ argon2Service,
4147
+ cryptoService,
4148
+ cookieService,
4149
+ config
4150
+ }) {
4151
+ async function verifyCredentials({
4152
+ email,
4153
+ password
4154
+ }) {
4155
+ const found = await adminQueryRepository.findWithPasswordHash({ email });
4156
+ if (found) {
4157
+ const isValid = await argon2Service.verify(found.passwordHash, password);
4158
+ if (isValid) return found;
4159
+ }
4160
+ throw new ServerError({ i18nKey: "error.credentials-incorrect" });
4161
+ }
4162
+ async function updatePassword({
4163
+ email,
4164
+ password
4165
+ }) {
4166
+ const updatedAdmin = await prisma.admin.update({
4167
+ where: { email },
4168
+ data: { passwordHash: await argon2Service.hash(password) }
4169
+ });
4170
+ return updatedAdmin;
4171
+ }
4172
+ async function createRefreshToken({
4173
+ admin,
4174
+ deviceInfo,
4175
+ ip
4176
+ }) {
4177
+ const token = cryptoService.generateToken();
4178
+ const tokenHash = cryptoService.hash(token);
4179
+ await adminRefreshTokenCommandRepository.create({
4180
+ tokenHash,
4181
+ ip,
4182
+ deviceInfo,
4183
+ expiresAt: new Date(Date.now() + config.refreshTokenTtl * 1e3),
4184
+ adminId: admin.id,
4185
+ email: admin.email
4186
+ });
4187
+ return token;
4188
+ }
4189
+ async function refreshTokens({
4190
+ admin,
4191
+ deviceInfo,
4192
+ ip
4193
+ }) {
4194
+ const token = await createRefreshToken({ admin, deviceInfo, ip });
4195
+ await cookieService.setSignedCookie({
4196
+ name: config.refreshTokenName,
4197
+ value: token,
4198
+ expireSeconds: config.refreshTokenTtl
4199
+ });
4200
+ const accessToken = jwtService.sign({
4201
+ payload: { id: admin.id },
4202
+ secret: cryptoService.hash(config.accessTokenSecret),
4203
+ expiresIn: config.accessTokenTtl
4204
+ });
4205
+ await cookieService.setSignedCookie({
4206
+ name: config.accessTokenName,
4207
+ value: accessToken,
4208
+ expireSeconds: config.accessTokenTtl
4209
+ });
4210
+ }
4211
+ function signPasswordResetToken({ admin }) {
4212
+ const payload = { email: admin.email };
4213
+ const passwordResetToken = jwtService.sign({
4214
+ payload,
4215
+ secret: config.resetPasswordSecret,
4216
+ expiresIn: config.resetPasswordTtl
4217
+ });
4218
+ return passwordResetToken;
4219
+ }
4220
+ function verifyPasswordResetToken({ token }) {
4221
+ const payload = jwtService.verify({
4222
+ token,
4223
+ secret: config.resetPasswordSecret
4224
+ });
4225
+ return payload;
4226
+ }
4227
+ function signEmailVerificationToken() {
4228
+ const emailVerificationToken = jwtService.sign({
4229
+ payload: {},
4230
+ secret: config.verifyEmailSecret,
4231
+ expiresIn: config.verifyEmailTtl
4232
+ });
4233
+ return emailVerificationToken;
4234
+ }
4235
+ async function verifyEmailAndUpdate({
4236
+ token,
4237
+ admin
4238
+ }) {
4239
+ jwtService.verify({
4240
+ token,
4241
+ secret: config.verifyEmailSecret
4242
+ });
4243
+ const updatedAdmin = await prisma.admin.update({
4244
+ where: { email: admin.email },
4245
+ data: { emailVerifiedAt: /* @__PURE__ */ new Date() }
4246
+ });
4247
+ return updatedAdmin;
4248
+ }
4249
+ return {
4250
+ verifyCredentials,
4251
+ updatePassword,
4252
+ createRefreshToken,
4253
+ refreshTokens,
4254
+ // reset password
4255
+ signPasswordResetToken,
4256
+ verifyPasswordResetToken,
4257
+ // verify email
4258
+ signEmailVerificationToken,
4259
+ verifyEmailAndUpdate
4260
+ };
4261
+ }
4262
+
4263
+ // src/server/applications/emails/create-email-verification-email.ts
4264
+ function createEmailVerificationEmail({
4265
+ renderEmailTemplate,
4266
+ sendEmail,
4267
+ authUseCases,
4268
+ webUrl
4269
+ }) {
4270
+ async function generateHtml(replacements) {
4271
+ return await renderEmailTemplate("auth/verify-email", replacements);
4272
+ }
4273
+ async function send({ translator, admin, newEmail }) {
4274
+ const email = newEmail ?? admin.email;
4275
+ const emailVerificationToken = authUseCases.signEmailVerificationToken();
4276
+ const emailVerificationUrl = `${webUrl}/cms/verify-email?email=${email}&emailVerificationToken=${emailVerificationToken}`;
4277
+ const html = await generateHtml({
4278
+ emailVerificationUrl
4279
+ });
4280
+ return sendEmail({
4281
+ to: email,
4282
+ subject: translator.t("email.verify-email.text"),
4283
+ html
4284
+ });
4285
+ }
4286
+ return {
4287
+ generateHtml,
4288
+ send
4289
+ };
4290
+ }
4291
+
4292
+ // src/server/applications/emails/create-forgot-password-email.ts
4293
+ function createForgotPasswordEmail({
4294
+ renderEmailTemplate,
4295
+ sendEmail,
4296
+ authUseCases,
4297
+ webUrl
4298
+ }) {
4299
+ async function generateHtml(replacements) {
4300
+ return await renderEmailTemplate("auth/forgot-password", replacements);
4301
+ }
4302
+ async function send({ translator, admin }) {
4303
+ const passwordResetToken = authUseCases.signPasswordResetToken({ admin });
4304
+ const passwordResetUrl = `${webUrl}/cms/reset-password?passwordResetToken=${passwordResetToken}`;
4305
+ const html = await generateHtml({ passwordResetUrl });
4306
+ return sendEmail({
4307
+ to: admin.email,
4308
+ subject: translator.t("email.forgot-password.text"),
4309
+ html
4310
+ });
4311
+ }
4312
+ return {
4313
+ generateHtml,
4314
+ send
4315
+ };
4316
+ }
4317
+
4318
+ export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, ServerError, createAdminCommandRepository, createAdminCreateAction, createAdminDeleteAction, createAdminFindFullAction, createAdminFindListCardsAction, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenDeleteAction, createAdminRefreshTokenFindManyAction, createAdminRefreshTokenQueryRepository, createAdminUpdateAction, createArgon2Service, createAuthMiddleware, createAuthUseCases, createCache, createCacheResult, createChangePasswordAction, createCookieService, createCryptoService, createEmailUnverifiedAction, createEmailVerificationEmail, createExecuteAction, createExecuteApi, createExist, createFileCommandRepository, createFileCreateAction, createFileCreateManyAction, createFileFindFullAction, createFileFindListCardsAction, createFilePurgeManyAction, createFileQueryRepository, createFileRestoreManyAction, createFileSchema, createFileSoftDeleteAction, createFileSoftDeleteManyAction, createFileUpdateAction, createFolderCommandRepository, createFolderCreateAction, createFolderDeleteAction, createFolderFindFullAction, createFolderFindListCardsAction, createFolderQueryRepository, createFolderUpdateAction, createForgotPasswordAction, createForgotPasswordEmail, createIpRateLimiter, createJwtService, createMultiFileSchema, createPostCommandRepository, createPostCreateAction, createPostDeleteAction, createPostFindAction, createPostFindFullAction, createPostFindListCardsAction, createPostFindManyAction, createPostQueryRepository, createPostUpdateAction, createRenderEmailTemplate, createResetPasswordAction, createSchemas, createSendEmail, createSeoMetadataCommandRepository, createSeoMetadataUpsertAction, createSignInAction, createSignOutAction, createTocItemSchema, createTransporter, createUnique, createVerifyAccessToken, createVerifyAction, createVerifyEmailAction, createVerifyRefreshToken, createZod, normalizeCacheKey };