@infuro/cms-core 1.0.0

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/index.js ADDED
@@ -0,0 +1,2928 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __decorateClass = (decorators, target, key, kind) => {
12
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
13
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
14
+ if (decorator = decorators[i])
15
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
16
+ if (kind && result) __defProp(target, key, result);
17
+ return result;
18
+ };
19
+
20
+ // src/plugins/email/email-service.ts
21
+ var email_service_exports = {};
22
+ __export(email_service_exports, {
23
+ EmailService: () => EmailService,
24
+ emailTemplates: () => emailTemplates
25
+ });
26
+ import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
27
+ import nodemailer from "nodemailer";
28
+ var EmailService, emailTemplates;
29
+ var init_email_service = __esm({
30
+ "src/plugins/email/email-service.ts"() {
31
+ "use strict";
32
+ EmailService = class {
33
+ config;
34
+ sesClient;
35
+ transporter;
36
+ constructor(config) {
37
+ this.config = config;
38
+ if (config.type === "AWS") {
39
+ if (!config.region || !config.accessKeyId || !config.secretAccessKey) {
40
+ throw new Error("AWS SES configuration incomplete");
41
+ }
42
+ this.sesClient = new SESClient({
43
+ region: config.region,
44
+ credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey }
45
+ });
46
+ } else if (config.type === "SMTP" || config.type === "GMAIL") {
47
+ if (!config.user || !config.password) throw new Error("SMTP configuration incomplete");
48
+ this.transporter = nodemailer.createTransport({
49
+ host: config.type === "GMAIL" ? "smtp.gmail.com" : void 0,
50
+ port: 587,
51
+ secure: false,
52
+ auth: { user: config.user, pass: config.password }
53
+ });
54
+ } else {
55
+ throw new Error(`Unsupported email type: ${config.type}`);
56
+ }
57
+ }
58
+ async send(emailData) {
59
+ try {
60
+ if (this.config.type === "AWS" && this.sesClient) {
61
+ await this.sesClient.send(
62
+ new SendEmailCommand({
63
+ Source: emailData.from || this.config.from,
64
+ Destination: { ToAddresses: [emailData.to || this.config.to] },
65
+ Message: {
66
+ Subject: { Data: emailData.subject, Charset: "UTF-8" },
67
+ Body: {
68
+ Html: { Data: emailData.html, Charset: "UTF-8" },
69
+ ...emailData.text && { Text: { Data: emailData.text, Charset: "UTF-8" } }
70
+ }
71
+ }
72
+ })
73
+ );
74
+ return true;
75
+ }
76
+ if ((this.config.type === "SMTP" || this.config.type === "GMAIL") && this.transporter) {
77
+ await this.transporter.sendMail({
78
+ from: emailData.from || this.config.from,
79
+ to: emailData.to || this.config.to,
80
+ subject: emailData.subject,
81
+ html: emailData.html,
82
+ text: emailData.text
83
+ });
84
+ return true;
85
+ }
86
+ return false;
87
+ } catch (error) {
88
+ console.error("Email sending failed:", error);
89
+ return false;
90
+ }
91
+ }
92
+ };
93
+ emailTemplates = {
94
+ formSubmission: (data) => ({
95
+ subject: `New Form Submission: ${data.formName}`,
96
+ html: `<h2>New Form Submission</h2><p><strong>Form:</strong> ${data.formName}</p><p><strong>Contact:</strong> ${data.contactName} (${data.contactEmail})</p><pre>${JSON.stringify(data.formData, null, 2)}</pre>`,
97
+ text: `New Form Submission
98
+ Form: ${data.formName}
99
+ Contact: ${data.contactName} (${data.contactEmail})
100
+ ${JSON.stringify(data.formData, null, 2)}`
101
+ }),
102
+ contactSubmission: (data) => ({
103
+ subject: `New Contact Form Submission from ${data.name}`,
104
+ html: `<h2>New Contact Form Submission</h2><p><strong>Name:</strong> ${data.name}</p><p><strong>Email:</strong> ${data.email}</p>${data.phone ? `<p><strong>Phone:</strong> ${data.phone}</p>` : ""}${data.message ? `<p><strong>Message:</strong></p><p>${data.message}</p>` : ""}`,
105
+ text: `New Contact Form Submission
106
+ Name: ${data.name}
107
+ Email: ${data.email}
108
+ ${data.phone ? `Phone: ${data.phone}
109
+ ` : ""}${data.message ? `Message: ${data.message}` : ""}`
110
+ }),
111
+ passwordReset: (data) => ({
112
+ subject: "Reset your password",
113
+ html: `<h2>Reset your password</h2><p>Click the link below to set a new password. This link expires in 1 hour.</p><p><a href="${data.resetLink}">${data.resetLink}</a></p>`,
114
+ text: `Reset your password: ${data.resetLink}
115
+
116
+ This link expires in 1 hour.`
117
+ })
118
+ };
119
+ }
120
+ });
121
+
122
+ // src/plugins/registry.ts
123
+ var noopLogger = {
124
+ info: () => {
125
+ },
126
+ warn: () => {
127
+ },
128
+ error: () => {
129
+ }
130
+ };
131
+ async function createCmsApp(options) {
132
+ const { dataSource, config = {}, plugins = [], logger = noopLogger } = options;
133
+ const context = { dataSource, config, logger };
134
+ const registry = /* @__PURE__ */ new Map();
135
+ for (const plugin of plugins) {
136
+ const instance = await plugin.init(context);
137
+ registry.set(plugin.name, instance !== void 0 ? instance : plugin);
138
+ }
139
+ return {
140
+ dataSource,
141
+ getPlugin(name) {
142
+ return registry.get(name);
143
+ }
144
+ };
145
+ }
146
+
147
+ // src/plugins/erp/erp-auth.ts
148
+ var ERPAuthService = class {
149
+ baseUrl;
150
+ credentials;
151
+ token = null;
152
+ constructor(config) {
153
+ this.baseUrl = config.baseUrl || "https://uat.infuroerp.com";
154
+ this.credentials = {
155
+ clientId: config.clientId,
156
+ clientSecret: config.clientSecret,
157
+ tenantId: config.tenantId
158
+ };
159
+ }
160
+ async authenticate() {
161
+ const authUrl = `${this.baseUrl}/api/auth/account/app-login`;
162
+ const authPayload = {
163
+ clientId: this.credentials.clientId,
164
+ clientSecret: this.credentials.clientSecret,
165
+ tenantId: this.credentials.tenantId
166
+ };
167
+ const response = await fetch(authUrl, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify(authPayload)
171
+ });
172
+ if (!response.ok) {
173
+ const errorData = await response.text();
174
+ throw new Error(`Authentication failed: ${response.status} - ${errorData}`);
175
+ }
176
+ const tokenData = await response.json();
177
+ const actualToken = tokenData.access_token || tokenData.token || tokenData.accessToken || tokenData.jwt || tokenData.bearer_token || tokenData.auth_token;
178
+ if (!actualToken) {
179
+ throw new Error("No access token found in authentication response");
180
+ }
181
+ const expiresIn = tokenData.expires_in || tokenData.expiresIn || 3600;
182
+ const expiresAt = Date.now() + expiresIn * 1e3;
183
+ const token = {
184
+ access_token: actualToken,
185
+ token_type: tokenData.token_type || tokenData.tokenType || "Bearer",
186
+ expires_in: expiresIn,
187
+ expires_at: expiresAt
188
+ };
189
+ this.token = token;
190
+ return token;
191
+ }
192
+ async getValidToken() {
193
+ if (this.token && this.token.expires_at && Date.now() < this.token.expires_at) {
194
+ return this.token.access_token;
195
+ }
196
+ const newToken = await this.authenticate();
197
+ return newToken.access_token;
198
+ }
199
+ async makeAuthenticatedRequest(url, options = {}) {
200
+ const token = await this.getValidToken();
201
+ const headers = {
202
+ "Content-Type": "application/json",
203
+ Authorization: `Bearer ${token}`,
204
+ ...options.headers
205
+ };
206
+ return fetch(url, { ...options, headers });
207
+ }
208
+ };
209
+
210
+ // src/plugins/erp/erp-submission.ts
211
+ var ERPSubmissionService = class {
212
+ baseUrl;
213
+ pipelineId;
214
+ pipelineStageId;
215
+ auth;
216
+ constructor(config) {
217
+ this.baseUrl = config.baseUrl || "https://uat.infuroerp.com";
218
+ this.pipelineId = config.pipelineId;
219
+ this.pipelineStageId = config.pipelineStageId;
220
+ this.auth = config.auth;
221
+ }
222
+ async submitContact(formData) {
223
+ try {
224
+ if (!formData.firstName || !formData.lastName || !formData.email) {
225
+ throw new Error("Missing required fields: First name, last name, and email are required");
226
+ }
227
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
228
+ if (!emailRegex.test(formData.email)) {
229
+ throw new Error("Invalid email format");
230
+ }
231
+ const contactPayload = {
232
+ contact: `${formData.firstName} ${formData.lastName}`,
233
+ type: "Individual",
234
+ status: "Active",
235
+ tag_ids: [],
236
+ company_id: null,
237
+ first_name: formData.firstName,
238
+ last_name: formData.lastName,
239
+ email: formData.email,
240
+ phone: formData.phone || "",
241
+ country: ""
242
+ };
243
+ const contactApiUrl = `${this.baseUrl}/api/crm/contacts`;
244
+ const contactResponse = await this.auth.makeAuthenticatedRequest(contactApiUrl, {
245
+ method: "POST",
246
+ body: JSON.stringify(contactPayload)
247
+ });
248
+ if (!contactResponse.ok) {
249
+ const errorData = await contactResponse.text();
250
+ throw new Error(`Failed to submit contact: ${contactResponse.status} - ${errorData}`);
251
+ }
252
+ const contactData = await contactResponse.json();
253
+ const contactId = contactData.id || contactData.data?.id;
254
+ if (!contactId) {
255
+ throw new Error("No contact ID returned from API");
256
+ }
257
+ let opportunityId;
258
+ if (this.pipelineId && this.pipelineStageId) {
259
+ const currentDate = (/* @__PURE__ */ new Date()).toISOString();
260
+ const opportunityPayload = {
261
+ contact_id: contactId,
262
+ name: `${formData.industry || "General"} Inquiry - ${formData.firstName} ${formData.lastName}`,
263
+ description: formData.message || "Inquiry from website contact form",
264
+ createdAt: currentDate,
265
+ pipeline_id: this.pipelineId,
266
+ pipeline_stage_id: this.pipelineStageId,
267
+ amount: null,
268
+ expected_close_date: currentDate,
269
+ actual_close_date: currentDate,
270
+ owner: null
271
+ };
272
+ const opportunityApiUrl = `${this.baseUrl}/api/crm/opportunities`;
273
+ const opportunityResponse = await this.auth.makeAuthenticatedRequest(opportunityApiUrl, {
274
+ method: "POST",
275
+ body: JSON.stringify(opportunityPayload)
276
+ });
277
+ if (opportunityResponse.ok) {
278
+ const opportunityData = await opportunityResponse.json();
279
+ opportunityId = opportunityData.id || opportunityData.data?.id;
280
+ }
281
+ }
282
+ return { success: true, contactId, opportunityId };
283
+ } catch (error) {
284
+ const message = error instanceof Error ? error.message : "Failed to submit to ERP";
285
+ return { success: false, contactId: "", error: message };
286
+ }
287
+ }
288
+ extractContactData(formData, formFields) {
289
+ const contactData = {
290
+ firstName: "",
291
+ lastName: "",
292
+ email: "",
293
+ phone: "",
294
+ industry: "",
295
+ message: ""
296
+ };
297
+ let hasEmail = false;
298
+ for (const field of formFields) {
299
+ const fieldValue = formData[field.id.toString()];
300
+ if (fieldValue == null) continue;
301
+ const label = field.label.toLowerCase();
302
+ const value = String(fieldValue).trim();
303
+ if (field.type === "email") {
304
+ contactData.email = value;
305
+ hasEmail = true;
306
+ } else if (field.type === "phone") {
307
+ contactData.phone = value;
308
+ } else if (field.type === "text" || field.type === "textarea") {
309
+ if (label.includes("first name") || label.includes("firstname")) {
310
+ contactData.firstName = value;
311
+ } else if (label.includes("last name") || label.includes("lastname")) {
312
+ contactData.lastName = value;
313
+ } else if (label.includes("name") && !contactData.firstName) {
314
+ const nameParts = value.split(" ");
315
+ if (nameParts.length >= 2) {
316
+ contactData.firstName = nameParts[0];
317
+ contactData.lastName = nameParts.slice(1).join(" ");
318
+ } else {
319
+ contactData.firstName = value;
320
+ }
321
+ } else if (label.includes("industry")) {
322
+ contactData.industry = value;
323
+ } else if (label.includes("message") || label.includes("comment") || label.includes("description") || label.includes("inquiry")) {
324
+ contactData.message = value;
325
+ }
326
+ }
327
+ }
328
+ if (!hasEmail && !contactData.firstName) return null;
329
+ if (!contactData.firstName && contactData.email) {
330
+ contactData.firstName = contactData.email.split("@")[0];
331
+ contactData.lastName = "";
332
+ }
333
+ return contactData;
334
+ }
335
+ };
336
+
337
+ // src/plugins/erp/index.ts
338
+ function erpPlugin(config) {
339
+ return {
340
+ name: "erp",
341
+ version: "1.0.0",
342
+ async init(context) {
343
+ const baseUrl = config.baseUrl || context.config.ERP_API_BASE_URL || "https://uat.infuroerp.com";
344
+ const clientId = config.clientId || context.config.CLIENT_ID || "";
345
+ const clientSecret = config.clientSecret || context.config.CLIENT_SECRET || "";
346
+ const tenantId = config.tenantId || context.config.TENANT_ID || "";
347
+ const pipelineId = config.pipelineId || context.config.PIPELINE_ID || "";
348
+ const pipelineStageId = config.pipelineStageId || context.config.PIPELINE_STAGE_ID || "";
349
+ const auth = new ERPAuthService({
350
+ baseUrl,
351
+ clientId,
352
+ clientSecret,
353
+ tenantId
354
+ });
355
+ const submission = new ERPSubmissionService({
356
+ baseUrl,
357
+ pipelineId,
358
+ pipelineStageId,
359
+ auth
360
+ });
361
+ return { auth, submission };
362
+ }
363
+ };
364
+ }
365
+
366
+ // src/plugins/email/index.ts
367
+ init_email_service();
368
+ init_email_service();
369
+ function emailPlugin(config) {
370
+ return {
371
+ name: "email",
372
+ version: "1.0.0",
373
+ async init(context) {
374
+ const from = config.from || context.config.SMTP_FROM || "no-reply@example.com";
375
+ const to = config.to || context.config.SMTP_TO || "info@example.com";
376
+ const merged = {
377
+ ...config,
378
+ from,
379
+ to,
380
+ type: config.type || context.config.SMTP_TYPE || "SMTP",
381
+ user: config.user ?? context.config.SMTP_USER,
382
+ password: config.password ?? context.config.SMTP_PASSWORD,
383
+ region: config.region ?? context.config.AWS_REGION,
384
+ accessKeyId: config.accessKeyId ?? context.config.AWS_ACCESS_KEY_ID,
385
+ secretAccessKey: config.secretAccessKey ?? context.config.AWS_SECRET_ACCESS_KEY
386
+ };
387
+ return new EmailService(merged);
388
+ }
389
+ };
390
+ }
391
+
392
+ // src/plugins/analytics/analytics-service.ts
393
+ import { google } from "googleapis";
394
+ var AnalyticsService = class {
395
+ analytics;
396
+ viewId;
397
+ constructor(config) {
398
+ const privateKey = config.privateKey.replace(/\\n/g, "\n");
399
+ const auth = new google.auth.JWT({
400
+ email: config.clientEmail,
401
+ key: privateKey,
402
+ scopes: ["https://www.googleapis.com/auth/analytics.readonly"]
403
+ });
404
+ this.analytics = google.analyticsdata({ version: "v1beta", auth });
405
+ this.viewId = config.viewId;
406
+ }
407
+ async getAnalyticsData(days = 30) {
408
+ const endDate = /* @__PURE__ */ new Date();
409
+ const startDate = /* @__PURE__ */ new Date();
410
+ startDate.setDate(startDate.getDate() - days);
411
+ const [visitors, pageViews, bounceRate, sessionDuration, topPages, trafficSources, geographicData, dailyUsers] = await Promise.all([
412
+ this.getVisitors(startDate, endDate),
413
+ this.getPageViews(startDate, endDate),
414
+ this.getBounceRate(startDate, endDate),
415
+ this.getAvgSessionDuration(startDate, endDate),
416
+ this.getTopPages(startDate, endDate),
417
+ this.getTrafficSources(startDate, endDate),
418
+ this.getGeographicData(startDate, endDate),
419
+ this.getDailyUsers(startDate, endDate)
420
+ ]);
421
+ return {
422
+ visitors,
423
+ pageViews,
424
+ bounceRate,
425
+ avgSessionDuration: sessionDuration,
426
+ topPages,
427
+ trafficSources,
428
+ geographicData,
429
+ dailyUsers
430
+ };
431
+ }
432
+ async runReport(requestBody) {
433
+ const response = await this.analytics.properties.runReport({
434
+ property: `properties/${this.viewId}`,
435
+ requestBody
436
+ });
437
+ return response?.data;
438
+ }
439
+ async getVisitors(startDate, endDate) {
440
+ const data = await this.runReport({
441
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
442
+ metrics: [{ name: "totalUsers" }]
443
+ });
444
+ return parseInt(data.rows?.[0]?.metricValues?.[0]?.value || "0");
445
+ }
446
+ async getPageViews(startDate, endDate) {
447
+ const data = await this.runReport({
448
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
449
+ metrics: [{ name: "screenPageViews" }]
450
+ });
451
+ return parseInt(data.rows?.[0]?.metricValues?.[0]?.value || "0");
452
+ }
453
+ async getBounceRate(startDate, endDate) {
454
+ const data = await this.runReport({
455
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
456
+ metrics: [{ name: "bounceRate" }]
457
+ });
458
+ return parseFloat(data.rows?.[0]?.metricValues?.[0]?.value || "0");
459
+ }
460
+ async getAvgSessionDuration(startDate, endDate) {
461
+ const data = await this.runReport({
462
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
463
+ metrics: [{ name: "averageSessionDuration" }]
464
+ });
465
+ return parseFloat(data.rows?.[0]?.metricValues?.[0]?.value || "0");
466
+ }
467
+ async getTopPages(startDate, endDate) {
468
+ const data = await this.runReport({
469
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
470
+ dimensions: [{ name: "pagePath" }],
471
+ metrics: [{ name: "screenPageViews" }],
472
+ limit: 10
473
+ });
474
+ return data.rows?.map((row) => ({ page: row.dimensionValues[0].value, views: parseInt(row.metricValues[0].value) })) || [];
475
+ }
476
+ async getTrafficSources(startDate, endDate) {
477
+ const data = await this.runReport({
478
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
479
+ dimensions: [{ name: "sessionSource" }],
480
+ metrics: [{ name: "sessions" }],
481
+ limit: 10
482
+ });
483
+ return data.rows?.map((row) => ({ source: row.dimensionValues[0].value, sessions: parseInt(row.metricValues[0].value) })) || [];
484
+ }
485
+ async getGeographicData(startDate, endDate) {
486
+ const data = await this.runReport({
487
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
488
+ dimensions: [{ name: "country" }],
489
+ metrics: [{ name: "sessions" }],
490
+ limit: 10
491
+ });
492
+ return data.rows?.map((row) => ({ country: row.dimensionValues[0].value, sessions: parseInt(row.metricValues[0].value) })) || [];
493
+ }
494
+ async getDailyUsers(startDate, endDate) {
495
+ const data = await this.runReport({
496
+ dateRanges: [{ startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0] }],
497
+ dimensions: [{ name: "date" }],
498
+ metrics: [{ name: "totalUsers" }],
499
+ orderBys: [{ dimension: { dimensionName: "date" } }]
500
+ });
501
+ return data.rows?.map((row) => {
502
+ const dateStr = row.dimensionValues[0].value;
503
+ return { date: `${dateStr.substring(4, 6)}/${dateStr.substring(6, 8)}`, users: parseInt(row.metricValues[0].value) };
504
+ }) || [];
505
+ }
506
+ };
507
+
508
+ // src/plugins/analytics/index.ts
509
+ function analyticsPlugin(config = {}) {
510
+ return {
511
+ name: "analytics",
512
+ version: "1.0.0",
513
+ async init(context) {
514
+ const privateKey = config.privateKey ?? context.config.GOOGLE_ANALYTICS_PRIVATE_KEY;
515
+ const clientEmail = config.clientEmail ?? context.config.GOOGLE_ANALYTICS_CLIENT_EMAIL;
516
+ const viewId = config.viewId ?? context.config.GOOGLE_ANALYTICS_VIEW_ID ?? "";
517
+ if (!privateKey || !clientEmail || !viewId) {
518
+ throw new Error("Google Analytics credentials not configured");
519
+ }
520
+ return new AnalyticsService({ privateKey, clientEmail, viewId });
521
+ }
522
+ };
523
+ }
524
+
525
+ // src/plugins/sms/index.ts
526
+ function smsPlugin(_config = {}) {
527
+ return {
528
+ name: "sms",
529
+ version: "1.0.0",
530
+ async init() {
531
+ return null;
532
+ }
533
+ };
534
+ }
535
+
536
+ // src/plugins/payment/index.ts
537
+ function paymentPlugin(_config = {}) {
538
+ return {
539
+ name: "payment",
540
+ version: "1.0.0",
541
+ async init() {
542
+ return null;
543
+ }
544
+ };
545
+ }
546
+
547
+ // src/plugins/storage/s3.ts
548
+ function getS3Storage(config) {
549
+ const prefix = (config.prefix ?? "uploads").replace(/\/$/, "");
550
+ const keyPrefix = prefix ? `${prefix}/` : "";
551
+ return {
552
+ async upload(buffer, key, contentType) {
553
+ const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
554
+ const client = new S3Client({
555
+ region: config.region,
556
+ credentials: {
557
+ accessKeyId: config.accessKeyId,
558
+ secretAccessKey: config.secretAccessKey
559
+ }
560
+ });
561
+ const fullKey = keyPrefix + key.replace(/^\/+/, "");
562
+ await client.send(
563
+ new PutObjectCommand({
564
+ Bucket: config.bucket,
565
+ Key: fullKey,
566
+ Body: buffer,
567
+ ContentType: contentType
568
+ })
569
+ );
570
+ if (config.baseUrl) {
571
+ return `${config.baseUrl.replace(/\/$/, "")}/${fullKey}`;
572
+ }
573
+ return `https://s3.${config.region}.amazonaws.com/${config.bucket}/${fullKey}`;
574
+ }
575
+ };
576
+ }
577
+ function s3StoragePlugin(config) {
578
+ return {
579
+ name: "storage",
580
+ version: "1.0.0",
581
+ async init(context) {
582
+ const region = config.region ?? context.config.AWS_REGION ?? "";
583
+ const accessKeyId = config.accessKeyId ?? context.config.AWS_ACCESS_KEY_ID ?? "";
584
+ const secretAccessKey = config.secretAccessKey ?? context.config.AWS_SECRET_ACCESS_KEY ?? "";
585
+ const bucket = config.bucket ?? context.config.AWS_BUCKET_NAME ?? "";
586
+ const baseUrl = config.baseUrl ?? context.config.S3_MEDIA_URL ?? context.config.S3_CUSTOM_DOMAIN;
587
+ const resolvedBaseUrl = typeof baseUrl === "string" && baseUrl ? baseUrl.startsWith("http") ? baseUrl : `https://${baseUrl}` : void 0;
588
+ if (!region || !accessKeyId || !secretAccessKey || !bucket) {
589
+ throw new Error("S3 storage plugin: missing region, accessKeyId, secretAccessKey or bucket");
590
+ }
591
+ return getS3Storage({
592
+ region,
593
+ accessKeyId,
594
+ secretAccessKey,
595
+ bucket,
596
+ baseUrl: resolvedBaseUrl,
597
+ prefix: config.prefix ?? "uploads"
598
+ });
599
+ }
600
+ };
601
+ }
602
+
603
+ // src/plugins/storage/local.ts
604
+ function getLocalStorage(config) {
605
+ const dir = (config.dir ?? "public/uploads").replace(/\/$/, "");
606
+ const publicPath = config.publicPath ?? `/${dir.replace(/^\/+/, "").replace(/\\/g, "/")}`;
607
+ return {
608
+ async upload(buffer, key, _contentType) {
609
+ const fs = await import("fs/promises");
610
+ const path = await import("path");
611
+ const fullDir = path.join(process.cwd(), dir);
612
+ await fs.mkdir(fullDir, { recursive: true });
613
+ const fileName = key.replace(/^\/+/, "").replace(/\.\./g, "");
614
+ const filePath = path.join(fullDir, fileName);
615
+ const fileDir = path.dirname(filePath);
616
+ await fs.mkdir(fileDir, { recursive: true });
617
+ await fs.writeFile(filePath, buffer);
618
+ return `${publicPath.replace(/\/$/, "")}/${fileName}`;
619
+ }
620
+ };
621
+ }
622
+ function localStoragePlugin(config = {}) {
623
+ return {
624
+ name: "storage",
625
+ version: "1.0.0",
626
+ async init(context) {
627
+ const dir = config.dir ?? context.config.UPLOAD_DIR ?? "public/uploads";
628
+ return getLocalStorage({ ...config, dir });
629
+ }
630
+ };
631
+ }
632
+
633
+ // src/lib/utils.ts
634
+ import { clsx } from "clsx";
635
+ import { twMerge } from "tailwind-merge";
636
+ function cn(...inputs) {
637
+ return twMerge(clsx(inputs));
638
+ }
639
+ function generateSlug(title) {
640
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
641
+ }
642
+ function validateSlug(slug) {
643
+ const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
644
+ return slugRegex.test(slug) && slug.length >= 3 && slug.length <= 50;
645
+ }
646
+ function formatDate(date) {
647
+ const d = new Date(date);
648
+ return d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
649
+ }
650
+ function formatDateTime(date) {
651
+ const d = new Date(date);
652
+ return d.toLocaleDateString("en-US", {
653
+ year: "numeric",
654
+ month: "short",
655
+ day: "numeric",
656
+ hour: "2-digit",
657
+ minute: "2-digit"
658
+ });
659
+ }
660
+ function formatDateOnly(date) {
661
+ const d = new Date(date);
662
+ return d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
663
+ }
664
+ function truncateText(text, maxLength) {
665
+ if (text.length <= maxLength) return text;
666
+ return text.substring(0, maxLength).trim() + "...";
667
+ }
668
+
669
+ // src/entities/user.entity.ts
670
+ import { Entity as Entity3, PrimaryGeneratedColumn as PrimaryGeneratedColumn3, Column as Column3, ManyToOne as ManyToOne2, JoinColumn as JoinColumn2 } from "typeorm";
671
+
672
+ // src/entities/user-group.entity.ts
673
+ import { Entity as Entity2, PrimaryGeneratedColumn as PrimaryGeneratedColumn2, Column as Column2, OneToMany } from "typeorm";
674
+
675
+ // src/entities/permission.entity.ts
676
+ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from "typeorm";
677
+ var Permission = class {
678
+ id;
679
+ groupId;
680
+ entity;
681
+ canCreate;
682
+ canRead;
683
+ canUpdate;
684
+ canDelete;
685
+ createdAt;
686
+ updatedAt;
687
+ deletedAt;
688
+ deleted;
689
+ createdBy;
690
+ updatedBy;
691
+ deletedBy;
692
+ group;
693
+ };
694
+ __decorateClass([
695
+ PrimaryGeneratedColumn()
696
+ ], Permission.prototype, "id", 2);
697
+ __decorateClass([
698
+ Column("int")
699
+ ], Permission.prototype, "groupId", 2);
700
+ __decorateClass([
701
+ Column("varchar")
702
+ ], Permission.prototype, "entity", 2);
703
+ __decorateClass([
704
+ Column("boolean", { default: false })
705
+ ], Permission.prototype, "canCreate", 2);
706
+ __decorateClass([
707
+ Column("boolean", { default: false })
708
+ ], Permission.prototype, "canRead", 2);
709
+ __decorateClass([
710
+ Column("boolean", { default: false })
711
+ ], Permission.prototype, "canUpdate", 2);
712
+ __decorateClass([
713
+ Column("boolean", { default: false })
714
+ ], Permission.prototype, "canDelete", 2);
715
+ __decorateClass([
716
+ Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
717
+ ], Permission.prototype, "createdAt", 2);
718
+ __decorateClass([
719
+ Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
720
+ ], Permission.prototype, "updatedAt", 2);
721
+ __decorateClass([
722
+ Column({ type: "timestamp", nullable: true })
723
+ ], Permission.prototype, "deletedAt", 2);
724
+ __decorateClass([
725
+ Column("boolean", { default: false })
726
+ ], Permission.prototype, "deleted", 2);
727
+ __decorateClass([
728
+ Column("int", { nullable: true })
729
+ ], Permission.prototype, "createdBy", 2);
730
+ __decorateClass([
731
+ Column("int", { nullable: true })
732
+ ], Permission.prototype, "updatedBy", 2);
733
+ __decorateClass([
734
+ Column("int", { nullable: true })
735
+ ], Permission.prototype, "deletedBy", 2);
736
+ __decorateClass([
737
+ ManyToOne(() => UserGroup, (g) => g.permissions, { onDelete: "CASCADE" }),
738
+ JoinColumn({ name: "groupId" })
739
+ ], Permission.prototype, "group", 2);
740
+ Permission = __decorateClass([
741
+ Entity("permissions")
742
+ ], Permission);
743
+
744
+ // src/entities/user-group.entity.ts
745
+ var UserGroup = class {
746
+ id;
747
+ name;
748
+ createdAt;
749
+ updatedAt;
750
+ deletedAt;
751
+ deleted;
752
+ createdBy;
753
+ updatedBy;
754
+ deletedBy;
755
+ permissions;
756
+ users;
757
+ };
758
+ __decorateClass([
759
+ PrimaryGeneratedColumn2()
760
+ ], UserGroup.prototype, "id", 2);
761
+ __decorateClass([
762
+ Column2("varchar", { unique: true })
763
+ ], UserGroup.prototype, "name", 2);
764
+ __decorateClass([
765
+ Column2({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
766
+ ], UserGroup.prototype, "createdAt", 2);
767
+ __decorateClass([
768
+ Column2({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
769
+ ], UserGroup.prototype, "updatedAt", 2);
770
+ __decorateClass([
771
+ Column2({ type: "timestamp", nullable: true })
772
+ ], UserGroup.prototype, "deletedAt", 2);
773
+ __decorateClass([
774
+ Column2("boolean", { default: false })
775
+ ], UserGroup.prototype, "deleted", 2);
776
+ __decorateClass([
777
+ Column2("int", { nullable: true })
778
+ ], UserGroup.prototype, "createdBy", 2);
779
+ __decorateClass([
780
+ Column2("int", { nullable: true })
781
+ ], UserGroup.prototype, "updatedBy", 2);
782
+ __decorateClass([
783
+ Column2("int", { nullable: true })
784
+ ], UserGroup.prototype, "deletedBy", 2);
785
+ __decorateClass([
786
+ OneToMany(() => Permission, (p) => p.group)
787
+ ], UserGroup.prototype, "permissions", 2);
788
+ __decorateClass([
789
+ OneToMany(() => User, (u) => u.group)
790
+ ], UserGroup.prototype, "users", 2);
791
+ UserGroup = __decorateClass([
792
+ Entity2("user_groups")
793
+ ], UserGroup);
794
+
795
+ // src/entities/user.entity.ts
796
+ var User = class {
797
+ id;
798
+ name;
799
+ email;
800
+ password;
801
+ blocked;
802
+ groupId;
803
+ createdAt;
804
+ updatedAt;
805
+ deletedAt;
806
+ deleted;
807
+ createdBy;
808
+ updatedBy;
809
+ deletedBy;
810
+ group;
811
+ };
812
+ __decorateClass([
813
+ PrimaryGeneratedColumn3()
814
+ ], User.prototype, "id", 2);
815
+ __decorateClass([
816
+ Column3("varchar")
817
+ ], User.prototype, "name", 2);
818
+ __decorateClass([
819
+ Column3("varchar", { unique: true })
820
+ ], User.prototype, "email", 2);
821
+ __decorateClass([
822
+ Column3("varchar", { nullable: true })
823
+ ], User.prototype, "password", 2);
824
+ __decorateClass([
825
+ Column3("boolean", { default: false })
826
+ ], User.prototype, "blocked", 2);
827
+ __decorateClass([
828
+ Column3("int", { nullable: true })
829
+ ], User.prototype, "groupId", 2);
830
+ __decorateClass([
831
+ Column3({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
832
+ ], User.prototype, "createdAt", 2);
833
+ __decorateClass([
834
+ Column3({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
835
+ ], User.prototype, "updatedAt", 2);
836
+ __decorateClass([
837
+ Column3({ type: "timestamp", nullable: true })
838
+ ], User.prototype, "deletedAt", 2);
839
+ __decorateClass([
840
+ Column3("boolean", { default: false })
841
+ ], User.prototype, "deleted", 2);
842
+ __decorateClass([
843
+ Column3("int", { nullable: true })
844
+ ], User.prototype, "createdBy", 2);
845
+ __decorateClass([
846
+ Column3("int", { nullable: true })
847
+ ], User.prototype, "updatedBy", 2);
848
+ __decorateClass([
849
+ Column3("int", { nullable: true })
850
+ ], User.prototype, "deletedBy", 2);
851
+ __decorateClass([
852
+ ManyToOne2(() => UserGroup, (g) => g.users, { onDelete: "SET NULL" }),
853
+ JoinColumn2({ name: "groupId" })
854
+ ], User.prototype, "group", 2);
855
+ User = __decorateClass([
856
+ Entity3("users")
857
+ ], User);
858
+
859
+ // src/entities/password-reset-token.entity.ts
860
+ import { Entity as Entity4, PrimaryGeneratedColumn as PrimaryGeneratedColumn4, Column as Column4 } from "typeorm";
861
+ var PasswordResetToken = class {
862
+ id;
863
+ email;
864
+ token;
865
+ expiresAt;
866
+ createdAt;
867
+ };
868
+ __decorateClass([
869
+ PrimaryGeneratedColumn4()
870
+ ], PasswordResetToken.prototype, "id", 2);
871
+ __decorateClass([
872
+ Column4("varchar")
873
+ ], PasswordResetToken.prototype, "email", 2);
874
+ __decorateClass([
875
+ Column4("varchar", { unique: true })
876
+ ], PasswordResetToken.prototype, "token", 2);
877
+ __decorateClass([
878
+ Column4({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
879
+ ], PasswordResetToken.prototype, "expiresAt", 2);
880
+ __decorateClass([
881
+ Column4({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
882
+ ], PasswordResetToken.prototype, "createdAt", 2);
883
+ PasswordResetToken = __decorateClass([
884
+ Entity4("password_reset_tokens")
885
+ ], PasswordResetToken);
886
+
887
+ // src/entities/blog.entity.ts
888
+ import {
889
+ Entity as Entity9,
890
+ PrimaryGeneratedColumn as PrimaryGeneratedColumn9,
891
+ Column as Column9,
892
+ ManyToOne as ManyToOne4,
893
+ OneToMany as OneToMany4,
894
+ ManyToMany as ManyToMany2,
895
+ JoinTable,
896
+ JoinColumn as JoinColumn4
897
+ } from "typeorm";
898
+
899
+ // src/entities/category.entity.ts
900
+ import { Entity as Entity5, PrimaryGeneratedColumn as PrimaryGeneratedColumn5, Column as Column5, OneToMany as OneToMany2 } from "typeorm";
901
+ var Category = class {
902
+ id;
903
+ name;
904
+ createdAt;
905
+ updatedAt;
906
+ deletedAt;
907
+ deleted;
908
+ createdBy;
909
+ updatedBy;
910
+ deletedBy;
911
+ blogs;
912
+ };
913
+ __decorateClass([
914
+ PrimaryGeneratedColumn5()
915
+ ], Category.prototype, "id", 2);
916
+ __decorateClass([
917
+ Column5("varchar")
918
+ ], Category.prototype, "name", 2);
919
+ __decorateClass([
920
+ Column5({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
921
+ ], Category.prototype, "createdAt", 2);
922
+ __decorateClass([
923
+ Column5({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
924
+ ], Category.prototype, "updatedAt", 2);
925
+ __decorateClass([
926
+ Column5({ type: "timestamp", nullable: true })
927
+ ], Category.prototype, "deletedAt", 2);
928
+ __decorateClass([
929
+ Column5("boolean", { default: false })
930
+ ], Category.prototype, "deleted", 2);
931
+ __decorateClass([
932
+ Column5("int", { nullable: true })
933
+ ], Category.prototype, "createdBy", 2);
934
+ __decorateClass([
935
+ Column5("int", { nullable: true })
936
+ ], Category.prototype, "updatedBy", 2);
937
+ __decorateClass([
938
+ Column5("int", { nullable: true })
939
+ ], Category.prototype, "deletedBy", 2);
940
+ __decorateClass([
941
+ OneToMany2("Blog", "category")
942
+ ], Category.prototype, "blogs", 2);
943
+ Category = __decorateClass([
944
+ Entity5("categories")
945
+ ], Category);
946
+
947
+ // src/entities/seo.entity.ts
948
+ import { Entity as Entity6, PrimaryGeneratedColumn as PrimaryGeneratedColumn6, Column as Column6, OneToMany as OneToMany3 } from "typeorm";
949
+ var Seo = class {
950
+ id;
951
+ title;
952
+ description;
953
+ keywords;
954
+ ogTitle;
955
+ ogDescription;
956
+ ogImage;
957
+ slug;
958
+ createdAt;
959
+ updatedAt;
960
+ deletedAt;
961
+ deleted;
962
+ createdBy;
963
+ updatedBy;
964
+ deletedBy;
965
+ blogs;
966
+ };
967
+ __decorateClass([
968
+ PrimaryGeneratedColumn6()
969
+ ], Seo.prototype, "id", 2);
970
+ __decorateClass([
971
+ Column6("varchar", { nullable: true })
972
+ ], Seo.prototype, "title", 2);
973
+ __decorateClass([
974
+ Column6("varchar", { nullable: true })
975
+ ], Seo.prototype, "description", 2);
976
+ __decorateClass([
977
+ Column6("varchar", { nullable: true })
978
+ ], Seo.prototype, "keywords", 2);
979
+ __decorateClass([
980
+ Column6("varchar", { nullable: true })
981
+ ], Seo.prototype, "ogTitle", 2);
982
+ __decorateClass([
983
+ Column6("varchar", { nullable: true })
984
+ ], Seo.prototype, "ogDescription", 2);
985
+ __decorateClass([
986
+ Column6("varchar", { nullable: true })
987
+ ], Seo.prototype, "ogImage", 2);
988
+ __decorateClass([
989
+ Column6("varchar", { unique: true })
990
+ ], Seo.prototype, "slug", 2);
991
+ __decorateClass([
992
+ Column6({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
993
+ ], Seo.prototype, "createdAt", 2);
994
+ __decorateClass([
995
+ Column6({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
996
+ ], Seo.prototype, "updatedAt", 2);
997
+ __decorateClass([
998
+ Column6({ type: "timestamp", nullable: true })
999
+ ], Seo.prototype, "deletedAt", 2);
1000
+ __decorateClass([
1001
+ Column6("boolean", { default: false })
1002
+ ], Seo.prototype, "deleted", 2);
1003
+ __decorateClass([
1004
+ Column6("int", { nullable: true })
1005
+ ], Seo.prototype, "createdBy", 2);
1006
+ __decorateClass([
1007
+ Column6("int", { nullable: true })
1008
+ ], Seo.prototype, "updatedBy", 2);
1009
+ __decorateClass([
1010
+ Column6("int", { nullable: true })
1011
+ ], Seo.prototype, "deletedBy", 2);
1012
+ __decorateClass([
1013
+ OneToMany3(() => Blog, (blog) => blog.seo)
1014
+ ], Seo.prototype, "blogs", 2);
1015
+ Seo = __decorateClass([
1016
+ Entity6("seos")
1017
+ ], Seo);
1018
+
1019
+ // src/entities/comment.entity.ts
1020
+ import { Entity as Entity7, PrimaryGeneratedColumn as PrimaryGeneratedColumn7, Column as Column7, ManyToOne as ManyToOne3, JoinColumn as JoinColumn3 } from "typeorm";
1021
+ var Comment = class {
1022
+ id;
1023
+ content;
1024
+ blogId;
1025
+ authorId;
1026
+ createdAt;
1027
+ updatedAt;
1028
+ deletedAt;
1029
+ deleted;
1030
+ createdBy;
1031
+ updatedBy;
1032
+ deletedBy;
1033
+ author;
1034
+ blog;
1035
+ };
1036
+ __decorateClass([
1037
+ PrimaryGeneratedColumn7()
1038
+ ], Comment.prototype, "id", 2);
1039
+ __decorateClass([
1040
+ Column7("text")
1041
+ ], Comment.prototype, "content", 2);
1042
+ __decorateClass([
1043
+ Column7("int")
1044
+ ], Comment.prototype, "blogId", 2);
1045
+ __decorateClass([
1046
+ Column7("int")
1047
+ ], Comment.prototype, "authorId", 2);
1048
+ __decorateClass([
1049
+ Column7({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1050
+ ], Comment.prototype, "createdAt", 2);
1051
+ __decorateClass([
1052
+ Column7({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1053
+ ], Comment.prototype, "updatedAt", 2);
1054
+ __decorateClass([
1055
+ Column7({ type: "timestamp", nullable: true })
1056
+ ], Comment.prototype, "deletedAt", 2);
1057
+ __decorateClass([
1058
+ Column7("boolean", { default: false })
1059
+ ], Comment.prototype, "deleted", 2);
1060
+ __decorateClass([
1061
+ Column7("int", { nullable: true })
1062
+ ], Comment.prototype, "createdBy", 2);
1063
+ __decorateClass([
1064
+ Column7("int", { nullable: true })
1065
+ ], Comment.prototype, "updatedBy", 2);
1066
+ __decorateClass([
1067
+ Column7("int", { nullable: true })
1068
+ ], Comment.prototype, "deletedBy", 2);
1069
+ __decorateClass([
1070
+ ManyToOne3(() => User, { onDelete: "CASCADE" }),
1071
+ JoinColumn3({ name: "authorId" })
1072
+ ], Comment.prototype, "author", 2);
1073
+ __decorateClass([
1074
+ ManyToOne3(() => Blog, (b) => b.comments, { onDelete: "CASCADE" }),
1075
+ JoinColumn3({ name: "blogId" })
1076
+ ], Comment.prototype, "blog", 2);
1077
+ Comment = __decorateClass([
1078
+ Entity7("comments")
1079
+ ], Comment);
1080
+
1081
+ // src/entities/tag.entity.ts
1082
+ import { Entity as Entity8, PrimaryGeneratedColumn as PrimaryGeneratedColumn8, Column as Column8, ManyToMany } from "typeorm";
1083
+ var Tag = class {
1084
+ id;
1085
+ name;
1086
+ createdAt;
1087
+ updatedAt;
1088
+ deletedAt;
1089
+ deleted;
1090
+ createdBy;
1091
+ updatedBy;
1092
+ deletedBy;
1093
+ blogs;
1094
+ };
1095
+ __decorateClass([
1096
+ PrimaryGeneratedColumn8()
1097
+ ], Tag.prototype, "id", 2);
1098
+ __decorateClass([
1099
+ Column8("varchar", { unique: true })
1100
+ ], Tag.prototype, "name", 2);
1101
+ __decorateClass([
1102
+ Column8({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1103
+ ], Tag.prototype, "createdAt", 2);
1104
+ __decorateClass([
1105
+ Column8({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1106
+ ], Tag.prototype, "updatedAt", 2);
1107
+ __decorateClass([
1108
+ Column8({ type: "timestamp", nullable: true })
1109
+ ], Tag.prototype, "deletedAt", 2);
1110
+ __decorateClass([
1111
+ Column8("boolean", { default: false })
1112
+ ], Tag.prototype, "deleted", 2);
1113
+ __decorateClass([
1114
+ Column8("int", { nullable: true })
1115
+ ], Tag.prototype, "createdBy", 2);
1116
+ __decorateClass([
1117
+ Column8("int", { nullable: true })
1118
+ ], Tag.prototype, "updatedBy", 2);
1119
+ __decorateClass([
1120
+ Column8("int", { nullable: true })
1121
+ ], Tag.prototype, "deletedBy", 2);
1122
+ __decorateClass([
1123
+ ManyToMany(() => Blog, (blog) => blog.tags)
1124
+ ], Tag.prototype, "blogs", 2);
1125
+ Tag = __decorateClass([
1126
+ Entity8("tags")
1127
+ ], Tag);
1128
+
1129
+ // src/entities/blog.entity.ts
1130
+ var Blog = class {
1131
+ id;
1132
+ title;
1133
+ content;
1134
+ coverImage;
1135
+ authorId;
1136
+ categoryId;
1137
+ seoId;
1138
+ published;
1139
+ createdAt;
1140
+ updatedAt;
1141
+ deletedAt;
1142
+ deleted;
1143
+ createdBy;
1144
+ updatedBy;
1145
+ deletedBy;
1146
+ slug;
1147
+ author;
1148
+ category;
1149
+ seo;
1150
+ comments;
1151
+ tags;
1152
+ };
1153
+ __decorateClass([
1154
+ PrimaryGeneratedColumn9()
1155
+ ], Blog.prototype, "id", 2);
1156
+ __decorateClass([
1157
+ Column9("varchar")
1158
+ ], Blog.prototype, "title", 2);
1159
+ __decorateClass([
1160
+ Column9("text")
1161
+ ], Blog.prototype, "content", 2);
1162
+ __decorateClass([
1163
+ Column9("varchar", { nullable: true })
1164
+ ], Blog.prototype, "coverImage", 2);
1165
+ __decorateClass([
1166
+ Column9("int")
1167
+ ], Blog.prototype, "authorId", 2);
1168
+ __decorateClass([
1169
+ Column9("int", { nullable: true })
1170
+ ], Blog.prototype, "categoryId", 2);
1171
+ __decorateClass([
1172
+ Column9("int", { nullable: true })
1173
+ ], Blog.prototype, "seoId", 2);
1174
+ __decorateClass([
1175
+ Column9("boolean", { default: false })
1176
+ ], Blog.prototype, "published", 2);
1177
+ __decorateClass([
1178
+ Column9({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1179
+ ], Blog.prototype, "createdAt", 2);
1180
+ __decorateClass([
1181
+ Column9({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1182
+ ], Blog.prototype, "updatedAt", 2);
1183
+ __decorateClass([
1184
+ Column9({ type: "timestamp", nullable: true })
1185
+ ], Blog.prototype, "deletedAt", 2);
1186
+ __decorateClass([
1187
+ Column9("boolean", { default: false })
1188
+ ], Blog.prototype, "deleted", 2);
1189
+ __decorateClass([
1190
+ Column9("int", { nullable: true })
1191
+ ], Blog.prototype, "createdBy", 2);
1192
+ __decorateClass([
1193
+ Column9("int", { nullable: true })
1194
+ ], Blog.prototype, "updatedBy", 2);
1195
+ __decorateClass([
1196
+ Column9("int", { nullable: true })
1197
+ ], Blog.prototype, "deletedBy", 2);
1198
+ __decorateClass([
1199
+ Column9("varchar", { unique: true })
1200
+ ], Blog.prototype, "slug", 2);
1201
+ __decorateClass([
1202
+ ManyToOne4(() => User, { onDelete: "CASCADE" }),
1203
+ JoinColumn4({ name: "authorId" })
1204
+ ], Blog.prototype, "author", 2);
1205
+ __decorateClass([
1206
+ ManyToOne4(() => Category, (c) => c.blogs, { onDelete: "SET NULL" }),
1207
+ JoinColumn4({ name: "categoryId" })
1208
+ ], Blog.prototype, "category", 2);
1209
+ __decorateClass([
1210
+ ManyToOne4(() => Seo, (s) => s.blogs, { onDelete: "SET NULL" }),
1211
+ JoinColumn4({ name: "seoId" })
1212
+ ], Blog.prototype, "seo", 2);
1213
+ __decorateClass([
1214
+ OneToMany4(() => Comment, (c) => c.blog)
1215
+ ], Blog.prototype, "comments", 2);
1216
+ __decorateClass([
1217
+ ManyToMany2(() => Tag, (t) => t.blogs),
1218
+ JoinTable({
1219
+ name: "blog_tags",
1220
+ joinColumn: { name: "blogId", referencedColumnName: "id" },
1221
+ inverseJoinColumn: { name: "tagId", referencedColumnName: "id" }
1222
+ })
1223
+ ], Blog.prototype, "tags", 2);
1224
+ Blog = __decorateClass([
1225
+ Entity9("blogs")
1226
+ ], Blog);
1227
+
1228
+ // src/entities/contact.entity.ts
1229
+ import { Entity as Entity13, PrimaryGeneratedColumn as PrimaryGeneratedColumn13, Column as Column13, OneToMany as OneToMany6 } from "typeorm";
1230
+
1231
+ // src/entities/form-submission.entity.ts
1232
+ import { Entity as Entity12, PrimaryGeneratedColumn as PrimaryGeneratedColumn12, Column as Column12, ManyToOne as ManyToOne6, JoinColumn as JoinColumn6 } from "typeorm";
1233
+
1234
+ // src/entities/form.entity.ts
1235
+ import { Entity as Entity11, PrimaryGeneratedColumn as PrimaryGeneratedColumn11, Column as Column11, OneToMany as OneToMany5 } from "typeorm";
1236
+
1237
+ // src/entities/form-field.entity.ts
1238
+ import { Entity as Entity10, PrimaryGeneratedColumn as PrimaryGeneratedColumn10, Column as Column10, ManyToOne as ManyToOne5, JoinColumn as JoinColumn5 } from "typeorm";
1239
+ var FormField = class {
1240
+ id;
1241
+ formId;
1242
+ label;
1243
+ type;
1244
+ placeholder;
1245
+ options;
1246
+ required;
1247
+ validation;
1248
+ order;
1249
+ groupId;
1250
+ columnWidth;
1251
+ createdAt;
1252
+ updatedAt;
1253
+ deletedAt;
1254
+ deleted;
1255
+ createdBy;
1256
+ updatedBy;
1257
+ deletedBy;
1258
+ form;
1259
+ };
1260
+ __decorateClass([
1261
+ PrimaryGeneratedColumn10()
1262
+ ], FormField.prototype, "id", 2);
1263
+ __decorateClass([
1264
+ Column10("int")
1265
+ ], FormField.prototype, "formId", 2);
1266
+ __decorateClass([
1267
+ Column10("varchar")
1268
+ ], FormField.prototype, "label", 2);
1269
+ __decorateClass([
1270
+ Column10("varchar")
1271
+ ], FormField.prototype, "type", 2);
1272
+ __decorateClass([
1273
+ Column10("varchar", { nullable: true })
1274
+ ], FormField.prototype, "placeholder", 2);
1275
+ __decorateClass([
1276
+ Column10("varchar", { nullable: true })
1277
+ ], FormField.prototype, "options", 2);
1278
+ __decorateClass([
1279
+ Column10("boolean", { default: false })
1280
+ ], FormField.prototype, "required", 2);
1281
+ __decorateClass([
1282
+ Column10("varchar", { nullable: true })
1283
+ ], FormField.prototype, "validation", 2);
1284
+ __decorateClass([
1285
+ Column10("int")
1286
+ ], FormField.prototype, "order", 2);
1287
+ __decorateClass([
1288
+ Column10("int", { default: 1 })
1289
+ ], FormField.prototype, "groupId", 2);
1290
+ __decorateClass([
1291
+ Column10("int", { default: 12 })
1292
+ ], FormField.prototype, "columnWidth", 2);
1293
+ __decorateClass([
1294
+ Column10({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1295
+ ], FormField.prototype, "createdAt", 2);
1296
+ __decorateClass([
1297
+ Column10({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1298
+ ], FormField.prototype, "updatedAt", 2);
1299
+ __decorateClass([
1300
+ Column10({ type: "timestamp", nullable: true })
1301
+ ], FormField.prototype, "deletedAt", 2);
1302
+ __decorateClass([
1303
+ Column10("boolean", { default: false })
1304
+ ], FormField.prototype, "deleted", 2);
1305
+ __decorateClass([
1306
+ Column10("int", { nullable: true })
1307
+ ], FormField.prototype, "createdBy", 2);
1308
+ __decorateClass([
1309
+ Column10("int", { nullable: true })
1310
+ ], FormField.prototype, "updatedBy", 2);
1311
+ __decorateClass([
1312
+ Column10("int", { nullable: true })
1313
+ ], FormField.prototype, "deletedBy", 2);
1314
+ __decorateClass([
1315
+ ManyToOne5(() => Form, (f) => f.fields, { onDelete: "CASCADE" }),
1316
+ JoinColumn5({ name: "formId" })
1317
+ ], FormField.prototype, "form", 2);
1318
+ FormField = __decorateClass([
1319
+ Entity10("form_fields")
1320
+ ], FormField);
1321
+
1322
+ // src/entities/form.entity.ts
1323
+ var Form = class {
1324
+ id;
1325
+ name;
1326
+ description;
1327
+ campaign;
1328
+ slug;
1329
+ published;
1330
+ createdAt;
1331
+ updatedAt;
1332
+ deletedAt;
1333
+ deleted;
1334
+ createdBy;
1335
+ updatedBy;
1336
+ deletedBy;
1337
+ fields;
1338
+ submissions;
1339
+ };
1340
+ __decorateClass([
1341
+ PrimaryGeneratedColumn11()
1342
+ ], Form.prototype, "id", 2);
1343
+ __decorateClass([
1344
+ Column11("varchar")
1345
+ ], Form.prototype, "name", 2);
1346
+ __decorateClass([
1347
+ Column11("text", { nullable: true })
1348
+ ], Form.prototype, "description", 2);
1349
+ __decorateClass([
1350
+ Column11("varchar", { nullable: true })
1351
+ ], Form.prototype, "campaign", 2);
1352
+ __decorateClass([
1353
+ Column11("varchar", { unique: true })
1354
+ ], Form.prototype, "slug", 2);
1355
+ __decorateClass([
1356
+ Column11("boolean", { default: false })
1357
+ ], Form.prototype, "published", 2);
1358
+ __decorateClass([
1359
+ Column11({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1360
+ ], Form.prototype, "createdAt", 2);
1361
+ __decorateClass([
1362
+ Column11({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1363
+ ], Form.prototype, "updatedAt", 2);
1364
+ __decorateClass([
1365
+ Column11({ type: "timestamp", nullable: true })
1366
+ ], Form.prototype, "deletedAt", 2);
1367
+ __decorateClass([
1368
+ Column11("boolean", { default: false })
1369
+ ], Form.prototype, "deleted", 2);
1370
+ __decorateClass([
1371
+ Column11("int", { nullable: true })
1372
+ ], Form.prototype, "createdBy", 2);
1373
+ __decorateClass([
1374
+ Column11("int", { nullable: true })
1375
+ ], Form.prototype, "updatedBy", 2);
1376
+ __decorateClass([
1377
+ Column11("int", { nullable: true })
1378
+ ], Form.prototype, "deletedBy", 2);
1379
+ __decorateClass([
1380
+ OneToMany5(() => FormField, (f) => f.form)
1381
+ ], Form.prototype, "fields", 2);
1382
+ __decorateClass([
1383
+ OneToMany5(() => FormSubmission, (s) => s.form)
1384
+ ], Form.prototype, "submissions", 2);
1385
+ Form = __decorateClass([
1386
+ Entity11("forms")
1387
+ ], Form);
1388
+
1389
+ // src/entities/form-submission.entity.ts
1390
+ var FormSubmission = class {
1391
+ id;
1392
+ formId;
1393
+ contactId;
1394
+ data;
1395
+ ipAddress;
1396
+ userAgent;
1397
+ createdAt;
1398
+ updatedAt;
1399
+ form;
1400
+ contact;
1401
+ };
1402
+ __decorateClass([
1403
+ PrimaryGeneratedColumn12()
1404
+ ], FormSubmission.prototype, "id", 2);
1405
+ __decorateClass([
1406
+ Column12("int")
1407
+ ], FormSubmission.prototype, "formId", 2);
1408
+ __decorateClass([
1409
+ Column12("int", { nullable: true })
1410
+ ], FormSubmission.prototype, "contactId", 2);
1411
+ __decorateClass([
1412
+ Column12("jsonb")
1413
+ ], FormSubmission.prototype, "data", 2);
1414
+ __decorateClass([
1415
+ Column12("varchar", { nullable: true })
1416
+ ], FormSubmission.prototype, "ipAddress", 2);
1417
+ __decorateClass([
1418
+ Column12("varchar", { nullable: true })
1419
+ ], FormSubmission.prototype, "userAgent", 2);
1420
+ __decorateClass([
1421
+ Column12({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1422
+ ], FormSubmission.prototype, "createdAt", 2);
1423
+ __decorateClass([
1424
+ Column12({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1425
+ ], FormSubmission.prototype, "updatedAt", 2);
1426
+ __decorateClass([
1427
+ ManyToOne6(() => Form, (f) => f.submissions, { onDelete: "CASCADE" }),
1428
+ JoinColumn6({ name: "formId" })
1429
+ ], FormSubmission.prototype, "form", 2);
1430
+ __decorateClass([
1431
+ ManyToOne6(() => Contact, (c) => c.form_submissions, { onDelete: "SET NULL" }),
1432
+ JoinColumn6({ name: "contactId" })
1433
+ ], FormSubmission.prototype, "contact", 2);
1434
+ FormSubmission = __decorateClass([
1435
+ Entity12("form_submissions")
1436
+ ], FormSubmission);
1437
+
1438
+ // src/entities/contact.entity.ts
1439
+ var Contact = class {
1440
+ id;
1441
+ name;
1442
+ email;
1443
+ phone;
1444
+ createdAt;
1445
+ updatedAt;
1446
+ deletedAt;
1447
+ deleted;
1448
+ createdBy;
1449
+ updatedBy;
1450
+ deletedBy;
1451
+ form_submissions;
1452
+ };
1453
+ __decorateClass([
1454
+ PrimaryGeneratedColumn13()
1455
+ ], Contact.prototype, "id", 2);
1456
+ __decorateClass([
1457
+ Column13("varchar")
1458
+ ], Contact.prototype, "name", 2);
1459
+ __decorateClass([
1460
+ Column13("varchar", { unique: true })
1461
+ ], Contact.prototype, "email", 2);
1462
+ __decorateClass([
1463
+ Column13("varchar", { nullable: true })
1464
+ ], Contact.prototype, "phone", 2);
1465
+ __decorateClass([
1466
+ Column13({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1467
+ ], Contact.prototype, "createdAt", 2);
1468
+ __decorateClass([
1469
+ Column13({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1470
+ ], Contact.prototype, "updatedAt", 2);
1471
+ __decorateClass([
1472
+ Column13({ type: "timestamp", nullable: true })
1473
+ ], Contact.prototype, "deletedAt", 2);
1474
+ __decorateClass([
1475
+ Column13("boolean", { default: false })
1476
+ ], Contact.prototype, "deleted", 2);
1477
+ __decorateClass([
1478
+ Column13("int", { nullable: true })
1479
+ ], Contact.prototype, "createdBy", 2);
1480
+ __decorateClass([
1481
+ Column13("int", { nullable: true })
1482
+ ], Contact.prototype, "updatedBy", 2);
1483
+ __decorateClass([
1484
+ Column13("int", { nullable: true })
1485
+ ], Contact.prototype, "deletedBy", 2);
1486
+ __decorateClass([
1487
+ OneToMany6(() => FormSubmission, (fs) => fs.contact)
1488
+ ], Contact.prototype, "form_submissions", 2);
1489
+ Contact = __decorateClass([
1490
+ Entity13("contacts")
1491
+ ], Contact);
1492
+
1493
+ // src/entities/config.entity.ts
1494
+ import { Entity as Entity14, PrimaryGeneratedColumn as PrimaryGeneratedColumn14, Column as Column14, Unique } from "typeorm";
1495
+ var Config = class {
1496
+ id;
1497
+ settings;
1498
+ key;
1499
+ value;
1500
+ type;
1501
+ encrypted;
1502
+ createdAt;
1503
+ updatedAt;
1504
+ deletedAt;
1505
+ deleted;
1506
+ createdBy;
1507
+ updatedBy;
1508
+ deletedBy;
1509
+ };
1510
+ __decorateClass([
1511
+ PrimaryGeneratedColumn14()
1512
+ ], Config.prototype, "id", 2);
1513
+ __decorateClass([
1514
+ Column14("varchar")
1515
+ ], Config.prototype, "settings", 2);
1516
+ __decorateClass([
1517
+ Column14("varchar")
1518
+ ], Config.prototype, "key", 2);
1519
+ __decorateClass([
1520
+ Column14("varchar")
1521
+ ], Config.prototype, "value", 2);
1522
+ __decorateClass([
1523
+ Column14("varchar", { default: "private" })
1524
+ ], Config.prototype, "type", 2);
1525
+ __decorateClass([
1526
+ Column14("boolean", { default: false })
1527
+ ], Config.prototype, "encrypted", 2);
1528
+ __decorateClass([
1529
+ Column14({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1530
+ ], Config.prototype, "createdAt", 2);
1531
+ __decorateClass([
1532
+ Column14({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1533
+ ], Config.prototype, "updatedAt", 2);
1534
+ __decorateClass([
1535
+ Column14({ type: "timestamp", nullable: true })
1536
+ ], Config.prototype, "deletedAt", 2);
1537
+ __decorateClass([
1538
+ Column14("boolean", { default: false })
1539
+ ], Config.prototype, "deleted", 2);
1540
+ __decorateClass([
1541
+ Column14("int", { nullable: true })
1542
+ ], Config.prototype, "createdBy", 2);
1543
+ __decorateClass([
1544
+ Column14("int", { nullable: true })
1545
+ ], Config.prototype, "updatedBy", 2);
1546
+ __decorateClass([
1547
+ Column14("int", { nullable: true })
1548
+ ], Config.prototype, "deletedBy", 2);
1549
+ Config = __decorateClass([
1550
+ Entity14("configs"),
1551
+ Unique(["settings", "key"])
1552
+ ], Config);
1553
+
1554
+ // src/entities/media.entity.ts
1555
+ import { Entity as Entity15, PrimaryGeneratedColumn as PrimaryGeneratedColumn15, Column as Column15 } from "typeorm";
1556
+ var Media = class {
1557
+ id;
1558
+ filename;
1559
+ url;
1560
+ mimeType;
1561
+ size;
1562
+ alt;
1563
+ isPublic;
1564
+ createdAt;
1565
+ updatedAt;
1566
+ deletedAt;
1567
+ deleted;
1568
+ };
1569
+ __decorateClass([
1570
+ PrimaryGeneratedColumn15()
1571
+ ], Media.prototype, "id", 2);
1572
+ __decorateClass([
1573
+ Column15("varchar")
1574
+ ], Media.prototype, "filename", 2);
1575
+ __decorateClass([
1576
+ Column15("varchar")
1577
+ ], Media.prototype, "url", 2);
1578
+ __decorateClass([
1579
+ Column15("varchar")
1580
+ ], Media.prototype, "mimeType", 2);
1581
+ __decorateClass([
1582
+ Column15("int", { default: 0 })
1583
+ ], Media.prototype, "size", 2);
1584
+ __decorateClass([
1585
+ Column15("varchar", { nullable: true })
1586
+ ], Media.prototype, "alt", 2);
1587
+ __decorateClass([
1588
+ Column15("boolean", { default: false })
1589
+ ], Media.prototype, "isPublic", 2);
1590
+ __decorateClass([
1591
+ Column15({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1592
+ ], Media.prototype, "createdAt", 2);
1593
+ __decorateClass([
1594
+ Column15({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1595
+ ], Media.prototype, "updatedAt", 2);
1596
+ __decorateClass([
1597
+ Column15({ type: "timestamp", nullable: true })
1598
+ ], Media.prototype, "deletedAt", 2);
1599
+ __decorateClass([
1600
+ Column15("boolean", { default: false })
1601
+ ], Media.prototype, "deleted", 2);
1602
+ Media = __decorateClass([
1603
+ Entity15("media")
1604
+ ], Media);
1605
+
1606
+ // src/entities/page.entity.ts
1607
+ import { Entity as Entity16, PrimaryGeneratedColumn as PrimaryGeneratedColumn16, Column as Column16, ManyToOne as ManyToOne7, JoinColumn as JoinColumn7 } from "typeorm";
1608
+ var Page = class {
1609
+ id;
1610
+ title;
1611
+ slug;
1612
+ content;
1613
+ published;
1614
+ theme;
1615
+ parentId;
1616
+ parent;
1617
+ seoId;
1618
+ seo;
1619
+ createdAt;
1620
+ updatedAt;
1621
+ deletedAt;
1622
+ deleted;
1623
+ createdBy;
1624
+ updatedBy;
1625
+ deletedBy;
1626
+ };
1627
+ __decorateClass([
1628
+ PrimaryGeneratedColumn16()
1629
+ ], Page.prototype, "id", 2);
1630
+ __decorateClass([
1631
+ Column16("varchar")
1632
+ ], Page.prototype, "title", 2);
1633
+ __decorateClass([
1634
+ Column16("varchar", { unique: true })
1635
+ ], Page.prototype, "slug", 2);
1636
+ __decorateClass([
1637
+ Column16({ type: "jsonb", default: {} })
1638
+ ], Page.prototype, "content", 2);
1639
+ __decorateClass([
1640
+ Column16("boolean", { default: false })
1641
+ ], Page.prototype, "published", 2);
1642
+ __decorateClass([
1643
+ Column16("varchar", { default: "default" })
1644
+ ], Page.prototype, "theme", 2);
1645
+ __decorateClass([
1646
+ Column16("int", { nullable: true })
1647
+ ], Page.prototype, "parentId", 2);
1648
+ __decorateClass([
1649
+ ManyToOne7(() => Page, { onDelete: "SET NULL" }),
1650
+ JoinColumn7({ name: "parentId" })
1651
+ ], Page.prototype, "parent", 2);
1652
+ __decorateClass([
1653
+ Column16("int", { nullable: true })
1654
+ ], Page.prototype, "seoId", 2);
1655
+ __decorateClass([
1656
+ ManyToOne7(() => Seo, { onDelete: "SET NULL" }),
1657
+ JoinColumn7({ name: "seoId" })
1658
+ ], Page.prototype, "seo", 2);
1659
+ __decorateClass([
1660
+ Column16({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1661
+ ], Page.prototype, "createdAt", 2);
1662
+ __decorateClass([
1663
+ Column16({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1664
+ ], Page.prototype, "updatedAt", 2);
1665
+ __decorateClass([
1666
+ Column16({ type: "timestamp", nullable: true })
1667
+ ], Page.prototype, "deletedAt", 2);
1668
+ __decorateClass([
1669
+ Column16("boolean", { default: false })
1670
+ ], Page.prototype, "deleted", 2);
1671
+ __decorateClass([
1672
+ Column16("int", { nullable: true })
1673
+ ], Page.prototype, "createdBy", 2);
1674
+ __decorateClass([
1675
+ Column16("int", { nullable: true })
1676
+ ], Page.prototype, "updatedBy", 2);
1677
+ __decorateClass([
1678
+ Column16("int", { nullable: true })
1679
+ ], Page.prototype, "deletedBy", 2);
1680
+ Page = __decorateClass([
1681
+ Entity16("pages")
1682
+ ], Page);
1683
+
1684
+ // src/entities/index.ts
1685
+ var CMS_ENTITY_MAP = {
1686
+ users: User,
1687
+ password_reset_tokens: PasswordResetToken,
1688
+ user_groups: UserGroup,
1689
+ permissions: Permission,
1690
+ blogs: Blog,
1691
+ tags: Tag,
1692
+ categories: Category,
1693
+ comments: Comment,
1694
+ contacts: Contact,
1695
+ forms: Form,
1696
+ form_fields: FormField,
1697
+ form_submissions: FormSubmission,
1698
+ seos: Seo,
1699
+ configs: Config,
1700
+ media: Media,
1701
+ pages: Page
1702
+ };
1703
+
1704
+ // src/auth/helpers.ts
1705
+ var OPEN_ENDPOINTS = [
1706
+ { "/api/contacts": ["POST"] },
1707
+ { "/api/form-submissions": ["POST"] },
1708
+ { "/api/blogs": ["GET"] }
1709
+ ];
1710
+ var PERMISSION_REQUIRED_ENDPOINTS = {};
1711
+ function isOpenEndpoint(pathname) {
1712
+ return OPEN_ENDPOINTS.some((endpoint) => pathname.startsWith(Object.keys(endpoint)[0]));
1713
+ }
1714
+ function getRequiredPermission(pathname) {
1715
+ return null;
1716
+ }
1717
+ function isPublicMethod(pathname, method) {
1718
+ for (const endpoint of OPEN_ENDPOINTS) {
1719
+ const key = Object.keys(endpoint)[0];
1720
+ if (pathname.startsWith(key) && endpoint[key].includes(method)) return true;
1721
+ }
1722
+ return false;
1723
+ }
1724
+ function createAuthHelpers(getSession, NextResponse) {
1725
+ return {
1726
+ async requireAuth() {
1727
+ const session = await getSession();
1728
+ if (!session?.user?.email) {
1729
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
1730
+ }
1731
+ return null;
1732
+ },
1733
+ async requirePermission() {
1734
+ const session = await getSession();
1735
+ if (!session?.user?.email) {
1736
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
1737
+ }
1738
+ return null;
1739
+ },
1740
+ async getAuthenticatedUser() {
1741
+ const session = await getSession();
1742
+ return session?.user ?? null;
1743
+ }
1744
+ };
1745
+ }
1746
+
1747
+ // src/auth/middleware.ts
1748
+ var defaultPublicApiMethods = {
1749
+ "/api/contacts": ["POST"],
1750
+ "/api/form-submissions": ["POST"],
1751
+ "/api/blogs": ["GET"],
1752
+ "/api/forms": ["GET"],
1753
+ "/api/auth": ["GET", "POST"],
1754
+ "/api/health": ["GET"],
1755
+ "/api/users/forgot-password": ["POST"],
1756
+ "/api/users/set-password": ["POST"],
1757
+ "/api/users/invite": ["POST"]
1758
+ };
1759
+ function defaultGetSessionToken(request) {
1760
+ return request.cookies.get("__Secure-next-auth.session-token")?.value ?? request.cookies.get("next-auth.session-token")?.value;
1761
+ }
1762
+ function isPublicMethod2(pathname, method, publicApiMethods) {
1763
+ for (const [endpoint, methods] of Object.entries(publicApiMethods)) {
1764
+ if (pathname.startsWith(endpoint) && methods.includes(method)) return true;
1765
+ }
1766
+ return false;
1767
+ }
1768
+ function createCmsMiddleware(config = {}) {
1769
+ const {
1770
+ publicAdminPaths = ["/admin/signin", "/admin/forgot-password", "/admin/reset-password", "/admin/invite"],
1771
+ publicApiMethods = defaultPublicApiMethods,
1772
+ signInPath = "/admin/signin",
1773
+ getSessionToken = defaultGetSessionToken
1774
+ } = config;
1775
+ return function cmsMiddleware(request) {
1776
+ const pathname = request.nextUrl.pathname;
1777
+ const method = request.method;
1778
+ if (publicAdminPaths.some((p) => pathname === p || pathname.startsWith(p + "/"))) {
1779
+ return { type: "next" };
1780
+ }
1781
+ if (pathname.startsWith("/admin")) {
1782
+ const token = getSessionToken(request);
1783
+ if (!token) {
1784
+ return { type: "redirect", url: new URL(signInPath, request.url).toString() };
1785
+ }
1786
+ }
1787
+ if (pathname.startsWith("/api")) {
1788
+ if (isPublicMethod2(pathname, method, publicApiMethods)) {
1789
+ return { type: "next" };
1790
+ }
1791
+ const token = getSessionToken(request);
1792
+ if (!token) {
1793
+ return { type: "json", status: 401, body: { error: "Unauthorized" } };
1794
+ }
1795
+ }
1796
+ return { type: "next" };
1797
+ };
1798
+ }
1799
+
1800
+ // src/auth/nextauth-options.ts
1801
+ import _CredentialsProvider from "next-auth/providers/credentials";
1802
+ var CredentialsProvider = _CredentialsProvider.default ?? _CredentialsProvider;
1803
+ function getNextAuthOptions(config) {
1804
+ const { getUserByEmail, comparePassword, signInPage = "/admin/signin", secret, extend } = config;
1805
+ const options = {
1806
+ secret: secret ?? process.env.NEXTAUTH_SECRET,
1807
+ providers: [
1808
+ CredentialsProvider({
1809
+ name: "credentials",
1810
+ credentials: {
1811
+ email: { label: "Email", type: "email" },
1812
+ password: { label: "Password", type: "password" }
1813
+ },
1814
+ async authorize(credentials) {
1815
+ if (!credentials?.email || !credentials?.password) return null;
1816
+ try {
1817
+ const user = await getUserByEmail(credentials.email);
1818
+ if (!user || user.blocked || user.deleted || !user.password) return null;
1819
+ const valid = await comparePassword(credentials.password, user.password);
1820
+ if (!valid) return null;
1821
+ return {
1822
+ id: user.id.toString(),
1823
+ email: user.email,
1824
+ name: user.name,
1825
+ groupId: user.groupId ?? void 0,
1826
+ permissions: ["admin"]
1827
+ };
1828
+ } catch {
1829
+ return null;
1830
+ }
1831
+ }
1832
+ })
1833
+ ],
1834
+ session: { strategy: "jwt" },
1835
+ pages: { signIn: signInPage },
1836
+ cookies: process.env.NODE_ENV === "production" ? {
1837
+ sessionToken: {
1838
+ name: "__Secure-next-auth.session-token",
1839
+ options: { httpOnly: true, sameSite: "lax", path: "/", secure: true }
1840
+ }
1841
+ } : {
1842
+ sessionToken: {
1843
+ name: "next-auth.session-token",
1844
+ options: {
1845
+ httpOnly: true,
1846
+ sameSite: "lax",
1847
+ path: "/",
1848
+ secure: process.env.NEXTAUTH_URL?.startsWith("https") ?? false
1849
+ }
1850
+ }
1851
+ },
1852
+ callbacks: {
1853
+ async jwt({ token, user }) {
1854
+ if (user) {
1855
+ token.id = user.id;
1856
+ token.groupId = user.groupId;
1857
+ token.permissions = user.permissions;
1858
+ }
1859
+ return token;
1860
+ },
1861
+ async session({ session, token }) {
1862
+ if (session.user) {
1863
+ session.user.id = token.id;
1864
+ session.user.groupId = token.groupId;
1865
+ session.user.permissions = token.permissions;
1866
+ }
1867
+ return session;
1868
+ }
1869
+ }
1870
+ };
1871
+ return extend ? extend(options) : options;
1872
+ }
1873
+
1874
+ // src/api/crud.ts
1875
+ import { ILike, Like } from "typeorm";
1876
+ var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
1877
+ "date",
1878
+ "datetime",
1879
+ "datetime2",
1880
+ "timestamp",
1881
+ "timestamptz",
1882
+ "timetz",
1883
+ "smalldatetime",
1884
+ "timestamp with time zone",
1885
+ "timestamp without time zone"
1886
+ ]);
1887
+ var TIMESTAMP_PROP_NAMES = /* @__PURE__ */ new Set(["createdAt", "updatedAt", "deletedAt"]);
1888
+ function isInvalidDateValue(v) {
1889
+ if (v === "" || v == null) return true;
1890
+ if (typeof v === "string") return isNaN(Date.parse(v)) || /NaN|Invalid/i.test(v);
1891
+ if (v instanceof Date) return isNaN(v.getTime());
1892
+ return false;
1893
+ }
1894
+ function sanitizeBodyForEntity(repo, body) {
1895
+ const meta = repo.metadata;
1896
+ for (const col of meta.columns) {
1897
+ if (!(col.propertyName in body)) continue;
1898
+ const v = body[col.propertyName];
1899
+ const t = typeof col.type === "string" ? col.type : col.type?.name ?? "";
1900
+ const isBoolean = t === "boolean" || t === "bool" || col.type === Boolean;
1901
+ const isNumber = ["int", "integer", "int2", "int4", "int8", "smallint", "bigint", "number", "Number"].includes(t) || col.type === Number;
1902
+ const isDate = DATE_COLUMN_TYPES.has(t) || col.type === Date || TIMESTAMP_PROP_NAMES.has(col.propertyName);
1903
+ if (v === "" && (isBoolean || isNumber)) {
1904
+ delete body[col.propertyName];
1905
+ } else if (isDate && isInvalidDateValue(v)) {
1906
+ delete body[col.propertyName];
1907
+ }
1908
+ }
1909
+ }
1910
+ function createCrudHandler(dataSource, entityMap, options) {
1911
+ const { requireAuth, json } = options;
1912
+ return {
1913
+ async GET(req, resource) {
1914
+ const authError = await requireAuth(req);
1915
+ if (authError) return authError;
1916
+ const entity = entityMap[resource];
1917
+ if (!resource || !entity) {
1918
+ return json({ error: "Invalid resource" }, { status: 400 });
1919
+ }
1920
+ const repo = dataSource.getRepository(entity);
1921
+ const { searchParams } = new URL(req.url);
1922
+ const page = Number(searchParams.get("page")) || 1;
1923
+ const limit = Number(searchParams.get("limit")) || 10;
1924
+ const skip = (page - 1) * limit;
1925
+ const sortField = searchParams.get("sortField") || "createdAt";
1926
+ const sortOrder = searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
1927
+ const search = searchParams.get("search");
1928
+ const typeFilter = searchParams.get("type");
1929
+ let where = {};
1930
+ if (resource === "media") {
1931
+ const mediaWhere = {};
1932
+ if (search) mediaWhere.filename = ILike(`%${search}%`);
1933
+ if (typeFilter) mediaWhere.mimeType = Like(`${typeFilter}/%`);
1934
+ where = Object.keys(mediaWhere).length > 0 ? mediaWhere : {};
1935
+ } else if (search) {
1936
+ where = [{ name: ILike(`%${search}%`) }, { title: ILike(`%${search}%`) }];
1937
+ }
1938
+ const [data, total] = await repo.findAndCount({
1939
+ skip,
1940
+ take: limit,
1941
+ order: { [sortField]: sortOrder },
1942
+ where
1943
+ });
1944
+ return json({ total, page, limit, totalPages: Math.ceil(total / limit), data });
1945
+ },
1946
+ async POST(req, resource) {
1947
+ const authError = await requireAuth(req);
1948
+ if (authError) return authError;
1949
+ const entity = entityMap[resource];
1950
+ if (!resource || !entity) {
1951
+ return json({ error: "Invalid resource" }, { status: 400 });
1952
+ }
1953
+ const body = await req.json();
1954
+ if (!body || typeof body !== "object" || Object.keys(body).length === 0) {
1955
+ return json({ error: "Invalid request payload" }, { status: 400 });
1956
+ }
1957
+ const repo = dataSource.getRepository(entity);
1958
+ sanitizeBodyForEntity(repo, body);
1959
+ const created = await repo.save(repo.create(body));
1960
+ return json(created, { status: 201 });
1961
+ }
1962
+ };
1963
+ }
1964
+ function createCrudByIdHandler(dataSource, entityMap, options) {
1965
+ const { requireAuth, json } = options;
1966
+ return {
1967
+ async GET(req, resource, id) {
1968
+ const authError = await requireAuth(req);
1969
+ if (authError) return authError;
1970
+ const entity = entityMap[resource];
1971
+ if (!entity) return json({ error: "Invalid resource" }, { status: 400 });
1972
+ const repo = dataSource.getRepository(entity);
1973
+ const item = await repo.findOne({ where: { id: Number(id) } });
1974
+ return item ? json(item) : json({ message: "Not found" }, { status: 404 });
1975
+ },
1976
+ async PUT(req, resource, id) {
1977
+ const authError = await requireAuth(req);
1978
+ if (authError) return authError;
1979
+ const entity = entityMap[resource];
1980
+ if (!entity) return json({ error: "Invalid resource" }, { status: 400 });
1981
+ const body = await req.json();
1982
+ const repo = dataSource.getRepository(entity);
1983
+ if (body && typeof body === "object") sanitizeBodyForEntity(repo, body);
1984
+ await repo.update(Number(id), body);
1985
+ const updated = await repo.findOne({ where: { id: Number(id) } });
1986
+ return updated ? json(updated) : json({ message: "Not found" }, { status: 404 });
1987
+ },
1988
+ async DELETE(req, resource, id) {
1989
+ const authError = await requireAuth(req);
1990
+ if (authError) return authError;
1991
+ const entity = entityMap[resource];
1992
+ if (!entity) return json({ error: "Invalid resource" }, { status: 400 });
1993
+ const repo = dataSource.getRepository(entity);
1994
+ const result = await repo.delete(Number(id));
1995
+ if (result.affected === 0) return json({ message: "Not found" }, { status: 404 });
1996
+ return json({ message: "Deleted successfully" }, { status: 200 });
1997
+ }
1998
+ };
1999
+ }
2000
+
2001
+ // src/api/auth-handlers.ts
2002
+ function createForgotPasswordHandler(config) {
2003
+ const { dataSource, entityMap, json, baseUrl, sendEmail, resetExpiryHours = 1, afterCreateToken } = config;
2004
+ return async function POST(request) {
2005
+ try {
2006
+ const body = await request.json().catch(() => ({}));
2007
+ const email = typeof body?.email === "string" ? body.email.trim().toLowerCase() : "";
2008
+ if (!email) return json({ error: "Email is required" }, { status: 400 });
2009
+ const userRepo = dataSource.getRepository(entityMap.users);
2010
+ const user = await userRepo.findOne({ where: { email }, select: ["email"] });
2011
+ const msg = "If an account exists with this email, you will receive a reset link shortly.";
2012
+ if (!user) return json({ message: msg }, { status: 200 });
2013
+ const crypto = await import("crypto");
2014
+ const token = crypto.randomBytes(32).toString("hex");
2015
+ const expiresAt = new Date(Date.now() + resetExpiryHours * 60 * 60 * 1e3);
2016
+ const tokenRepo = dataSource.getRepository(entityMap.password_reset_tokens);
2017
+ await tokenRepo.save(tokenRepo.create({ email: user.email, token, expiresAt }));
2018
+ const resetLink = `${baseUrl}/admin/reset-password?token=${token}`;
2019
+ if (sendEmail) await sendEmail({ to: user.email, subject: "Password reset", html: `<a href="${resetLink}">Reset password</a>`, text: resetLink });
2020
+ if (afterCreateToken) await afterCreateToken(user.email, resetLink);
2021
+ return json({ message: msg }, { status: 200 });
2022
+ } catch (err) {
2023
+ return json({ error: "Something went wrong. Please try again." }, { status: 500 });
2024
+ }
2025
+ };
2026
+ }
2027
+ function createSetPasswordHandler(config) {
2028
+ const { dataSource, entityMap, json, hashPassword, minPasswordLength = 6, beforeUpdate } = config;
2029
+ return async function POST(request) {
2030
+ try {
2031
+ const body = await request.json().catch(() => ({}));
2032
+ const { token, newPassword } = body;
2033
+ if (!token || !newPassword) return json({ error: "Token and new password are required" }, { status: 400 });
2034
+ if (newPassword.length < minPasswordLength) return json({ error: "Password must be at least 6 characters" }, { status: 400 });
2035
+ const tokenRepo = dataSource.getRepository(entityMap.password_reset_tokens);
2036
+ const record = await tokenRepo.findOne({ where: { token } });
2037
+ if (!record || record.expiresAt < /* @__PURE__ */ new Date()) return json({ error: "Invalid or expired reset link. Please request a new one." }, { status: 400 });
2038
+ const userRepo = dataSource.getRepository(entityMap.users);
2039
+ const user = await userRepo.findOne({ where: { email: record.email }, select: ["id"] });
2040
+ if (!user) return json({ error: "User not found" }, { status: 400 });
2041
+ if (beforeUpdate) await beforeUpdate(record.email, user.id);
2042
+ const hashedPassword = await hashPassword(newPassword);
2043
+ await userRepo.update(user.id, { password: hashedPassword, updatedAt: /* @__PURE__ */ new Date() });
2044
+ await tokenRepo.delete({ email: record.email });
2045
+ return json({ message: "Password updated successfully. You can now sign in." });
2046
+ } catch {
2047
+ return json({ error: "Something went wrong. Please try again." }, { status: 500 });
2048
+ }
2049
+ };
2050
+ }
2051
+ function createInviteAcceptHandler(config) {
2052
+ const { dataSource, entityMap, json, hashPassword, beforeActivate } = config;
2053
+ return async function POST(request) {
2054
+ try {
2055
+ const body = await request.json().catch(() => ({}));
2056
+ const { token, password } = body;
2057
+ if (!token || !password) return json({ error: "Missing required fields: token, password" }, { status: 400 });
2058
+ let email;
2059
+ try {
2060
+ email = Buffer.from(token, "base64").toString("utf8");
2061
+ } catch {
2062
+ return json({ error: "Invalid or expired invite token" }, { status: 400 });
2063
+ }
2064
+ const userRepo = dataSource.getRepository(entityMap.users);
2065
+ const user = await userRepo.findOne({ where: { email }, select: ["id", "blocked"] });
2066
+ if (!user) return json({ error: "User not found" }, { status: 400 });
2067
+ if (!user.blocked) return json({ error: "User is already active" }, { status: 400 });
2068
+ if (beforeActivate) await beforeActivate(email, user.id);
2069
+ const hashedPassword = await hashPassword(password);
2070
+ await userRepo.update(user.id, { password: hashedPassword, blocked: false });
2071
+ return json({ message: "User account activated successfully" }, { status: 200 });
2072
+ } catch (err) {
2073
+ return json({ error: "Server Error" }, { status: 500 });
2074
+ }
2075
+ };
2076
+ }
2077
+ function createChangePasswordHandler(config) {
2078
+ const { dataSource, entityMap, json, comparePassword, hashPassword, getSession, minPasswordLength = 6, beforeUpdate } = config;
2079
+ return async function POST(request) {
2080
+ try {
2081
+ const session = await getSession();
2082
+ if (!session?.user?.email) return json({ error: "Unauthorized" }, { status: 401 });
2083
+ const body = await request.json().catch(() => ({}));
2084
+ const { currentPassword, newPassword } = body;
2085
+ if (!currentPassword || !newPassword) return json({ error: "Current password and new password are required" }, { status: 400 });
2086
+ if (newPassword.length < minPasswordLength) return json({ error: "New password must be at least 6 characters long" }, { status: 400 });
2087
+ const userRepo = dataSource.getRepository(entityMap.users);
2088
+ const user = await userRepo.findOne({ where: { email: session.user.email }, select: ["password"] });
2089
+ if (!user) return json({ error: "User not found" }, { status: 404 });
2090
+ if (!user.password) return json({ error: "Current password is incorrect" }, { status: 400 });
2091
+ const valid = await comparePassword(currentPassword, user.password);
2092
+ if (!valid) return json({ error: "Current password is incorrect" }, { status: 400 });
2093
+ if (beforeUpdate) await beforeUpdate(session.user.email);
2094
+ const hashedPassword = await hashPassword(newPassword);
2095
+ await userRepo.update({ email: session.user.email }, { password: hashedPassword, updatedAt: /* @__PURE__ */ new Date() });
2096
+ return json({ message: "Password updated successfully" });
2097
+ } catch {
2098
+ return json({ error: "Internal server error" }, { status: 500 });
2099
+ }
2100
+ };
2101
+ }
2102
+ var USER_AUTH_PATHS = ["forgot-password", "invite", "set-password", "reset-password"];
2103
+ function createUserAuthApiRouter(config) {
2104
+ const forgot = createForgotPasswordHandler(config);
2105
+ const setPass = createSetPasswordHandler(config);
2106
+ const invite = createInviteAcceptHandler(config);
2107
+ const changePass = config.getSession ? createChangePasswordHandler({
2108
+ ...config,
2109
+ getSession: config.getSession,
2110
+ beforeUpdate: config.beforeChangePasswordUpdate
2111
+ }) : null;
2112
+ return {
2113
+ async POST(req, pathname) {
2114
+ const path = pathname.replace(/\/$/, "");
2115
+ if (!USER_AUTH_PATHS.includes(path)) {
2116
+ return config.json({ error: "Not found" }, { status: 404 });
2117
+ }
2118
+ if (path === "forgot-password") return forgot(req);
2119
+ if (path === "set-password") return setPass(req);
2120
+ if (path === "invite") return invite(req);
2121
+ if (path === "reset-password" && changePass) return changePass(req);
2122
+ return config.json({ error: "Not found" }, { status: 404 });
2123
+ }
2124
+ };
2125
+ }
2126
+
2127
+ // src/api/cms-handlers.ts
2128
+ import { MoreThanOrEqual, ILike as ILike2 } from "typeorm";
2129
+ function createDashboardStatsHandler(config) {
2130
+ const { dataSource, entityMap, json, requireAuth, requirePermission } = config;
2131
+ return async function GET(req) {
2132
+ const authErr = await requireAuth(req);
2133
+ if (authErr) return authErr;
2134
+ if (requirePermission) {
2135
+ const permErr = await requirePermission(req, "view_dashboard");
2136
+ if (permErr) return permErr;
2137
+ }
2138
+ try {
2139
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
2140
+ const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
2141
+ const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions] = await Promise.all([
2142
+ repo("contacts")?.count() ?? 0,
2143
+ repo("forms")?.count({ where: { deleted: false } }) ?? 0,
2144
+ repo("form_submissions")?.count() ?? 0,
2145
+ repo("users")?.count({ where: { deleted: false } }) ?? 0,
2146
+ repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
2147
+ repo("contacts")?.count({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
2148
+ repo("form_submissions")?.count({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0
2149
+ ]);
2150
+ return json({
2151
+ contacts: { total: contactsCount, recent: recentContacts },
2152
+ forms: { total: formsCount, submissions: formSubmissionsCount, recentSubmissions },
2153
+ users: usersCount,
2154
+ blogs: blogsCount
2155
+ });
2156
+ } catch (err) {
2157
+ return json({ error: "Failed to fetch dashboard stats" }, { status: 500 });
2158
+ }
2159
+ };
2160
+ }
2161
+ function createAnalyticsHandlers(config) {
2162
+ const { json, getAnalyticsData, getPropertyId, getPermissions } = config;
2163
+ return {
2164
+ async GET(req) {
2165
+ if (!getAnalyticsData) return json({ error: "Analytics not configured" }, { status: 404 });
2166
+ try {
2167
+ const url = new URL(req.url);
2168
+ const days = parseInt(url.searchParams.get("days") || "30", 10);
2169
+ const data = await getAnalyticsData(days);
2170
+ return json(data);
2171
+ } catch (err) {
2172
+ const msg = err instanceof Error ? err.message : "";
2173
+ if (msg.includes("authentication credential")) return json({ error: "Google Analytics authentication failed." }, { status: 401 });
2174
+ if (msg.includes("sufficient permissions")) return json({ error: "Service account does not have access." }, { status: 403 });
2175
+ return json({ error: "Failed to fetch analytics data" }, { status: 500 });
2176
+ }
2177
+ },
2178
+ propertyId: async () => {
2179
+ const payload = getPropertyId ? getPropertyId() : { currentViewId: process.env.GOOGLE_ANALYTICS_VIEW_ID };
2180
+ return json({ message: "Property ID Information", ...payload });
2181
+ },
2182
+ permissions: async () => {
2183
+ const payload = getPermissions ? getPermissions() : {
2184
+ serviceAccountEmail: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
2185
+ currentViewId: process.env.GOOGLE_ANALYTICS_VIEW_ID
2186
+ };
2187
+ return json({ message: "Permission Troubleshooting Guide", ...payload });
2188
+ }
2189
+ };
2190
+ }
2191
+ function createUploadHandler(config) {
2192
+ const { json, requireAuth, storage, localUploadDir = "public/uploads", allowedTypes, maxSizeBytes = 10 * 1024 * 1024 } = config;
2193
+ const allowed = allowedTypes ?? ["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf", "text/plain"];
2194
+ return async function POST(req) {
2195
+ const authErr = await requireAuth(req);
2196
+ if (authErr) return authErr;
2197
+ try {
2198
+ const formData = await req.formData();
2199
+ const file = formData.get("file");
2200
+ if (!file) return json({ error: "No file uploaded" }, { status: 400 });
2201
+ if (!allowed.includes(file.type)) return json({ error: "File type not allowed" }, { status: 400 });
2202
+ if (file.size > maxSizeBytes) return json({ error: "File size exceeds limit" }, { status: 400 });
2203
+ const buffer = Buffer.from(await file.arrayBuffer());
2204
+ const fileName = `${Date.now()}-${file.name}`;
2205
+ const contentType = file.type || "application/octet-stream";
2206
+ const raw = typeof storage === "function" ? storage() : storage;
2207
+ const storageService = raw instanceof Promise ? await raw : raw;
2208
+ if (storageService) {
2209
+ const fileUrl = await storageService.upload(buffer, `uploads/${fileName}`, contentType);
2210
+ return json({ filePath: fileUrl });
2211
+ }
2212
+ const fs = await import("fs/promises");
2213
+ const path = await import("path");
2214
+ const dir = path.join(process.cwd(), localUploadDir);
2215
+ await fs.mkdir(dir, { recursive: true });
2216
+ const filePath = path.join(dir, fileName);
2217
+ await fs.writeFile(filePath, buffer);
2218
+ return json({ filePath: `/${localUploadDir.replace(/^\/+/, "").replace(/\\/g, "/")}/${fileName}` });
2219
+ } catch (err) {
2220
+ return json({ error: "File upload failed" }, { status: 500 });
2221
+ }
2222
+ };
2223
+ }
2224
+ function createBlogBySlugHandler(config) {
2225
+ const { dataSource, entityMap, json } = config;
2226
+ return async function GET(_req, slug) {
2227
+ try {
2228
+ const blogRepo = dataSource.getRepository(entityMap.blogs);
2229
+ const blog = await blogRepo.findOne({
2230
+ where: { slug, published: true },
2231
+ relations: ["author", "category", "tags", "seo"]
2232
+ });
2233
+ if (!blog) return json({ error: "Blog not found" }, { status: 404 });
2234
+ return json(blog);
2235
+ } catch {
2236
+ return json({ error: "Server Error" }, { status: 500 });
2237
+ }
2238
+ };
2239
+ }
2240
+ function createFormBySlugHandler(config) {
2241
+ const { dataSource, entityMap, json } = config;
2242
+ return async function GET(_req, slug) {
2243
+ try {
2244
+ const formRepo = dataSource.getRepository(entityMap.forms);
2245
+ const form = await formRepo.findOne({
2246
+ where: { slug, published: true, deleted: false },
2247
+ relations: ["fields"],
2248
+ order: { fields: { order: "ASC" } }
2249
+ });
2250
+ if (!form) return json({ error: "Form not found" }, { status: 404 });
2251
+ const out = form;
2252
+ if (Array.isArray(out.fields)) out.fields = out.fields.filter((f) => !f.deleted);
2253
+ return json(form);
2254
+ } catch {
2255
+ return json({ error: "Server Error" }, { status: 500 });
2256
+ }
2257
+ };
2258
+ }
2259
+ function normalizeFieldRow(f, formId) {
2260
+ const order = typeof f.order === "number" ? f.order : Number(f.order) || 0;
2261
+ const groupId = typeof f.groupId === "number" ? f.groupId : Number(f.groupId) || 1;
2262
+ const columnWidth = typeof f.columnWidth === "number" ? f.columnWidth : Number(f.columnWidth) || 12;
2263
+ return {
2264
+ formId,
2265
+ label: f.label != null ? String(f.label) : "",
2266
+ type: f.type != null ? String(f.type) : "text",
2267
+ placeholder: f.placeholder != null ? String(f.placeholder) : null,
2268
+ options: f.options != null ? typeof f.options === "string" ? f.options : JSON.stringify(f.options) : null,
2269
+ required: Boolean(f.required),
2270
+ validation: f.validation != null ? typeof f.validation === "string" ? f.validation : JSON.stringify(f.validation) : null,
2271
+ order,
2272
+ groupId,
2273
+ columnWidth
2274
+ };
2275
+ }
2276
+ function createFormSaveHandlers(config) {
2277
+ const { dataSource, entityMap, json, requireAuth } = config;
2278
+ const formRepo = () => dataSource.getRepository(entityMap.forms);
2279
+ const fieldRepo = () => dataSource.getRepository(entityMap.form_fields);
2280
+ return {
2281
+ async GET(req, id) {
2282
+ const authErr = await requireAuth(req);
2283
+ if (authErr) return authErr;
2284
+ try {
2285
+ const formId = Number(id);
2286
+ if (!Number.isInteger(formId) || formId <= 0) return json({ error: "Invalid form id" }, { status: 400 });
2287
+ const form = await formRepo().findOne({
2288
+ where: { id: formId },
2289
+ relations: ["fields"],
2290
+ order: { fields: { order: "ASC" } }
2291
+ });
2292
+ if (!form) return json({ message: "Not found" }, { status: 404 });
2293
+ const out = form;
2294
+ if (Array.isArray(out.fields)) out.fields = out.fields.filter((f) => !f.deleted);
2295
+ return json(form);
2296
+ } catch {
2297
+ return json({ error: "Server Error" }, { status: 500 });
2298
+ }
2299
+ },
2300
+ async POST(req) {
2301
+ const authErr = await requireAuth(req);
2302
+ if (authErr) return authErr;
2303
+ try {
2304
+ const body = await req.json();
2305
+ if (!body || typeof body !== "object") return json({ error: "Invalid request payload" }, { status: 400 });
2306
+ const fields = Array.isArray(body.fields) ? body.fields : [];
2307
+ const { fields: _f, ...formRow } = body;
2308
+ const form = await formRepo().save(formRepo().create(formRow));
2309
+ for (let i = 0; i < fields.length; i++) {
2310
+ const row = normalizeFieldRow(fields[i], form.id);
2311
+ row.order = i + 1;
2312
+ await fieldRepo().save(fieldRepo().create(row));
2313
+ }
2314
+ const saved = await formRepo().findOne({ where: { id: form.id }, relations: ["fields"], order: { fields: { order: "ASC" } } });
2315
+ return json(saved ?? form, { status: 201 });
2316
+ } catch (e) {
2317
+ return json({ error: "Server Error" }, { status: 500 });
2318
+ }
2319
+ },
2320
+ async PUT(req, id) {
2321
+ const authErr = await requireAuth(req);
2322
+ if (authErr) return authErr;
2323
+ try {
2324
+ const formId = Number(id);
2325
+ if (!Number.isInteger(formId) || formId <= 0) return json({ error: "Invalid form id" }, { status: 400 });
2326
+ const body = await req.json();
2327
+ if (!body || typeof body !== "object") return json({ error: "Invalid request payload" }, { status: 400 });
2328
+ const existing = await formRepo().findOne({ where: { id: formId } });
2329
+ if (!existing) return json({ message: "Not found" }, { status: 404 });
2330
+ const fields = Array.isArray(body.fields) ? body.fields : [];
2331
+ const formRow = {};
2332
+ for (const key of ["name", "description", "campaign", "slug", "published"]) {
2333
+ if (body[key] !== void 0) formRow[key] = body[key];
2334
+ }
2335
+ if (Object.keys(formRow).length > 0) await formRepo().update(formId, formRow);
2336
+ await fieldRepo().delete({ formId });
2337
+ for (let i = 0; i < fields.length; i++) {
2338
+ const row = normalizeFieldRow(fields[i], formId);
2339
+ row.order = i + 1;
2340
+ await fieldRepo().save(fieldRepo().create(row));
2341
+ }
2342
+ const saved = await formRepo().findOne({ where: { id: formId }, relations: ["fields"], order: { fields: { order: "ASC" } } });
2343
+ return saved ? json(saved) : json({ message: "Not found" }, { status: 404 });
2344
+ } catch (e) {
2345
+ return json({ error: "Server Error" }, { status: 500 });
2346
+ }
2347
+ }
2348
+ };
2349
+ }
2350
+ function createFormSubmissionGetByIdHandler(config) {
2351
+ const { dataSource, entityMap, json, requireAuth } = config;
2352
+ return async function GET(req, id) {
2353
+ const authErr = await requireAuth(req);
2354
+ if (authErr) return authErr;
2355
+ try {
2356
+ const submissionId = Number(id);
2357
+ if (!Number.isInteger(submissionId) || submissionId <= 0) return json({ error: "Invalid id" }, { status: 400 });
2358
+ const repo = dataSource.getRepository(entityMap.form_submissions);
2359
+ const submission = await repo.findOne({
2360
+ where: { id: submissionId },
2361
+ relations: ["form", "contact"]
2362
+ });
2363
+ if (!submission) return json({ message: "Not found" }, { status: 404 });
2364
+ const form = submission;
2365
+ if (form.form?.id) {
2366
+ const formRepo = dataSource.getRepository(entityMap.forms);
2367
+ const formWithFields = await formRepo.findOne({
2368
+ where: { id: form.form.id },
2369
+ relations: ["fields"],
2370
+ order: { fields: { order: "ASC" } }
2371
+ });
2372
+ if (formWithFields) submission.form = formWithFields;
2373
+ }
2374
+ return json(submission);
2375
+ } catch {
2376
+ return json({ error: "Server Error" }, { status: 500 });
2377
+ }
2378
+ };
2379
+ }
2380
+ function createFormSubmissionListHandler(config) {
2381
+ const { dataSource, entityMap, json, requireAuth } = config;
2382
+ return async function GET(req) {
2383
+ const authErr = await requireAuth(req);
2384
+ if (authErr) return authErr;
2385
+ try {
2386
+ const repo = dataSource.getRepository(entityMap.form_submissions);
2387
+ const { searchParams } = new URL(req.url);
2388
+ const page = Number(searchParams.get("page")) || 1;
2389
+ const limit = Math.min(100, Number(searchParams.get("limit")) || 10);
2390
+ const skip = (page - 1) * limit;
2391
+ const sortField = searchParams.get("sortField") || "createdAt";
2392
+ const sortOrder = searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
2393
+ const [data, total] = await repo.findAndCount({
2394
+ skip,
2395
+ take: limit,
2396
+ order: { [sortField]: sortOrder },
2397
+ relations: ["form", "contact"]
2398
+ });
2399
+ return json({ total, page, limit, totalPages: Math.ceil(total / limit), data });
2400
+ } catch {
2401
+ return json({ error: "Server Error" }, { status: 500 });
2402
+ }
2403
+ };
2404
+ }
2405
+ function pickContactFromSubmission(fields, data) {
2406
+ let email = null;
2407
+ let name = null;
2408
+ let phone = null;
2409
+ for (const f of fields) {
2410
+ const val = data[String(f.id)];
2411
+ if (val == null || val === "") continue;
2412
+ const str = String(val).trim();
2413
+ if (f.type === "email" || f.label && f.label.toLowerCase().includes("email")) {
2414
+ if (str && !email) email = str;
2415
+ } else if (f.type === "phone" || f.label && f.label.toLowerCase().includes("phone")) {
2416
+ if (str && !phone) phone = str;
2417
+ } else if (f.label && f.label.toLowerCase().includes("name") && (f.type === "text" || !f.type)) {
2418
+ if (str && !name) name = str;
2419
+ }
2420
+ }
2421
+ if (!email) return null;
2422
+ return { name: name || email, email, phone: phone || null };
2423
+ }
2424
+ function createFormSubmissionHandler(config) {
2425
+ const { dataSource, entityMap, json } = config;
2426
+ return async function POST(req) {
2427
+ try {
2428
+ const body = await req.json();
2429
+ if (!body || typeof body !== "object") {
2430
+ return json({ error: "Invalid request payload" }, { status: 400 });
2431
+ }
2432
+ const formId = typeof body.formId === "number" ? body.formId : Number(body.formId);
2433
+ if (!Number.isInteger(formId) || formId <= 0) {
2434
+ return json({ error: "formId is required and must be a positive integer" }, { status: 400 });
2435
+ }
2436
+ const data = body.data;
2437
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
2438
+ return json({ error: "data is required and must be an object" }, { status: 400 });
2439
+ }
2440
+ const formRepo = dataSource.getRepository(entityMap.forms);
2441
+ const form = await formRepo.findOne({
2442
+ where: { id: formId, published: true, deleted: false },
2443
+ relations: ["fields"]
2444
+ });
2445
+ if (!form) {
2446
+ return json({ error: "Form not found" }, { status: 404 });
2447
+ }
2448
+ const fields = form.fields ?? [];
2449
+ const activeFields = fields.filter((f) => !f.deleted);
2450
+ let contactId = body.contactId != null && body.contactId !== "" ? typeof body.contactId === "number" ? body.contactId : Number(body.contactId) : null;
2451
+ if (!contactId) {
2452
+ const contactData = pickContactFromSubmission(activeFields, data);
2453
+ if (contactData) {
2454
+ const contactRepo = dataSource.getRepository(entityMap.contacts);
2455
+ let contact = await contactRepo.findOne({ where: { email: contactData.email } });
2456
+ if (!contact) {
2457
+ contact = await contactRepo.save(
2458
+ contactRepo.create({
2459
+ name: contactData.name,
2460
+ email: contactData.email,
2461
+ phone: contactData.phone
2462
+ })
2463
+ );
2464
+ }
2465
+ contactId = contact.id;
2466
+ }
2467
+ }
2468
+ const ipAddress = req.headers.get("x-forwarded-for") ?? req.headers.get("x-real-ip") ?? null;
2469
+ const userAgent = req.headers.get("user-agent") ?? null;
2470
+ const submissionRepo = dataSource.getRepository(entityMap.form_submissions);
2471
+ const created = await submissionRepo.save(
2472
+ submissionRepo.create({
2473
+ formId,
2474
+ contactId: Number.isInteger(contactId) ? contactId : null,
2475
+ data,
2476
+ ipAddress: ipAddress?.slice(0, 255) ?? null,
2477
+ userAgent: userAgent?.slice(0, 500) ?? null
2478
+ })
2479
+ );
2480
+ return json(created, { status: 201 });
2481
+ } catch {
2482
+ return json({ error: "Server Error" }, { status: 500 });
2483
+ }
2484
+ };
2485
+ }
2486
+ function createUsersApiHandlers(config) {
2487
+ const { dataSource, entityMap, json, requireAuth, baseUrl } = config;
2488
+ const userRepo = () => dataSource.getRepository(entityMap.users);
2489
+ return {
2490
+ async list(req) {
2491
+ const authErr = await requireAuth(req);
2492
+ if (authErr) return authErr;
2493
+ try {
2494
+ const url = new URL(req.url);
2495
+ const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
2496
+ const limit = Math.min(100, parseInt(url.searchParams.get("limit") || "10", 10));
2497
+ const skip = (page - 1) * limit;
2498
+ const sortField = url.searchParams.get("sortField") || "createdAt";
2499
+ const sortOrder = url.searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
2500
+ const search = url.searchParams.get("search");
2501
+ const where = search ? [{ name: ILike2(`%${search}%`) }, { email: ILike2(`%${search}%`) }] : {};
2502
+ const [data, total] = await userRepo().findAndCount({
2503
+ skip,
2504
+ take: limit,
2505
+ order: { [sortField]: sortOrder },
2506
+ where,
2507
+ relations: ["group"],
2508
+ select: ["id", "name", "email", "blocked", "createdAt", "updatedAt", "groupId"]
2509
+ });
2510
+ return json({ total, page, limit, totalPages: Math.ceil(total / limit), data });
2511
+ } catch {
2512
+ return json({ error: "Server Error" }, { status: 500 });
2513
+ }
2514
+ },
2515
+ async create(req) {
2516
+ const authErr = await requireAuth(req);
2517
+ if (authErr) return authErr;
2518
+ try {
2519
+ const body = await req.json();
2520
+ if (!body?.name || !body?.email) return json({ error: "Name and email are required" }, { status: 400 });
2521
+ const existing = await userRepo().findOne({ where: { email: body.email } });
2522
+ if (existing) return json({ error: "User with this email already exists" }, { status: 400 });
2523
+ const newUser = await userRepo().save(
2524
+ userRepo().create({ name: body.name, email: body.email, password: null, blocked: true, groupId: body.groupId ?? null })
2525
+ );
2526
+ const emailToken = Buffer.from(newUser.email).toString("base64");
2527
+ const inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
2528
+ return json({ message: "User created successfully (blocked until password is set)", user: newUser, inviteLink }, { status: 201 });
2529
+ } catch {
2530
+ return json({ error: "Server Error" }, { status: 500 });
2531
+ }
2532
+ },
2533
+ async getById(_req, id) {
2534
+ const authErr = await requireAuth(new Request(_req.url));
2535
+ if (authErr) return authErr;
2536
+ try {
2537
+ const user = await userRepo().findOne({
2538
+ where: { id: parseInt(id, 10) },
2539
+ relations: ["group"],
2540
+ select: ["id", "name", "email", "blocked", "createdAt", "updatedAt", "groupId"]
2541
+ });
2542
+ if (!user) return json({ error: "User not found" }, { status: 404 });
2543
+ return json(user);
2544
+ } catch {
2545
+ return json({ error: "Server Error" }, { status: 500 });
2546
+ }
2547
+ },
2548
+ async update(req, id) {
2549
+ const authErr = await requireAuth(req);
2550
+ if (authErr) return authErr;
2551
+ try {
2552
+ const body = await req.json();
2553
+ const { password: _p, ...safe } = body;
2554
+ await userRepo().update(parseInt(id, 10), safe);
2555
+ const updated = await userRepo().findOne({
2556
+ where: { id: parseInt(id, 10) },
2557
+ relations: ["group"],
2558
+ select: ["id", "name", "email", "blocked", "createdAt", "updatedAt", "groupId"]
2559
+ });
2560
+ return updated ? json(updated) : json({ error: "Not found" }, { status: 404 });
2561
+ } catch {
2562
+ return json({ error: "Server Error" }, { status: 500 });
2563
+ }
2564
+ },
2565
+ async delete(_req, id) {
2566
+ const authErr = await requireAuth(new Request(_req.url));
2567
+ if (authErr) return authErr;
2568
+ try {
2569
+ const r = await userRepo().delete(parseInt(id, 10));
2570
+ if (r.affected === 0) return json({ error: "User not found" }, { status: 404 });
2571
+ return json({ message: "User deleted successfully" });
2572
+ } catch {
2573
+ return json({ error: "Server Error" }, { status: 500 });
2574
+ }
2575
+ },
2576
+ async regenerateInvite(_req, id) {
2577
+ const authErr = await requireAuth(new Request(_req.url));
2578
+ if (authErr) return authErr;
2579
+ try {
2580
+ const user = await userRepo().findOne({ where: { id: parseInt(id, 10) }, select: ["email"] });
2581
+ if (!user) return json({ error: "User not found" }, { status: 404 });
2582
+ const emailToken = Buffer.from(user.email).toString("base64");
2583
+ const inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
2584
+ return json({ message: "New invite link generated successfully", inviteLink });
2585
+ } catch {
2586
+ return json({ error: "Server Error" }, { status: 500 });
2587
+ }
2588
+ }
2589
+ };
2590
+ }
2591
+ function createUserAvatarHandler(config) {
2592
+ const { json, getSession, saveAvatar } = config;
2593
+ return async function POST(req) {
2594
+ const session = await getSession();
2595
+ if (!session?.user?.email) return json({ error: "Unauthorized" }, { status: 401 });
2596
+ try {
2597
+ const formData = await req.formData();
2598
+ const file = formData.get("avatar");
2599
+ if (!file) return json({ error: "No file uploaded" }, { status: 400 });
2600
+ if (!file.type.startsWith("image/")) return json({ error: "File must be an image" }, { status: 400 });
2601
+ if (file.size > 5 * 1024 * 1024) return json({ error: "File size must be less than 5MB" }, { status: 400 });
2602
+ const buffer = Buffer.from(await file.arrayBuffer());
2603
+ const ext = file.name.split(".").pop() || "jpg";
2604
+ const fileName = `avatar_${session.user.email}_${Date.now()}.${ext}`;
2605
+ const avatarUrl = saveAvatar ? await saveAvatar(buffer, fileName) : await (async () => {
2606
+ const fs = await import("fs/promises");
2607
+ const path = await import("path");
2608
+ const dir = path.join(process.cwd(), "public", "uploads", "avatars");
2609
+ await fs.mkdir(dir, { recursive: true });
2610
+ await fs.writeFile(path.join(dir, fileName), buffer);
2611
+ return `/uploads/avatars/${fileName}`;
2612
+ })();
2613
+ return json({ message: "Avatar uploaded successfully", avatarUrl });
2614
+ } catch {
2615
+ return json({ error: "Internal server error" }, { status: 500 });
2616
+ }
2617
+ };
2618
+ }
2619
+ function createUserProfileHandler(config) {
2620
+ const { dataSource, entityMap, json, getSession } = config;
2621
+ return async function PUT(req) {
2622
+ const session = await getSession();
2623
+ if (!session?.user?.email) return json({ error: "Unauthorized" }, { status: 401 });
2624
+ try {
2625
+ const body = await req.json();
2626
+ if (!body?.name) return json({ error: "Name is required" }, { status: 400 });
2627
+ const userRepo = dataSource.getRepository(entityMap.users);
2628
+ await userRepo.update({ email: session.user.email }, { name: body.name, updatedAt: /* @__PURE__ */ new Date() });
2629
+ const updated = await userRepo.findOne({ where: { email: session.user.email }, select: ["id", "name", "email"] });
2630
+ if (!updated) return json({ error: "Not found" }, { status: 404 });
2631
+ return json({ message: "Profile updated successfully", user: { id: updated.id, name: updated.name, email: updated.email } });
2632
+ } catch {
2633
+ return json({ error: "Internal server error" }, { status: 500 });
2634
+ }
2635
+ };
2636
+ }
2637
+ function simpleEncrypt(text, key) {
2638
+ const buf = Buffer.from(text, "utf8");
2639
+ const keyBuf = Buffer.from(key.padEnd(32, "0").slice(0, 32), "utf8");
2640
+ const out = Buffer.alloc(buf.length);
2641
+ for (let i = 0; i < buf.length; i++) out[i] = buf[i] ^ keyBuf[i % keyBuf.length];
2642
+ return out.toString("base64");
2643
+ }
2644
+ function simpleDecrypt(encoded, key) {
2645
+ const buf = Buffer.from(encoded, "base64");
2646
+ const keyBuf = Buffer.from(key.padEnd(32, "0").slice(0, 32), "utf8");
2647
+ const out = Buffer.alloc(buf.length);
2648
+ for (let i = 0; i < buf.length; i++) out[i] = buf[i] ^ keyBuf[i % keyBuf.length];
2649
+ return out.toString("utf8");
2650
+ }
2651
+ function createSettingsApiHandlers(config) {
2652
+ const { dataSource, entityMap, json, requireAuth, encryptionKey, publicGetGroups } = config;
2653
+ const configRepo = () => dataSource.getRepository(entityMap.configs);
2654
+ return {
2655
+ async GET(req, group) {
2656
+ const isPublicGroup = publicGetGroups?.includes(group);
2657
+ const authErr = isPublicGroup ? null : await requireAuth(req);
2658
+ const isAuthed = !authErr;
2659
+ try {
2660
+ const where = { settings: group, deleted: false };
2661
+ if (!isAuthed && !isPublicGroup) where.type = "public";
2662
+ const rows = await configRepo().find({ where });
2663
+ const result = {};
2664
+ for (const row of rows) {
2665
+ const r = row;
2666
+ let val = r.value;
2667
+ if (r.encrypted && encryptionKey) {
2668
+ try {
2669
+ val = simpleDecrypt(val, encryptionKey);
2670
+ } catch {
2671
+ }
2672
+ }
2673
+ result[r.key] = val;
2674
+ }
2675
+ return json(result);
2676
+ } catch {
2677
+ return json({ error: "Failed to fetch settings" }, { status: 500 });
2678
+ }
2679
+ },
2680
+ async PUT(req, group) {
2681
+ const authErr = await requireAuth(req);
2682
+ if (authErr) return authErr;
2683
+ try {
2684
+ const body = await req.json();
2685
+ if (!body || typeof body !== "object") return json({ error: "Invalid payload" }, { status: 400 });
2686
+ const repo = configRepo();
2687
+ for (const [key, entry] of Object.entries(body)) {
2688
+ const val = typeof entry === "string" ? entry : entry.value;
2689
+ const type = typeof entry === "object" && entry.type || "private";
2690
+ const encrypted = !!(typeof entry === "object" && entry.encrypted);
2691
+ let storedValue = val;
2692
+ if (encrypted && encryptionKey) {
2693
+ storedValue = simpleEncrypt(val, encryptionKey);
2694
+ }
2695
+ const existing = await repo.findOne({ where: { settings: group, key } });
2696
+ if (existing) {
2697
+ await repo.update(existing.id, { value: storedValue, type, encrypted, updatedAt: /* @__PURE__ */ new Date() });
2698
+ } else {
2699
+ await repo.save(repo.create({ settings: group, key, value: storedValue, type, encrypted }));
2700
+ }
2701
+ }
2702
+ return json({ message: "Settings saved" });
2703
+ } catch {
2704
+ return json({ error: "Failed to save settings" }, { status: 500 });
2705
+ }
2706
+ }
2707
+ };
2708
+ }
2709
+
2710
+ // src/api/cms-api-handler.ts
2711
+ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set(["users", "password_reset_tokens", "user_groups", "permissions", "comments", "form_fields", "configs"]);
2712
+ function createCmsApiHandler(config) {
2713
+ const {
2714
+ dataSource,
2715
+ entityMap,
2716
+ pathToModel = (s) => s,
2717
+ crudResources = Object.keys(entityMap).filter((k) => !DEFAULT_EXCLUDE.has(k)),
2718
+ getCms,
2719
+ userAuth: userAuthConfig,
2720
+ dashboard,
2721
+ analytics: analyticsConfig,
2722
+ upload,
2723
+ blogBySlug,
2724
+ formBySlug,
2725
+ formSave: formSaveConfig,
2726
+ formSubmission: formSubmissionConfig,
2727
+ formSubmissionGetById: formSubmissionGetByIdConfig,
2728
+ usersApi,
2729
+ userAvatar,
2730
+ userProfile,
2731
+ settings: settingsConfig
2732
+ } = config;
2733
+ const analytics = analyticsConfig ?? (getCms ? {
2734
+ json: config.json,
2735
+ requireAuth: async () => null,
2736
+ getAnalyticsData: async (days) => {
2737
+ const cms = await getCms();
2738
+ const a = cms.getPlugin("analytics");
2739
+ if (!a?.getAnalyticsData) throw new Error("Analytics not configured");
2740
+ return a.getAnalyticsData(days);
2741
+ },
2742
+ getPropertyId: () => ({ currentViewId: process.env.GOOGLE_ANALYTICS_VIEW_ID }),
2743
+ getPermissions: () => ({
2744
+ serviceAccountEmail: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
2745
+ currentViewId: process.env.GOOGLE_ANALYTICS_VIEW_ID
2746
+ })
2747
+ } : void 0);
2748
+ const userAuth = userAuthConfig && getCms && userAuthConfig.sendEmail === void 0 ? {
2749
+ ...userAuthConfig,
2750
+ sendEmail: async (opts) => {
2751
+ const cms = await getCms();
2752
+ const email = cms.getPlugin("email");
2753
+ if (!email?.send) return;
2754
+ const { emailTemplates: emailTemplates2 } = await Promise.resolve().then(() => (init_email_service(), email_service_exports));
2755
+ const { subject, html, text } = emailTemplates2.passwordReset({ resetLink: opts.text || opts.html });
2756
+ await email.send({ subject, html, text, to: opts.to });
2757
+ }
2758
+ } : userAuthConfig;
2759
+ const crudOpts = { requireAuth: config.requireAuth, json: config.json };
2760
+ const crud = createCrudHandler(dataSource, entityMap, crudOpts);
2761
+ const crudById = createCrudByIdHandler(dataSource, entityMap, crudOpts);
2762
+ const userAuthRouter = userAuth ? createUserAuthApiRouter(userAuth) : null;
2763
+ const dashboardGet = dashboard ? createDashboardStatsHandler(dashboard) : null;
2764
+ const analyticsHandlers = analytics ? createAnalyticsHandlers(analytics) : null;
2765
+ const uploadPost = upload ? createUploadHandler(upload) : null;
2766
+ const blogBySlugGet = blogBySlug ? createBlogBySlugHandler(blogBySlug) : null;
2767
+ const formBySlugGet = formBySlug ? createFormBySlugHandler(formBySlug) : null;
2768
+ const formSaveHandlers = formSaveConfig ? createFormSaveHandlers(formSaveConfig) : null;
2769
+ const formSubmissionPost = formSubmissionConfig ? createFormSubmissionHandler(formSubmissionConfig) : null;
2770
+ const formSubmissionGetById = formSubmissionGetByIdConfig ? createFormSubmissionGetByIdHandler(formSubmissionGetByIdConfig) : null;
2771
+ const formSubmissionList = formSubmissionGetByIdConfig ? createFormSubmissionListHandler(formSubmissionGetByIdConfig) : null;
2772
+ const usersHandlers = usersApi ? createUsersApiHandlers(usersApi) : null;
2773
+ const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
2774
+ const profilePut = userProfile ? createUserProfileHandler(userProfile) : null;
2775
+ const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
2776
+ function resolveResource(segment) {
2777
+ const model = pathToModel(segment);
2778
+ return crudResources.includes(model) ? model : segment;
2779
+ }
2780
+ return {
2781
+ async handle(method, path, req) {
2782
+ if (path[0] === "dashboard" && path[1] === "stats" && path.length === 2 && method === "GET" && dashboardGet) {
2783
+ return dashboardGet(req);
2784
+ }
2785
+ if (path[0] === "analytics" && analyticsHandlers) {
2786
+ if (path.length === 1 && method === "GET") return analyticsHandlers.GET(req);
2787
+ if (path.length === 2 && path[1] === "property-id" && method === "GET") return analyticsHandlers.propertyId();
2788
+ if (path.length === 2 && path[1] === "permissions" && method === "GET") return analyticsHandlers.permissions();
2789
+ }
2790
+ if (path[0] === "upload" && path.length === 1 && method === "POST" && uploadPost) return uploadPost(req);
2791
+ if (path[0] === "blogs" && path[1] === "slug" && path.length === 3 && method === "GET" && blogBySlugGet) {
2792
+ return blogBySlugGet(req, path[2]);
2793
+ }
2794
+ if (path[0] === "forms" && path[1] === "slug" && path.length === 3 && method === "GET" && formBySlugGet) {
2795
+ return formBySlugGet(req, path[2]);
2796
+ }
2797
+ if (path[0] === "form-submissions") {
2798
+ if (path.length === 1) {
2799
+ if (method === "GET" && formSubmissionList) return formSubmissionList(req);
2800
+ if (method === "POST" && formSubmissionPost) return formSubmissionPost(req);
2801
+ }
2802
+ if (path.length === 2 && method === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path[1]);
2803
+ }
2804
+ if (path[0] === "forms" && formSaveHandlers) {
2805
+ if (path.length === 1 && method === "POST") return formSaveHandlers.POST(req);
2806
+ if (path.length === 2) {
2807
+ if (method === "GET") return formSaveHandlers.GET(req, path[1]);
2808
+ if (method === "PUT" || method === "PATCH") return formSaveHandlers.PUT(req, path[1]);
2809
+ }
2810
+ }
2811
+ if (path[0] === "users" && usersHandlers) {
2812
+ if (path.length === 1) {
2813
+ if (method === "GET") return usersHandlers.list(req);
2814
+ if (method === "POST") return usersHandlers.create(req);
2815
+ }
2816
+ if (path.length === 2) {
2817
+ if (path[1] === "avatar" && method === "POST" && avatarPost) return avatarPost(req);
2818
+ if (path[1] === "profile" && method === "PUT" && profilePut) return profilePut(req);
2819
+ const id = path[1];
2820
+ if (method === "GET") return usersHandlers.getById(req, id);
2821
+ if (method === "PUT" || method === "PATCH") return usersHandlers.update(req, id);
2822
+ if (method === "DELETE") return usersHandlers.delete(req, id);
2823
+ }
2824
+ if (path.length === 3 && path[2] === "regenerate-invite" && method === "POST") {
2825
+ return usersHandlers.regenerateInvite(req, path[1]);
2826
+ }
2827
+ }
2828
+ if (path[0] === "users" && path.length === 2 && userAuthRouter && method === "POST") {
2829
+ return userAuthRouter.POST(req, path[1]);
2830
+ }
2831
+ if (path[0] === "settings" && path.length === 2 && settingsHandlers) {
2832
+ if (method === "GET") return settingsHandlers.GET(req, path[1]);
2833
+ if (method === "PUT") return settingsHandlers.PUT(req, path[1]);
2834
+ }
2835
+ if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
2836
+ const resource = resolveResource(path[0]);
2837
+ if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
2838
+ if (path.length === 1) {
2839
+ if (method === "GET") return crud.GET(req, resource);
2840
+ if (method === "POST") return crud.POST(req, resource);
2841
+ return config.json({ error: "Method not allowed" }, { status: 405 });
2842
+ }
2843
+ if (path.length === 2) {
2844
+ const id = path[1];
2845
+ if (method === "GET") return crudById.GET(req, resource, id);
2846
+ if (method === "PUT" || method === "PATCH") return crudById.PUT(req, resource, id);
2847
+ if (method === "DELETE") return crudById.DELETE(req, resource, id);
2848
+ return config.json({ error: "Method not allowed" }, { status: 405 });
2849
+ }
2850
+ return config.json({ error: "Not found" }, { status: 404 });
2851
+ }
2852
+ };
2853
+ }
2854
+
2855
+ // src/admin/config.ts
2856
+ var DEFAULT_ADMIN_NAV = [
2857
+ { href: "/admin/dashboard", label: "Dashboard" },
2858
+ { href: "/admin/contacts", label: "Contacts" },
2859
+ { href: "/admin/blogs", label: "Blogs" },
2860
+ { href: "/admin/users", label: "Users" },
2861
+ { href: "/admin/forms", label: "Forms" },
2862
+ { href: "/admin/submissions", label: "Submissions" },
2863
+ { href: "/admin/pages", label: "Pages" }
2864
+ ];
2865
+ export {
2866
+ Blog,
2867
+ CMS_ENTITY_MAP,
2868
+ Category,
2869
+ Comment,
2870
+ Config,
2871
+ Contact,
2872
+ DEFAULT_ADMIN_NAV,
2873
+ EmailService,
2874
+ Form,
2875
+ FormField,
2876
+ FormSubmission,
2877
+ Media,
2878
+ OPEN_ENDPOINTS,
2879
+ PERMISSION_REQUIRED_ENDPOINTS,
2880
+ Page,
2881
+ PasswordResetToken,
2882
+ Permission,
2883
+ Seo,
2884
+ Tag,
2885
+ User,
2886
+ UserGroup,
2887
+ analyticsPlugin,
2888
+ cn,
2889
+ createAnalyticsHandlers,
2890
+ createAuthHelpers,
2891
+ createBlogBySlugHandler,
2892
+ createChangePasswordHandler,
2893
+ createCmsApiHandler,
2894
+ createCmsApp,
2895
+ createCmsMiddleware,
2896
+ createCrudByIdHandler,
2897
+ createCrudHandler,
2898
+ createDashboardStatsHandler,
2899
+ createForgotPasswordHandler,
2900
+ createFormBySlugHandler,
2901
+ createInviteAcceptHandler,
2902
+ createSetPasswordHandler,
2903
+ createSettingsApiHandlers,
2904
+ createUploadHandler,
2905
+ createUserAuthApiRouter,
2906
+ createUserAvatarHandler,
2907
+ createUserProfileHandler,
2908
+ createUsersApiHandlers,
2909
+ defaultPublicApiMethods,
2910
+ emailPlugin,
2911
+ emailTemplates,
2912
+ erpPlugin,
2913
+ formatDate,
2914
+ formatDateOnly,
2915
+ formatDateTime,
2916
+ generateSlug,
2917
+ getNextAuthOptions,
2918
+ getRequiredPermission,
2919
+ isOpenEndpoint,
2920
+ isPublicMethod,
2921
+ localStoragePlugin,
2922
+ paymentPlugin,
2923
+ s3StoragePlugin,
2924
+ smsPlugin,
2925
+ truncateText,
2926
+ validateSlug
2927
+ };
2928
+ //# sourceMappingURL=index.js.map