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