@gzl10/nexus-backend 0.19.0 → 0.20.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/claude-commands/nexus-docs.md +2 -2
- package/claude-commands/nexus-update-tutorial-app.md +2 -2
- package/claude-commands/nexus-update-tutorial-plugin.md +1 -1
- package/dist/cli.js +264 -403
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +684 -714
- package/dist/index.js.map +1 -1
- package/dist/main.js +679 -709
- package/dist/main.js.map +1 -1
- package/dist/migration-helpers/index.js +388 -526
- package/dist/migration-helpers/index.js.map +1 -1
- package/dist/testing/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var init_package = __esm({
|
|
|
14
14
|
"package.json"() {
|
|
15
15
|
package_default = {
|
|
16
16
|
name: "@gzl10/nexus-backend",
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "0.20.0",
|
|
18
18
|
description: "Backend as a Service (BaaS) with Express 5, Knex and CASL",
|
|
19
19
|
type: "module",
|
|
20
20
|
main: "./dist/index.js",
|
|
@@ -9287,18 +9287,20 @@ function createSystemController(ctx) {
|
|
|
9287
9287
|
/**
|
|
9288
9288
|
* GET /system/capabilities
|
|
9289
9289
|
* Public endpoint — no authentication required.
|
|
9290
|
-
*
|
|
9290
|
+
* Resolves all registered capabilities (core + plugin-contributed).
|
|
9291
9291
|
*/
|
|
9292
|
-
getCapabilities(_req, res) {
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9292
|
+
async getCapabilities(_req, res) {
|
|
9293
|
+
try {
|
|
9294
|
+
res.set("Cache-Control", "public, max-age=300");
|
|
9295
|
+
const body = await ctx.capabilities.resolve();
|
|
9296
|
+
res.json(body);
|
|
9297
|
+
} catch (err) {
|
|
9298
|
+
ctx.core.logger.error({ err }, "Failed to resolve capabilities");
|
|
9299
|
+
res.set("Cache-Control", "no-cache");
|
|
9300
|
+
res.status(500).json({
|
|
9301
|
+
error: { code: "INTERNAL_ERROR", message: "Failed to resolve capabilities" }
|
|
9302
|
+
});
|
|
9303
|
+
}
|
|
9302
9304
|
}
|
|
9303
9305
|
};
|
|
9304
9306
|
function toManifestDTO(manifest, req) {
|
|
@@ -9871,8 +9873,8 @@ function registerCoreVars(registry2) {
|
|
|
9871
9873
|
registry2.register("auth", "core", [
|
|
9872
9874
|
{
|
|
9873
9875
|
name: "AUTH_SECRET",
|
|
9874
|
-
description: { en: "JWT signing secret. Must be at least 32 characters.
|
|
9875
|
-
required:
|
|
9876
|
+
description: { en: "JWT signing secret (optional). If not set, auto-generated and persisted in database. Must be at least 32 characters. Set explicitly for multi-instance deployments", es: "Clave de firma JWT (opcional). Si no se establece, se genera autom\xE1ticamente y se persiste en la base de datos. M\xEDnimo 32 caracteres. Establecer expl\xEDcitamente para despliegues multi-instancia" },
|
|
9877
|
+
required: false,
|
|
9876
9878
|
sensitive: true
|
|
9877
9879
|
},
|
|
9878
9880
|
{
|
|
@@ -10389,96 +10391,70 @@ var init_system = __esm({
|
|
|
10389
10391
|
}
|
|
10390
10392
|
});
|
|
10391
10393
|
|
|
10392
|
-
// src/modules/ui-settings/ui-
|
|
10393
|
-
import { useTextField as useTextField7, useImageField } from "@gzl10/nexus-sdk/fields";
|
|
10394
|
-
var
|
|
10395
|
-
var
|
|
10396
|
-
"src/modules/ui-settings/ui-
|
|
10394
|
+
// src/modules/ui-settings/ui-style-guide.entity.ts
|
|
10395
|
+
import { useTextField as useTextField7, useImageField, useSelectField as useSelectField6, useColorField, useNumberField as useNumberField4, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
|
|
10396
|
+
var uiStyleGuideEntity;
|
|
10397
|
+
var init_ui_style_guide_entity = __esm({
|
|
10398
|
+
"src/modules/ui-settings/ui-style-guide.entity.ts"() {
|
|
10397
10399
|
"use strict";
|
|
10398
|
-
|
|
10400
|
+
uiStyleGuideEntity = {
|
|
10399
10401
|
type: "single",
|
|
10400
10402
|
realtime: "sync",
|
|
10401
|
-
key: "
|
|
10402
|
-
label: { en: "
|
|
10403
|
-
icon: "mdi:palette-
|
|
10403
|
+
key: "ui_style_guide",
|
|
10404
|
+
label: { en: "Style Guide", es: "Gu\xEDa de Estilos" },
|
|
10405
|
+
icon: "mdi:palette-outline",
|
|
10404
10406
|
public: true,
|
|
10405
|
-
routePrefix: "/ui-
|
|
10407
|
+
routePrefix: "/ui-style-guide",
|
|
10406
10408
|
defaults: {
|
|
10407
10409
|
appName: "Nexus",
|
|
10408
10410
|
logo: null,
|
|
10409
10411
|
logoDark: null,
|
|
10410
|
-
favicon: null
|
|
10412
|
+
favicon: null,
|
|
10413
|
+
primaryColor: "#3B82F6",
|
|
10414
|
+
font: "space-grotesk",
|
|
10415
|
+
typographyScale: "default",
|
|
10416
|
+
borderRadius: 8,
|
|
10417
|
+
glassEffect: true,
|
|
10418
|
+
theme: "system",
|
|
10419
|
+
loginLayout: "centered"
|
|
10411
10420
|
},
|
|
10412
10421
|
fields: {
|
|
10413
10422
|
appName: useTextField7({
|
|
10414
10423
|
label: { en: "App Name", es: "Nombre de la App" },
|
|
10415
|
-
hint: { en: "Displayed in
|
|
10424
|
+
hint: { en: "Displayed in header, browser tab and emails", es: "Se muestra en header, pesta\xF1a del navegador y emails" },
|
|
10416
10425
|
size: 100,
|
|
10417
10426
|
required: true
|
|
10418
10427
|
}),
|
|
10419
10428
|
logo: useImageField({
|
|
10420
10429
|
label: { en: "Logo (Light Theme)", es: "Logo (Tema Claro)" },
|
|
10421
|
-
hint: { en: "
|
|
10430
|
+
hint: { en: "SVG or PNG with transparent background, max 200px height", es: "SVG o PNG con fondo transparente, m\xE1x 200px de alto" },
|
|
10422
10431
|
folder: "branding",
|
|
10423
10432
|
isPublic: true,
|
|
10424
10433
|
dedupe: true
|
|
10425
10434
|
}),
|
|
10426
10435
|
logoDark: useImageField({
|
|
10427
10436
|
label: { en: "Logo (Dark Theme)", es: "Logo (Tema Oscuro)" },
|
|
10428
|
-
hint: { en: "
|
|
10437
|
+
hint: { en: "Lighter version for dark backgrounds", es: "Versi\xF3n m\xE1s clara para fondos oscuros" },
|
|
10429
10438
|
folder: "branding",
|
|
10430
10439
|
isPublic: true,
|
|
10431
10440
|
dedupe: true
|
|
10432
10441
|
}),
|
|
10433
10442
|
favicon: useImageField({
|
|
10434
10443
|
label: { en: "Favicon", es: "Favicon" },
|
|
10435
|
-
hint: { en: "
|
|
10444
|
+
hint: { en: "32x32 or 64x64 PNG/ICO", es: "32x32 o 64x64 PNG/ICO" },
|
|
10436
10445
|
accept: "image/x-icon,image/png,image/svg+xml",
|
|
10437
10446
|
maxSize: "256KB",
|
|
10438
10447
|
folder: "branding",
|
|
10439
10448
|
isPublic: true,
|
|
10440
10449
|
dedupe: true
|
|
10441
|
-
})
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
ADMIN: { actions: ["read", "update"] }
|
|
10447
|
-
}
|
|
10448
|
-
}
|
|
10449
|
-
};
|
|
10450
|
-
}
|
|
10451
|
-
});
|
|
10452
|
-
|
|
10453
|
-
// src/modules/ui-settings/ui-theme.entity.ts
|
|
10454
|
-
import { useSelectField as useSelectField6, useColorField } from "@gzl10/nexus-sdk/fields";
|
|
10455
|
-
var uiThemeEntity;
|
|
10456
|
-
var init_ui_theme_entity = __esm({
|
|
10457
|
-
"src/modules/ui-settings/ui-theme.entity.ts"() {
|
|
10458
|
-
"use strict";
|
|
10459
|
-
uiThemeEntity = {
|
|
10460
|
-
type: "single",
|
|
10461
|
-
realtime: "sync",
|
|
10462
|
-
key: "ui_theme",
|
|
10463
|
-
label: { en: "Theme & Colors", es: "Tema y Colores" },
|
|
10464
|
-
icon: "mdi:palette",
|
|
10465
|
-
public: true,
|
|
10466
|
-
routePrefix: "/ui-theme",
|
|
10467
|
-
defaults: {
|
|
10468
|
-
// Typography
|
|
10469
|
-
font: "space-grotesk",
|
|
10470
|
-
// Theme & Colors
|
|
10471
|
-
theme: "system",
|
|
10472
|
-
primaryColor: "#3B82F6",
|
|
10473
|
-
dopamineTheme: "none",
|
|
10474
|
-
// Layout
|
|
10475
|
-
loginLayout: "centered"
|
|
10476
|
-
},
|
|
10477
|
-
fields: {
|
|
10478
|
-
// === Typography ===
|
|
10450
|
+
}),
|
|
10451
|
+
primaryColor: useColorField({
|
|
10452
|
+
label: { en: "Primary Color", es: "Color Principal" },
|
|
10453
|
+
hint: { en: "Accent color for buttons, links and highlights", es: "Color de acento para botones, enlaces y resaltados" }
|
|
10454
|
+
}),
|
|
10479
10455
|
font: useSelectField6({
|
|
10480
10456
|
label: { en: "Font", es: "Fuente" },
|
|
10481
|
-
hint: { en: "Primary font for headings and UI
|
|
10457
|
+
hint: { en: "Primary font for headings and UI", es: "Fuente principal para t\xEDtulos e interfaz" },
|
|
10482
10458
|
options: [
|
|
10483
10459
|
{ value: "space-grotesk", label: "Space Grotesk" },
|
|
10484
10460
|
{ value: "inter", label: "Inter" },
|
|
@@ -10488,37 +10464,38 @@ var init_ui_theme_entity = __esm({
|
|
|
10488
10464
|
{ value: "system", label: { en: "System Default", es: "Sistema" } }
|
|
10489
10465
|
]
|
|
10490
10466
|
}),
|
|
10491
|
-
|
|
10467
|
+
typographyScale: useSelectField6({
|
|
10468
|
+
label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
|
|
10469
|
+
hint: { en: "Font size scaling (WCAG 1.4.4)", es: "Escala de tama\xF1os de fuente (WCAG 1.4.4)" },
|
|
10470
|
+
options: [
|
|
10471
|
+
{ value: "compact", label: { en: "Compact", es: "Compacta" } },
|
|
10472
|
+
{ value: "default", label: { en: "Default", es: "Por defecto" } },
|
|
10473
|
+
{ value: "relaxed", label: { en: "Relaxed", es: "Relajada" } }
|
|
10474
|
+
]
|
|
10475
|
+
}),
|
|
10476
|
+
borderRadius: useNumberField4({
|
|
10477
|
+
label: { en: "Border Radius", es: "Radio de Bordes" },
|
|
10478
|
+
hint: { en: "Corner roundness in pixels (0 = sharp, 16 = very round)", es: "Redondeo de esquinas en p\xEDxeles (0 = cuadrado, 16 = muy redondo)" },
|
|
10479
|
+
defaultValue: 8,
|
|
10480
|
+
validation: { min: 0, max: 16 }
|
|
10481
|
+
}),
|
|
10482
|
+
glassEffect: useSwitchField2({
|
|
10483
|
+
label: { en: "Glass Effect", es: "Efecto Glass" },
|
|
10484
|
+
hint: { en: "Glassmorphism blur on cards and modals", es: "Desenfoque glassmorphism en cards y modales" },
|
|
10485
|
+
defaultValue: true
|
|
10486
|
+
}),
|
|
10492
10487
|
theme: useSelectField6({
|
|
10493
10488
|
label: { en: "Theme", es: "Tema" },
|
|
10494
|
-
hint: { en: "System follows
|
|
10489
|
+
hint: { en: "System follows device preferences", es: "Sistema sigue las preferencias del dispositivo" },
|
|
10495
10490
|
options: [
|
|
10496
10491
|
{ value: "light", label: { en: "Light", es: "Claro" } },
|
|
10497
10492
|
{ value: "dark", label: { en: "Dark", es: "Oscuro" } },
|
|
10498
10493
|
{ value: "system", label: { en: "System", es: "Sistema" } }
|
|
10499
10494
|
]
|
|
10500
10495
|
}),
|
|
10501
|
-
dopamineTheme: useSelectField6({
|
|
10502
|
-
label: { en: "Dopamine Theme", es: "Tema Dopamina" },
|
|
10503
|
-
hint: { en: "Vibrant color presets optimized for light and dark modes (2025/2026 trends)", es: "Presets de colores vibrantes optimizados para modo claro y oscuro (tendencias 2025/2026)" },
|
|
10504
|
-
options: [
|
|
10505
|
-
{ value: "none", label: { en: "None (Custom Color)", es: "Ninguno (Color personalizado)" } },
|
|
10506
|
-
{ value: "electric", label: { en: "Electric (Cobalt Blue)", es: "El\xE9ctrico (Azul Cobalto)" } },
|
|
10507
|
-
{ value: "sunset", label: { en: "Sunset (Coral Red)", es: "Atardecer (Rojo Coral)" } },
|
|
10508
|
-
{ value: "ocean", label: { en: "Ocean (Teal)", es: "Oc\xE9ano (Verde Azulado)" } },
|
|
10509
|
-
{ value: "forest", label: { en: "Forest (Mint)", es: "Bosque (Menta)" } },
|
|
10510
|
-
{ value: "lavender", label: { en: "Lavender (Violet)", es: "Lavanda (Violeta)" } },
|
|
10511
|
-
{ value: "cherry", label: { en: "Cherry (Fuchsia)", es: "Cereza (Fucsia)" } },
|
|
10512
|
-
{ value: "amber", label: { en: "Amber (Golden)", es: "\xC1mbar (Dorado)" } },
|
|
10513
|
-
{ value: "tangerine", label: { en: "Tangerine (Orange)", es: "Mandarina (Naranja)" } },
|
|
10514
|
-
{ value: "slate", label: { en: "Slate (Cool Gray)", es: "Pizarra (Gris Fr\xEDo)" } },
|
|
10515
|
-
{ value: "bronze", label: { en: "Bronze (Earth)", es: "Bronce (Tierra)" } }
|
|
10516
|
-
]
|
|
10517
|
-
}),
|
|
10518
|
-
// === Login Layout ===
|
|
10519
10496
|
loginLayout: useSelectField6({
|
|
10520
10497
|
label: { en: "Login Layout", es: "Dise\xF1o de Login" },
|
|
10521
|
-
hint: { en: "
|
|
10498
|
+
hint: { en: "Auth page visual layout", es: "Dise\xF1o visual de p\xE1ginas de autenticaci\xF3n" },
|
|
10522
10499
|
options: [
|
|
10523
10500
|
{ value: "centered", label: { en: "Centered", es: "Centrado" } },
|
|
10524
10501
|
{ value: "split", label: { en: "Split", es: "Dividido" } },
|
|
@@ -10526,140 +10503,10 @@ var init_ui_theme_entity = __esm({
|
|
|
10526
10503
|
{ value: "floating", label: { en: "Floating", es: "Flotante" } },
|
|
10527
10504
|
{ value: "minimal", label: { en: "Minimal", es: "Minimalista" } }
|
|
10528
10505
|
]
|
|
10529
|
-
}),
|
|
10530
|
-
primaryColor: {
|
|
10531
|
-
...useColorField({
|
|
10532
|
-
label: { en: "Primary Color", es: "Color Principal" },
|
|
10533
|
-
hint: { en: "Custom accent color for buttons, links and highlights", es: "Color de acento personalizado para botones, enlaces y resaltados" }
|
|
10534
|
-
}),
|
|
10535
|
-
// Hide when a dopamine theme is active (show only if 'none')
|
|
10536
|
-
hidden: { field: "dopamineTheme", $ne: "none" }
|
|
10537
|
-
}
|
|
10538
|
-
},
|
|
10539
|
-
casl: {
|
|
10540
|
-
subject: "UiTheme",
|
|
10541
|
-
permissions: {
|
|
10542
|
-
ADMIN: { actions: ["read", "update"] }
|
|
10543
|
-
}
|
|
10544
|
-
}
|
|
10545
|
-
};
|
|
10546
|
-
}
|
|
10547
|
-
});
|
|
10548
|
-
|
|
10549
|
-
// src/modules/ui-settings/ui-effects.entity.ts
|
|
10550
|
-
import { useSelectField as useSelectField7, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
|
|
10551
|
-
var uiEffectsEntity;
|
|
10552
|
-
var init_ui_effects_entity = __esm({
|
|
10553
|
-
"src/modules/ui-settings/ui-effects.entity.ts"() {
|
|
10554
|
-
"use strict";
|
|
10555
|
-
uiEffectsEntity = {
|
|
10556
|
-
type: "single",
|
|
10557
|
-
realtime: "sync",
|
|
10558
|
-
key: "ui_effects",
|
|
10559
|
-
label: { en: "Visual Effects", es: "Efectos Visuales" },
|
|
10560
|
-
icon: "mdi:shimmer",
|
|
10561
|
-
public: true,
|
|
10562
|
-
routePrefix: "/ui-effects",
|
|
10563
|
-
defaults: {
|
|
10564
|
-
glassIntensity: "medium",
|
|
10565
|
-
borderStyle: "rounded",
|
|
10566
|
-
enableAnimations: true,
|
|
10567
|
-
enableOrganicShapes: false
|
|
10568
|
-
},
|
|
10569
|
-
fields: {
|
|
10570
|
-
glassIntensity: useSelectField7({
|
|
10571
|
-
label: { en: "Glass Intensity", es: "Intensidad Glass" },
|
|
10572
|
-
hint: { en: "Glassmorphism blur effect on cards and modals", es: "Efecto de desenfoque glassmorphism en cards y modales" },
|
|
10573
|
-
options: [
|
|
10574
|
-
{ value: "none", label: { en: "None", es: "Ninguno" } },
|
|
10575
|
-
{ value: "low", label: { en: "Low", es: "Baja" } },
|
|
10576
|
-
{ value: "medium", label: { en: "Medium", es: "Media" } },
|
|
10577
|
-
{ value: "high", label: { en: "High", es: "Alta" } }
|
|
10578
|
-
]
|
|
10579
|
-
}),
|
|
10580
|
-
borderStyle: useSelectField7({
|
|
10581
|
-
label: { en: "Border Style", es: "Estilo de Bordes" },
|
|
10582
|
-
hint: { en: "Corner radius for buttons, cards and inputs", es: "Radio de esquinas para botones, cards e inputs" },
|
|
10583
|
-
options: [
|
|
10584
|
-
{ value: "sharp", label: { en: "Sharp", es: "Cuadrados" } },
|
|
10585
|
-
{ value: "rounded", label: { en: "Rounded", es: "Redondeados" } },
|
|
10586
|
-
{ value: "organic", label: { en: "Organic", es: "Org\xE1nicos" } }
|
|
10587
|
-
]
|
|
10588
|
-
}),
|
|
10589
|
-
enableAnimations: useSwitchField2({
|
|
10590
|
-
label: { en: "Enable Animations", es: "Habilitar Animaciones" },
|
|
10591
|
-
hint: { en: "Micro-interactions and transitions (hover, focus, page changes)", es: "Micro-interacciones y transiciones (hover, focus, cambios de p\xE1gina)" },
|
|
10592
|
-
defaultValue: true,
|
|
10593
|
-
meta: { sortable: true }
|
|
10594
|
-
}),
|
|
10595
|
-
enableOrganicShapes: useSwitchField2({
|
|
10596
|
-
label: { en: "Enable Organic Shapes", es: "Habilitar Formas Org\xE1nicas" },
|
|
10597
|
-
hint: { en: "Asymmetric border-radius for a more natural, playful look", es: "Border-radius asim\xE9trico para un aspecto m\xE1s natural y divertido" },
|
|
10598
|
-
defaultValue: false,
|
|
10599
|
-
meta: { sortable: true },
|
|
10600
|
-
hidden: { field: "borderStyle", $ne: "organic" }
|
|
10601
10506
|
})
|
|
10602
10507
|
},
|
|
10603
10508
|
casl: {
|
|
10604
|
-
subject: "
|
|
10605
|
-
permissions: {
|
|
10606
|
-
ADMIN: { actions: ["read", "update"] }
|
|
10607
|
-
}
|
|
10608
|
-
}
|
|
10609
|
-
};
|
|
10610
|
-
}
|
|
10611
|
-
});
|
|
10612
|
-
|
|
10613
|
-
// src/modules/ui-settings/ui-accessibility.entity.ts
|
|
10614
|
-
import { useSelectField as useSelectField8, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
|
|
10615
|
-
var uiAccessibilityEntity;
|
|
10616
|
-
var init_ui_accessibility_entity = __esm({
|
|
10617
|
-
"src/modules/ui-settings/ui-accessibility.entity.ts"() {
|
|
10618
|
-
"use strict";
|
|
10619
|
-
uiAccessibilityEntity = {
|
|
10620
|
-
type: "single",
|
|
10621
|
-
realtime: "sync",
|
|
10622
|
-
key: "ui_accessibility",
|
|
10623
|
-
label: { en: "Accessibility", es: "Accesibilidad" },
|
|
10624
|
-
icon: "mdi:human",
|
|
10625
|
-
public: true,
|
|
10626
|
-
routePrefix: "/ui-accessibility",
|
|
10627
|
-
defaults: {
|
|
10628
|
-
typographyScale: "default",
|
|
10629
|
-
reducedMotion: false,
|
|
10630
|
-
highContrast: false
|
|
10631
|
-
},
|
|
10632
|
-
fields: {
|
|
10633
|
-
typographyScale: useSelectField8({
|
|
10634
|
-
label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
|
|
10635
|
-
hint: { en: "Adjusts font sizes for better readability (WCAG 1.4.4)", es: "Ajusta tama\xF1os de fuente para mejor legibilidad (WCAG 1.4.4)" },
|
|
10636
|
-
options: [
|
|
10637
|
-
{ value: "compact", label: { en: "Compact (smaller)", es: "Compacta (m\xE1s peque\xF1a)" } },
|
|
10638
|
-
{ value: "default", label: { en: "Default", es: "Por defecto" } },
|
|
10639
|
-
{ value: "relaxed", label: { en: "Relaxed (larger)", es: "Relajada (m\xE1s grande)" } }
|
|
10640
|
-
]
|
|
10641
|
-
}),
|
|
10642
|
-
reducedMotion: useSwitchField3({
|
|
10643
|
-
label: { en: "Reduced Motion", es: "Movimiento Reducido" },
|
|
10644
|
-
defaultValue: false,
|
|
10645
|
-
meta: { sortable: true },
|
|
10646
|
-
hint: {
|
|
10647
|
-
en: "Minimizes animations for users sensitive to motion (WCAG 2.1)",
|
|
10648
|
-
es: "Minimiza animaciones para usuarios sensibles al movimiento (WCAG 2.1)"
|
|
10649
|
-
}
|
|
10650
|
-
}),
|
|
10651
|
-
highContrast: useSwitchField3({
|
|
10652
|
-
label: { en: "High Contrast", es: "Alto Contraste" },
|
|
10653
|
-
defaultValue: false,
|
|
10654
|
-
meta: { sortable: true },
|
|
10655
|
-
hint: {
|
|
10656
|
-
en: "Increases text contrast for better readability (WCAG AAA)",
|
|
10657
|
-
es: "Aumenta el contraste del texto para mejor legibilidad (WCAG AAA)"
|
|
10658
|
-
}
|
|
10659
|
-
})
|
|
10660
|
-
},
|
|
10661
|
-
casl: {
|
|
10662
|
-
subject: "UiAccessibility",
|
|
10509
|
+
subject: "UiStyleGuide",
|
|
10663
10510
|
permissions: {
|
|
10664
10511
|
ADMIN: { actions: ["read", "update"] }
|
|
10665
10512
|
}
|
|
@@ -10669,44 +10516,62 @@ var init_ui_accessibility_entity = __esm({
|
|
|
10669
10516
|
});
|
|
10670
10517
|
|
|
10671
10518
|
// src/modules/ui-settings/index.ts
|
|
10519
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
10520
|
+
import { join as join6 } from "path";
|
|
10672
10521
|
var uiSettingsModule;
|
|
10673
10522
|
var init_ui_settings = __esm({
|
|
10674
10523
|
"src/modules/ui-settings/index.ts"() {
|
|
10675
10524
|
"use strict";
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
init_ui_effects_entity();
|
|
10679
|
-
init_ui_accessibility_entity();
|
|
10680
|
-
init_ui_branding_entity();
|
|
10681
|
-
init_ui_theme_entity();
|
|
10682
|
-
init_ui_effects_entity();
|
|
10683
|
-
init_ui_accessibility_entity();
|
|
10525
|
+
init_ui_style_guide_entity();
|
|
10526
|
+
init_ui_style_guide_entity();
|
|
10684
10527
|
uiSettingsModule = {
|
|
10685
10528
|
name: "ui-settings",
|
|
10686
|
-
label: { en: "
|
|
10529
|
+
label: { en: "Style Guide", es: "Gu\xEDa de Estilos" },
|
|
10687
10530
|
icon: "mdi:palette-outline",
|
|
10688
10531
|
description: {
|
|
10689
|
-
en: "
|
|
10690
|
-
es: "Configuraci\xF3n de
|
|
10532
|
+
en: "Brand identity and visual style configuration",
|
|
10533
|
+
es: "Configuraci\xF3n de identidad de marca y estilo visual"
|
|
10691
10534
|
},
|
|
10692
10535
|
type: "core",
|
|
10693
10536
|
category: "settings",
|
|
10694
10537
|
dependencies: ["logger"],
|
|
10695
|
-
definitions: [
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10538
|
+
definitions: [uiStyleGuideEntity],
|
|
10539
|
+
routePrefix: "/ui-settings",
|
|
10540
|
+
/**
|
|
10541
|
+
* Seed style guide from data/seeds/ui-style-guide.json if it exists.
|
|
10542
|
+
* Idempotent: only inserts if no record exists yet.
|
|
10543
|
+
*
|
|
10544
|
+
* Workflow:
|
|
10545
|
+
* 1. Configure style guide in dev via admin UI
|
|
10546
|
+
* 2. Export: GET /api/v1/ui-settings/ui-style-guide → save to data/seeds/ui-style-guide.json
|
|
10547
|
+
* 3. Commit the seed file
|
|
10548
|
+
* 4. On other environments, seed runs automatically on startup
|
|
10549
|
+
*/
|
|
10550
|
+
seed: async (ctx) => {
|
|
10551
|
+
const db2 = ctx.db.knex;
|
|
10552
|
+
const existing = await db2("single_records").where("key", "ui_style_guide").first();
|
|
10553
|
+
if (existing) return;
|
|
10554
|
+
const seedPath = join6(process.cwd(), "data", "seeds", "ui-style-guide.json");
|
|
10555
|
+
if (!existsSync5(seedPath)) return;
|
|
10556
|
+
try {
|
|
10557
|
+
const seedData = JSON.parse(readFileSync4(seedPath, "utf-8"));
|
|
10558
|
+
await db2("single_records").insert({
|
|
10559
|
+
key: "ui_style_guide",
|
|
10560
|
+
value: JSON.stringify(seedData)
|
|
10561
|
+
});
|
|
10562
|
+
ctx.core.logger.info("Seeded ui_style_guide from data/seeds/ui-style-guide.json");
|
|
10563
|
+
} catch {
|
|
10564
|
+
ctx.core.logger.warn("Failed to parse data/seeds/ui-style-guide.json, skipping seed");
|
|
10565
|
+
}
|
|
10566
|
+
}
|
|
10702
10567
|
};
|
|
10703
10568
|
}
|
|
10704
10569
|
});
|
|
10705
10570
|
|
|
10706
10571
|
// src/modules/storage/drivers/filesystem.driver.ts
|
|
10707
|
-
import { createReadStream, createWriteStream, existsSync as
|
|
10572
|
+
import { createReadStream, createWriteStream, existsSync as existsSync6, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
10708
10573
|
import { readFile, readdir, stat, copyFile, rename, mkdir } from "fs/promises";
|
|
10709
|
-
import { join as
|
|
10574
|
+
import { join as join7, dirname as dirname4, extname, basename } from "path";
|
|
10710
10575
|
import { createHash } from "crypto";
|
|
10711
10576
|
var FilesystemDriver;
|
|
10712
10577
|
var init_filesystem_driver = __esm({
|
|
@@ -10720,7 +10585,7 @@ var init_filesystem_driver = __esm({
|
|
|
10720
10585
|
this.basePath = config3.basePath;
|
|
10721
10586
|
this.baseUrl = config3.baseUrl;
|
|
10722
10587
|
this.generateId = config3.generateId;
|
|
10723
|
-
if (!
|
|
10588
|
+
if (!existsSync6(this.basePath)) {
|
|
10724
10589
|
mkdirSync2(this.basePath, { recursive: true });
|
|
10725
10590
|
}
|
|
10726
10591
|
}
|
|
@@ -10730,9 +10595,9 @@ var init_filesystem_driver = __esm({
|
|
|
10730
10595
|
const diskFilename = `${id}${ext}`;
|
|
10731
10596
|
const folder = options?.folder || "";
|
|
10732
10597
|
const relativePath = folder ? `${folder}/${diskFilename}` : diskFilename;
|
|
10733
|
-
const fullPath =
|
|
10598
|
+
const fullPath = join7(this.basePath, relativePath);
|
|
10734
10599
|
const dir = dirname4(fullPath);
|
|
10735
|
-
if (!
|
|
10600
|
+
if (!existsSync6(dir)) {
|
|
10736
10601
|
mkdirSync2(dir, { recursive: true });
|
|
10737
10602
|
}
|
|
10738
10603
|
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
@@ -10756,25 +10621,25 @@ var init_filesystem_driver = __esm({
|
|
|
10756
10621
|
};
|
|
10757
10622
|
}
|
|
10758
10623
|
async get(path4) {
|
|
10759
|
-
const fullPath =
|
|
10760
|
-
if (!
|
|
10624
|
+
const fullPath = join7(this.basePath, path4);
|
|
10625
|
+
if (!existsSync6(fullPath)) {
|
|
10761
10626
|
throw new Error(`File not found: ${path4}`);
|
|
10762
10627
|
}
|
|
10763
10628
|
return createReadStream(fullPath);
|
|
10764
10629
|
}
|
|
10765
10630
|
async getBuffer(path4) {
|
|
10766
|
-
const fullPath =
|
|
10631
|
+
const fullPath = join7(this.basePath, path4);
|
|
10767
10632
|
return readFile(fullPath);
|
|
10768
10633
|
}
|
|
10769
10634
|
async delete(path4) {
|
|
10770
|
-
const fullPath =
|
|
10771
|
-
if (
|
|
10635
|
+
const fullPath = join7(this.basePath, path4);
|
|
10636
|
+
if (existsSync6(fullPath)) {
|
|
10772
10637
|
unlinkSync(fullPath);
|
|
10773
10638
|
}
|
|
10774
10639
|
}
|
|
10775
10640
|
async exists(path4) {
|
|
10776
|
-
const fullPath =
|
|
10777
|
-
return
|
|
10641
|
+
const fullPath = join7(this.basePath, path4);
|
|
10642
|
+
return existsSync6(fullPath);
|
|
10778
10643
|
}
|
|
10779
10644
|
getUrl(path4) {
|
|
10780
10645
|
if (!this.baseUrl) return null;
|
|
@@ -10784,14 +10649,14 @@ var init_filesystem_driver = __esm({
|
|
|
10784
10649
|
// EXTENDED METHODS
|
|
10785
10650
|
// ============================================================================
|
|
10786
10651
|
async list(folder) {
|
|
10787
|
-
const targetPath = folder ?
|
|
10788
|
-
if (!
|
|
10652
|
+
const targetPath = folder ? join7(this.basePath, folder) : this.basePath;
|
|
10653
|
+
if (!existsSync6(targetPath)) {
|
|
10789
10654
|
return [];
|
|
10790
10655
|
}
|
|
10791
10656
|
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
10792
10657
|
const results = [];
|
|
10793
10658
|
for (const entry of entries) {
|
|
10794
|
-
const entryPath =
|
|
10659
|
+
const entryPath = join7(targetPath, entry.name);
|
|
10795
10660
|
const relativePath = folder ? `${folder}/${entry.name}` : entry.name;
|
|
10796
10661
|
const stats = await stat(entryPath);
|
|
10797
10662
|
results.push({
|
|
@@ -10805,10 +10670,10 @@ var init_filesystem_driver = __esm({
|
|
|
10805
10670
|
return results;
|
|
10806
10671
|
}
|
|
10807
10672
|
async copy(src, dst) {
|
|
10808
|
-
const srcPath =
|
|
10809
|
-
const dstPath =
|
|
10673
|
+
const srcPath = join7(this.basePath, src);
|
|
10674
|
+
const dstPath = join7(this.basePath, dst);
|
|
10810
10675
|
const dstDir = dirname4(dstPath);
|
|
10811
|
-
if (!
|
|
10676
|
+
if (!existsSync6(dstDir)) {
|
|
10812
10677
|
await mkdir(dstDir, { recursive: true });
|
|
10813
10678
|
}
|
|
10814
10679
|
await copyFile(srcPath, dstPath);
|
|
@@ -10831,10 +10696,10 @@ var init_filesystem_driver = __esm({
|
|
|
10831
10696
|
};
|
|
10832
10697
|
}
|
|
10833
10698
|
async move(src, dst) {
|
|
10834
|
-
const srcPath =
|
|
10835
|
-
const dstPath =
|
|
10699
|
+
const srcPath = join7(this.basePath, src);
|
|
10700
|
+
const dstPath = join7(this.basePath, dst);
|
|
10836
10701
|
const dstDir = dirname4(dstPath);
|
|
10837
|
-
if (!
|
|
10702
|
+
if (!existsSync6(dstDir)) {
|
|
10838
10703
|
await mkdir(dstDir, { recursive: true });
|
|
10839
10704
|
}
|
|
10840
10705
|
await rename(srcPath, dstPath);
|
|
@@ -10857,7 +10722,7 @@ var init_filesystem_driver = __esm({
|
|
|
10857
10722
|
};
|
|
10858
10723
|
}
|
|
10859
10724
|
async getMetadata(path4) {
|
|
10860
|
-
const fullPath =
|
|
10725
|
+
const fullPath = join7(this.basePath, path4);
|
|
10861
10726
|
const stats = await stat(fullPath);
|
|
10862
10727
|
return {
|
|
10863
10728
|
path: path4,
|
|
@@ -10871,8 +10736,8 @@ var init_filesystem_driver = __esm({
|
|
|
10871
10736
|
throw new Error("Signed URLs are not supported by the filesystem driver");
|
|
10872
10737
|
}
|
|
10873
10738
|
async createFolder(path4) {
|
|
10874
|
-
const fullPath =
|
|
10875
|
-
if (!
|
|
10739
|
+
const fullPath = join7(this.basePath, path4);
|
|
10740
|
+
if (!existsSync6(fullPath)) {
|
|
10876
10741
|
await mkdir(fullPath, { recursive: true });
|
|
10877
10742
|
}
|
|
10878
10743
|
}
|
|
@@ -11103,8 +10968,8 @@ var init_s3_driver = __esm({
|
|
|
11103
10968
|
});
|
|
11104
10969
|
|
|
11105
10970
|
// src/modules/storage/storage.config.ts
|
|
11106
|
-
import { join as
|
|
11107
|
-
import { existsSync as
|
|
10971
|
+
import { join as join8, isAbsolute } from "path";
|
|
10972
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
11108
10973
|
function getDefaultScope(driver) {
|
|
11109
10974
|
return driver === "s3" ? DEFAULT_S3_SCOPE : DEFAULT_FILESYSTEM_SCOPE;
|
|
11110
10975
|
}
|
|
@@ -11119,7 +10984,7 @@ function resolveStoragePath(path4, projPath) {
|
|
|
11119
10984
|
return path4;
|
|
11120
10985
|
}
|
|
11121
10986
|
const cleanPath = path4.startsWith("./") ? path4.slice(2) : path4;
|
|
11122
|
-
return
|
|
10987
|
+
return join8(projPath, "data", cleanPath);
|
|
11123
10988
|
}
|
|
11124
10989
|
async function getConfigByScope(db2, scope) {
|
|
11125
10990
|
if (!generateIdFn) {
|
|
@@ -11150,7 +11015,7 @@ function buildConfigFromRow(row) {
|
|
|
11150
11015
|
if (row.driver === "filesystem") {
|
|
11151
11016
|
const fsMeta = metadata;
|
|
11152
11017
|
config3.basePath = resolveStoragePath(fsMeta.basePath || "./storage", projectPath);
|
|
11153
|
-
if (!
|
|
11018
|
+
if (!existsSync7(config3.basePath)) {
|
|
11154
11019
|
mkdirSync3(config3.basePath, { recursive: true });
|
|
11155
11020
|
}
|
|
11156
11021
|
} else if (row.driver === "s3") {
|
|
@@ -11182,7 +11047,7 @@ function buildConfigFromEnv(driver) {
|
|
|
11182
11047
|
if (driver === "filesystem") {
|
|
11183
11048
|
const rawPath = process.env["STORAGE_PATH"] || "./storage";
|
|
11184
11049
|
config3.basePath = resolveStoragePath(rawPath, projectPath);
|
|
11185
|
-
if (!
|
|
11050
|
+
if (!existsSync7(config3.basePath)) {
|
|
11186
11051
|
mkdirSync3(config3.basePath, { recursive: true });
|
|
11187
11052
|
}
|
|
11188
11053
|
} else if (driver === "s3") {
|
|
@@ -11621,7 +11486,7 @@ var init_storage_service = __esm({
|
|
|
11621
11486
|
});
|
|
11622
11487
|
|
|
11623
11488
|
// src/modules/storage/storage.entity.ts
|
|
11624
|
-
import { useIdField as useIdField3, useTextField as useTextField8, useSelectField as
|
|
11489
|
+
import { useIdField as useIdField3, useTextField as useTextField8, useSelectField as useSelectField7, useUrlField, useNumberField as useNumberField5, useCheckboxField as useCheckboxField3, useJsonField as useJsonField2, useMetadataField, usePublicField } from "@gzl10/nexus-sdk/fields";
|
|
11625
11490
|
var DEFAULT_MAX_SIZE2, storageConfigEntity, storageFilesEntity;
|
|
11626
11491
|
var init_storage_entity = __esm({
|
|
11627
11492
|
"src/modules/storage/storage.entity.ts"() {
|
|
@@ -11656,7 +11521,7 @@ var init_storage_entity = __esm({
|
|
|
11656
11521
|
hint: { en: "Unique identifier (e.g. default_filesystem, default_s3)", es: "Identificador \xFAnico (ej: default_filesystem, default_s3)" }
|
|
11657
11522
|
}),
|
|
11658
11523
|
driver: {
|
|
11659
|
-
...
|
|
11524
|
+
...useSelectField7({
|
|
11660
11525
|
label: { en: "Driver", es: "Controlador" },
|
|
11661
11526
|
required: true,
|
|
11662
11527
|
hint: { en: "Storage backend", es: "Backend de almacenamiento" },
|
|
@@ -11673,7 +11538,7 @@ var init_storage_entity = __esm({
|
|
|
11673
11538
|
size: 500,
|
|
11674
11539
|
nullable: true
|
|
11675
11540
|
}),
|
|
11676
|
-
max_file_size:
|
|
11541
|
+
max_file_size: useNumberField5({
|
|
11677
11542
|
label: { en: "Max File Size (bytes)", es: "Tama\xF1o m\xE1ximo de archivo (bytes)" },
|
|
11678
11543
|
hint: { en: "Maximum size in bytes (default: 10MB = 10485760)", es: "Tama\xF1o m\xE1ximo en bytes (default: 10MB = 10485760)" },
|
|
11679
11544
|
nullable: false,
|
|
@@ -11866,7 +11731,7 @@ var init_storage_entity = __esm({
|
|
|
11866
11731
|
index: true,
|
|
11867
11732
|
meta: { sortable: true, searchable: true }
|
|
11868
11733
|
}),
|
|
11869
|
-
size:
|
|
11734
|
+
size: useNumberField5({
|
|
11870
11735
|
label: { en: "Size", es: "Tama\xF1o" },
|
|
11871
11736
|
required: true,
|
|
11872
11737
|
nullable: false,
|
|
@@ -12408,7 +12273,7 @@ var init_storage = __esm({
|
|
|
12408
12273
|
});
|
|
12409
12274
|
|
|
12410
12275
|
// src/modules/users/users.entity.ts
|
|
12411
|
-
import { useIdField as useIdField4, useSelectField as
|
|
12276
|
+
import { useIdField as useIdField4, useSelectField as useSelectField8, useEmailField, usePasswordField, useTextField as useTextField9, useDatetimeField as useDatetimeField3, useCheckboxField as useCheckboxField4, useImageField as useImageField2, useNameField as useNameField2, useMetadataField as useMetadataField2, useDescriptionField as useDescriptionField2 } from "@gzl10/nexus-sdk/fields";
|
|
12412
12277
|
import { z as z2 } from "zod";
|
|
12413
12278
|
var userEntity, roleEntity, userRoleEntity;
|
|
12414
12279
|
var init_users_entity = __esm({
|
|
@@ -12482,7 +12347,7 @@ var init_users_entity = __esm({
|
|
|
12482
12347
|
label: { en: "Marketing Opt-in", es: "Aceptar marketing" },
|
|
12483
12348
|
meta: { exportable: true, showInForm: false, showInDisplay: false }
|
|
12484
12349
|
}),
|
|
12485
|
-
locale:
|
|
12350
|
+
locale: useSelectField8({
|
|
12486
12351
|
label: { en: "Language", es: "Idioma" },
|
|
12487
12352
|
options: [
|
|
12488
12353
|
{ value: "es", label: { en: "Spanish", es: "Espa\xF1ol" } },
|
|
@@ -12492,13 +12357,13 @@ var init_users_entity = __esm({
|
|
|
12492
12357
|
meta: { sortable: true },
|
|
12493
12358
|
defaultValue: "en"
|
|
12494
12359
|
}),
|
|
12495
|
-
timezone:
|
|
12360
|
+
timezone: useSelectField8({
|
|
12496
12361
|
label: { en: "Timezone", es: "Zona horaria" },
|
|
12497
12362
|
master: "timezones",
|
|
12498
12363
|
meta: { sortable: true },
|
|
12499
12364
|
defaultValue: "timezones:Europe/Madrid"
|
|
12500
12365
|
}),
|
|
12501
|
-
type:
|
|
12366
|
+
type: useSelectField8({
|
|
12502
12367
|
label: { en: "Type", es: "Tipo" },
|
|
12503
12368
|
defaultValue: "human",
|
|
12504
12369
|
options: [
|
|
@@ -12667,7 +12532,7 @@ var init_users_entity = __esm({
|
|
|
12667
12532
|
expose: false,
|
|
12668
12533
|
fields: {
|
|
12669
12534
|
id: useIdField4(),
|
|
12670
|
-
user_id:
|
|
12535
|
+
user_id: useSelectField8({
|
|
12671
12536
|
label: { en: "User", es: "Usuario" },
|
|
12672
12537
|
required: true,
|
|
12673
12538
|
table: "users",
|
|
@@ -12678,7 +12543,7 @@ var init_users_entity = __esm({
|
|
|
12678
12543
|
labelField: "name",
|
|
12679
12544
|
meta: { searchable: true }
|
|
12680
12545
|
}),
|
|
12681
|
-
role_id:
|
|
12546
|
+
role_id: useSelectField8({
|
|
12682
12547
|
label: { en: "Role", es: "Rol" },
|
|
12683
12548
|
required: true,
|
|
12684
12549
|
table: "roles",
|
|
@@ -13622,7 +13487,7 @@ var init_users = __esm({
|
|
|
13622
13487
|
});
|
|
13623
13488
|
|
|
13624
13489
|
// src/modules/auth/auth.entity.ts
|
|
13625
|
-
import { useIdField as useIdField5, useTextField as useTextField10, useSelectField as
|
|
13490
|
+
import { useIdField as useIdField5, useTextField as useTextField10, useSelectField as useSelectField9, useDatetimeField as useDatetimeField4, useEmailField as useEmailField2, useMetadataField as useMetadataField3, useExpiresAtField } from "@gzl10/nexus-sdk/fields";
|
|
13626
13491
|
var refreshTokenEntity, authIdentitiesEntity;
|
|
13627
13492
|
var init_auth_entity = __esm({
|
|
13628
13493
|
"src/modules/auth/auth.entity.ts"() {
|
|
@@ -13703,7 +13568,7 @@ var init_auth_entity = __esm({
|
|
|
13703
13568
|
order: 5,
|
|
13704
13569
|
fields: {
|
|
13705
13570
|
id: useIdField5(),
|
|
13706
|
-
user_id:
|
|
13571
|
+
user_id: useSelectField9({
|
|
13707
13572
|
label: { en: "User", es: "Usuario" },
|
|
13708
13573
|
table: "users",
|
|
13709
13574
|
column: "id",
|
|
@@ -13787,8 +13652,327 @@ var init_auth_routes = __esm({
|
|
|
13787
13652
|
}
|
|
13788
13653
|
});
|
|
13789
13654
|
|
|
13790
|
-
// src/
|
|
13655
|
+
// src/config/env.ts
|
|
13791
13656
|
import { z as z4 } from "zod";
|
|
13657
|
+
function resolveConfig() {
|
|
13658
|
+
env = envSchema.parse(process.env);
|
|
13659
|
+
process.env["TZ"] = env.TZ;
|
|
13660
|
+
resolvedConfig = {
|
|
13661
|
+
nodeEnv: env.NODE_ENV,
|
|
13662
|
+
port: env.PORT,
|
|
13663
|
+
host: "0.0.0.0",
|
|
13664
|
+
ui: {
|
|
13665
|
+
enabled: env.NEXUS_UI_ENABLED,
|
|
13666
|
+
base: env.NEXUS_UI_BASE,
|
|
13667
|
+
path: env.NEXUS_UI_PATH
|
|
13668
|
+
},
|
|
13669
|
+
corsOrigin: env.CORS_ORIGIN,
|
|
13670
|
+
databaseUrl: env.DATABASE_URL,
|
|
13671
|
+
adminEmail: env.ADMIN_EMAIL,
|
|
13672
|
+
adminPassword: env.ADMIN_PASSWORD,
|
|
13673
|
+
timezone: env.TZ
|
|
13674
|
+
};
|
|
13675
|
+
return resolvedConfig;
|
|
13676
|
+
}
|
|
13677
|
+
function getConfig() {
|
|
13678
|
+
if (!resolvedConfig) {
|
|
13679
|
+
return resolveConfig();
|
|
13680
|
+
}
|
|
13681
|
+
return resolvedConfig;
|
|
13682
|
+
}
|
|
13683
|
+
function resetConfig() {
|
|
13684
|
+
resolvedConfig = null;
|
|
13685
|
+
}
|
|
13686
|
+
var envSchema, env, resolvedConfig;
|
|
13687
|
+
var init_env = __esm({
|
|
13688
|
+
"src/config/env.ts"() {
|
|
13689
|
+
"use strict";
|
|
13690
|
+
envSchema = z4.object({
|
|
13691
|
+
NODE_ENV: z4.enum(["development", "production", "test"]).default("development"),
|
|
13692
|
+
PORT: z4.coerce.number().default(3e3),
|
|
13693
|
+
CORS_ORIGIN: z4.string().default("*"),
|
|
13694
|
+
BACKEND_URL: z4.string().optional(),
|
|
13695
|
+
DATABASE_URL: z4.string().default("file:./dev.db"),
|
|
13696
|
+
REDIS_URL: z4.string().url().optional(),
|
|
13697
|
+
REDIS_PREFIX: z4.string().default("nexus"),
|
|
13698
|
+
ADMIN_EMAIL: z4.string().email().optional(),
|
|
13699
|
+
ADMIN_PASSWORD: z4.string().min(6).optional(),
|
|
13700
|
+
COOKIE_DOMAIN: z4.string().optional(),
|
|
13701
|
+
TZ: z4.string().default("UTC"),
|
|
13702
|
+
TRUST_PROXY: z4.coerce.boolean().default(false),
|
|
13703
|
+
NEXUS_UI_ENABLED: z4.coerce.boolean().default(true),
|
|
13704
|
+
NEXUS_UI_BASE: z4.string().default("/"),
|
|
13705
|
+
NEXUS_UI_PATH: z4.string().default("../ui/dist"),
|
|
13706
|
+
NEXUS_CHECK_DRIFT: z4.coerce.boolean().optional(),
|
|
13707
|
+
NEXUS_FAIL_ON_DRIFT: z4.coerce.boolean().optional(),
|
|
13708
|
+
FRPC_SERVER: z4.string().optional(),
|
|
13709
|
+
FRPC_SERVER_PORT: z4.coerce.number().default(7e3),
|
|
13710
|
+
FRPC_TOKEN: z4.string().optional(),
|
|
13711
|
+
FRPC_SUBDOMAIN: z4.string().optional()
|
|
13712
|
+
});
|
|
13713
|
+
env = envSchema.parse(process.env);
|
|
13714
|
+
process.env["TZ"] = env.TZ;
|
|
13715
|
+
resolvedConfig = null;
|
|
13716
|
+
}
|
|
13717
|
+
});
|
|
13718
|
+
|
|
13719
|
+
// src/db/sql-utils.ts
|
|
13720
|
+
function extractTableFromSql(sql, type2) {
|
|
13721
|
+
const match = sql.match(SQL_PATTERNS[type2]);
|
|
13722
|
+
return match?.[1];
|
|
13723
|
+
}
|
|
13724
|
+
function extractTableFromSelect(sql) {
|
|
13725
|
+
return extractTableFromSql(sql, "select");
|
|
13726
|
+
}
|
|
13727
|
+
function extractTableFromInsert(sql) {
|
|
13728
|
+
return extractTableFromSql(sql, "insert");
|
|
13729
|
+
}
|
|
13730
|
+
function extractTableFromUpdate(sql) {
|
|
13731
|
+
return extractTableFromSql(sql, "update");
|
|
13732
|
+
}
|
|
13733
|
+
function extractTableFromDelete(sql) {
|
|
13734
|
+
return extractTableFromSql(sql, "delete");
|
|
13735
|
+
}
|
|
13736
|
+
var SQL_PATTERNS;
|
|
13737
|
+
var init_sql_utils = __esm({
|
|
13738
|
+
"src/db/sql-utils.ts"() {
|
|
13739
|
+
"use strict";
|
|
13740
|
+
SQL_PATTERNS = {
|
|
13741
|
+
select: /from\s+["'`]?(\w+)["'`]?/i,
|
|
13742
|
+
insert: /insert into\s+["'`]?(\w+)["'`]?/i,
|
|
13743
|
+
update: /update\s+["'`]?(\w+)["'`]?/i,
|
|
13744
|
+
delete: /delete from\s+["'`]?(\w+)["'`]?/i
|
|
13745
|
+
};
|
|
13746
|
+
}
|
|
13747
|
+
});
|
|
13748
|
+
|
|
13749
|
+
// src/db/sqlite-compat.ts
|
|
13750
|
+
function createSqliteBooleanProcessor() {
|
|
13751
|
+
const booleanColumns = /* @__PURE__ */ new Map();
|
|
13752
|
+
function registerBooleanColumn2(table, column) {
|
|
13753
|
+
if (!booleanColumns.has(table)) {
|
|
13754
|
+
booleanColumns.set(table, /* @__PURE__ */ new Set());
|
|
13755
|
+
}
|
|
13756
|
+
booleanColumns.get(table).add(column);
|
|
13757
|
+
}
|
|
13758
|
+
function convertBooleans2(table, row) {
|
|
13759
|
+
const columns = booleanColumns.get(table);
|
|
13760
|
+
if (!columns || columns.size === 0) return row;
|
|
13761
|
+
const result = { ...row };
|
|
13762
|
+
for (const column of columns) {
|
|
13763
|
+
if (column in result) {
|
|
13764
|
+
const value = result[column];
|
|
13765
|
+
if (value === 0 || value === 1) {
|
|
13766
|
+
result[column] = value === 1;
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
}
|
|
13770
|
+
return result;
|
|
13771
|
+
}
|
|
13772
|
+
function postProcess(result, queryContext) {
|
|
13773
|
+
const sql = queryContext?.sql?.toLowerCase() ?? "";
|
|
13774
|
+
if (!sql.startsWith("select")) return result;
|
|
13775
|
+
const table = extractTableFromSelect(sql);
|
|
13776
|
+
if (!table) return result;
|
|
13777
|
+
if (Array.isArray(result)) {
|
|
13778
|
+
return result.map(
|
|
13779
|
+
(row) => typeof row === "object" && row !== null ? convertBooleans2(table, row) : row
|
|
13780
|
+
);
|
|
13781
|
+
}
|
|
13782
|
+
if (typeof result === "object" && result !== null) {
|
|
13783
|
+
return convertBooleans2(table, result);
|
|
13784
|
+
}
|
|
13785
|
+
return result;
|
|
13786
|
+
}
|
|
13787
|
+
function clear() {
|
|
13788
|
+
booleanColumns.clear();
|
|
13789
|
+
}
|
|
13790
|
+
return { registerBooleanColumn: registerBooleanColumn2, convertBooleans: convertBooleans2, postProcess, clear };
|
|
13791
|
+
}
|
|
13792
|
+
var defaultProcessor, registerBooleanColumn, convertBooleans, sqlitePostProcess;
|
|
13793
|
+
var init_sqlite_compat = __esm({
|
|
13794
|
+
"src/db/sqlite-compat.ts"() {
|
|
13795
|
+
"use strict";
|
|
13796
|
+
init_sql_utils();
|
|
13797
|
+
defaultProcessor = createSqliteBooleanProcessor();
|
|
13798
|
+
registerBooleanColumn = defaultProcessor.registerBooleanColumn;
|
|
13799
|
+
convertBooleans = defaultProcessor.convertBooleans;
|
|
13800
|
+
sqlitePostProcess = defaultProcessor.postProcess;
|
|
13801
|
+
}
|
|
13802
|
+
});
|
|
13803
|
+
|
|
13804
|
+
// src/config/database.ts
|
|
13805
|
+
import { join as join9, dirname as dirname6, isAbsolute as isAbsolute2 } from "path";
|
|
13806
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
13807
|
+
function getDatabaseConfig() {
|
|
13808
|
+
const url = getConfig().databaseUrl;
|
|
13809
|
+
if (IN_MEMORY_RE.test(url)) {
|
|
13810
|
+
return {
|
|
13811
|
+
client: "better-sqlite3",
|
|
13812
|
+
connection: { filename: ":memory:" },
|
|
13813
|
+
useNullAsDefault: true,
|
|
13814
|
+
postProcessResponse: sqlitePostProcess,
|
|
13815
|
+
pool: { min: 0, max: 1 }
|
|
13816
|
+
};
|
|
13817
|
+
}
|
|
13818
|
+
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
13819
|
+
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
13820
|
+
if (!isAbsolute2(filename)) {
|
|
13821
|
+
filename = join9(getProjectPath(), "data", filename);
|
|
13822
|
+
}
|
|
13823
|
+
mkdirSync4(dirname6(filename), { recursive: true });
|
|
13824
|
+
return {
|
|
13825
|
+
client: "better-sqlite3",
|
|
13826
|
+
connection: { filename },
|
|
13827
|
+
useNullAsDefault: true,
|
|
13828
|
+
postProcessResponse: sqlitePostProcess
|
|
13829
|
+
};
|
|
13830
|
+
}
|
|
13831
|
+
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) {
|
|
13832
|
+
return {
|
|
13833
|
+
client: "pg",
|
|
13834
|
+
connection: url,
|
|
13835
|
+
pool: { min: 2, max: 10 }
|
|
13836
|
+
};
|
|
13837
|
+
}
|
|
13838
|
+
if (url.startsWith("mysql://")) {
|
|
13839
|
+
const offsetMinutes = (/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
13840
|
+
const offsetHours = Math.abs(Math.floor(offsetMinutes / 60));
|
|
13841
|
+
const offsetMins = Math.abs(offsetMinutes % 60);
|
|
13842
|
+
const sign = offsetMinutes <= 0 ? "+" : "-";
|
|
13843
|
+
const tzOffset = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMins).padStart(2, "0")}`;
|
|
13844
|
+
return {
|
|
13845
|
+
client: "mysql2",
|
|
13846
|
+
connection: {
|
|
13847
|
+
uri: url,
|
|
13848
|
+
timezone: tzOffset
|
|
13849
|
+
// mysql2 requiere formato "+HH:MM"
|
|
13850
|
+
},
|
|
13851
|
+
pool: { min: 2, max: 10 }
|
|
13852
|
+
};
|
|
13853
|
+
}
|
|
13854
|
+
throw new Error(`Unsupported database URL: ${url}`);
|
|
13855
|
+
}
|
|
13856
|
+
function getDatabaseType() {
|
|
13857
|
+
const url = getConfig().databaseUrl;
|
|
13858
|
+
if (IN_MEMORY_RE.test(url) || url.startsWith("file:") || url.startsWith("sqlite:")) return "sqlite";
|
|
13859
|
+
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) return "postgresql";
|
|
13860
|
+
if (url.startsWith("mysql://")) return "mysql";
|
|
13861
|
+
return "sqlite";
|
|
13862
|
+
}
|
|
13863
|
+
function getDatabasePath() {
|
|
13864
|
+
const url = getConfig().databaseUrl;
|
|
13865
|
+
if (IN_MEMORY_RE.test(url)) return ":memory:";
|
|
13866
|
+
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
13867
|
+
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
13868
|
+
if (!isAbsolute2(filename)) {
|
|
13869
|
+
filename = join9(getProjectPath(), "data", filename);
|
|
13870
|
+
}
|
|
13871
|
+
return filename;
|
|
13872
|
+
}
|
|
13873
|
+
try {
|
|
13874
|
+
const parsed = new URL(url);
|
|
13875
|
+
parsed.password = "***";
|
|
13876
|
+
return parsed.toString();
|
|
13877
|
+
} catch {
|
|
13878
|
+
return url.replace(/:\/\/[^@]+@/, "://***@");
|
|
13879
|
+
}
|
|
13880
|
+
}
|
|
13881
|
+
var IN_MEMORY_RE;
|
|
13882
|
+
var init_database = __esm({
|
|
13883
|
+
"src/config/database.ts"() {
|
|
13884
|
+
"use strict";
|
|
13885
|
+
init_env();
|
|
13886
|
+
init_paths();
|
|
13887
|
+
init_sqlite_compat();
|
|
13888
|
+
IN_MEMORY_RE = /^((file:|sqlite:)?:memory:?)$/;
|
|
13889
|
+
}
|
|
13890
|
+
});
|
|
13891
|
+
|
|
13892
|
+
// src/core/crypto/secret-resolver.ts
|
|
13893
|
+
import { randomBytes } from "crypto";
|
|
13894
|
+
function setSecretResolverDb(db2) {
|
|
13895
|
+
_db = db2;
|
|
13896
|
+
}
|
|
13897
|
+
function _resetSecretResolver() {
|
|
13898
|
+
_secret = null;
|
|
13899
|
+
_db = null;
|
|
13900
|
+
}
|
|
13901
|
+
async function initAuthSecret(logger2) {
|
|
13902
|
+
const envSecret = process.env["AUTH_SECRET"];
|
|
13903
|
+
if (envSecret) {
|
|
13904
|
+
if (envSecret.length < 32) {
|
|
13905
|
+
throw new Error(`AUTH_SECRET must be at least 32 characters (current: ${envSecret.length})`);
|
|
13906
|
+
}
|
|
13907
|
+
_secret = envSecret;
|
|
13908
|
+
return;
|
|
13909
|
+
}
|
|
13910
|
+
if (!_db) {
|
|
13911
|
+
throw new Error("Secret resolver: database not initialized. Call setSecretResolverDb() first.");
|
|
13912
|
+
}
|
|
13913
|
+
const existing = await _db(SINGLE_RECORDS).where("key", SECRET_KEY).first();
|
|
13914
|
+
if (existing?.value) {
|
|
13915
|
+
const parsed2 = typeof existing.value === "string" ? JSON.parse(existing.value) : existing.value;
|
|
13916
|
+
if (parsed2.secret) {
|
|
13917
|
+
_secret = parsed2.secret;
|
|
13918
|
+
logger2.info("AUTH_SECRET loaded from database");
|
|
13919
|
+
warnIfProduction(logger2);
|
|
13920
|
+
return;
|
|
13921
|
+
}
|
|
13922
|
+
}
|
|
13923
|
+
const generated = randomBytes(SECRET_LENGTH).toString("base64url");
|
|
13924
|
+
const value = JSON.stringify({ secret: generated });
|
|
13925
|
+
const id = randomBytes(13).toString("base64url");
|
|
13926
|
+
const now = /* @__PURE__ */ new Date();
|
|
13927
|
+
const dialect = getDatabaseType();
|
|
13928
|
+
if (dialect === "postgresql") {
|
|
13929
|
+
await _db.raw(
|
|
13930
|
+
`INSERT INTO ${SINGLE_RECORDS} (id, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT (key) DO NOTHING`,
|
|
13931
|
+
[id, SECRET_KEY, value, now, now]
|
|
13932
|
+
);
|
|
13933
|
+
} else if (dialect === "mysql") {
|
|
13934
|
+
await _db.raw(
|
|
13935
|
+
"INSERT IGNORE INTO `single_records` (id, `key`, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
13936
|
+
[id, SECRET_KEY, value, now, now]
|
|
13937
|
+
);
|
|
13938
|
+
} else {
|
|
13939
|
+
await _db.raw(
|
|
13940
|
+
`INSERT OR IGNORE INTO ${SINGLE_RECORDS} (id, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
|
|
13941
|
+
[id, SECRET_KEY, value, now, now]
|
|
13942
|
+
);
|
|
13943
|
+
}
|
|
13944
|
+
const persisted = await _db(SINGLE_RECORDS).where("key", SECRET_KEY).first();
|
|
13945
|
+
const parsed = typeof persisted.value === "string" ? JSON.parse(persisted.value) : persisted.value;
|
|
13946
|
+
_secret = parsed.secret;
|
|
13947
|
+
logger2.info("AUTH_SECRET auto-generated and persisted in database");
|
|
13948
|
+
warnIfProduction(logger2);
|
|
13949
|
+
}
|
|
13950
|
+
function warnIfProduction(logger2) {
|
|
13951
|
+
if (process.env["NODE_ENV"] === "production") {
|
|
13952
|
+
logger2.warn("AUTH_SECRET not set via environment. Using auto-generated secret from database. For multi-instance deployments, set AUTH_SECRET explicitly.");
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13955
|
+
function getAuthSecret() {
|
|
13956
|
+
if (_secret) return _secret;
|
|
13957
|
+
const envSecret = process.env["AUTH_SECRET"];
|
|
13958
|
+
if (envSecret) return envSecret;
|
|
13959
|
+
throw new Error("AUTH_SECRET not initialized. Call initAuthSecret() during startup.");
|
|
13960
|
+
}
|
|
13961
|
+
var SINGLE_RECORDS, SECRET_KEY, SECRET_LENGTH, _secret, _db;
|
|
13962
|
+
var init_secret_resolver = __esm({
|
|
13963
|
+
"src/core/crypto/secret-resolver.ts"() {
|
|
13964
|
+
"use strict";
|
|
13965
|
+
init_database();
|
|
13966
|
+
SINGLE_RECORDS = "single_records";
|
|
13967
|
+
SECRET_KEY = "nexus_auth_secret";
|
|
13968
|
+
SECRET_LENGTH = 48;
|
|
13969
|
+
_secret = null;
|
|
13970
|
+
_db = null;
|
|
13971
|
+
}
|
|
13972
|
+
});
|
|
13973
|
+
|
|
13974
|
+
// src/modules/auth/auth.config.ts
|
|
13975
|
+
import { z as z5 } from "zod";
|
|
13792
13976
|
function configError(module, errors) {
|
|
13793
13977
|
console.error(`
|
|
13794
13978
|
${"\u2550".repeat(60)}`);
|
|
@@ -13803,12 +13987,6 @@ function parseAuthEnv() {
|
|
|
13803
13987
|
if (!result.success) {
|
|
13804
13988
|
const errors = result.error.issues.map((issue) => {
|
|
13805
13989
|
const path4 = issue.path.join(".");
|
|
13806
|
-
if (path4 === "AUTH_SECRET" && issue.code === "invalid_type") {
|
|
13807
|
-
return "AUTH_SECRET is required. Set it in your .env file or environment.";
|
|
13808
|
-
}
|
|
13809
|
-
if (path4 === "AUTH_SECRET" && issue.code === "too_small") {
|
|
13810
|
-
return `AUTH_SECRET must be at least 32 characters (current: ${String(process.env["AUTH_SECRET"]).length})`;
|
|
13811
|
-
}
|
|
13812
13990
|
return `${path4}: ${issue.message}`;
|
|
13813
13991
|
});
|
|
13814
13992
|
configError("auth", errors);
|
|
@@ -13827,7 +14005,7 @@ function getAuthEnv() {
|
|
|
13827
14005
|
function getAuthConfig() {
|
|
13828
14006
|
const authEnv = getAuthEnv();
|
|
13829
14007
|
return {
|
|
13830
|
-
secret:
|
|
14008
|
+
secret: getAuthSecret(),
|
|
13831
14009
|
accessExpires: authEnv.AUTH_ACCESS_EXPIRES,
|
|
13832
14010
|
refreshExpires: authEnv.AUTH_REFRESH_EXPIRES,
|
|
13833
14011
|
rateLimitMax: authEnv.AUTH_RATE_LIMIT_MAX,
|
|
@@ -13844,24 +14022,24 @@ var authEnvSchema, _authEnv;
|
|
|
13844
14022
|
var init_auth_config = __esm({
|
|
13845
14023
|
"src/modules/auth/auth.config.ts"() {
|
|
13846
14024
|
"use strict";
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
AUTH_ACCESS_EXPIRES:
|
|
13850
|
-
AUTH_REFRESH_EXPIRES:
|
|
14025
|
+
init_secret_resolver();
|
|
14026
|
+
authEnvSchema = z5.object({
|
|
14027
|
+
AUTH_ACCESS_EXPIRES: z5.string().default("15m"),
|
|
14028
|
+
AUTH_REFRESH_EXPIRES: z5.string().default("7d"),
|
|
13851
14029
|
// Rate limiting (default: 5 requests per 15 minutes)
|
|
13852
|
-
AUTH_RATE_LIMIT_MAX:
|
|
13853
|
-
AUTH_RATE_LIMIT_WINDOW:
|
|
14030
|
+
AUTH_RATE_LIMIT_MAX: z5.coerce.number().default(5),
|
|
14031
|
+
AUTH_RATE_LIMIT_WINDOW: z5.coerce.number().default(900),
|
|
13854
14032
|
// seconds
|
|
13855
14033
|
// Cookie domain for SSO across subdomains (e.g., '.example.com')
|
|
13856
|
-
AUTH_COOKIE_DOMAIN:
|
|
14034
|
+
AUTH_COOKIE_DOMAIN: z5.string().optional(),
|
|
13857
14035
|
// Challenge threshold: failed attempts before requiring OTP (default: 2)
|
|
13858
|
-
AUTH_CHALLENGE_THRESHOLD:
|
|
14036
|
+
AUTH_CHALLENGE_THRESHOLD: z5.coerce.number().default(2),
|
|
13859
14037
|
// Skip OTP verification for registration (DEVELOPMENT ONLY - rejected in production)
|
|
13860
|
-
AUTH_SKIP_REGISTER_OTP:
|
|
14038
|
+
AUTH_SKIP_REGISTER_OTP: z5.coerce.boolean().default(false),
|
|
13861
14039
|
// Disable self-registration via POST /auth/register (default: false)
|
|
13862
|
-
AUTH_DISABLE_REGISTRATION:
|
|
14040
|
+
AUTH_DISABLE_REGISTRATION: z5.coerce.boolean().default(false),
|
|
13863
14041
|
// Disable auto-creation of users on first OIDC login (default: false)
|
|
13864
|
-
AUTH_DISABLE_AUTO_CREATE:
|
|
14042
|
+
AUTH_DISABLE_AUTO_CREATE: z5.coerce.boolean().default(false)
|
|
13865
14043
|
});
|
|
13866
14044
|
}
|
|
13867
14045
|
});
|
|
@@ -14019,7 +14197,7 @@ var init_auth_middleware = __esm({
|
|
|
14019
14197
|
});
|
|
14020
14198
|
|
|
14021
14199
|
// src/modules/auth/auth.pat.entity.ts
|
|
14022
|
-
import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as
|
|
14200
|
+
import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as useSelectField10, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
|
|
14023
14201
|
var personalTokenEntity;
|
|
14024
14202
|
var init_auth_pat_entity = __esm({
|
|
14025
14203
|
"src/modules/auth/auth.pat.entity.ts"() {
|
|
@@ -14036,7 +14214,7 @@ var init_auth_pat_entity = __esm({
|
|
|
14036
14214
|
routePrefix: "/personal-tokens",
|
|
14037
14215
|
fields: {
|
|
14038
14216
|
id: useIdField6(),
|
|
14039
|
-
user_id:
|
|
14217
|
+
user_id: useSelectField10({
|
|
14040
14218
|
label: { en: "User", es: "Usuario" },
|
|
14041
14219
|
table: "users",
|
|
14042
14220
|
column: "id",
|
|
@@ -14071,7 +14249,7 @@ var init_auth_pat_entity = __esm({
|
|
|
14071
14249
|
unique: true,
|
|
14072
14250
|
meta: { exportable: false }
|
|
14073
14251
|
}),
|
|
14074
|
-
scope:
|
|
14252
|
+
scope: useSelectField10({
|
|
14075
14253
|
label: { en: "Permission", es: "Permiso" },
|
|
14076
14254
|
required: true,
|
|
14077
14255
|
options: [
|
|
@@ -14096,44 +14274,8 @@ var init_auth_pat_entity = __esm({
|
|
|
14096
14274
|
permissions: {
|
|
14097
14275
|
"*": [
|
|
14098
14276
|
{ actions: ["read", "delete"], conditions: { user_id: "${user.id}" } }
|
|
14099
|
-
]
|
|
14100
|
-
}
|
|
14101
|
-
}
|
|
14102
|
-
};
|
|
14103
|
-
}
|
|
14104
|
-
});
|
|
14105
|
-
|
|
14106
|
-
// src/modules/auth/actions/providers.action.ts
|
|
14107
|
-
var providersAction;
|
|
14108
|
-
var init_providers_action = __esm({
|
|
14109
|
-
"src/modules/auth/actions/providers.action.ts"() {
|
|
14110
|
-
"use strict";
|
|
14111
|
-
init_auth_config();
|
|
14112
|
-
providersAction = {
|
|
14113
|
-
key: "providers",
|
|
14114
|
-
label: { en: "Get Auth Providers", es: "Obtener proveedores de autenticaci\xF3n" },
|
|
14115
|
-
icon: "mdi:account-key",
|
|
14116
|
-
scope: "module",
|
|
14117
|
-
hidden: true,
|
|
14118
|
-
method: "GET",
|
|
14119
|
-
skipAuth: true,
|
|
14120
|
-
handler: async (ctx) => {
|
|
14121
|
-
const providerServices = ctx.services.getBySuffix(".provider");
|
|
14122
|
-
const results = await Promise.all(
|
|
14123
|
-
providerServices.map(async ({ service }) => {
|
|
14124
|
-
try {
|
|
14125
|
-
return await service.getInfo();
|
|
14126
|
-
} catch {
|
|
14127
|
-
return null;
|
|
14128
|
-
}
|
|
14129
|
-
})
|
|
14130
|
-
);
|
|
14131
|
-
const providers = results.filter((info) => info !== null);
|
|
14132
|
-
const config3 = getAuthConfig();
|
|
14133
|
-
return {
|
|
14134
|
-
providers,
|
|
14135
|
-
registrationEnabled: !config3.disableRegistration
|
|
14136
|
-
};
|
|
14277
|
+
]
|
|
14278
|
+
}
|
|
14137
14279
|
}
|
|
14138
14280
|
};
|
|
14139
14281
|
}
|
|
@@ -14695,7 +14837,6 @@ var authActions;
|
|
|
14695
14837
|
var init_actions2 = __esm({
|
|
14696
14838
|
"src/modules/auth/actions/index.ts"() {
|
|
14697
14839
|
"use strict";
|
|
14698
|
-
init_providers_action();
|
|
14699
14840
|
init_login_action();
|
|
14700
14841
|
init_register_action();
|
|
14701
14842
|
init_forgot_password_action();
|
|
@@ -14711,7 +14852,6 @@ var init_actions2 = __esm({
|
|
|
14711
14852
|
init_list_tokens_action();
|
|
14712
14853
|
init_revoke_token_action();
|
|
14713
14854
|
init_impersonate_action();
|
|
14714
|
-
init_providers_action();
|
|
14715
14855
|
init_login_action();
|
|
14716
14856
|
init_register_action();
|
|
14717
14857
|
init_forgot_password_action();
|
|
@@ -14727,7 +14867,6 @@ var init_actions2 = __esm({
|
|
|
14727
14867
|
init_list_tokens_action();
|
|
14728
14868
|
init_revoke_token_action();
|
|
14729
14869
|
authActions = [
|
|
14730
|
-
providersAction,
|
|
14731
14870
|
loginAction,
|
|
14732
14871
|
registerAction,
|
|
14733
14872
|
forgotPasswordAction,
|
|
@@ -14946,7 +15085,7 @@ var init_otp_manager = __esm({
|
|
|
14946
15085
|
});
|
|
14947
15086
|
|
|
14948
15087
|
// src/modules/auth/auth.service.ts
|
|
14949
|
-
import { createHash as createHash4, randomBytes } from "crypto";
|
|
15088
|
+
import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
|
|
14950
15089
|
function createAuthService(ctx) {
|
|
14951
15090
|
const { errors, abilities, crypto: crypto3 } = ctx.core;
|
|
14952
15091
|
const { generateId: generateId4 } = ctx.core;
|
|
@@ -15363,7 +15502,7 @@ function createAuthService(ctx) {
|
|
|
15363
15502
|
},
|
|
15364
15503
|
// === Personal Access Tokens ===
|
|
15365
15504
|
async createPersonalToken(userId, input, requestInfo) {
|
|
15366
|
-
const rawToken = "nxs_" +
|
|
15505
|
+
const rawToken = "nxs_" + randomBytes2(32).toString("hex");
|
|
15367
15506
|
const tokenHash = createHash4("sha256").update(rawToken).digest("hex");
|
|
15368
15507
|
const tokenPrefix = "nxs_..." + rawToken.slice(-6);
|
|
15369
15508
|
const id = generateId4();
|
|
@@ -15529,38 +15668,38 @@ var init_auth_service = __esm({
|
|
|
15529
15668
|
});
|
|
15530
15669
|
|
|
15531
15670
|
// src/modules/auth/auth.types.ts
|
|
15532
|
-
import { z as
|
|
15671
|
+
import { z as z6 } from "zod";
|
|
15533
15672
|
var loginSchema, registerSchema, forgotPasswordSchema, resetPasswordSchema, createPersonalTokenSchema;
|
|
15534
15673
|
var init_auth_types = __esm({
|
|
15535
15674
|
"src/modules/auth/auth.types.ts"() {
|
|
15536
15675
|
"use strict";
|
|
15537
|
-
loginSchema =
|
|
15538
|
-
email:
|
|
15539
|
-
password:
|
|
15540
|
-
otp:
|
|
15541
|
-
deviceId:
|
|
15542
|
-
deviceName:
|
|
15676
|
+
loginSchema = z6.object({
|
|
15677
|
+
email: z6.string().email("Invalid email"),
|
|
15678
|
+
password: z6.string().min(1, "Password required"),
|
|
15679
|
+
otp: z6.string().length(6).optional(),
|
|
15680
|
+
deviceId: z6.string().max(64).optional(),
|
|
15681
|
+
deviceName: z6.string().max(100).optional()
|
|
15543
15682
|
});
|
|
15544
|
-
registerSchema =
|
|
15545
|
-
email:
|
|
15546
|
-
password:
|
|
15547
|
-
name:
|
|
15548
|
-
otp:
|
|
15549
|
-
deviceId:
|
|
15550
|
-
deviceName:
|
|
15683
|
+
registerSchema = z6.object({
|
|
15684
|
+
email: z6.string().email("Invalid email"),
|
|
15685
|
+
password: z6.string().min(8, "Password must be at least 8 characters"),
|
|
15686
|
+
name: z6.string().min(2, "Name must be at least 2 characters"),
|
|
15687
|
+
otp: z6.string().length(6).optional(),
|
|
15688
|
+
deviceId: z6.string().max(64).optional(),
|
|
15689
|
+
deviceName: z6.string().max(100).optional()
|
|
15551
15690
|
});
|
|
15552
|
-
forgotPasswordSchema =
|
|
15553
|
-
email:
|
|
15691
|
+
forgotPasswordSchema = z6.object({
|
|
15692
|
+
email: z6.string().email("Invalid email")
|
|
15554
15693
|
});
|
|
15555
|
-
resetPasswordSchema =
|
|
15556
|
-
email:
|
|
15557
|
-
otp:
|
|
15558
|
-
newPassword:
|
|
15694
|
+
resetPasswordSchema = z6.object({
|
|
15695
|
+
email: z6.string().email("Invalid email"),
|
|
15696
|
+
otp: z6.string().length(6, "OTP must be 6 digits"),
|
|
15697
|
+
newPassword: z6.string().min(8, "Password must be at least 8 characters")
|
|
15559
15698
|
});
|
|
15560
|
-
createPersonalTokenSchema =
|
|
15561
|
-
name:
|
|
15562
|
-
scope:
|
|
15563
|
-
expires_at:
|
|
15699
|
+
createPersonalTokenSchema = z6.object({
|
|
15700
|
+
name: z6.string().min(1).max(100),
|
|
15701
|
+
scope: z6.enum(["readonly", "readwrite"]),
|
|
15702
|
+
expires_at: z6.string().datetime().optional()
|
|
15564
15703
|
});
|
|
15565
15704
|
}
|
|
15566
15705
|
});
|
|
@@ -15574,12 +15713,12 @@ async function seed3(ctx) {
|
|
|
15574
15713
|
const db2 = ctx.db.knex;
|
|
15575
15714
|
const { logger: logger2, generateId: generateId4 } = ctx.core;
|
|
15576
15715
|
const { nowTimestamp: nowTimestamp2 } = ctx.db;
|
|
15577
|
-
const hasTable = await db2.schema.hasTable(
|
|
15716
|
+
const hasTable = await db2.schema.hasTable(SINGLE_RECORDS2);
|
|
15578
15717
|
if (!hasTable) {
|
|
15579
15718
|
logger2.debug("single_records table not found, skipping auth seed");
|
|
15580
15719
|
return;
|
|
15581
15720
|
}
|
|
15582
|
-
const existing = await db2(
|
|
15721
|
+
const existing = await db2(SINGLE_RECORDS2).where({ key: AUTH_CONFIG_KEY }).first();
|
|
15583
15722
|
if (existing) {
|
|
15584
15723
|
logger2.debug("Auth config already seeded");
|
|
15585
15724
|
return;
|
|
@@ -15592,7 +15731,7 @@ async function seed3(ctx) {
|
|
|
15592
15731
|
rate_limit_window: parseInt(process.env["AUTH_RATE_LIMIT_WINDOW"] || "") || 900,
|
|
15593
15732
|
cookie_domain: process.env["AUTH_COOKIE_DOMAIN"] || null
|
|
15594
15733
|
};
|
|
15595
|
-
await db2(
|
|
15734
|
+
await db2(SINGLE_RECORDS2).insert({
|
|
15596
15735
|
id: generateId4(),
|
|
15597
15736
|
key: AUTH_CONFIG_KEY,
|
|
15598
15737
|
value: JSON.stringify(defaults),
|
|
@@ -15601,11 +15740,11 @@ async function seed3(ctx) {
|
|
|
15601
15740
|
});
|
|
15602
15741
|
logger2.info("Auth config seeded from environment variables");
|
|
15603
15742
|
}
|
|
15604
|
-
var
|
|
15743
|
+
var SINGLE_RECORDS2, AUTH_CONFIG_KEY;
|
|
15605
15744
|
var init_auth_seed = __esm({
|
|
15606
15745
|
"src/modules/auth/auth.seed.ts"() {
|
|
15607
15746
|
"use strict";
|
|
15608
|
-
|
|
15747
|
+
SINGLE_RECORDS2 = "single_records";
|
|
15609
15748
|
AUTH_CONFIG_KEY = "auth_config";
|
|
15610
15749
|
}
|
|
15611
15750
|
});
|
|
@@ -15671,7 +15810,7 @@ var init_auth = __esm({
|
|
|
15671
15810
|
});
|
|
15672
15811
|
|
|
15673
15812
|
// src/modules/mail/mail.config.ts
|
|
15674
|
-
import { z as
|
|
15813
|
+
import { z as z7 } from "zod";
|
|
15675
15814
|
function getMailConfig() {
|
|
15676
15815
|
return {
|
|
15677
15816
|
host: mailEnv.SMTP_HOST,
|
|
@@ -15685,13 +15824,13 @@ var mailEnvSchema, mailEnv;
|
|
|
15685
15824
|
var init_mail_config = __esm({
|
|
15686
15825
|
"src/modules/mail/mail.config.ts"() {
|
|
15687
15826
|
"use strict";
|
|
15688
|
-
mailEnvSchema =
|
|
15689
|
-
SMTP_HOST:
|
|
15690
|
-
SMTP_PORT:
|
|
15691
|
-
SMTP_SECURE:
|
|
15692
|
-
SMTP_USER:
|
|
15693
|
-
SMTP_PASS:
|
|
15694
|
-
SMTP_FROM:
|
|
15827
|
+
mailEnvSchema = z7.object({
|
|
15828
|
+
SMTP_HOST: z7.string().default("localhost"),
|
|
15829
|
+
SMTP_PORT: z7.coerce.number().default(1025),
|
|
15830
|
+
SMTP_SECURE: z7.string().default("false").transform((v) => v === "true" || v === "1"),
|
|
15831
|
+
SMTP_USER: z7.string().optional(),
|
|
15832
|
+
SMTP_PASS: z7.string().optional(),
|
|
15833
|
+
SMTP_FROM: z7.string().default("noreply@nexus.local")
|
|
15695
15834
|
});
|
|
15696
15835
|
mailEnv = mailEnvSchema.parse(process.env);
|
|
15697
15836
|
}
|
|
@@ -15699,8 +15838,8 @@ var init_mail_config = __esm({
|
|
|
15699
15838
|
|
|
15700
15839
|
// src/modules/mail/mail.service.ts
|
|
15701
15840
|
import nodemailer from "nodemailer";
|
|
15702
|
-
import { readFileSync as
|
|
15703
|
-
import { join as
|
|
15841
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
|
|
15842
|
+
import { join as join10 } from "path";
|
|
15704
15843
|
function getMailService() {
|
|
15705
15844
|
if (!mailServiceInstance) {
|
|
15706
15845
|
throw new Error("MailService not initialized. Call initMailService() first.");
|
|
@@ -15717,8 +15856,8 @@ var init_mail_service = __esm({
|
|
|
15717
15856
|
"src/modules/mail/mail.service.ts"() {
|
|
15718
15857
|
"use strict";
|
|
15719
15858
|
init_mail_config();
|
|
15720
|
-
TEMPLATE_REL_PATH =
|
|
15721
|
-
LOGO_REL_PATH =
|
|
15859
|
+
TEMPLATE_REL_PATH = join10("public", "mail", "base.html");
|
|
15860
|
+
LOGO_REL_PATH = join10("public", "nexus", "nexus-light-512.png");
|
|
15722
15861
|
mailServiceInstance = null;
|
|
15723
15862
|
MailService = class {
|
|
15724
15863
|
transporter;
|
|
@@ -15732,10 +15871,10 @@ var init_mail_service = __esm({
|
|
|
15732
15871
|
this.logger = logger2.child({ service: "mail" });
|
|
15733
15872
|
this.loggerService = loggerService;
|
|
15734
15873
|
const libPath = options?.libPath ?? process.cwd();
|
|
15735
|
-
this.template =
|
|
15736
|
-
const logoPath =
|
|
15737
|
-
if (
|
|
15738
|
-
const logoBase64 =
|
|
15874
|
+
this.template = readFileSync5(join10(libPath, TEMPLATE_REL_PATH), "utf-8");
|
|
15875
|
+
const logoPath = join10(libPath, LOGO_REL_PATH);
|
|
15876
|
+
if (existsSync8(logoPath)) {
|
|
15877
|
+
const logoBase64 = readFileSync5(logoPath).toString("base64");
|
|
15739
15878
|
this.defaultLogoUrl = `data:image/png;base64,${logoBase64}`;
|
|
15740
15879
|
} else {
|
|
15741
15880
|
this.defaultLogoUrl = "";
|
|
@@ -15827,7 +15966,7 @@ var init_mail_service = __esm({
|
|
|
15827
15966
|
});
|
|
15828
15967
|
|
|
15829
15968
|
// src/modules/mail/mail.entity.ts
|
|
15830
|
-
import { useIdField as useIdField7, useTextField as useTextField12, useSelectField as
|
|
15969
|
+
import { useIdField as useIdField7, useTextField as useTextField12, useSelectField as useSelectField11, useNumberField as useNumberField6, useSwitchField as useSwitchField3, useEmailField as useEmailField3, usePasswordField as usePasswordField2, useTextareaField as useTextareaField3, useTagsField as useTagsField2, useDatetimeField as useDatetimeField6 } from "@gzl10/nexus-sdk/fields";
|
|
15831
15970
|
import nodemailer2 from "nodemailer";
|
|
15832
15971
|
async function getMailConfigFromDB(ctx) {
|
|
15833
15972
|
const configService = ctx.services["config"];
|
|
@@ -15896,12 +16035,12 @@ var init_mail_entity = __esm({
|
|
|
15896
16035
|
nullable: false,
|
|
15897
16036
|
hint: { en: "Default: SMTP_HOST env var", es: "Por defecto: variable SMTP_HOST" }
|
|
15898
16037
|
}),
|
|
15899
|
-
port:
|
|
16038
|
+
port: useNumberField6({
|
|
15900
16039
|
label: { en: "Port", es: "Puerto" },
|
|
15901
16040
|
nullable: false,
|
|
15902
16041
|
hint: { en: "Default: SMTP_PORT env var", es: "Por defecto: variable SMTP_PORT" }
|
|
15903
16042
|
}),
|
|
15904
|
-
secure:
|
|
16043
|
+
secure: useSwitchField3({
|
|
15905
16044
|
label: { en: "TLS/SSL", es: "TLS/SSL" },
|
|
15906
16045
|
hint: { en: "Default: SMTP_SECURE env var", es: "Por defecto: variable SMTP_SECURE" }
|
|
15907
16046
|
}),
|
|
@@ -16104,7 +16243,7 @@ var init_mail_entity = __esm({
|
|
|
16104
16243
|
meta: { searchable: true, sortable: true }
|
|
16105
16244
|
}),
|
|
16106
16245
|
status: {
|
|
16107
|
-
...
|
|
16246
|
+
...useSelectField11({
|
|
16108
16247
|
label: { en: "Status", es: "Estado" },
|
|
16109
16248
|
options: [
|
|
16110
16249
|
{ value: "pending", label: { en: "Pending", es: "Pendiente" } },
|
|
@@ -16128,7 +16267,7 @@ var init_mail_entity = __esm({
|
|
|
16128
16267
|
label: { en: "Error", es: "Error" },
|
|
16129
16268
|
nullable: true
|
|
16130
16269
|
}),
|
|
16131
|
-
sent_by:
|
|
16270
|
+
sent_by: useSelectField11({
|
|
16132
16271
|
label: { en: "Sent by", es: "Enviado por" },
|
|
16133
16272
|
table: "users",
|
|
16134
16273
|
column: "id",
|
|
@@ -16337,7 +16476,7 @@ var init_observability_service = __esm({
|
|
|
16337
16476
|
});
|
|
16338
16477
|
|
|
16339
16478
|
// src/modules/observability/observability.config.ts
|
|
16340
|
-
import { z as
|
|
16479
|
+
import { z as z8 } from "zod";
|
|
16341
16480
|
function getOtelConfig() {
|
|
16342
16481
|
if (cachedConfig) return cachedConfig;
|
|
16343
16482
|
const env2 = otelEnvSchema.parse(process.env);
|
|
@@ -16354,12 +16493,12 @@ var otelEnvSchema, cachedConfig;
|
|
|
16354
16493
|
var init_observability_config = __esm({
|
|
16355
16494
|
"src/modules/observability/observability.config.ts"() {
|
|
16356
16495
|
"use strict";
|
|
16357
|
-
otelEnvSchema =
|
|
16358
|
-
OTEL_ENABLED:
|
|
16359
|
-
OTEL_SERVICE_NAME:
|
|
16360
|
-
OTEL_PROMETHEUS_PORT:
|
|
16361
|
-
OTEL_EXPORTER_OTLP_ENDPOINT:
|
|
16362
|
-
OTEL_TRACE_SAMPLE_RATE:
|
|
16496
|
+
otelEnvSchema = z8.object({
|
|
16497
|
+
OTEL_ENABLED: z8.coerce.boolean().default(false),
|
|
16498
|
+
OTEL_SERVICE_NAME: z8.string().default("nexus"),
|
|
16499
|
+
OTEL_PROMETHEUS_PORT: z8.coerce.number().int().min(0).max(65535).default(9464),
|
|
16500
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: z8.string().url().optional(),
|
|
16501
|
+
OTEL_TRACE_SAMPLE_RATE: z8.coerce.number().min(0).max(1).default(1)
|
|
16363
16502
|
});
|
|
16364
16503
|
cachedConfig = null;
|
|
16365
16504
|
}
|
|
@@ -16763,7 +16902,7 @@ var init_toggle_plugin_action = __esm({
|
|
|
16763
16902
|
});
|
|
16764
16903
|
|
|
16765
16904
|
// src/modules/plugins/plugins.entity.ts
|
|
16766
|
-
import { useTextField as useTextField13, useSelectField as
|
|
16905
|
+
import { useTextField as useTextField13, useSelectField as useSelectField12, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
|
|
16767
16906
|
import { OFFICIAL_PLUGINS } from "@gzl10/nexus-sdk";
|
|
16768
16907
|
var allowPluginManagement, pluginsEntity;
|
|
16769
16908
|
var init_plugins_entity = __esm({
|
|
@@ -16805,7 +16944,7 @@ var init_plugins_entity = __esm({
|
|
|
16805
16944
|
size: 20,
|
|
16806
16945
|
nullable: false
|
|
16807
16946
|
}),
|
|
16808
|
-
category:
|
|
16947
|
+
category: useSelectField12({
|
|
16809
16948
|
label: { en: "Category", es: "Categor\xEDa" },
|
|
16810
16949
|
options: [
|
|
16811
16950
|
{ value: "content", label: { en: "Content", es: "Contenido" } },
|
|
@@ -16884,7 +17023,7 @@ var init_plugins_entity = __esm({
|
|
|
16884
17023
|
|
|
16885
17024
|
// src/modules/plugins/plugins.routes.ts
|
|
16886
17025
|
import { Router } from "express";
|
|
16887
|
-
import { existsSync as
|
|
17026
|
+
import { existsSync as existsSync9 } from "fs";
|
|
16888
17027
|
function createPluginRoutes(ctx) {
|
|
16889
17028
|
const router = Router();
|
|
16890
17029
|
let imageMap = null;
|
|
@@ -16892,7 +17031,7 @@ function createPluginRoutes(ctx) {
|
|
|
16892
17031
|
if (!imageMap) {
|
|
16893
17032
|
const discovered = await ctx.core.plugins.discover();
|
|
16894
17033
|
imageMap = new Map(
|
|
16895
|
-
discovered.filter((p) => p.image &&
|
|
17034
|
+
discovered.filter((p) => p.image && existsSync9(p.image)).map((p) => [p.code, p.image])
|
|
16896
17035
|
);
|
|
16897
17036
|
}
|
|
16898
17037
|
return imageMap;
|
|
@@ -16947,7 +17086,7 @@ var init_plugins = __esm({
|
|
|
16947
17086
|
import {
|
|
16948
17087
|
useIdField as useIdField8,
|
|
16949
17088
|
useTextField as useTextField14,
|
|
16950
|
-
useSelectField as
|
|
17089
|
+
useSelectField as useSelectField13,
|
|
16951
17090
|
useTextareaField as useTextareaField4,
|
|
16952
17091
|
useJsonField as useJsonField3,
|
|
16953
17092
|
useDatetimeField as useDatetimeField7,
|
|
@@ -16989,7 +17128,7 @@ var init_audit_entity = __esm({
|
|
|
16989
17128
|
}),
|
|
16990
17129
|
validation: { min: 1, max: 100 }
|
|
16991
17130
|
},
|
|
16992
|
-
actor_id:
|
|
17131
|
+
actor_id: useSelectField13({
|
|
16993
17132
|
label: { en: "Actor", es: "Actor" },
|
|
16994
17133
|
table: "users",
|
|
16995
17134
|
column: "id",
|
|
@@ -17276,7 +17415,11 @@ import { Server as SocketServer } from "socket.io";
|
|
|
17276
17415
|
import jwt3 from "jsonwebtoken";
|
|
17277
17416
|
import { DEFAULT_TENANT_ID, entityRoom } from "@gzl10/nexus-sdk";
|
|
17278
17417
|
function initSocketIO(httpServer, options) {
|
|
17279
|
-
|
|
17418
|
+
try {
|
|
17419
|
+
jwtSecret = options?.jwtSecret ?? getAuthSecret();
|
|
17420
|
+
} catch {
|
|
17421
|
+
jwtSecret = null;
|
|
17422
|
+
}
|
|
17280
17423
|
if (!jwtSecret) {
|
|
17281
17424
|
logger.warn("Socket.IO authentication disabled: AUTH_SECRET not configured. All connections will be treated as guests.");
|
|
17282
17425
|
}
|
|
@@ -17521,6 +17664,7 @@ var init_socket = __esm({
|
|
|
17521
17664
|
"use strict";
|
|
17522
17665
|
init_emitter();
|
|
17523
17666
|
init_logger();
|
|
17667
|
+
init_secret_resolver();
|
|
17524
17668
|
io = null;
|
|
17525
17669
|
jwtSecret = null;
|
|
17526
17670
|
userSockets = /* @__PURE__ */ new Map();
|
|
@@ -18072,70 +18216,6 @@ var init_validate_middleware = __esm({
|
|
|
18072
18216
|
}
|
|
18073
18217
|
});
|
|
18074
18218
|
|
|
18075
|
-
// src/config/env.ts
|
|
18076
|
-
import { z as z8 } from "zod";
|
|
18077
|
-
function resolveConfig() {
|
|
18078
|
-
env = envSchema.parse(process.env);
|
|
18079
|
-
process.env["TZ"] = env.TZ;
|
|
18080
|
-
resolvedConfig = {
|
|
18081
|
-
nodeEnv: env.NODE_ENV,
|
|
18082
|
-
port: env.PORT,
|
|
18083
|
-
host: "0.0.0.0",
|
|
18084
|
-
ui: {
|
|
18085
|
-
enabled: env.NEXUS_UI_ENABLED,
|
|
18086
|
-
base: env.NEXUS_UI_BASE,
|
|
18087
|
-
path: env.NEXUS_UI_PATH
|
|
18088
|
-
},
|
|
18089
|
-
corsOrigin: env.CORS_ORIGIN,
|
|
18090
|
-
databaseUrl: env.DATABASE_URL,
|
|
18091
|
-
adminEmail: env.ADMIN_EMAIL,
|
|
18092
|
-
adminPassword: env.ADMIN_PASSWORD,
|
|
18093
|
-
timezone: env.TZ
|
|
18094
|
-
};
|
|
18095
|
-
return resolvedConfig;
|
|
18096
|
-
}
|
|
18097
|
-
function getConfig() {
|
|
18098
|
-
if (!resolvedConfig) {
|
|
18099
|
-
return resolveConfig();
|
|
18100
|
-
}
|
|
18101
|
-
return resolvedConfig;
|
|
18102
|
-
}
|
|
18103
|
-
function resetConfig() {
|
|
18104
|
-
resolvedConfig = null;
|
|
18105
|
-
}
|
|
18106
|
-
var envSchema, env, resolvedConfig;
|
|
18107
|
-
var init_env = __esm({
|
|
18108
|
-
"src/config/env.ts"() {
|
|
18109
|
-
"use strict";
|
|
18110
|
-
envSchema = z8.object({
|
|
18111
|
-
NODE_ENV: z8.enum(["development", "production", "test"]).default("development"),
|
|
18112
|
-
PORT: z8.coerce.number().default(3e3),
|
|
18113
|
-
CORS_ORIGIN: z8.string().default("*"),
|
|
18114
|
-
BACKEND_URL: z8.string().optional(),
|
|
18115
|
-
DATABASE_URL: z8.string().default("file:./dev.db"),
|
|
18116
|
-
REDIS_URL: z8.string().url().optional(),
|
|
18117
|
-
REDIS_PREFIX: z8.string().default("nexus"),
|
|
18118
|
-
ADMIN_EMAIL: z8.string().email().optional(),
|
|
18119
|
-
ADMIN_PASSWORD: z8.string().min(6).optional(),
|
|
18120
|
-
COOKIE_DOMAIN: z8.string().optional(),
|
|
18121
|
-
TZ: z8.string().default("UTC"),
|
|
18122
|
-
TRUST_PROXY: z8.coerce.boolean().default(false),
|
|
18123
|
-
NEXUS_UI_ENABLED: z8.coerce.boolean().default(true),
|
|
18124
|
-
NEXUS_UI_BASE: z8.string().default("/"),
|
|
18125
|
-
NEXUS_UI_PATH: z8.string().default("../ui/dist"),
|
|
18126
|
-
NEXUS_CHECK_DRIFT: z8.coerce.boolean().optional(),
|
|
18127
|
-
NEXUS_FAIL_ON_DRIFT: z8.coerce.boolean().optional(),
|
|
18128
|
-
FRPC_SERVER: z8.string().optional(),
|
|
18129
|
-
FRPC_SERVER_PORT: z8.coerce.number().default(7e3),
|
|
18130
|
-
FRPC_TOKEN: z8.string().optional(),
|
|
18131
|
-
FRPC_SUBDOMAIN: z8.string().optional()
|
|
18132
|
-
});
|
|
18133
|
-
env = envSchema.parse(process.env);
|
|
18134
|
-
process.env["TZ"] = env.TZ;
|
|
18135
|
-
resolvedConfig = null;
|
|
18136
|
-
}
|
|
18137
|
-
});
|
|
18138
|
-
|
|
18139
18219
|
// src/core/middleware/error.middleware.ts
|
|
18140
18220
|
import { ZodError } from "zod";
|
|
18141
18221
|
import { ForbiddenError as CASLForbiddenError2 } from "@casl/ability";
|
|
@@ -18313,20 +18393,17 @@ var init_hash = __esm({
|
|
|
18313
18393
|
});
|
|
18314
18394
|
|
|
18315
18395
|
// src/core/crypto/symmetric.ts
|
|
18316
|
-
import { createCipheriv, createDecipheriv, randomBytes as
|
|
18396
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, hkdfSync } from "crypto";
|
|
18317
18397
|
function getKey() {
|
|
18318
18398
|
if (derivedKey) return derivedKey;
|
|
18319
|
-
const secret =
|
|
18320
|
-
if (!secret) {
|
|
18321
|
-
throw new Error("AUTH_SECRET not configured. Required for symmetric encryption.");
|
|
18322
|
-
}
|
|
18399
|
+
const secret = getAuthSecret();
|
|
18323
18400
|
const keyBytes = hkdfSync("sha256", secret, "", HKDF_INFO, KEY_LENGTH);
|
|
18324
18401
|
derivedKey = Buffer.from(keyBytes);
|
|
18325
18402
|
return derivedKey;
|
|
18326
18403
|
}
|
|
18327
18404
|
function encrypt(plaintext) {
|
|
18328
18405
|
const key = getKey();
|
|
18329
|
-
const iv =
|
|
18406
|
+
const iv = randomBytes3(IV_LENGTH);
|
|
18330
18407
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
18331
18408
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
18332
18409
|
const authTag = cipher.getAuthTag();
|
|
@@ -18350,6 +18427,7 @@ var ALGORITHM, IV_LENGTH, KEY_LENGTH, HKDF_INFO, derivedKey;
|
|
|
18350
18427
|
var init_symmetric = __esm({
|
|
18351
18428
|
"src/core/crypto/symmetric.ts"() {
|
|
18352
18429
|
"use strict";
|
|
18430
|
+
init_secret_resolver();
|
|
18353
18431
|
ALGORITHM = "aes-256-gcm";
|
|
18354
18432
|
IV_LENGTH = 12;
|
|
18355
18433
|
KEY_LENGTH = 32;
|
|
@@ -18575,8 +18653,8 @@ var init_safe_json = __esm({
|
|
|
18575
18653
|
|
|
18576
18654
|
// src/core/spa-handler.ts
|
|
18577
18655
|
import express from "express";
|
|
18578
|
-
import { resolve, join as
|
|
18579
|
-
import { existsSync as
|
|
18656
|
+
import { resolve, join as join11 } from "path";
|
|
18657
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
18580
18658
|
function createServeSPA(app, httpServer) {
|
|
18581
18659
|
return async (endpoint, distPath, options = {}) => {
|
|
18582
18660
|
const {
|
|
@@ -18598,7 +18676,7 @@ function createServeSPA(app, httpServer) {
|
|
|
18598
18676
|
registeredEndpoints.add(endpoint);
|
|
18599
18677
|
if (env.NODE_ENV === "development" && viteSrc) {
|
|
18600
18678
|
const srcPath = resolve(getProjectPath(), viteSrc);
|
|
18601
|
-
if (!
|
|
18679
|
+
if (!existsSync10(srcPath)) {
|
|
18602
18680
|
logger.warn({ endpoint, viteSrc, resolved: srcPath }, "Vite source not found \u2014 falling back to static");
|
|
18603
18681
|
} else {
|
|
18604
18682
|
const mounted = await mountViteDevMiddleware(app, endpoint, srcPath, httpServer);
|
|
@@ -18612,6 +18690,7 @@ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
|
|
|
18612
18690
|
try {
|
|
18613
18691
|
const vite = await import("vite");
|
|
18614
18692
|
const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
|
|
18693
|
+
const singletonDeps = ["vue", "vue-router", "pinia", "naive-ui"];
|
|
18615
18694
|
const server2 = await vite.createServer({
|
|
18616
18695
|
root: srcPath,
|
|
18617
18696
|
server: {
|
|
@@ -18619,6 +18698,12 @@ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
|
|
|
18619
18698
|
allowedHosts: true,
|
|
18620
18699
|
hmr: httpServer ? { server: httpServer } : true
|
|
18621
18700
|
},
|
|
18701
|
+
resolve: {
|
|
18702
|
+
dedupe: singletonDeps
|
|
18703
|
+
},
|
|
18704
|
+
optimizeDeps: {
|
|
18705
|
+
include: singletonDeps
|
|
18706
|
+
},
|
|
18622
18707
|
plugins: [{
|
|
18623
18708
|
name: "nexus-config-inject",
|
|
18624
18709
|
transformIndexHtml(html) {
|
|
@@ -18654,24 +18739,24 @@ function mountStaticSPA(app, endpoint, distPath, options) {
|
|
|
18654
18739
|
resolvedPath = distPath;
|
|
18655
18740
|
} else {
|
|
18656
18741
|
const projectPath2 = resolve(getProjectPath(), distPath);
|
|
18657
|
-
if (
|
|
18742
|
+
if (existsSync10(projectPath2)) {
|
|
18658
18743
|
resolvedPath = projectPath2;
|
|
18659
18744
|
} else {
|
|
18660
18745
|
resolvedPath = resolve(getLibPath(), distPath);
|
|
18661
18746
|
}
|
|
18662
18747
|
}
|
|
18663
|
-
if (!
|
|
18748
|
+
if (!existsSync10(resolvedPath)) {
|
|
18664
18749
|
logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
|
|
18665
18750
|
return;
|
|
18666
18751
|
}
|
|
18667
|
-
const indexPath =
|
|
18668
|
-
if (!
|
|
18752
|
+
const indexPath = join11(resolvedPath, index);
|
|
18753
|
+
if (!existsSync10(indexPath)) {
|
|
18669
18754
|
logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
|
|
18670
18755
|
}
|
|
18671
18756
|
app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
|
|
18672
18757
|
let injectedHtml = "";
|
|
18673
|
-
if (
|
|
18674
|
-
const rawHtml =
|
|
18758
|
+
if (existsSync10(indexPath)) {
|
|
18759
|
+
const rawHtml = readFileSync6(indexPath, "utf-8");
|
|
18675
18760
|
const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
|
|
18676
18761
|
const nexusConfig = JSON.stringify({ apiUrl });
|
|
18677
18762
|
injectedHtml = rawHtml.replace(
|
|
@@ -19246,11 +19331,7 @@ var init_cache = __esm({
|
|
|
19246
19331
|
// src/core/jwt/index.ts
|
|
19247
19332
|
import jwt4 from "jsonwebtoken";
|
|
19248
19333
|
function getSecret() {
|
|
19249
|
-
|
|
19250
|
-
if (!secret) {
|
|
19251
|
-
throw new Error("AUTH_SECRET not configured. Set AUTH_SECRET env var.");
|
|
19252
|
-
}
|
|
19253
|
-
return secret;
|
|
19334
|
+
return getAuthSecret();
|
|
19254
19335
|
}
|
|
19255
19336
|
function verifyAccessToken(token) {
|
|
19256
19337
|
return jwt4.verify(token, getSecret());
|
|
@@ -19258,6 +19339,7 @@ function verifyAccessToken(token) {
|
|
|
19258
19339
|
var init_jwt = __esm({
|
|
19259
19340
|
"src/core/jwt/index.ts"() {
|
|
19260
19341
|
"use strict";
|
|
19342
|
+
init_secret_resolver();
|
|
19261
19343
|
}
|
|
19262
19344
|
});
|
|
19263
19345
|
|
|
@@ -19367,91 +19449,6 @@ var init_schema_helpers = __esm({
|
|
|
19367
19449
|
}
|
|
19368
19450
|
});
|
|
19369
19451
|
|
|
19370
|
-
// src/db/sql-utils.ts
|
|
19371
|
-
function extractTableFromSql(sql, type2) {
|
|
19372
|
-
const match = sql.match(SQL_PATTERNS[type2]);
|
|
19373
|
-
return match?.[1];
|
|
19374
|
-
}
|
|
19375
|
-
function extractTableFromSelect(sql) {
|
|
19376
|
-
return extractTableFromSql(sql, "select");
|
|
19377
|
-
}
|
|
19378
|
-
function extractTableFromInsert(sql) {
|
|
19379
|
-
return extractTableFromSql(sql, "insert");
|
|
19380
|
-
}
|
|
19381
|
-
function extractTableFromUpdate(sql) {
|
|
19382
|
-
return extractTableFromSql(sql, "update");
|
|
19383
|
-
}
|
|
19384
|
-
function extractTableFromDelete(sql) {
|
|
19385
|
-
return extractTableFromSql(sql, "delete");
|
|
19386
|
-
}
|
|
19387
|
-
var SQL_PATTERNS;
|
|
19388
|
-
var init_sql_utils = __esm({
|
|
19389
|
-
"src/db/sql-utils.ts"() {
|
|
19390
|
-
"use strict";
|
|
19391
|
-
SQL_PATTERNS = {
|
|
19392
|
-
select: /from\s+["'`]?(\w+)["'`]?/i,
|
|
19393
|
-
insert: /insert into\s+["'`]?(\w+)["'`]?/i,
|
|
19394
|
-
update: /update\s+["'`]?(\w+)["'`]?/i,
|
|
19395
|
-
delete: /delete from\s+["'`]?(\w+)["'`]?/i
|
|
19396
|
-
};
|
|
19397
|
-
}
|
|
19398
|
-
});
|
|
19399
|
-
|
|
19400
|
-
// src/db/sqlite-compat.ts
|
|
19401
|
-
function createSqliteBooleanProcessor() {
|
|
19402
|
-
const booleanColumns = /* @__PURE__ */ new Map();
|
|
19403
|
-
function registerBooleanColumn2(table, column) {
|
|
19404
|
-
if (!booleanColumns.has(table)) {
|
|
19405
|
-
booleanColumns.set(table, /* @__PURE__ */ new Set());
|
|
19406
|
-
}
|
|
19407
|
-
booleanColumns.get(table).add(column);
|
|
19408
|
-
}
|
|
19409
|
-
function convertBooleans2(table, row) {
|
|
19410
|
-
const columns = booleanColumns.get(table);
|
|
19411
|
-
if (!columns || columns.size === 0) return row;
|
|
19412
|
-
const result = { ...row };
|
|
19413
|
-
for (const column of columns) {
|
|
19414
|
-
if (column in result) {
|
|
19415
|
-
const value = result[column];
|
|
19416
|
-
if (value === 0 || value === 1) {
|
|
19417
|
-
result[column] = value === 1;
|
|
19418
|
-
}
|
|
19419
|
-
}
|
|
19420
|
-
}
|
|
19421
|
-
return result;
|
|
19422
|
-
}
|
|
19423
|
-
function postProcess(result, queryContext) {
|
|
19424
|
-
const sql = queryContext?.sql?.toLowerCase() ?? "";
|
|
19425
|
-
if (!sql.startsWith("select")) return result;
|
|
19426
|
-
const table = extractTableFromSelect(sql);
|
|
19427
|
-
if (!table) return result;
|
|
19428
|
-
if (Array.isArray(result)) {
|
|
19429
|
-
return result.map(
|
|
19430
|
-
(row) => typeof row === "object" && row !== null ? convertBooleans2(table, row) : row
|
|
19431
|
-
);
|
|
19432
|
-
}
|
|
19433
|
-
if (typeof result === "object" && result !== null) {
|
|
19434
|
-
return convertBooleans2(table, result);
|
|
19435
|
-
}
|
|
19436
|
-
return result;
|
|
19437
|
-
}
|
|
19438
|
-
function clear() {
|
|
19439
|
-
booleanColumns.clear();
|
|
19440
|
-
}
|
|
19441
|
-
return { registerBooleanColumn: registerBooleanColumn2, convertBooleans: convertBooleans2, postProcess, clear };
|
|
19442
|
-
}
|
|
19443
|
-
var defaultProcessor, registerBooleanColumn, convertBooleans, sqlitePostProcess;
|
|
19444
|
-
var init_sqlite_compat = __esm({
|
|
19445
|
-
"src/db/sqlite-compat.ts"() {
|
|
19446
|
-
"use strict";
|
|
19447
|
-
init_sql_utils();
|
|
19448
|
-
defaultProcessor = createSqliteBooleanProcessor();
|
|
19449
|
-
registerBooleanColumn = defaultProcessor.registerBooleanColumn;
|
|
19450
|
-
convertBooleans = defaultProcessor.convertBooleans;
|
|
19451
|
-
sqlitePostProcess = defaultProcessor.postProcess;
|
|
19452
|
-
}
|
|
19453
|
-
});
|
|
19454
|
-
|
|
19455
19452
|
// src/runtime/types.ts
|
|
19456
19453
|
var init_types = __esm({
|
|
19457
19454
|
"src/runtime/types.ts"() {
|
|
@@ -23737,8 +23734,8 @@ var init_runtime = __esm({
|
|
|
23737
23734
|
});
|
|
23738
23735
|
|
|
23739
23736
|
// src/db/seed-runner.ts
|
|
23740
|
-
import { existsSync as
|
|
23741
|
-
import { join as
|
|
23737
|
+
import { existsSync as existsSync11 } from "fs";
|
|
23738
|
+
import { join as join12 } from "path";
|
|
23742
23739
|
import { pathToFileURL } from "url";
|
|
23743
23740
|
async function runModuleSeed(mod, ctx) {
|
|
23744
23741
|
let seeded = false;
|
|
@@ -23746,8 +23743,8 @@ async function runModuleSeed(mod, ctx) {
|
|
|
23746
23743
|
await mod.seed(ctx);
|
|
23747
23744
|
seeded = true;
|
|
23748
23745
|
} else {
|
|
23749
|
-
const seedPath =
|
|
23750
|
-
if (
|
|
23746
|
+
const seedPath = join12(getLibPath(), "dist", "modules", mod.name, `${mod.name}.seed.js`);
|
|
23747
|
+
if (existsSync11(seedPath)) {
|
|
23751
23748
|
const seedModule = await import(pathToFileURL(seedPath).href);
|
|
23752
23749
|
if (typeof seedModule.seed === "function") {
|
|
23753
23750
|
await seedModule.seed(ctx);
|
|
@@ -23901,94 +23898,6 @@ var init_migration_sources = __esm({
|
|
|
23901
23898
|
}
|
|
23902
23899
|
});
|
|
23903
23900
|
|
|
23904
|
-
// src/config/database.ts
|
|
23905
|
-
import { join as join11, dirname as dirname6, isAbsolute as isAbsolute2 } from "path";
|
|
23906
|
-
import { mkdirSync as mkdirSync4 } from "fs";
|
|
23907
|
-
function getDatabaseConfig() {
|
|
23908
|
-
const url = getConfig().databaseUrl;
|
|
23909
|
-
if (IN_MEMORY_RE.test(url)) {
|
|
23910
|
-
return {
|
|
23911
|
-
client: "better-sqlite3",
|
|
23912
|
-
connection: { filename: ":memory:" },
|
|
23913
|
-
useNullAsDefault: true,
|
|
23914
|
-
postProcessResponse: sqlitePostProcess,
|
|
23915
|
-
pool: { min: 0, max: 1 }
|
|
23916
|
-
};
|
|
23917
|
-
}
|
|
23918
|
-
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
23919
|
-
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
23920
|
-
if (!isAbsolute2(filename)) {
|
|
23921
|
-
filename = join11(getProjectPath(), "data", filename);
|
|
23922
|
-
}
|
|
23923
|
-
mkdirSync4(dirname6(filename), { recursive: true });
|
|
23924
|
-
return {
|
|
23925
|
-
client: "better-sqlite3",
|
|
23926
|
-
connection: { filename },
|
|
23927
|
-
useNullAsDefault: true,
|
|
23928
|
-
postProcessResponse: sqlitePostProcess
|
|
23929
|
-
};
|
|
23930
|
-
}
|
|
23931
|
-
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) {
|
|
23932
|
-
return {
|
|
23933
|
-
client: "pg",
|
|
23934
|
-
connection: url,
|
|
23935
|
-
pool: { min: 2, max: 10 }
|
|
23936
|
-
};
|
|
23937
|
-
}
|
|
23938
|
-
if (url.startsWith("mysql://")) {
|
|
23939
|
-
const offsetMinutes = (/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
23940
|
-
const offsetHours = Math.abs(Math.floor(offsetMinutes / 60));
|
|
23941
|
-
const offsetMins = Math.abs(offsetMinutes % 60);
|
|
23942
|
-
const sign = offsetMinutes <= 0 ? "+" : "-";
|
|
23943
|
-
const tzOffset = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMins).padStart(2, "0")}`;
|
|
23944
|
-
return {
|
|
23945
|
-
client: "mysql2",
|
|
23946
|
-
connection: {
|
|
23947
|
-
uri: url,
|
|
23948
|
-
timezone: tzOffset
|
|
23949
|
-
// mysql2 requiere formato "+HH:MM"
|
|
23950
|
-
},
|
|
23951
|
-
pool: { min: 2, max: 10 }
|
|
23952
|
-
};
|
|
23953
|
-
}
|
|
23954
|
-
throw new Error(`Unsupported database URL: ${url}`);
|
|
23955
|
-
}
|
|
23956
|
-
function getDatabaseType() {
|
|
23957
|
-
const url = getConfig().databaseUrl;
|
|
23958
|
-
if (IN_MEMORY_RE.test(url) || url.startsWith("file:") || url.startsWith("sqlite:")) return "sqlite";
|
|
23959
|
-
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) return "postgresql";
|
|
23960
|
-
if (url.startsWith("mysql://")) return "mysql";
|
|
23961
|
-
return "sqlite";
|
|
23962
|
-
}
|
|
23963
|
-
function getDatabasePath() {
|
|
23964
|
-
const url = getConfig().databaseUrl;
|
|
23965
|
-
if (IN_MEMORY_RE.test(url)) return ":memory:";
|
|
23966
|
-
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
23967
|
-
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
23968
|
-
if (!isAbsolute2(filename)) {
|
|
23969
|
-
filename = join11(getProjectPath(), "data", filename);
|
|
23970
|
-
}
|
|
23971
|
-
return filename;
|
|
23972
|
-
}
|
|
23973
|
-
try {
|
|
23974
|
-
const parsed = new URL(url);
|
|
23975
|
-
parsed.password = "***";
|
|
23976
|
-
return parsed.toString();
|
|
23977
|
-
} catch {
|
|
23978
|
-
return url.replace(/:\/\/[^@]+@/, "://***@");
|
|
23979
|
-
}
|
|
23980
|
-
}
|
|
23981
|
-
var IN_MEMORY_RE;
|
|
23982
|
-
var init_database = __esm({
|
|
23983
|
-
"src/config/database.ts"() {
|
|
23984
|
-
"use strict";
|
|
23985
|
-
init_env();
|
|
23986
|
-
init_paths();
|
|
23987
|
-
init_sqlite_compat();
|
|
23988
|
-
IN_MEMORY_RE = /^((file:|sqlite:)?:memory:?)$/;
|
|
23989
|
-
}
|
|
23990
|
-
});
|
|
23991
|
-
|
|
23992
23901
|
// src/db/query-interceptor.ts
|
|
23993
23902
|
function setupQueryInterceptor(knexInstance) {
|
|
23994
23903
|
const queryTimings = /* @__PURE__ */ new Map();
|
|
@@ -24629,7 +24538,7 @@ var init_migration_helpers = __esm({
|
|
|
24629
24538
|
// src/db/migration-generator.ts
|
|
24630
24539
|
import path2 from "path";
|
|
24631
24540
|
import fs2 from "fs/promises";
|
|
24632
|
-
import { readFileSync as
|
|
24541
|
+
import { readFileSync as readFileSync7, mkdirSync as mkdirSync5, realpathSync } from "fs";
|
|
24633
24542
|
function getColumnIndexBytes(field) {
|
|
24634
24543
|
if (!field?.db) return 255 * MYSQL_BYTES_PER_CHAR;
|
|
24635
24544
|
const size = field.db.size ?? 255;
|
|
@@ -26543,8 +26452,8 @@ var init_db = __esm({
|
|
|
26543
26452
|
});
|
|
26544
26453
|
|
|
26545
26454
|
// src/config/load-config.ts
|
|
26546
|
-
import { join as
|
|
26547
|
-
import { existsSync as
|
|
26455
|
+
import { join as join13 } from "path";
|
|
26456
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, readdirSync, statSync } from "fs";
|
|
26548
26457
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
26549
26458
|
function isPluginManifest(value) {
|
|
26550
26459
|
if (!value || typeof value !== "object" || typeof value === "function") return false;
|
|
@@ -26560,25 +26469,25 @@ function extractPluginManifest(mod) {
|
|
|
26560
26469
|
return null;
|
|
26561
26470
|
}
|
|
26562
26471
|
function resolvePluginEntry(projectPath2, pkgName) {
|
|
26563
|
-
const pkgDir =
|
|
26564
|
-
const pkgJsonPath =
|
|
26565
|
-
if (!
|
|
26472
|
+
const pkgDir = join13(projectPath2, "node_modules", pkgName);
|
|
26473
|
+
const pkgJsonPath = join13(pkgDir, "package.json");
|
|
26474
|
+
if (!existsSync12(pkgJsonPath)) return null;
|
|
26566
26475
|
try {
|
|
26567
|
-
const pluginPkg = JSON.parse(
|
|
26476
|
+
const pluginPkg = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
|
|
26568
26477
|
const exports = pluginPkg.exports;
|
|
26569
26478
|
const importEntry = exports?.["."]?.import;
|
|
26570
26479
|
const entry = (typeof importEntry === "string" ? importEntry : importEntry?.default) ?? (typeof exports === "string" ? exports : null) ?? pluginPkg.main ?? pluginPkg.module ?? "index.js";
|
|
26571
|
-
const entryPath =
|
|
26572
|
-
return
|
|
26480
|
+
const entryPath = join13(pkgDir, entry);
|
|
26481
|
+
return existsSync12(entryPath) ? entryPath : null;
|
|
26573
26482
|
} catch {
|
|
26574
26483
|
return null;
|
|
26575
26484
|
}
|
|
26576
26485
|
}
|
|
26577
26486
|
async function discoverPlugins(projectPath2) {
|
|
26578
|
-
const pkgPath =
|
|
26487
|
+
const pkgPath = join13(projectPath2, "package.json");
|
|
26579
26488
|
let pkg2;
|
|
26580
26489
|
try {
|
|
26581
|
-
pkg2 = JSON.parse(
|
|
26490
|
+
pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
26582
26491
|
} catch {
|
|
26583
26492
|
return [];
|
|
26584
26493
|
}
|
|
@@ -26600,21 +26509,21 @@ async function discoverPlugins(projectPath2) {
|
|
|
26600
26509
|
const manifest = extractPluginManifest(mod);
|
|
26601
26510
|
if (manifest) {
|
|
26602
26511
|
if (!manifest.migrationsDir) {
|
|
26603
|
-
const defaultMigrationsDir =
|
|
26604
|
-
if (
|
|
26512
|
+
const defaultMigrationsDir = join13(projectPath2, "node_modules", pkgName, "migrations");
|
|
26513
|
+
if (existsSync12(defaultMigrationsDir)) {
|
|
26605
26514
|
manifest.migrationsDir = defaultMigrationsDir;
|
|
26606
26515
|
}
|
|
26607
26516
|
}
|
|
26608
26517
|
if (!manifest.image) {
|
|
26609
|
-
const imagePath =
|
|
26610
|
-
if (
|
|
26518
|
+
const imagePath = join13(projectPath2, "node_modules", pkgName, "image.png");
|
|
26519
|
+
if (existsSync12(imagePath)) {
|
|
26611
26520
|
manifest.image = imagePath;
|
|
26612
26521
|
}
|
|
26613
26522
|
}
|
|
26614
26523
|
if (!manifest.llms) {
|
|
26615
|
-
const llmsPath =
|
|
26616
|
-
if (
|
|
26617
|
-
manifest.llms =
|
|
26524
|
+
const llmsPath = join13(projectPath2, "node_modules", pkgName, "llms.txt");
|
|
26525
|
+
if (existsSync12(llmsPath)) {
|
|
26526
|
+
manifest.llms = readFileSync8(llmsPath, "utf-8");
|
|
26618
26527
|
}
|
|
26619
26528
|
}
|
|
26620
26529
|
discovered.push(manifest);
|
|
@@ -26650,8 +26559,8 @@ function extractModuleManifests(mod) {
|
|
|
26650
26559
|
return manifests;
|
|
26651
26560
|
}
|
|
26652
26561
|
async function discoverModules(projectPath2) {
|
|
26653
|
-
const modulesDir = process.env["MODULES_DIR"] ||
|
|
26654
|
-
if (!
|
|
26562
|
+
const modulesDir = process.env["MODULES_DIR"] || join13(projectPath2, "src", "modules");
|
|
26563
|
+
if (!existsSync12(modulesDir)) return [];
|
|
26655
26564
|
let entries;
|
|
26656
26565
|
try {
|
|
26657
26566
|
entries = readdirSync(modulesDir);
|
|
@@ -26660,7 +26569,7 @@ async function discoverModules(projectPath2) {
|
|
|
26660
26569
|
}
|
|
26661
26570
|
const discovered = [];
|
|
26662
26571
|
for (const entry of entries) {
|
|
26663
|
-
const entryPath =
|
|
26572
|
+
const entryPath = join13(modulesDir, entry);
|
|
26664
26573
|
try {
|
|
26665
26574
|
if (!statSync(entryPath).isDirectory()) continue;
|
|
26666
26575
|
} catch {
|
|
@@ -26668,8 +26577,8 @@ async function discoverModules(projectPath2) {
|
|
|
26668
26577
|
}
|
|
26669
26578
|
let indexPath = null;
|
|
26670
26579
|
for (const ext of ["index.js", "index.ts"]) {
|
|
26671
|
-
const candidate =
|
|
26672
|
-
if (
|
|
26580
|
+
const candidate = join13(entryPath, ext);
|
|
26581
|
+
if (existsSync12(candidate)) {
|
|
26673
26582
|
indexPath = candidate;
|
|
26674
26583
|
break;
|
|
26675
26584
|
}
|
|
@@ -26783,6 +26692,49 @@ var init_events_api = __esm({
|
|
|
26783
26692
|
}
|
|
26784
26693
|
});
|
|
26785
26694
|
|
|
26695
|
+
// src/engine/capabilities-registry.ts
|
|
26696
|
+
var CapabilitiesRegistry;
|
|
26697
|
+
var init_capabilities_registry = __esm({
|
|
26698
|
+
"src/engine/capabilities-registry.ts"() {
|
|
26699
|
+
"use strict";
|
|
26700
|
+
CapabilitiesRegistry = class {
|
|
26701
|
+
resolvers = /* @__PURE__ */ new Map();
|
|
26702
|
+
logger;
|
|
26703
|
+
constructor(logger2) {
|
|
26704
|
+
this.logger = logger2;
|
|
26705
|
+
}
|
|
26706
|
+
register(key, resolver) {
|
|
26707
|
+
if (this.resolvers.has(key)) {
|
|
26708
|
+
this.logger?.warn({ key }, "Capability key already registered, overwriting");
|
|
26709
|
+
}
|
|
26710
|
+
this.resolvers.set(key, resolver);
|
|
26711
|
+
}
|
|
26712
|
+
async resolve() {
|
|
26713
|
+
const entries = Array.from(this.resolvers.entries());
|
|
26714
|
+
const results = await Promise.allSettled(
|
|
26715
|
+
entries.map(async ([key, resolver]) => {
|
|
26716
|
+
const value = await resolver();
|
|
26717
|
+
return [key, value];
|
|
26718
|
+
})
|
|
26719
|
+
);
|
|
26720
|
+
const capabilities = {};
|
|
26721
|
+
for (const result of results) {
|
|
26722
|
+
if (result.status === "fulfilled") {
|
|
26723
|
+
const [key, value] = result.value;
|
|
26724
|
+
capabilities[key] = value;
|
|
26725
|
+
} else {
|
|
26726
|
+
this.logger?.warn(
|
|
26727
|
+
{ err: result.reason?.message ?? String(result.reason) },
|
|
26728
|
+
"Capability resolver failed"
|
|
26729
|
+
);
|
|
26730
|
+
}
|
|
26731
|
+
}
|
|
26732
|
+
return capabilities;
|
|
26733
|
+
}
|
|
26734
|
+
};
|
|
26735
|
+
}
|
|
26736
|
+
});
|
|
26737
|
+
|
|
26786
26738
|
// src/engine/context.ts
|
|
26787
26739
|
import { ForbiddenError as CASLForbiddenError3, subject } from "@casl/ability";
|
|
26788
26740
|
import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2, DEFAULT_LOCALES } from "@gzl10/nexus-sdk";
|
|
@@ -27056,6 +27008,9 @@ function createModuleContext() {
|
|
|
27056
27008
|
return name in adaptersRegistry;
|
|
27057
27009
|
}
|
|
27058
27010
|
};
|
|
27011
|
+
const capabilitiesRegistry = new CapabilitiesRegistry(
|
|
27012
|
+
logger.child({ service: "capabilities" })
|
|
27013
|
+
);
|
|
27059
27014
|
const ctx = {
|
|
27060
27015
|
tenantId: DEFAULT_TENANT_ID2,
|
|
27061
27016
|
core: coreContext,
|
|
@@ -27066,6 +27021,7 @@ function createModuleContext() {
|
|
|
27066
27021
|
engine: engineContext,
|
|
27067
27022
|
services: servicesContext,
|
|
27068
27023
|
adapters: adaptersContext,
|
|
27024
|
+
capabilities: capabilitiesRegistry,
|
|
27069
27025
|
// Root-level shortcuts for frequently used utilities
|
|
27070
27026
|
events: createEventsApi(nexusEvents, logger),
|
|
27071
27027
|
createRouter: () => createRouter(),
|
|
@@ -27079,6 +27035,9 @@ function createModuleContext() {
|
|
|
27079
27035
|
createEntityRouter: (controller, def) => createEntityRouter(controller, def, ctx)
|
|
27080
27036
|
};
|
|
27081
27037
|
ctx.db.seedModule = (mod) => runModuleSeed(mod, ctx);
|
|
27038
|
+
capabilitiesRegistry.register("version", () => getCoreManifest().version);
|
|
27039
|
+
capabilitiesRegistry.register("plugins", () => getPlugins().map((p) => p.code));
|
|
27040
|
+
capabilitiesRegistry.register("locales", () => platformLocales);
|
|
27082
27041
|
return ctx;
|
|
27083
27042
|
}
|
|
27084
27043
|
var platformLocales, sharedCacheManager, sharedTempAdapter;
|
|
@@ -27094,6 +27053,7 @@ var init_context = __esm({
|
|
|
27094
27053
|
init_plugin_ops();
|
|
27095
27054
|
init_load_config();
|
|
27096
27055
|
init_events_api();
|
|
27056
|
+
init_capabilities_registry();
|
|
27097
27057
|
init_seed_runner();
|
|
27098
27058
|
init_cache_manager();
|
|
27099
27059
|
platformLocales = DEFAULT_LOCALES;
|
|
@@ -27113,6 +27073,7 @@ var init_engine = __esm({
|
|
|
27113
27073
|
init_subject_extractor();
|
|
27114
27074
|
init_definition_extractors();
|
|
27115
27075
|
init_context();
|
|
27076
|
+
init_capabilities_registry();
|
|
27116
27077
|
}
|
|
27117
27078
|
});
|
|
27118
27079
|
|
|
@@ -27855,7 +27816,7 @@ var init_port_check = __esm({
|
|
|
27855
27816
|
// src/core/tunnel.ts
|
|
27856
27817
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
27857
27818
|
import { writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
27858
|
-
import { join as
|
|
27819
|
+
import { join as join14 } from "path";
|
|
27859
27820
|
import { tmpdir } from "os";
|
|
27860
27821
|
function buildFrpcConfig(config3) {
|
|
27861
27822
|
const lines = [
|
|
@@ -27893,7 +27854,7 @@ async function startTunnel(config3) {
|
|
|
27893
27854
|
return false;
|
|
27894
27855
|
}
|
|
27895
27856
|
const toml = buildFrpcConfig(config3);
|
|
27896
|
-
tmpConfigPath =
|
|
27857
|
+
tmpConfigPath = join14(tmpdir(), `nexus-frpc-${process.pid}.toml`);
|
|
27897
27858
|
writeFileSync2(tmpConfigPath, toml);
|
|
27898
27859
|
tunnelProcess = spawn("frpc", ["-c", tmpConfigPath], {
|
|
27899
27860
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -27984,22 +27945,22 @@ var init_shared = __esm({
|
|
|
27984
27945
|
});
|
|
27985
27946
|
|
|
27986
27947
|
// src/cli/migrate-commands.ts
|
|
27987
|
-
import { readFileSync as
|
|
27988
|
-
import { join as
|
|
27948
|
+
import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
|
|
27949
|
+
import { join as join15 } from "path";
|
|
27989
27950
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
27990
27951
|
import Table from "cli-table3";
|
|
27991
27952
|
import { consola as consola2 } from "consola";
|
|
27992
27953
|
async function loadSelfPlugin() {
|
|
27993
27954
|
const projectPath2 = getProjectPath();
|
|
27994
|
-
const pkgPath =
|
|
27995
|
-
if (!
|
|
27955
|
+
const pkgPath = join15(projectPath2, "package.json");
|
|
27956
|
+
if (!existsSync13(pkgPath)) return;
|
|
27996
27957
|
try {
|
|
27997
|
-
const pkg2 = JSON.parse(
|
|
27958
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
27998
27959
|
const pkgName = pkg2?.name;
|
|
27999
27960
|
if (!pkgName || !/nexus-plugin-/.test(pkgName)) return;
|
|
28000
|
-
const srcEntry =
|
|
28001
|
-
const distEntry =
|
|
28002
|
-
if (
|
|
27961
|
+
const srcEntry = join15(projectPath2, "src", "index.ts");
|
|
27962
|
+
const distEntry = join15(projectPath2, "dist", "index.js");
|
|
27963
|
+
if (existsSync13(srcEntry)) {
|
|
28003
27964
|
try {
|
|
28004
27965
|
const { tsImport } = await import("tsx/esm/api");
|
|
28005
27966
|
const mod = await tsImport(
|
|
@@ -28009,7 +27970,7 @@ async function loadSelfPlugin() {
|
|
|
28009
27970
|
const manifest = extractPluginManifest(mod);
|
|
28010
27971
|
if (manifest) {
|
|
28011
27972
|
if (!manifest.migrationsDir) {
|
|
28012
|
-
manifest.migrationsDir =
|
|
27973
|
+
manifest.migrationsDir = join15(projectPath2, "migrations");
|
|
28013
27974
|
}
|
|
28014
27975
|
registerPlugin(manifest);
|
|
28015
27976
|
return;
|
|
@@ -28018,13 +27979,13 @@ async function loadSelfPlugin() {
|
|
|
28018
27979
|
console.error(` \u26A0 Failed to load plugin src/index.ts: ${err.message}`);
|
|
28019
27980
|
}
|
|
28020
27981
|
}
|
|
28021
|
-
if (
|
|
27982
|
+
if (existsSync13(distEntry)) {
|
|
28022
27983
|
try {
|
|
28023
27984
|
const mod = await import(pathToFileURL3(distEntry).href);
|
|
28024
27985
|
const manifest = extractPluginManifest(mod);
|
|
28025
27986
|
if (manifest) {
|
|
28026
27987
|
if (!manifest.migrationsDir) {
|
|
28027
|
-
manifest.migrationsDir =
|
|
27988
|
+
manifest.migrationsDir = join15(projectPath2, "migrations");
|
|
28028
27989
|
}
|
|
28029
27990
|
registerPlugin(manifest);
|
|
28030
27991
|
return;
|
|
@@ -28074,8 +28035,8 @@ __export(seed_commands_exports, {
|
|
|
28074
28035
|
handleSeedExport: () => handleSeedExport,
|
|
28075
28036
|
importSeedFiles: () => importSeedFiles
|
|
28076
28037
|
});
|
|
28077
|
-
import { existsSync as
|
|
28078
|
-
import { join as
|
|
28038
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
|
|
28039
|
+
import { join as join16, basename as basename4 } from "path";
|
|
28079
28040
|
import { consola as consola3 } from "consola";
|
|
28080
28041
|
function deserializeJsonFields(record, fields) {
|
|
28081
28042
|
const result = { ...record };
|
|
@@ -28104,7 +28065,7 @@ async function handleSeedExport(entity) {
|
|
|
28104
28065
|
const db2 = getDb();
|
|
28105
28066
|
try {
|
|
28106
28067
|
const modules = getOrderedModules();
|
|
28107
|
-
const seedDir =
|
|
28068
|
+
const seedDir = join16(getProjectPath(), "data", "seeds");
|
|
28108
28069
|
const seedableEntities = [];
|
|
28109
28070
|
for (const mod of modules) {
|
|
28110
28071
|
for (const def of mod.definitions ?? []) {
|
|
@@ -28125,7 +28086,7 @@ async function handleSeedExport(entity) {
|
|
|
28125
28086
|
}
|
|
28126
28087
|
return;
|
|
28127
28088
|
}
|
|
28128
|
-
if (!
|
|
28089
|
+
if (!existsSync14(seedDir)) {
|
|
28129
28090
|
mkdirSync6(seedDir, { recursive: true });
|
|
28130
28091
|
}
|
|
28131
28092
|
for (const { module: modName, table, fields } of seedableEntities) {
|
|
@@ -28137,7 +28098,7 @@ async function handleSeedExport(entity) {
|
|
|
28137
28098
|
const exported = rows.map(
|
|
28138
28099
|
(row) => deserializeJsonFields(row, fields)
|
|
28139
28100
|
);
|
|
28140
|
-
const filePath =
|
|
28101
|
+
const filePath = join16(seedDir, `${table}.json`);
|
|
28141
28102
|
writeFileSync3(filePath, JSON.stringify(exported, null, 2) + "\n", "utf-8");
|
|
28142
28103
|
consola3.success(`${table}: exported ${rows.length} records to data/seeds/${table}.json (module: ${modName})`);
|
|
28143
28104
|
}
|
|
@@ -28149,8 +28110,8 @@ async function handleSeedExport(entity) {
|
|
|
28149
28110
|
}
|
|
28150
28111
|
}
|
|
28151
28112
|
async function importSeedFiles(db2, modules, logger2) {
|
|
28152
|
-
const seedDir =
|
|
28153
|
-
if (!
|
|
28113
|
+
const seedDir = join16(getProjectPath(), "data", "seeds");
|
|
28114
|
+
if (!existsSync14(seedDir)) return;
|
|
28154
28115
|
const files = readdirSync2(seedDir).filter((f) => f.endsWith(".json"));
|
|
28155
28116
|
if (files.length === 0) return;
|
|
28156
28117
|
const seedableDefs = /* @__PURE__ */ new Map();
|
|
@@ -28171,8 +28132,8 @@ async function importSeedFiles(db2, modules, logger2) {
|
|
|
28171
28132
|
logger2.debug(`data/seeds/${file}: skipped (entity "${table}" is not seedable)`);
|
|
28172
28133
|
continue;
|
|
28173
28134
|
}
|
|
28174
|
-
const filePath =
|
|
28175
|
-
const raw =
|
|
28135
|
+
const filePath = join16(seedDir, file);
|
|
28136
|
+
const raw = readFileSync10(filePath, "utf-8");
|
|
28176
28137
|
let records;
|
|
28177
28138
|
try {
|
|
28178
28139
|
records = JSON.parse(raw);
|
|
@@ -28418,6 +28379,12 @@ async function runMigrationsAndSeeds(config3) {
|
|
|
28418
28379
|
loadCoreModules();
|
|
28419
28380
|
logger.info({ type: getDatabaseType(), path: getDatabasePath() }, "Database ready");
|
|
28420
28381
|
await ensureSystemTables(getDb());
|
|
28382
|
+
setSecretResolverDb(getDb());
|
|
28383
|
+
await initAuthSecret(logger);
|
|
28384
|
+
const isPM2Cluster = !!(process.env["PM2_HOME"] && process.env["NODE_APP_INSTANCE"]);
|
|
28385
|
+
if (isPM2Cluster && getDatabaseType() === "sqlite") {
|
|
28386
|
+
logger.warn("SQLite detected with PM2 cluster mode. SQLite does not support concurrent writes reliably. Use PostgreSQL for multi-process deployments, or set PM2 instances to 1.");
|
|
28387
|
+
}
|
|
28421
28388
|
const fileConfig = await loadNexusConfig();
|
|
28422
28389
|
const effectiveConfig = { ...config3 };
|
|
28423
28390
|
const programmaticPluginNames = new Set((config3?.plugins ?? []).map((p) => p.name));
|
|
@@ -28672,6 +28639,7 @@ async function stop() {
|
|
|
28672
28639
|
resetConfigCache();
|
|
28673
28640
|
clearCustomCaslRules();
|
|
28674
28641
|
clearSeedPermissions();
|
|
28642
|
+
_resetSecretResolver();
|
|
28675
28643
|
await resetServeSPA();
|
|
28676
28644
|
return;
|
|
28677
28645
|
}
|
|
@@ -28707,6 +28675,7 @@ async function stop() {
|
|
|
28707
28675
|
resetConfigCache();
|
|
28708
28676
|
clearCustomCaslRules();
|
|
28709
28677
|
clearSeedPermissions();
|
|
28678
|
+
_resetSecretResolver();
|
|
28710
28679
|
await resetServeSPA();
|
|
28711
28680
|
currentConfig = void 0;
|
|
28712
28681
|
server = null;
|
|
@@ -28776,6 +28745,7 @@ var init_server = __esm({
|
|
|
28776
28745
|
init_ensure_system_tables();
|
|
28777
28746
|
init_load_config();
|
|
28778
28747
|
init_tunnel();
|
|
28748
|
+
init_secret_resolver();
|
|
28779
28749
|
server = null;
|
|
28780
28750
|
gracefulShutdownRegistered = false;
|
|
28781
28751
|
setupGracefulShutdown();
|