@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/main.js
CHANGED
|
@@ -106,7 +106,7 @@ var init_package = __esm({
|
|
|
106
106
|
"package.json"() {
|
|
107
107
|
package_default = {
|
|
108
108
|
name: "@gzl10/nexus-backend",
|
|
109
|
-
version: "0.
|
|
109
|
+
version: "0.20.0",
|
|
110
110
|
description: "Backend as a Service (BaaS) with Express 5, Knex and CASL",
|
|
111
111
|
type: "module",
|
|
112
112
|
main: "./dist/index.js",
|
|
@@ -9367,18 +9367,20 @@ function createSystemController(ctx) {
|
|
|
9367
9367
|
/**
|
|
9368
9368
|
* GET /system/capabilities
|
|
9369
9369
|
* Public endpoint — no authentication required.
|
|
9370
|
-
*
|
|
9370
|
+
* Resolves all registered capabilities (core + plugin-contributed).
|
|
9371
9371
|
*/
|
|
9372
|
-
getCapabilities(_req, res) {
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9372
|
+
async getCapabilities(_req, res) {
|
|
9373
|
+
try {
|
|
9374
|
+
res.set("Cache-Control", "public, max-age=300");
|
|
9375
|
+
const body = await ctx.capabilities.resolve();
|
|
9376
|
+
res.json(body);
|
|
9377
|
+
} catch (err) {
|
|
9378
|
+
ctx.core.logger.error({ err }, "Failed to resolve capabilities");
|
|
9379
|
+
res.set("Cache-Control", "no-cache");
|
|
9380
|
+
res.status(500).json({
|
|
9381
|
+
error: { code: "INTERNAL_ERROR", message: "Failed to resolve capabilities" }
|
|
9382
|
+
});
|
|
9383
|
+
}
|
|
9382
9384
|
}
|
|
9383
9385
|
};
|
|
9384
9386
|
function toManifestDTO(manifest, req) {
|
|
@@ -9951,8 +9953,8 @@ function registerCoreVars(registry2) {
|
|
|
9951
9953
|
registry2.register("auth", "core", [
|
|
9952
9954
|
{
|
|
9953
9955
|
name: "AUTH_SECRET",
|
|
9954
|
-
description: { en: "JWT signing secret. Must be at least 32 characters.
|
|
9955
|
-
required:
|
|
9956
|
+
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" },
|
|
9957
|
+
required: false,
|
|
9956
9958
|
sensitive: true
|
|
9957
9959
|
},
|
|
9958
9960
|
{
|
|
@@ -10469,96 +10471,70 @@ var init_system = __esm({
|
|
|
10469
10471
|
}
|
|
10470
10472
|
});
|
|
10471
10473
|
|
|
10472
|
-
// src/modules/ui-settings/ui-
|
|
10473
|
-
import { useTextField as useTextField7, useImageField } from "@gzl10/nexus-sdk/fields";
|
|
10474
|
-
var
|
|
10475
|
-
var
|
|
10476
|
-
"src/modules/ui-settings/ui-
|
|
10474
|
+
// src/modules/ui-settings/ui-style-guide.entity.ts
|
|
10475
|
+
import { useTextField as useTextField7, useImageField, useSelectField as useSelectField6, useColorField, useNumberField as useNumberField4, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
|
|
10476
|
+
var uiStyleGuideEntity;
|
|
10477
|
+
var init_ui_style_guide_entity = __esm({
|
|
10478
|
+
"src/modules/ui-settings/ui-style-guide.entity.ts"() {
|
|
10477
10479
|
"use strict";
|
|
10478
|
-
|
|
10480
|
+
uiStyleGuideEntity = {
|
|
10479
10481
|
type: "single",
|
|
10480
10482
|
realtime: "sync",
|
|
10481
|
-
key: "
|
|
10482
|
-
label: { en: "
|
|
10483
|
-
icon: "mdi:palette-
|
|
10483
|
+
key: "ui_style_guide",
|
|
10484
|
+
label: { en: "Style Guide", es: "Gu\xEDa de Estilos" },
|
|
10485
|
+
icon: "mdi:palette-outline",
|
|
10484
10486
|
public: true,
|
|
10485
|
-
routePrefix: "/ui-
|
|
10487
|
+
routePrefix: "/ui-style-guide",
|
|
10486
10488
|
defaults: {
|
|
10487
10489
|
appName: "Nexus",
|
|
10488
10490
|
logo: null,
|
|
10489
10491
|
logoDark: null,
|
|
10490
|
-
favicon: null
|
|
10492
|
+
favicon: null,
|
|
10493
|
+
primaryColor: "#3B82F6",
|
|
10494
|
+
font: "space-grotesk",
|
|
10495
|
+
typographyScale: "default",
|
|
10496
|
+
borderRadius: 8,
|
|
10497
|
+
glassEffect: true,
|
|
10498
|
+
theme: "system",
|
|
10499
|
+
loginLayout: "centered"
|
|
10491
10500
|
},
|
|
10492
10501
|
fields: {
|
|
10493
10502
|
appName: useTextField7({
|
|
10494
10503
|
label: { en: "App Name", es: "Nombre de la App" },
|
|
10495
|
-
hint: { en: "Displayed in
|
|
10504
|
+
hint: { en: "Displayed in header, browser tab and emails", es: "Se muestra en header, pesta\xF1a del navegador y emails" },
|
|
10496
10505
|
size: 100,
|
|
10497
10506
|
required: true
|
|
10498
10507
|
}),
|
|
10499
10508
|
logo: useImageField({
|
|
10500
10509
|
label: { en: "Logo (Light Theme)", es: "Logo (Tema Claro)" },
|
|
10501
|
-
hint: { en: "
|
|
10510
|
+
hint: { en: "SVG or PNG with transparent background, max 200px height", es: "SVG o PNG con fondo transparente, m\xE1x 200px de alto" },
|
|
10502
10511
|
folder: "branding",
|
|
10503
10512
|
isPublic: true,
|
|
10504
10513
|
dedupe: true
|
|
10505
10514
|
}),
|
|
10506
10515
|
logoDark: useImageField({
|
|
10507
10516
|
label: { en: "Logo (Dark Theme)", es: "Logo (Tema Oscuro)" },
|
|
10508
|
-
hint: { en: "
|
|
10517
|
+
hint: { en: "Lighter version for dark backgrounds", es: "Versi\xF3n m\xE1s clara para fondos oscuros" },
|
|
10509
10518
|
folder: "branding",
|
|
10510
10519
|
isPublic: true,
|
|
10511
10520
|
dedupe: true
|
|
10512
10521
|
}),
|
|
10513
10522
|
favicon: useImageField({
|
|
10514
10523
|
label: { en: "Favicon", es: "Favicon" },
|
|
10515
|
-
hint: { en: "
|
|
10524
|
+
hint: { en: "32x32 or 64x64 PNG/ICO", es: "32x32 o 64x64 PNG/ICO" },
|
|
10516
10525
|
accept: "image/x-icon,image/png,image/svg+xml",
|
|
10517
10526
|
maxSize: "256KB",
|
|
10518
10527
|
folder: "branding",
|
|
10519
10528
|
isPublic: true,
|
|
10520
10529
|
dedupe: true
|
|
10521
|
-
})
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
ADMIN: { actions: ["read", "update"] }
|
|
10527
|
-
}
|
|
10528
|
-
}
|
|
10529
|
-
};
|
|
10530
|
-
}
|
|
10531
|
-
});
|
|
10532
|
-
|
|
10533
|
-
// src/modules/ui-settings/ui-theme.entity.ts
|
|
10534
|
-
import { useSelectField as useSelectField6, useColorField } from "@gzl10/nexus-sdk/fields";
|
|
10535
|
-
var uiThemeEntity;
|
|
10536
|
-
var init_ui_theme_entity = __esm({
|
|
10537
|
-
"src/modules/ui-settings/ui-theme.entity.ts"() {
|
|
10538
|
-
"use strict";
|
|
10539
|
-
uiThemeEntity = {
|
|
10540
|
-
type: "single",
|
|
10541
|
-
realtime: "sync",
|
|
10542
|
-
key: "ui_theme",
|
|
10543
|
-
label: { en: "Theme & Colors", es: "Tema y Colores" },
|
|
10544
|
-
icon: "mdi:palette",
|
|
10545
|
-
public: true,
|
|
10546
|
-
routePrefix: "/ui-theme",
|
|
10547
|
-
defaults: {
|
|
10548
|
-
// Typography
|
|
10549
|
-
font: "space-grotesk",
|
|
10550
|
-
// Theme & Colors
|
|
10551
|
-
theme: "system",
|
|
10552
|
-
primaryColor: "#3B82F6",
|
|
10553
|
-
dopamineTheme: "none",
|
|
10554
|
-
// Layout
|
|
10555
|
-
loginLayout: "centered"
|
|
10556
|
-
},
|
|
10557
|
-
fields: {
|
|
10558
|
-
// === Typography ===
|
|
10530
|
+
}),
|
|
10531
|
+
primaryColor: useColorField({
|
|
10532
|
+
label: { en: "Primary Color", es: "Color Principal" },
|
|
10533
|
+
hint: { en: "Accent color for buttons, links and highlights", es: "Color de acento para botones, enlaces y resaltados" }
|
|
10534
|
+
}),
|
|
10559
10535
|
font: useSelectField6({
|
|
10560
10536
|
label: { en: "Font", es: "Fuente" },
|
|
10561
|
-
hint: { en: "Primary font for headings and UI
|
|
10537
|
+
hint: { en: "Primary font for headings and UI", es: "Fuente principal para t\xEDtulos e interfaz" },
|
|
10562
10538
|
options: [
|
|
10563
10539
|
{ value: "space-grotesk", label: "Space Grotesk" },
|
|
10564
10540
|
{ value: "inter", label: "Inter" },
|
|
@@ -10568,37 +10544,38 @@ var init_ui_theme_entity = __esm({
|
|
|
10568
10544
|
{ value: "system", label: { en: "System Default", es: "Sistema" } }
|
|
10569
10545
|
]
|
|
10570
10546
|
}),
|
|
10571
|
-
|
|
10547
|
+
typographyScale: useSelectField6({
|
|
10548
|
+
label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
|
|
10549
|
+
hint: { en: "Font size scaling (WCAG 1.4.4)", es: "Escala de tama\xF1os de fuente (WCAG 1.4.4)" },
|
|
10550
|
+
options: [
|
|
10551
|
+
{ value: "compact", label: { en: "Compact", es: "Compacta" } },
|
|
10552
|
+
{ value: "default", label: { en: "Default", es: "Por defecto" } },
|
|
10553
|
+
{ value: "relaxed", label: { en: "Relaxed", es: "Relajada" } }
|
|
10554
|
+
]
|
|
10555
|
+
}),
|
|
10556
|
+
borderRadius: useNumberField4({
|
|
10557
|
+
label: { en: "Border Radius", es: "Radio de Bordes" },
|
|
10558
|
+
hint: { en: "Corner roundness in pixels (0 = sharp, 16 = very round)", es: "Redondeo de esquinas en p\xEDxeles (0 = cuadrado, 16 = muy redondo)" },
|
|
10559
|
+
defaultValue: 8,
|
|
10560
|
+
validation: { min: 0, max: 16 }
|
|
10561
|
+
}),
|
|
10562
|
+
glassEffect: useSwitchField2({
|
|
10563
|
+
label: { en: "Glass Effect", es: "Efecto Glass" },
|
|
10564
|
+
hint: { en: "Glassmorphism blur on cards and modals", es: "Desenfoque glassmorphism en cards y modales" },
|
|
10565
|
+
defaultValue: true
|
|
10566
|
+
}),
|
|
10572
10567
|
theme: useSelectField6({
|
|
10573
10568
|
label: { en: "Theme", es: "Tema" },
|
|
10574
|
-
hint: { en: "System follows
|
|
10569
|
+
hint: { en: "System follows device preferences", es: "Sistema sigue las preferencias del dispositivo" },
|
|
10575
10570
|
options: [
|
|
10576
10571
|
{ value: "light", label: { en: "Light", es: "Claro" } },
|
|
10577
10572
|
{ value: "dark", label: { en: "Dark", es: "Oscuro" } },
|
|
10578
10573
|
{ value: "system", label: { en: "System", es: "Sistema" } }
|
|
10579
10574
|
]
|
|
10580
10575
|
}),
|
|
10581
|
-
dopamineTheme: useSelectField6({
|
|
10582
|
-
label: { en: "Dopamine Theme", es: "Tema Dopamina" },
|
|
10583
|
-
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)" },
|
|
10584
|
-
options: [
|
|
10585
|
-
{ value: "none", label: { en: "None (Custom Color)", es: "Ninguno (Color personalizado)" } },
|
|
10586
|
-
{ value: "electric", label: { en: "Electric (Cobalt Blue)", es: "El\xE9ctrico (Azul Cobalto)" } },
|
|
10587
|
-
{ value: "sunset", label: { en: "Sunset (Coral Red)", es: "Atardecer (Rojo Coral)" } },
|
|
10588
|
-
{ value: "ocean", label: { en: "Ocean (Teal)", es: "Oc\xE9ano (Verde Azulado)" } },
|
|
10589
|
-
{ value: "forest", label: { en: "Forest (Mint)", es: "Bosque (Menta)" } },
|
|
10590
|
-
{ value: "lavender", label: { en: "Lavender (Violet)", es: "Lavanda (Violeta)" } },
|
|
10591
|
-
{ value: "cherry", label: { en: "Cherry (Fuchsia)", es: "Cereza (Fucsia)" } },
|
|
10592
|
-
{ value: "amber", label: { en: "Amber (Golden)", es: "\xC1mbar (Dorado)" } },
|
|
10593
|
-
{ value: "tangerine", label: { en: "Tangerine (Orange)", es: "Mandarina (Naranja)" } },
|
|
10594
|
-
{ value: "slate", label: { en: "Slate (Cool Gray)", es: "Pizarra (Gris Fr\xEDo)" } },
|
|
10595
|
-
{ value: "bronze", label: { en: "Bronze (Earth)", es: "Bronce (Tierra)" } }
|
|
10596
|
-
]
|
|
10597
|
-
}),
|
|
10598
|
-
// === Login Layout ===
|
|
10599
10576
|
loginLayout: useSelectField6({
|
|
10600
10577
|
label: { en: "Login Layout", es: "Dise\xF1o de Login" },
|
|
10601
|
-
hint: { en: "
|
|
10578
|
+
hint: { en: "Auth page visual layout", es: "Dise\xF1o visual de p\xE1ginas de autenticaci\xF3n" },
|
|
10602
10579
|
options: [
|
|
10603
10580
|
{ value: "centered", label: { en: "Centered", es: "Centrado" } },
|
|
10604
10581
|
{ value: "split", label: { en: "Split", es: "Dividido" } },
|
|
@@ -10606,140 +10583,10 @@ var init_ui_theme_entity = __esm({
|
|
|
10606
10583
|
{ value: "floating", label: { en: "Floating", es: "Flotante" } },
|
|
10607
10584
|
{ value: "minimal", label: { en: "Minimal", es: "Minimalista" } }
|
|
10608
10585
|
]
|
|
10609
|
-
}),
|
|
10610
|
-
primaryColor: {
|
|
10611
|
-
...useColorField({
|
|
10612
|
-
label: { en: "Primary Color", es: "Color Principal" },
|
|
10613
|
-
hint: { en: "Custom accent color for buttons, links and highlights", es: "Color de acento personalizado para botones, enlaces y resaltados" }
|
|
10614
|
-
}),
|
|
10615
|
-
// Hide when a dopamine theme is active (show only if 'none')
|
|
10616
|
-
hidden: { field: "dopamineTheme", $ne: "none" }
|
|
10617
|
-
}
|
|
10618
|
-
},
|
|
10619
|
-
casl: {
|
|
10620
|
-
subject: "UiTheme",
|
|
10621
|
-
permissions: {
|
|
10622
|
-
ADMIN: { actions: ["read", "update"] }
|
|
10623
|
-
}
|
|
10624
|
-
}
|
|
10625
|
-
};
|
|
10626
|
-
}
|
|
10627
|
-
});
|
|
10628
|
-
|
|
10629
|
-
// src/modules/ui-settings/ui-effects.entity.ts
|
|
10630
|
-
import { useSelectField as useSelectField7, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
|
|
10631
|
-
var uiEffectsEntity;
|
|
10632
|
-
var init_ui_effects_entity = __esm({
|
|
10633
|
-
"src/modules/ui-settings/ui-effects.entity.ts"() {
|
|
10634
|
-
"use strict";
|
|
10635
|
-
uiEffectsEntity = {
|
|
10636
|
-
type: "single",
|
|
10637
|
-
realtime: "sync",
|
|
10638
|
-
key: "ui_effects",
|
|
10639
|
-
label: { en: "Visual Effects", es: "Efectos Visuales" },
|
|
10640
|
-
icon: "mdi:shimmer",
|
|
10641
|
-
public: true,
|
|
10642
|
-
routePrefix: "/ui-effects",
|
|
10643
|
-
defaults: {
|
|
10644
|
-
glassIntensity: "medium",
|
|
10645
|
-
borderStyle: "rounded",
|
|
10646
|
-
enableAnimations: true,
|
|
10647
|
-
enableOrganicShapes: false
|
|
10648
|
-
},
|
|
10649
|
-
fields: {
|
|
10650
|
-
glassIntensity: useSelectField7({
|
|
10651
|
-
label: { en: "Glass Intensity", es: "Intensidad Glass" },
|
|
10652
|
-
hint: { en: "Glassmorphism blur effect on cards and modals", es: "Efecto de desenfoque glassmorphism en cards y modales" },
|
|
10653
|
-
options: [
|
|
10654
|
-
{ value: "none", label: { en: "None", es: "Ninguno" } },
|
|
10655
|
-
{ value: "low", label: { en: "Low", es: "Baja" } },
|
|
10656
|
-
{ value: "medium", label: { en: "Medium", es: "Media" } },
|
|
10657
|
-
{ value: "high", label: { en: "High", es: "Alta" } }
|
|
10658
|
-
]
|
|
10659
|
-
}),
|
|
10660
|
-
borderStyle: useSelectField7({
|
|
10661
|
-
label: { en: "Border Style", es: "Estilo de Bordes" },
|
|
10662
|
-
hint: { en: "Corner radius for buttons, cards and inputs", es: "Radio de esquinas para botones, cards e inputs" },
|
|
10663
|
-
options: [
|
|
10664
|
-
{ value: "sharp", label: { en: "Sharp", es: "Cuadrados" } },
|
|
10665
|
-
{ value: "rounded", label: { en: "Rounded", es: "Redondeados" } },
|
|
10666
|
-
{ value: "organic", label: { en: "Organic", es: "Org\xE1nicos" } }
|
|
10667
|
-
]
|
|
10668
|
-
}),
|
|
10669
|
-
enableAnimations: useSwitchField2({
|
|
10670
|
-
label: { en: "Enable Animations", es: "Habilitar Animaciones" },
|
|
10671
|
-
hint: { en: "Micro-interactions and transitions (hover, focus, page changes)", es: "Micro-interacciones y transiciones (hover, focus, cambios de p\xE1gina)" },
|
|
10672
|
-
defaultValue: true,
|
|
10673
|
-
meta: { sortable: true }
|
|
10674
|
-
}),
|
|
10675
|
-
enableOrganicShapes: useSwitchField2({
|
|
10676
|
-
label: { en: "Enable Organic Shapes", es: "Habilitar Formas Org\xE1nicas" },
|
|
10677
|
-
hint: { en: "Asymmetric border-radius for a more natural, playful look", es: "Border-radius asim\xE9trico para un aspecto m\xE1s natural y divertido" },
|
|
10678
|
-
defaultValue: false,
|
|
10679
|
-
meta: { sortable: true },
|
|
10680
|
-
hidden: { field: "borderStyle", $ne: "organic" }
|
|
10681
|
-
})
|
|
10682
|
-
},
|
|
10683
|
-
casl: {
|
|
10684
|
-
subject: "UiEffects",
|
|
10685
|
-
permissions: {
|
|
10686
|
-
ADMIN: { actions: ["read", "update"] }
|
|
10687
|
-
}
|
|
10688
|
-
}
|
|
10689
|
-
};
|
|
10690
|
-
}
|
|
10691
|
-
});
|
|
10692
|
-
|
|
10693
|
-
// src/modules/ui-settings/ui-accessibility.entity.ts
|
|
10694
|
-
import { useSelectField as useSelectField8, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
|
|
10695
|
-
var uiAccessibilityEntity;
|
|
10696
|
-
var init_ui_accessibility_entity = __esm({
|
|
10697
|
-
"src/modules/ui-settings/ui-accessibility.entity.ts"() {
|
|
10698
|
-
"use strict";
|
|
10699
|
-
uiAccessibilityEntity = {
|
|
10700
|
-
type: "single",
|
|
10701
|
-
realtime: "sync",
|
|
10702
|
-
key: "ui_accessibility",
|
|
10703
|
-
label: { en: "Accessibility", es: "Accesibilidad" },
|
|
10704
|
-
icon: "mdi:human",
|
|
10705
|
-
public: true,
|
|
10706
|
-
routePrefix: "/ui-accessibility",
|
|
10707
|
-
defaults: {
|
|
10708
|
-
typographyScale: "default",
|
|
10709
|
-
reducedMotion: false,
|
|
10710
|
-
highContrast: false
|
|
10711
|
-
},
|
|
10712
|
-
fields: {
|
|
10713
|
-
typographyScale: useSelectField8({
|
|
10714
|
-
label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
|
|
10715
|
-
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)" },
|
|
10716
|
-
options: [
|
|
10717
|
-
{ value: "compact", label: { en: "Compact (smaller)", es: "Compacta (m\xE1s peque\xF1a)" } },
|
|
10718
|
-
{ value: "default", label: { en: "Default", es: "Por defecto" } },
|
|
10719
|
-
{ value: "relaxed", label: { en: "Relaxed (larger)", es: "Relajada (m\xE1s grande)" } }
|
|
10720
|
-
]
|
|
10721
|
-
}),
|
|
10722
|
-
reducedMotion: useSwitchField3({
|
|
10723
|
-
label: { en: "Reduced Motion", es: "Movimiento Reducido" },
|
|
10724
|
-
defaultValue: false,
|
|
10725
|
-
meta: { sortable: true },
|
|
10726
|
-
hint: {
|
|
10727
|
-
en: "Minimizes animations for users sensitive to motion (WCAG 2.1)",
|
|
10728
|
-
es: "Minimiza animaciones para usuarios sensibles al movimiento (WCAG 2.1)"
|
|
10729
|
-
}
|
|
10730
|
-
}),
|
|
10731
|
-
highContrast: useSwitchField3({
|
|
10732
|
-
label: { en: "High Contrast", es: "Alto Contraste" },
|
|
10733
|
-
defaultValue: false,
|
|
10734
|
-
meta: { sortable: true },
|
|
10735
|
-
hint: {
|
|
10736
|
-
en: "Increases text contrast for better readability (WCAG AAA)",
|
|
10737
|
-
es: "Aumenta el contraste del texto para mejor legibilidad (WCAG AAA)"
|
|
10738
|
-
}
|
|
10739
10586
|
})
|
|
10740
10587
|
},
|
|
10741
10588
|
casl: {
|
|
10742
|
-
subject: "
|
|
10589
|
+
subject: "UiStyleGuide",
|
|
10743
10590
|
permissions: {
|
|
10744
10591
|
ADMIN: { actions: ["read", "update"] }
|
|
10745
10592
|
}
|
|
@@ -10749,44 +10596,62 @@ var init_ui_accessibility_entity = __esm({
|
|
|
10749
10596
|
});
|
|
10750
10597
|
|
|
10751
10598
|
// src/modules/ui-settings/index.ts
|
|
10599
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
10600
|
+
import { join as join6 } from "path";
|
|
10752
10601
|
var uiSettingsModule;
|
|
10753
10602
|
var init_ui_settings = __esm({
|
|
10754
10603
|
"src/modules/ui-settings/index.ts"() {
|
|
10755
10604
|
"use strict";
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
init_ui_effects_entity();
|
|
10759
|
-
init_ui_accessibility_entity();
|
|
10760
|
-
init_ui_branding_entity();
|
|
10761
|
-
init_ui_theme_entity();
|
|
10762
|
-
init_ui_effects_entity();
|
|
10763
|
-
init_ui_accessibility_entity();
|
|
10605
|
+
init_ui_style_guide_entity();
|
|
10606
|
+
init_ui_style_guide_entity();
|
|
10764
10607
|
uiSettingsModule = {
|
|
10765
10608
|
name: "ui-settings",
|
|
10766
|
-
label: { en: "
|
|
10609
|
+
label: { en: "Style Guide", es: "Gu\xEDa de Estilos" },
|
|
10767
10610
|
icon: "mdi:palette-outline",
|
|
10768
10611
|
description: {
|
|
10769
|
-
en: "
|
|
10770
|
-
es: "Configuraci\xF3n de
|
|
10612
|
+
en: "Brand identity and visual style configuration",
|
|
10613
|
+
es: "Configuraci\xF3n de identidad de marca y estilo visual"
|
|
10771
10614
|
},
|
|
10772
10615
|
type: "core",
|
|
10773
10616
|
category: "settings",
|
|
10774
10617
|
dependencies: ["logger"],
|
|
10775
|
-
definitions: [
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10618
|
+
definitions: [uiStyleGuideEntity],
|
|
10619
|
+
routePrefix: "/ui-settings",
|
|
10620
|
+
/**
|
|
10621
|
+
* Seed style guide from data/seeds/ui-style-guide.json if it exists.
|
|
10622
|
+
* Idempotent: only inserts if no record exists yet.
|
|
10623
|
+
*
|
|
10624
|
+
* Workflow:
|
|
10625
|
+
* 1. Configure style guide in dev via admin UI
|
|
10626
|
+
* 2. Export: GET /api/v1/ui-settings/ui-style-guide → save to data/seeds/ui-style-guide.json
|
|
10627
|
+
* 3. Commit the seed file
|
|
10628
|
+
* 4. On other environments, seed runs automatically on startup
|
|
10629
|
+
*/
|
|
10630
|
+
seed: async (ctx) => {
|
|
10631
|
+
const db2 = ctx.db.knex;
|
|
10632
|
+
const existing = await db2("single_records").where("key", "ui_style_guide").first();
|
|
10633
|
+
if (existing) return;
|
|
10634
|
+
const seedPath = join6(process.cwd(), "data", "seeds", "ui-style-guide.json");
|
|
10635
|
+
if (!existsSync5(seedPath)) return;
|
|
10636
|
+
try {
|
|
10637
|
+
const seedData = JSON.parse(readFileSync4(seedPath, "utf-8"));
|
|
10638
|
+
await db2("single_records").insert({
|
|
10639
|
+
key: "ui_style_guide",
|
|
10640
|
+
value: JSON.stringify(seedData)
|
|
10641
|
+
});
|
|
10642
|
+
ctx.core.logger.info("Seeded ui_style_guide from data/seeds/ui-style-guide.json");
|
|
10643
|
+
} catch {
|
|
10644
|
+
ctx.core.logger.warn("Failed to parse data/seeds/ui-style-guide.json, skipping seed");
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10782
10647
|
};
|
|
10783
10648
|
}
|
|
10784
10649
|
});
|
|
10785
10650
|
|
|
10786
10651
|
// src/modules/storage/drivers/filesystem.driver.ts
|
|
10787
|
-
import { createReadStream, createWriteStream, existsSync as
|
|
10652
|
+
import { createReadStream, createWriteStream, existsSync as existsSync6, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
10788
10653
|
import { readFile, readdir, stat, copyFile, rename, mkdir } from "fs/promises";
|
|
10789
|
-
import { join as
|
|
10654
|
+
import { join as join7, dirname as dirname4, extname, basename } from "path";
|
|
10790
10655
|
import { createHash } from "crypto";
|
|
10791
10656
|
var FilesystemDriver;
|
|
10792
10657
|
var init_filesystem_driver = __esm({
|
|
@@ -10800,7 +10665,7 @@ var init_filesystem_driver = __esm({
|
|
|
10800
10665
|
this.basePath = config3.basePath;
|
|
10801
10666
|
this.baseUrl = config3.baseUrl;
|
|
10802
10667
|
this.generateId = config3.generateId;
|
|
10803
|
-
if (!
|
|
10668
|
+
if (!existsSync6(this.basePath)) {
|
|
10804
10669
|
mkdirSync2(this.basePath, { recursive: true });
|
|
10805
10670
|
}
|
|
10806
10671
|
}
|
|
@@ -10810,9 +10675,9 @@ var init_filesystem_driver = __esm({
|
|
|
10810
10675
|
const diskFilename = `${id}${ext}`;
|
|
10811
10676
|
const folder = options?.folder || "";
|
|
10812
10677
|
const relativePath = folder ? `${folder}/${diskFilename}` : diskFilename;
|
|
10813
|
-
const fullPath =
|
|
10678
|
+
const fullPath = join7(this.basePath, relativePath);
|
|
10814
10679
|
const dir = dirname4(fullPath);
|
|
10815
|
-
if (!
|
|
10680
|
+
if (!existsSync6(dir)) {
|
|
10816
10681
|
mkdirSync2(dir, { recursive: true });
|
|
10817
10682
|
}
|
|
10818
10683
|
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
@@ -10836,25 +10701,25 @@ var init_filesystem_driver = __esm({
|
|
|
10836
10701
|
};
|
|
10837
10702
|
}
|
|
10838
10703
|
async get(path4) {
|
|
10839
|
-
const fullPath =
|
|
10840
|
-
if (!
|
|
10704
|
+
const fullPath = join7(this.basePath, path4);
|
|
10705
|
+
if (!existsSync6(fullPath)) {
|
|
10841
10706
|
throw new Error(`File not found: ${path4}`);
|
|
10842
10707
|
}
|
|
10843
10708
|
return createReadStream(fullPath);
|
|
10844
10709
|
}
|
|
10845
10710
|
async getBuffer(path4) {
|
|
10846
|
-
const fullPath =
|
|
10711
|
+
const fullPath = join7(this.basePath, path4);
|
|
10847
10712
|
return readFile(fullPath);
|
|
10848
10713
|
}
|
|
10849
10714
|
async delete(path4) {
|
|
10850
|
-
const fullPath =
|
|
10851
|
-
if (
|
|
10715
|
+
const fullPath = join7(this.basePath, path4);
|
|
10716
|
+
if (existsSync6(fullPath)) {
|
|
10852
10717
|
unlinkSync(fullPath);
|
|
10853
10718
|
}
|
|
10854
10719
|
}
|
|
10855
10720
|
async exists(path4) {
|
|
10856
|
-
const fullPath =
|
|
10857
|
-
return
|
|
10721
|
+
const fullPath = join7(this.basePath, path4);
|
|
10722
|
+
return existsSync6(fullPath);
|
|
10858
10723
|
}
|
|
10859
10724
|
getUrl(path4) {
|
|
10860
10725
|
if (!this.baseUrl) return null;
|
|
@@ -10864,14 +10729,14 @@ var init_filesystem_driver = __esm({
|
|
|
10864
10729
|
// EXTENDED METHODS
|
|
10865
10730
|
// ============================================================================
|
|
10866
10731
|
async list(folder) {
|
|
10867
|
-
const targetPath = folder ?
|
|
10868
|
-
if (!
|
|
10732
|
+
const targetPath = folder ? join7(this.basePath, folder) : this.basePath;
|
|
10733
|
+
if (!existsSync6(targetPath)) {
|
|
10869
10734
|
return [];
|
|
10870
10735
|
}
|
|
10871
10736
|
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
10872
10737
|
const results = [];
|
|
10873
10738
|
for (const entry of entries) {
|
|
10874
|
-
const entryPath =
|
|
10739
|
+
const entryPath = join7(targetPath, entry.name);
|
|
10875
10740
|
const relativePath = folder ? `${folder}/${entry.name}` : entry.name;
|
|
10876
10741
|
const stats = await stat(entryPath);
|
|
10877
10742
|
results.push({
|
|
@@ -10885,10 +10750,10 @@ var init_filesystem_driver = __esm({
|
|
|
10885
10750
|
return results;
|
|
10886
10751
|
}
|
|
10887
10752
|
async copy(src, dst) {
|
|
10888
|
-
const srcPath =
|
|
10889
|
-
const dstPath =
|
|
10753
|
+
const srcPath = join7(this.basePath, src);
|
|
10754
|
+
const dstPath = join7(this.basePath, dst);
|
|
10890
10755
|
const dstDir = dirname4(dstPath);
|
|
10891
|
-
if (!
|
|
10756
|
+
if (!existsSync6(dstDir)) {
|
|
10892
10757
|
await mkdir(dstDir, { recursive: true });
|
|
10893
10758
|
}
|
|
10894
10759
|
await copyFile(srcPath, dstPath);
|
|
@@ -10911,10 +10776,10 @@ var init_filesystem_driver = __esm({
|
|
|
10911
10776
|
};
|
|
10912
10777
|
}
|
|
10913
10778
|
async move(src, dst) {
|
|
10914
|
-
const srcPath =
|
|
10915
|
-
const dstPath =
|
|
10779
|
+
const srcPath = join7(this.basePath, src);
|
|
10780
|
+
const dstPath = join7(this.basePath, dst);
|
|
10916
10781
|
const dstDir = dirname4(dstPath);
|
|
10917
|
-
if (!
|
|
10782
|
+
if (!existsSync6(dstDir)) {
|
|
10918
10783
|
await mkdir(dstDir, { recursive: true });
|
|
10919
10784
|
}
|
|
10920
10785
|
await rename(srcPath, dstPath);
|
|
@@ -10937,7 +10802,7 @@ var init_filesystem_driver = __esm({
|
|
|
10937
10802
|
};
|
|
10938
10803
|
}
|
|
10939
10804
|
async getMetadata(path4) {
|
|
10940
|
-
const fullPath =
|
|
10805
|
+
const fullPath = join7(this.basePath, path4);
|
|
10941
10806
|
const stats = await stat(fullPath);
|
|
10942
10807
|
return {
|
|
10943
10808
|
path: path4,
|
|
@@ -10951,8 +10816,8 @@ var init_filesystem_driver = __esm({
|
|
|
10951
10816
|
throw new Error("Signed URLs are not supported by the filesystem driver");
|
|
10952
10817
|
}
|
|
10953
10818
|
async createFolder(path4) {
|
|
10954
|
-
const fullPath =
|
|
10955
|
-
if (!
|
|
10819
|
+
const fullPath = join7(this.basePath, path4);
|
|
10820
|
+
if (!existsSync6(fullPath)) {
|
|
10956
10821
|
await mkdir(fullPath, { recursive: true });
|
|
10957
10822
|
}
|
|
10958
10823
|
}
|
|
@@ -11183,8 +11048,8 @@ var init_s3_driver = __esm({
|
|
|
11183
11048
|
});
|
|
11184
11049
|
|
|
11185
11050
|
// src/modules/storage/storage.config.ts
|
|
11186
|
-
import { join as
|
|
11187
|
-
import { existsSync as
|
|
11051
|
+
import { join as join8, isAbsolute } from "path";
|
|
11052
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
11188
11053
|
function getDefaultScope(driver) {
|
|
11189
11054
|
return driver === "s3" ? DEFAULT_S3_SCOPE : DEFAULT_FILESYSTEM_SCOPE;
|
|
11190
11055
|
}
|
|
@@ -11199,7 +11064,7 @@ function resolveStoragePath(path4, projPath) {
|
|
|
11199
11064
|
return path4;
|
|
11200
11065
|
}
|
|
11201
11066
|
const cleanPath = path4.startsWith("./") ? path4.slice(2) : path4;
|
|
11202
|
-
return
|
|
11067
|
+
return join8(projPath, "data", cleanPath);
|
|
11203
11068
|
}
|
|
11204
11069
|
async function getConfigByScope(db2, scope) {
|
|
11205
11070
|
if (!generateIdFn) {
|
|
@@ -11230,7 +11095,7 @@ function buildConfigFromRow(row) {
|
|
|
11230
11095
|
if (row.driver === "filesystem") {
|
|
11231
11096
|
const fsMeta = metadata;
|
|
11232
11097
|
config3.basePath = resolveStoragePath(fsMeta.basePath || "./storage", projectPath);
|
|
11233
|
-
if (!
|
|
11098
|
+
if (!existsSync7(config3.basePath)) {
|
|
11234
11099
|
mkdirSync3(config3.basePath, { recursive: true });
|
|
11235
11100
|
}
|
|
11236
11101
|
} else if (row.driver === "s3") {
|
|
@@ -11262,7 +11127,7 @@ function buildConfigFromEnv(driver) {
|
|
|
11262
11127
|
if (driver === "filesystem") {
|
|
11263
11128
|
const rawPath = process.env["STORAGE_PATH"] || "./storage";
|
|
11264
11129
|
config3.basePath = resolveStoragePath(rawPath, projectPath);
|
|
11265
|
-
if (!
|
|
11130
|
+
if (!existsSync7(config3.basePath)) {
|
|
11266
11131
|
mkdirSync3(config3.basePath, { recursive: true });
|
|
11267
11132
|
}
|
|
11268
11133
|
} else if (driver === "s3") {
|
|
@@ -11701,7 +11566,7 @@ var init_storage_service = __esm({
|
|
|
11701
11566
|
});
|
|
11702
11567
|
|
|
11703
11568
|
// src/modules/storage/storage.entity.ts
|
|
11704
|
-
import { useIdField as useIdField3, useTextField as useTextField8, useSelectField as
|
|
11569
|
+
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";
|
|
11705
11570
|
var DEFAULT_MAX_SIZE2, storageConfigEntity, storageFilesEntity;
|
|
11706
11571
|
var init_storage_entity = __esm({
|
|
11707
11572
|
"src/modules/storage/storage.entity.ts"() {
|
|
@@ -11736,7 +11601,7 @@ var init_storage_entity = __esm({
|
|
|
11736
11601
|
hint: { en: "Unique identifier (e.g. default_filesystem, default_s3)", es: "Identificador \xFAnico (ej: default_filesystem, default_s3)" }
|
|
11737
11602
|
}),
|
|
11738
11603
|
driver: {
|
|
11739
|
-
...
|
|
11604
|
+
...useSelectField7({
|
|
11740
11605
|
label: { en: "Driver", es: "Controlador" },
|
|
11741
11606
|
required: true,
|
|
11742
11607
|
hint: { en: "Storage backend", es: "Backend de almacenamiento" },
|
|
@@ -11753,7 +11618,7 @@ var init_storage_entity = __esm({
|
|
|
11753
11618
|
size: 500,
|
|
11754
11619
|
nullable: true
|
|
11755
11620
|
}),
|
|
11756
|
-
max_file_size:
|
|
11621
|
+
max_file_size: useNumberField5({
|
|
11757
11622
|
label: { en: "Max File Size (bytes)", es: "Tama\xF1o m\xE1ximo de archivo (bytes)" },
|
|
11758
11623
|
hint: { en: "Maximum size in bytes (default: 10MB = 10485760)", es: "Tama\xF1o m\xE1ximo en bytes (default: 10MB = 10485760)" },
|
|
11759
11624
|
nullable: false,
|
|
@@ -11946,7 +11811,7 @@ var init_storage_entity = __esm({
|
|
|
11946
11811
|
index: true,
|
|
11947
11812
|
meta: { sortable: true, searchable: true }
|
|
11948
11813
|
}),
|
|
11949
|
-
size:
|
|
11814
|
+
size: useNumberField5({
|
|
11950
11815
|
label: { en: "Size", es: "Tama\xF1o" },
|
|
11951
11816
|
required: true,
|
|
11952
11817
|
nullable: false,
|
|
@@ -12488,7 +12353,7 @@ var init_storage = __esm({
|
|
|
12488
12353
|
});
|
|
12489
12354
|
|
|
12490
12355
|
// src/modules/users/users.entity.ts
|
|
12491
|
-
import { useIdField as useIdField4, useSelectField as
|
|
12356
|
+
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";
|
|
12492
12357
|
import { z as z3 } from "zod";
|
|
12493
12358
|
var userEntity, roleEntity, userRoleEntity;
|
|
12494
12359
|
var init_users_entity = __esm({
|
|
@@ -12562,7 +12427,7 @@ var init_users_entity = __esm({
|
|
|
12562
12427
|
label: { en: "Marketing Opt-in", es: "Aceptar marketing" },
|
|
12563
12428
|
meta: { exportable: true, showInForm: false, showInDisplay: false }
|
|
12564
12429
|
}),
|
|
12565
|
-
locale:
|
|
12430
|
+
locale: useSelectField8({
|
|
12566
12431
|
label: { en: "Language", es: "Idioma" },
|
|
12567
12432
|
options: [
|
|
12568
12433
|
{ value: "es", label: { en: "Spanish", es: "Espa\xF1ol" } },
|
|
@@ -12572,13 +12437,13 @@ var init_users_entity = __esm({
|
|
|
12572
12437
|
meta: { sortable: true },
|
|
12573
12438
|
defaultValue: "en"
|
|
12574
12439
|
}),
|
|
12575
|
-
timezone:
|
|
12440
|
+
timezone: useSelectField8({
|
|
12576
12441
|
label: { en: "Timezone", es: "Zona horaria" },
|
|
12577
12442
|
master: "timezones",
|
|
12578
12443
|
meta: { sortable: true },
|
|
12579
12444
|
defaultValue: "timezones:Europe/Madrid"
|
|
12580
12445
|
}),
|
|
12581
|
-
type:
|
|
12446
|
+
type: useSelectField8({
|
|
12582
12447
|
label: { en: "Type", es: "Tipo" },
|
|
12583
12448
|
defaultValue: "human",
|
|
12584
12449
|
options: [
|
|
@@ -12747,7 +12612,7 @@ var init_users_entity = __esm({
|
|
|
12747
12612
|
expose: false,
|
|
12748
12613
|
fields: {
|
|
12749
12614
|
id: useIdField4(),
|
|
12750
|
-
user_id:
|
|
12615
|
+
user_id: useSelectField8({
|
|
12751
12616
|
label: { en: "User", es: "Usuario" },
|
|
12752
12617
|
required: true,
|
|
12753
12618
|
table: "users",
|
|
@@ -12758,7 +12623,7 @@ var init_users_entity = __esm({
|
|
|
12758
12623
|
labelField: "name",
|
|
12759
12624
|
meta: { searchable: true }
|
|
12760
12625
|
}),
|
|
12761
|
-
role_id:
|
|
12626
|
+
role_id: useSelectField8({
|
|
12762
12627
|
label: { en: "Role", es: "Rol" },
|
|
12763
12628
|
required: true,
|
|
12764
12629
|
table: "roles",
|
|
@@ -13702,7 +13567,7 @@ var init_users = __esm({
|
|
|
13702
13567
|
});
|
|
13703
13568
|
|
|
13704
13569
|
// src/modules/auth/auth.entity.ts
|
|
13705
|
-
import { useIdField as useIdField5, useTextField as useTextField10, useSelectField as
|
|
13570
|
+
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";
|
|
13706
13571
|
var refreshTokenEntity, authIdentitiesEntity;
|
|
13707
13572
|
var init_auth_entity = __esm({
|
|
13708
13573
|
"src/modules/auth/auth.entity.ts"() {
|
|
@@ -13783,7 +13648,7 @@ var init_auth_entity = __esm({
|
|
|
13783
13648
|
order: 5,
|
|
13784
13649
|
fields: {
|
|
13785
13650
|
id: useIdField5(),
|
|
13786
|
-
user_id:
|
|
13651
|
+
user_id: useSelectField9({
|
|
13787
13652
|
label: { en: "User", es: "Usuario" },
|
|
13788
13653
|
table: "users",
|
|
13789
13654
|
column: "id",
|
|
@@ -13867,8 +13732,327 @@ var init_auth_routes = __esm({
|
|
|
13867
13732
|
}
|
|
13868
13733
|
});
|
|
13869
13734
|
|
|
13870
|
-
// src/
|
|
13735
|
+
// src/config/env.ts
|
|
13871
13736
|
import { z as z5 } from "zod";
|
|
13737
|
+
function resolveConfig() {
|
|
13738
|
+
env = envSchema.parse(process.env);
|
|
13739
|
+
process.env["TZ"] = env.TZ;
|
|
13740
|
+
resolvedConfig = {
|
|
13741
|
+
nodeEnv: env.NODE_ENV,
|
|
13742
|
+
port: env.PORT,
|
|
13743
|
+
host: "0.0.0.0",
|
|
13744
|
+
ui: {
|
|
13745
|
+
enabled: env.NEXUS_UI_ENABLED,
|
|
13746
|
+
base: env.NEXUS_UI_BASE,
|
|
13747
|
+
path: env.NEXUS_UI_PATH
|
|
13748
|
+
},
|
|
13749
|
+
corsOrigin: env.CORS_ORIGIN,
|
|
13750
|
+
databaseUrl: env.DATABASE_URL,
|
|
13751
|
+
adminEmail: env.ADMIN_EMAIL,
|
|
13752
|
+
adminPassword: env.ADMIN_PASSWORD,
|
|
13753
|
+
timezone: env.TZ
|
|
13754
|
+
};
|
|
13755
|
+
return resolvedConfig;
|
|
13756
|
+
}
|
|
13757
|
+
function getConfig() {
|
|
13758
|
+
if (!resolvedConfig) {
|
|
13759
|
+
return resolveConfig();
|
|
13760
|
+
}
|
|
13761
|
+
return resolvedConfig;
|
|
13762
|
+
}
|
|
13763
|
+
function resetConfig() {
|
|
13764
|
+
resolvedConfig = null;
|
|
13765
|
+
}
|
|
13766
|
+
var envSchema, env, resolvedConfig;
|
|
13767
|
+
var init_env = __esm({
|
|
13768
|
+
"src/config/env.ts"() {
|
|
13769
|
+
"use strict";
|
|
13770
|
+
envSchema = z5.object({
|
|
13771
|
+
NODE_ENV: z5.enum(["development", "production", "test"]).default("development"),
|
|
13772
|
+
PORT: z5.coerce.number().default(3e3),
|
|
13773
|
+
CORS_ORIGIN: z5.string().default("*"),
|
|
13774
|
+
BACKEND_URL: z5.string().optional(),
|
|
13775
|
+
DATABASE_URL: z5.string().default("file:./dev.db"),
|
|
13776
|
+
REDIS_URL: z5.string().url().optional(),
|
|
13777
|
+
REDIS_PREFIX: z5.string().default("nexus"),
|
|
13778
|
+
ADMIN_EMAIL: z5.string().email().optional(),
|
|
13779
|
+
ADMIN_PASSWORD: z5.string().min(6).optional(),
|
|
13780
|
+
COOKIE_DOMAIN: z5.string().optional(),
|
|
13781
|
+
TZ: z5.string().default("UTC"),
|
|
13782
|
+
TRUST_PROXY: z5.coerce.boolean().default(false),
|
|
13783
|
+
NEXUS_UI_ENABLED: z5.coerce.boolean().default(true),
|
|
13784
|
+
NEXUS_UI_BASE: z5.string().default("/"),
|
|
13785
|
+
NEXUS_UI_PATH: z5.string().default("../ui/dist"),
|
|
13786
|
+
NEXUS_CHECK_DRIFT: z5.coerce.boolean().optional(),
|
|
13787
|
+
NEXUS_FAIL_ON_DRIFT: z5.coerce.boolean().optional(),
|
|
13788
|
+
FRPC_SERVER: z5.string().optional(),
|
|
13789
|
+
FRPC_SERVER_PORT: z5.coerce.number().default(7e3),
|
|
13790
|
+
FRPC_TOKEN: z5.string().optional(),
|
|
13791
|
+
FRPC_SUBDOMAIN: z5.string().optional()
|
|
13792
|
+
});
|
|
13793
|
+
env = envSchema.parse(process.env);
|
|
13794
|
+
process.env["TZ"] = env.TZ;
|
|
13795
|
+
resolvedConfig = null;
|
|
13796
|
+
}
|
|
13797
|
+
});
|
|
13798
|
+
|
|
13799
|
+
// src/db/sql-utils.ts
|
|
13800
|
+
function extractTableFromSql(sql, type2) {
|
|
13801
|
+
const match = sql.match(SQL_PATTERNS[type2]);
|
|
13802
|
+
return match?.[1];
|
|
13803
|
+
}
|
|
13804
|
+
function extractTableFromSelect(sql) {
|
|
13805
|
+
return extractTableFromSql(sql, "select");
|
|
13806
|
+
}
|
|
13807
|
+
function extractTableFromInsert(sql) {
|
|
13808
|
+
return extractTableFromSql(sql, "insert");
|
|
13809
|
+
}
|
|
13810
|
+
function extractTableFromUpdate(sql) {
|
|
13811
|
+
return extractTableFromSql(sql, "update");
|
|
13812
|
+
}
|
|
13813
|
+
function extractTableFromDelete(sql) {
|
|
13814
|
+
return extractTableFromSql(sql, "delete");
|
|
13815
|
+
}
|
|
13816
|
+
var SQL_PATTERNS;
|
|
13817
|
+
var init_sql_utils = __esm({
|
|
13818
|
+
"src/db/sql-utils.ts"() {
|
|
13819
|
+
"use strict";
|
|
13820
|
+
SQL_PATTERNS = {
|
|
13821
|
+
select: /from\s+["'`]?(\w+)["'`]?/i,
|
|
13822
|
+
insert: /insert into\s+["'`]?(\w+)["'`]?/i,
|
|
13823
|
+
update: /update\s+["'`]?(\w+)["'`]?/i,
|
|
13824
|
+
delete: /delete from\s+["'`]?(\w+)["'`]?/i
|
|
13825
|
+
};
|
|
13826
|
+
}
|
|
13827
|
+
});
|
|
13828
|
+
|
|
13829
|
+
// src/db/sqlite-compat.ts
|
|
13830
|
+
function createSqliteBooleanProcessor() {
|
|
13831
|
+
const booleanColumns = /* @__PURE__ */ new Map();
|
|
13832
|
+
function registerBooleanColumn2(table, column) {
|
|
13833
|
+
if (!booleanColumns.has(table)) {
|
|
13834
|
+
booleanColumns.set(table, /* @__PURE__ */ new Set());
|
|
13835
|
+
}
|
|
13836
|
+
booleanColumns.get(table).add(column);
|
|
13837
|
+
}
|
|
13838
|
+
function convertBooleans2(table, row) {
|
|
13839
|
+
const columns = booleanColumns.get(table);
|
|
13840
|
+
if (!columns || columns.size === 0) return row;
|
|
13841
|
+
const result = { ...row };
|
|
13842
|
+
for (const column of columns) {
|
|
13843
|
+
if (column in result) {
|
|
13844
|
+
const value = result[column];
|
|
13845
|
+
if (value === 0 || value === 1) {
|
|
13846
|
+
result[column] = value === 1;
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
13849
|
+
}
|
|
13850
|
+
return result;
|
|
13851
|
+
}
|
|
13852
|
+
function postProcess(result, queryContext) {
|
|
13853
|
+
const sql = queryContext?.sql?.toLowerCase() ?? "";
|
|
13854
|
+
if (!sql.startsWith("select")) return result;
|
|
13855
|
+
const table = extractTableFromSelect(sql);
|
|
13856
|
+
if (!table) return result;
|
|
13857
|
+
if (Array.isArray(result)) {
|
|
13858
|
+
return result.map(
|
|
13859
|
+
(row) => typeof row === "object" && row !== null ? convertBooleans2(table, row) : row
|
|
13860
|
+
);
|
|
13861
|
+
}
|
|
13862
|
+
if (typeof result === "object" && result !== null) {
|
|
13863
|
+
return convertBooleans2(table, result);
|
|
13864
|
+
}
|
|
13865
|
+
return result;
|
|
13866
|
+
}
|
|
13867
|
+
function clear() {
|
|
13868
|
+
booleanColumns.clear();
|
|
13869
|
+
}
|
|
13870
|
+
return { registerBooleanColumn: registerBooleanColumn2, convertBooleans: convertBooleans2, postProcess, clear };
|
|
13871
|
+
}
|
|
13872
|
+
var defaultProcessor, registerBooleanColumn, convertBooleans, sqlitePostProcess;
|
|
13873
|
+
var init_sqlite_compat = __esm({
|
|
13874
|
+
"src/db/sqlite-compat.ts"() {
|
|
13875
|
+
"use strict";
|
|
13876
|
+
init_sql_utils();
|
|
13877
|
+
defaultProcessor = createSqliteBooleanProcessor();
|
|
13878
|
+
registerBooleanColumn = defaultProcessor.registerBooleanColumn;
|
|
13879
|
+
convertBooleans = defaultProcessor.convertBooleans;
|
|
13880
|
+
sqlitePostProcess = defaultProcessor.postProcess;
|
|
13881
|
+
}
|
|
13882
|
+
});
|
|
13883
|
+
|
|
13884
|
+
// src/config/database.ts
|
|
13885
|
+
import { join as join9, dirname as dirname6, isAbsolute as isAbsolute2 } from "path";
|
|
13886
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
13887
|
+
function getDatabaseConfig() {
|
|
13888
|
+
const url = getConfig().databaseUrl;
|
|
13889
|
+
if (IN_MEMORY_RE.test(url)) {
|
|
13890
|
+
return {
|
|
13891
|
+
client: "better-sqlite3",
|
|
13892
|
+
connection: { filename: ":memory:" },
|
|
13893
|
+
useNullAsDefault: true,
|
|
13894
|
+
postProcessResponse: sqlitePostProcess,
|
|
13895
|
+
pool: { min: 0, max: 1 }
|
|
13896
|
+
};
|
|
13897
|
+
}
|
|
13898
|
+
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
13899
|
+
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
13900
|
+
if (!isAbsolute2(filename)) {
|
|
13901
|
+
filename = join9(getProjectPath(), "data", filename);
|
|
13902
|
+
}
|
|
13903
|
+
mkdirSync4(dirname6(filename), { recursive: true });
|
|
13904
|
+
return {
|
|
13905
|
+
client: "better-sqlite3",
|
|
13906
|
+
connection: { filename },
|
|
13907
|
+
useNullAsDefault: true,
|
|
13908
|
+
postProcessResponse: sqlitePostProcess
|
|
13909
|
+
};
|
|
13910
|
+
}
|
|
13911
|
+
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) {
|
|
13912
|
+
return {
|
|
13913
|
+
client: "pg",
|
|
13914
|
+
connection: url,
|
|
13915
|
+
pool: { min: 2, max: 10 }
|
|
13916
|
+
};
|
|
13917
|
+
}
|
|
13918
|
+
if (url.startsWith("mysql://")) {
|
|
13919
|
+
const offsetMinutes = (/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
13920
|
+
const offsetHours = Math.abs(Math.floor(offsetMinutes / 60));
|
|
13921
|
+
const offsetMins = Math.abs(offsetMinutes % 60);
|
|
13922
|
+
const sign = offsetMinutes <= 0 ? "+" : "-";
|
|
13923
|
+
const tzOffset = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMins).padStart(2, "0")}`;
|
|
13924
|
+
return {
|
|
13925
|
+
client: "mysql2",
|
|
13926
|
+
connection: {
|
|
13927
|
+
uri: url,
|
|
13928
|
+
timezone: tzOffset
|
|
13929
|
+
// mysql2 requiere formato "+HH:MM"
|
|
13930
|
+
},
|
|
13931
|
+
pool: { min: 2, max: 10 }
|
|
13932
|
+
};
|
|
13933
|
+
}
|
|
13934
|
+
throw new Error(`Unsupported database URL: ${url}`);
|
|
13935
|
+
}
|
|
13936
|
+
function getDatabaseType() {
|
|
13937
|
+
const url = getConfig().databaseUrl;
|
|
13938
|
+
if (IN_MEMORY_RE.test(url) || url.startsWith("file:") || url.startsWith("sqlite:")) return "sqlite";
|
|
13939
|
+
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) return "postgresql";
|
|
13940
|
+
if (url.startsWith("mysql://")) return "mysql";
|
|
13941
|
+
return "sqlite";
|
|
13942
|
+
}
|
|
13943
|
+
function getDatabasePath() {
|
|
13944
|
+
const url = getConfig().databaseUrl;
|
|
13945
|
+
if (IN_MEMORY_RE.test(url)) return ":memory:";
|
|
13946
|
+
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
13947
|
+
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
13948
|
+
if (!isAbsolute2(filename)) {
|
|
13949
|
+
filename = join9(getProjectPath(), "data", filename);
|
|
13950
|
+
}
|
|
13951
|
+
return filename;
|
|
13952
|
+
}
|
|
13953
|
+
try {
|
|
13954
|
+
const parsed = new URL(url);
|
|
13955
|
+
parsed.password = "***";
|
|
13956
|
+
return parsed.toString();
|
|
13957
|
+
} catch {
|
|
13958
|
+
return url.replace(/:\/\/[^@]+@/, "://***@");
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
var IN_MEMORY_RE;
|
|
13962
|
+
var init_database = __esm({
|
|
13963
|
+
"src/config/database.ts"() {
|
|
13964
|
+
"use strict";
|
|
13965
|
+
init_env();
|
|
13966
|
+
init_paths();
|
|
13967
|
+
init_sqlite_compat();
|
|
13968
|
+
IN_MEMORY_RE = /^((file:|sqlite:)?:memory:?)$/;
|
|
13969
|
+
}
|
|
13970
|
+
});
|
|
13971
|
+
|
|
13972
|
+
// src/core/crypto/secret-resolver.ts
|
|
13973
|
+
import { randomBytes } from "crypto";
|
|
13974
|
+
function setSecretResolverDb(db2) {
|
|
13975
|
+
_db = db2;
|
|
13976
|
+
}
|
|
13977
|
+
function _resetSecretResolver() {
|
|
13978
|
+
_secret = null;
|
|
13979
|
+
_db = null;
|
|
13980
|
+
}
|
|
13981
|
+
async function initAuthSecret(logger2) {
|
|
13982
|
+
const envSecret = process.env["AUTH_SECRET"];
|
|
13983
|
+
if (envSecret) {
|
|
13984
|
+
if (envSecret.length < 32) {
|
|
13985
|
+
throw new Error(`AUTH_SECRET must be at least 32 characters (current: ${envSecret.length})`);
|
|
13986
|
+
}
|
|
13987
|
+
_secret = envSecret;
|
|
13988
|
+
return;
|
|
13989
|
+
}
|
|
13990
|
+
if (!_db) {
|
|
13991
|
+
throw new Error("Secret resolver: database not initialized. Call setSecretResolverDb() first.");
|
|
13992
|
+
}
|
|
13993
|
+
const existing = await _db(SINGLE_RECORDS).where("key", SECRET_KEY).first();
|
|
13994
|
+
if (existing?.value) {
|
|
13995
|
+
const parsed2 = typeof existing.value === "string" ? JSON.parse(existing.value) : existing.value;
|
|
13996
|
+
if (parsed2.secret) {
|
|
13997
|
+
_secret = parsed2.secret;
|
|
13998
|
+
logger2.info("AUTH_SECRET loaded from database");
|
|
13999
|
+
warnIfProduction(logger2);
|
|
14000
|
+
return;
|
|
14001
|
+
}
|
|
14002
|
+
}
|
|
14003
|
+
const generated = randomBytes(SECRET_LENGTH).toString("base64url");
|
|
14004
|
+
const value = JSON.stringify({ secret: generated });
|
|
14005
|
+
const id = randomBytes(13).toString("base64url");
|
|
14006
|
+
const now = /* @__PURE__ */ new Date();
|
|
14007
|
+
const dialect = getDatabaseType();
|
|
14008
|
+
if (dialect === "postgresql") {
|
|
14009
|
+
await _db.raw(
|
|
14010
|
+
`INSERT INTO ${SINGLE_RECORDS} (id, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT (key) DO NOTHING`,
|
|
14011
|
+
[id, SECRET_KEY, value, now, now]
|
|
14012
|
+
);
|
|
14013
|
+
} else if (dialect === "mysql") {
|
|
14014
|
+
await _db.raw(
|
|
14015
|
+
"INSERT IGNORE INTO `single_records` (id, `key`, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
14016
|
+
[id, SECRET_KEY, value, now, now]
|
|
14017
|
+
);
|
|
14018
|
+
} else {
|
|
14019
|
+
await _db.raw(
|
|
14020
|
+
`INSERT OR IGNORE INTO ${SINGLE_RECORDS} (id, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
|
|
14021
|
+
[id, SECRET_KEY, value, now, now]
|
|
14022
|
+
);
|
|
14023
|
+
}
|
|
14024
|
+
const persisted = await _db(SINGLE_RECORDS).where("key", SECRET_KEY).first();
|
|
14025
|
+
const parsed = typeof persisted.value === "string" ? JSON.parse(persisted.value) : persisted.value;
|
|
14026
|
+
_secret = parsed.secret;
|
|
14027
|
+
logger2.info("AUTH_SECRET auto-generated and persisted in database");
|
|
14028
|
+
warnIfProduction(logger2);
|
|
14029
|
+
}
|
|
14030
|
+
function warnIfProduction(logger2) {
|
|
14031
|
+
if (process.env["NODE_ENV"] === "production") {
|
|
14032
|
+
logger2.warn("AUTH_SECRET not set via environment. Using auto-generated secret from database. For multi-instance deployments, set AUTH_SECRET explicitly.");
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
function getAuthSecret() {
|
|
14036
|
+
if (_secret) return _secret;
|
|
14037
|
+
const envSecret = process.env["AUTH_SECRET"];
|
|
14038
|
+
if (envSecret) return envSecret;
|
|
14039
|
+
throw new Error("AUTH_SECRET not initialized. Call initAuthSecret() during startup.");
|
|
14040
|
+
}
|
|
14041
|
+
var SINGLE_RECORDS, SECRET_KEY, SECRET_LENGTH, _secret, _db;
|
|
14042
|
+
var init_secret_resolver = __esm({
|
|
14043
|
+
"src/core/crypto/secret-resolver.ts"() {
|
|
14044
|
+
"use strict";
|
|
14045
|
+
init_database();
|
|
14046
|
+
SINGLE_RECORDS = "single_records";
|
|
14047
|
+
SECRET_KEY = "nexus_auth_secret";
|
|
14048
|
+
SECRET_LENGTH = 48;
|
|
14049
|
+
_secret = null;
|
|
14050
|
+
_db = null;
|
|
14051
|
+
}
|
|
14052
|
+
});
|
|
14053
|
+
|
|
14054
|
+
// src/modules/auth/auth.config.ts
|
|
14055
|
+
import { z as z6 } from "zod";
|
|
13872
14056
|
function configError(module, errors) {
|
|
13873
14057
|
console.error(`
|
|
13874
14058
|
${"\u2550".repeat(60)}`);
|
|
@@ -13883,12 +14067,6 @@ function parseAuthEnv() {
|
|
|
13883
14067
|
if (!result.success) {
|
|
13884
14068
|
const errors = result.error.issues.map((issue) => {
|
|
13885
14069
|
const path4 = issue.path.join(".");
|
|
13886
|
-
if (path4 === "AUTH_SECRET" && issue.code === "invalid_type") {
|
|
13887
|
-
return "AUTH_SECRET is required. Set it in your .env file or environment.";
|
|
13888
|
-
}
|
|
13889
|
-
if (path4 === "AUTH_SECRET" && issue.code === "too_small") {
|
|
13890
|
-
return `AUTH_SECRET must be at least 32 characters (current: ${String(process.env["AUTH_SECRET"]).length})`;
|
|
13891
|
-
}
|
|
13892
14070
|
return `${path4}: ${issue.message}`;
|
|
13893
14071
|
});
|
|
13894
14072
|
configError("auth", errors);
|
|
@@ -13907,7 +14085,7 @@ function getAuthEnv() {
|
|
|
13907
14085
|
function getAuthConfig() {
|
|
13908
14086
|
const authEnv = getAuthEnv();
|
|
13909
14087
|
return {
|
|
13910
|
-
secret:
|
|
14088
|
+
secret: getAuthSecret(),
|
|
13911
14089
|
accessExpires: authEnv.AUTH_ACCESS_EXPIRES,
|
|
13912
14090
|
refreshExpires: authEnv.AUTH_REFRESH_EXPIRES,
|
|
13913
14091
|
rateLimitMax: authEnv.AUTH_RATE_LIMIT_MAX,
|
|
@@ -13924,24 +14102,24 @@ var authEnvSchema, _authEnv;
|
|
|
13924
14102
|
var init_auth_config = __esm({
|
|
13925
14103
|
"src/modules/auth/auth.config.ts"() {
|
|
13926
14104
|
"use strict";
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
AUTH_ACCESS_EXPIRES:
|
|
13930
|
-
AUTH_REFRESH_EXPIRES:
|
|
14105
|
+
init_secret_resolver();
|
|
14106
|
+
authEnvSchema = z6.object({
|
|
14107
|
+
AUTH_ACCESS_EXPIRES: z6.string().default("15m"),
|
|
14108
|
+
AUTH_REFRESH_EXPIRES: z6.string().default("7d"),
|
|
13931
14109
|
// Rate limiting (default: 5 requests per 15 minutes)
|
|
13932
|
-
AUTH_RATE_LIMIT_MAX:
|
|
13933
|
-
AUTH_RATE_LIMIT_WINDOW:
|
|
14110
|
+
AUTH_RATE_LIMIT_MAX: z6.coerce.number().default(5),
|
|
14111
|
+
AUTH_RATE_LIMIT_WINDOW: z6.coerce.number().default(900),
|
|
13934
14112
|
// seconds
|
|
13935
14113
|
// Cookie domain for SSO across subdomains (e.g., '.example.com')
|
|
13936
|
-
AUTH_COOKIE_DOMAIN:
|
|
14114
|
+
AUTH_COOKIE_DOMAIN: z6.string().optional(),
|
|
13937
14115
|
// Challenge threshold: failed attempts before requiring OTP (default: 2)
|
|
13938
|
-
AUTH_CHALLENGE_THRESHOLD:
|
|
14116
|
+
AUTH_CHALLENGE_THRESHOLD: z6.coerce.number().default(2),
|
|
13939
14117
|
// Skip OTP verification for registration (DEVELOPMENT ONLY - rejected in production)
|
|
13940
|
-
AUTH_SKIP_REGISTER_OTP:
|
|
14118
|
+
AUTH_SKIP_REGISTER_OTP: z6.coerce.boolean().default(false),
|
|
13941
14119
|
// Disable self-registration via POST /auth/register (default: false)
|
|
13942
|
-
AUTH_DISABLE_REGISTRATION:
|
|
14120
|
+
AUTH_DISABLE_REGISTRATION: z6.coerce.boolean().default(false),
|
|
13943
14121
|
// Disable auto-creation of users on first OIDC login (default: false)
|
|
13944
|
-
AUTH_DISABLE_AUTO_CREATE:
|
|
14122
|
+
AUTH_DISABLE_AUTO_CREATE: z6.coerce.boolean().default(false)
|
|
13945
14123
|
});
|
|
13946
14124
|
}
|
|
13947
14125
|
});
|
|
@@ -14099,7 +14277,7 @@ var init_auth_middleware = __esm({
|
|
|
14099
14277
|
});
|
|
14100
14278
|
|
|
14101
14279
|
// src/modules/auth/auth.pat.entity.ts
|
|
14102
|
-
import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as
|
|
14280
|
+
import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as useSelectField10, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
|
|
14103
14281
|
var personalTokenEntity;
|
|
14104
14282
|
var init_auth_pat_entity = __esm({
|
|
14105
14283
|
"src/modules/auth/auth.pat.entity.ts"() {
|
|
@@ -14116,7 +14294,7 @@ var init_auth_pat_entity = __esm({
|
|
|
14116
14294
|
routePrefix: "/personal-tokens",
|
|
14117
14295
|
fields: {
|
|
14118
14296
|
id: useIdField6(),
|
|
14119
|
-
user_id:
|
|
14297
|
+
user_id: useSelectField10({
|
|
14120
14298
|
label: { en: "User", es: "Usuario" },
|
|
14121
14299
|
table: "users",
|
|
14122
14300
|
column: "id",
|
|
@@ -14151,7 +14329,7 @@ var init_auth_pat_entity = __esm({
|
|
|
14151
14329
|
unique: true,
|
|
14152
14330
|
meta: { exportable: false }
|
|
14153
14331
|
}),
|
|
14154
|
-
scope:
|
|
14332
|
+
scope: useSelectField10({
|
|
14155
14333
|
label: { en: "Permission", es: "Permiso" },
|
|
14156
14334
|
required: true,
|
|
14157
14335
|
options: [
|
|
@@ -14174,46 +14352,10 @@ var init_auth_pat_entity = __esm({
|
|
|
14174
14352
|
casl: {
|
|
14175
14353
|
subject: "AuthPersonalToken",
|
|
14176
14354
|
permissions: {
|
|
14177
|
-
"*": [
|
|
14178
|
-
{ actions: ["read", "delete"], conditions: { user_id: "${user.id}" } }
|
|
14179
|
-
]
|
|
14180
|
-
}
|
|
14181
|
-
}
|
|
14182
|
-
};
|
|
14183
|
-
}
|
|
14184
|
-
});
|
|
14185
|
-
|
|
14186
|
-
// src/modules/auth/actions/providers.action.ts
|
|
14187
|
-
var providersAction;
|
|
14188
|
-
var init_providers_action = __esm({
|
|
14189
|
-
"src/modules/auth/actions/providers.action.ts"() {
|
|
14190
|
-
"use strict";
|
|
14191
|
-
init_auth_config();
|
|
14192
|
-
providersAction = {
|
|
14193
|
-
key: "providers",
|
|
14194
|
-
label: { en: "Get Auth Providers", es: "Obtener proveedores de autenticaci\xF3n" },
|
|
14195
|
-
icon: "mdi:account-key",
|
|
14196
|
-
scope: "module",
|
|
14197
|
-
hidden: true,
|
|
14198
|
-
method: "GET",
|
|
14199
|
-
skipAuth: true,
|
|
14200
|
-
handler: async (ctx) => {
|
|
14201
|
-
const providerServices = ctx.services.getBySuffix(".provider");
|
|
14202
|
-
const results = await Promise.all(
|
|
14203
|
-
providerServices.map(async ({ service }) => {
|
|
14204
|
-
try {
|
|
14205
|
-
return await service.getInfo();
|
|
14206
|
-
} catch {
|
|
14207
|
-
return null;
|
|
14208
|
-
}
|
|
14209
|
-
})
|
|
14210
|
-
);
|
|
14211
|
-
const providers = results.filter((info) => info !== null);
|
|
14212
|
-
const config3 = getAuthConfig();
|
|
14213
|
-
return {
|
|
14214
|
-
providers,
|
|
14215
|
-
registrationEnabled: !config3.disableRegistration
|
|
14216
|
-
};
|
|
14355
|
+
"*": [
|
|
14356
|
+
{ actions: ["read", "delete"], conditions: { user_id: "${user.id}" } }
|
|
14357
|
+
]
|
|
14358
|
+
}
|
|
14217
14359
|
}
|
|
14218
14360
|
};
|
|
14219
14361
|
}
|
|
@@ -14775,7 +14917,6 @@ var authActions;
|
|
|
14775
14917
|
var init_actions2 = __esm({
|
|
14776
14918
|
"src/modules/auth/actions/index.ts"() {
|
|
14777
14919
|
"use strict";
|
|
14778
|
-
init_providers_action();
|
|
14779
14920
|
init_login_action();
|
|
14780
14921
|
init_register_action();
|
|
14781
14922
|
init_forgot_password_action();
|
|
@@ -14791,7 +14932,6 @@ var init_actions2 = __esm({
|
|
|
14791
14932
|
init_list_tokens_action();
|
|
14792
14933
|
init_revoke_token_action();
|
|
14793
14934
|
init_impersonate_action();
|
|
14794
|
-
init_providers_action();
|
|
14795
14935
|
init_login_action();
|
|
14796
14936
|
init_register_action();
|
|
14797
14937
|
init_forgot_password_action();
|
|
@@ -14807,7 +14947,6 @@ var init_actions2 = __esm({
|
|
|
14807
14947
|
init_list_tokens_action();
|
|
14808
14948
|
init_revoke_token_action();
|
|
14809
14949
|
authActions = [
|
|
14810
|
-
providersAction,
|
|
14811
14950
|
loginAction,
|
|
14812
14951
|
registerAction,
|
|
14813
14952
|
forgotPasswordAction,
|
|
@@ -15026,7 +15165,7 @@ var init_otp_manager = __esm({
|
|
|
15026
15165
|
});
|
|
15027
15166
|
|
|
15028
15167
|
// src/modules/auth/auth.service.ts
|
|
15029
|
-
import { createHash as createHash4, randomBytes } from "crypto";
|
|
15168
|
+
import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
|
|
15030
15169
|
function createAuthService(ctx) {
|
|
15031
15170
|
const { errors, abilities, crypto: crypto3 } = ctx.core;
|
|
15032
15171
|
const { generateId: generateId4 } = ctx.core;
|
|
@@ -15443,7 +15582,7 @@ function createAuthService(ctx) {
|
|
|
15443
15582
|
},
|
|
15444
15583
|
// === Personal Access Tokens ===
|
|
15445
15584
|
async createPersonalToken(userId, input, requestInfo) {
|
|
15446
|
-
const rawToken = "nxs_" +
|
|
15585
|
+
const rawToken = "nxs_" + randomBytes2(32).toString("hex");
|
|
15447
15586
|
const tokenHash = createHash4("sha256").update(rawToken).digest("hex");
|
|
15448
15587
|
const tokenPrefix = "nxs_..." + rawToken.slice(-6);
|
|
15449
15588
|
const id = generateId4();
|
|
@@ -15609,38 +15748,38 @@ var init_auth_service = __esm({
|
|
|
15609
15748
|
});
|
|
15610
15749
|
|
|
15611
15750
|
// src/modules/auth/auth.types.ts
|
|
15612
|
-
import { z as
|
|
15751
|
+
import { z as z7 } from "zod";
|
|
15613
15752
|
var loginSchema, registerSchema, forgotPasswordSchema, resetPasswordSchema, createPersonalTokenSchema;
|
|
15614
15753
|
var init_auth_types = __esm({
|
|
15615
15754
|
"src/modules/auth/auth.types.ts"() {
|
|
15616
15755
|
"use strict";
|
|
15617
|
-
loginSchema =
|
|
15618
|
-
email:
|
|
15619
|
-
password:
|
|
15620
|
-
otp:
|
|
15621
|
-
deviceId:
|
|
15622
|
-
deviceName:
|
|
15756
|
+
loginSchema = z7.object({
|
|
15757
|
+
email: z7.string().email("Invalid email"),
|
|
15758
|
+
password: z7.string().min(1, "Password required"),
|
|
15759
|
+
otp: z7.string().length(6).optional(),
|
|
15760
|
+
deviceId: z7.string().max(64).optional(),
|
|
15761
|
+
deviceName: z7.string().max(100).optional()
|
|
15623
15762
|
});
|
|
15624
|
-
registerSchema =
|
|
15625
|
-
email:
|
|
15626
|
-
password:
|
|
15627
|
-
name:
|
|
15628
|
-
otp:
|
|
15629
|
-
deviceId:
|
|
15630
|
-
deviceName:
|
|
15763
|
+
registerSchema = z7.object({
|
|
15764
|
+
email: z7.string().email("Invalid email"),
|
|
15765
|
+
password: z7.string().min(8, "Password must be at least 8 characters"),
|
|
15766
|
+
name: z7.string().min(2, "Name must be at least 2 characters"),
|
|
15767
|
+
otp: z7.string().length(6).optional(),
|
|
15768
|
+
deviceId: z7.string().max(64).optional(),
|
|
15769
|
+
deviceName: z7.string().max(100).optional()
|
|
15631
15770
|
});
|
|
15632
|
-
forgotPasswordSchema =
|
|
15633
|
-
email:
|
|
15771
|
+
forgotPasswordSchema = z7.object({
|
|
15772
|
+
email: z7.string().email("Invalid email")
|
|
15634
15773
|
});
|
|
15635
|
-
resetPasswordSchema =
|
|
15636
|
-
email:
|
|
15637
|
-
otp:
|
|
15638
|
-
newPassword:
|
|
15774
|
+
resetPasswordSchema = z7.object({
|
|
15775
|
+
email: z7.string().email("Invalid email"),
|
|
15776
|
+
otp: z7.string().length(6, "OTP must be 6 digits"),
|
|
15777
|
+
newPassword: z7.string().min(8, "Password must be at least 8 characters")
|
|
15639
15778
|
});
|
|
15640
|
-
createPersonalTokenSchema =
|
|
15641
|
-
name:
|
|
15642
|
-
scope:
|
|
15643
|
-
expires_at:
|
|
15779
|
+
createPersonalTokenSchema = z7.object({
|
|
15780
|
+
name: z7.string().min(1).max(100),
|
|
15781
|
+
scope: z7.enum(["readonly", "readwrite"]),
|
|
15782
|
+
expires_at: z7.string().datetime().optional()
|
|
15644
15783
|
});
|
|
15645
15784
|
}
|
|
15646
15785
|
});
|
|
@@ -15654,12 +15793,12 @@ async function seed3(ctx) {
|
|
|
15654
15793
|
const db2 = ctx.db.knex;
|
|
15655
15794
|
const { logger: logger2, generateId: generateId4 } = ctx.core;
|
|
15656
15795
|
const { nowTimestamp: nowTimestamp2 } = ctx.db;
|
|
15657
|
-
const hasTable = await db2.schema.hasTable(
|
|
15796
|
+
const hasTable = await db2.schema.hasTable(SINGLE_RECORDS2);
|
|
15658
15797
|
if (!hasTable) {
|
|
15659
15798
|
logger2.debug("single_records table not found, skipping auth seed");
|
|
15660
15799
|
return;
|
|
15661
15800
|
}
|
|
15662
|
-
const existing = await db2(
|
|
15801
|
+
const existing = await db2(SINGLE_RECORDS2).where({ key: AUTH_CONFIG_KEY }).first();
|
|
15663
15802
|
if (existing) {
|
|
15664
15803
|
logger2.debug("Auth config already seeded");
|
|
15665
15804
|
return;
|
|
@@ -15672,7 +15811,7 @@ async function seed3(ctx) {
|
|
|
15672
15811
|
rate_limit_window: parseInt(process.env["AUTH_RATE_LIMIT_WINDOW"] || "") || 900,
|
|
15673
15812
|
cookie_domain: process.env["AUTH_COOKIE_DOMAIN"] || null
|
|
15674
15813
|
};
|
|
15675
|
-
await db2(
|
|
15814
|
+
await db2(SINGLE_RECORDS2).insert({
|
|
15676
15815
|
id: generateId4(),
|
|
15677
15816
|
key: AUTH_CONFIG_KEY,
|
|
15678
15817
|
value: JSON.stringify(defaults),
|
|
@@ -15681,11 +15820,11 @@ async function seed3(ctx) {
|
|
|
15681
15820
|
});
|
|
15682
15821
|
logger2.info("Auth config seeded from environment variables");
|
|
15683
15822
|
}
|
|
15684
|
-
var
|
|
15823
|
+
var SINGLE_RECORDS2, AUTH_CONFIG_KEY;
|
|
15685
15824
|
var init_auth_seed = __esm({
|
|
15686
15825
|
"src/modules/auth/auth.seed.ts"() {
|
|
15687
15826
|
"use strict";
|
|
15688
|
-
|
|
15827
|
+
SINGLE_RECORDS2 = "single_records";
|
|
15689
15828
|
AUTH_CONFIG_KEY = "auth_config";
|
|
15690
15829
|
}
|
|
15691
15830
|
});
|
|
@@ -15751,7 +15890,7 @@ var init_auth = __esm({
|
|
|
15751
15890
|
});
|
|
15752
15891
|
|
|
15753
15892
|
// src/modules/mail/mail.config.ts
|
|
15754
|
-
import { z as
|
|
15893
|
+
import { z as z8 } from "zod";
|
|
15755
15894
|
function getMailConfig() {
|
|
15756
15895
|
return {
|
|
15757
15896
|
host: mailEnv.SMTP_HOST,
|
|
@@ -15765,13 +15904,13 @@ var mailEnvSchema, mailEnv;
|
|
|
15765
15904
|
var init_mail_config = __esm({
|
|
15766
15905
|
"src/modules/mail/mail.config.ts"() {
|
|
15767
15906
|
"use strict";
|
|
15768
|
-
mailEnvSchema =
|
|
15769
|
-
SMTP_HOST:
|
|
15770
|
-
SMTP_PORT:
|
|
15771
|
-
SMTP_SECURE:
|
|
15772
|
-
SMTP_USER:
|
|
15773
|
-
SMTP_PASS:
|
|
15774
|
-
SMTP_FROM:
|
|
15907
|
+
mailEnvSchema = z8.object({
|
|
15908
|
+
SMTP_HOST: z8.string().default("localhost"),
|
|
15909
|
+
SMTP_PORT: z8.coerce.number().default(1025),
|
|
15910
|
+
SMTP_SECURE: z8.string().default("false").transform((v) => v === "true" || v === "1"),
|
|
15911
|
+
SMTP_USER: z8.string().optional(),
|
|
15912
|
+
SMTP_PASS: z8.string().optional(),
|
|
15913
|
+
SMTP_FROM: z8.string().default("noreply@nexus.local")
|
|
15775
15914
|
});
|
|
15776
15915
|
mailEnv = mailEnvSchema.parse(process.env);
|
|
15777
15916
|
}
|
|
@@ -15779,8 +15918,8 @@ var init_mail_config = __esm({
|
|
|
15779
15918
|
|
|
15780
15919
|
// src/modules/mail/mail.service.ts
|
|
15781
15920
|
import nodemailer from "nodemailer";
|
|
15782
|
-
import { readFileSync as
|
|
15783
|
-
import { join as
|
|
15921
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
|
|
15922
|
+
import { join as join10 } from "path";
|
|
15784
15923
|
function getMailService() {
|
|
15785
15924
|
if (!mailServiceInstance) {
|
|
15786
15925
|
throw new Error("MailService not initialized. Call initMailService() first.");
|
|
@@ -15797,8 +15936,8 @@ var init_mail_service = __esm({
|
|
|
15797
15936
|
"src/modules/mail/mail.service.ts"() {
|
|
15798
15937
|
"use strict";
|
|
15799
15938
|
init_mail_config();
|
|
15800
|
-
TEMPLATE_REL_PATH =
|
|
15801
|
-
LOGO_REL_PATH =
|
|
15939
|
+
TEMPLATE_REL_PATH = join10("public", "mail", "base.html");
|
|
15940
|
+
LOGO_REL_PATH = join10("public", "nexus", "nexus-light-512.png");
|
|
15802
15941
|
mailServiceInstance = null;
|
|
15803
15942
|
MailService = class {
|
|
15804
15943
|
transporter;
|
|
@@ -15812,10 +15951,10 @@ var init_mail_service = __esm({
|
|
|
15812
15951
|
this.logger = logger2.child({ service: "mail" });
|
|
15813
15952
|
this.loggerService = loggerService;
|
|
15814
15953
|
const libPath = options?.libPath ?? process.cwd();
|
|
15815
|
-
this.template =
|
|
15816
|
-
const logoPath =
|
|
15817
|
-
if (
|
|
15818
|
-
const logoBase64 =
|
|
15954
|
+
this.template = readFileSync5(join10(libPath, TEMPLATE_REL_PATH), "utf-8");
|
|
15955
|
+
const logoPath = join10(libPath, LOGO_REL_PATH);
|
|
15956
|
+
if (existsSync8(logoPath)) {
|
|
15957
|
+
const logoBase64 = readFileSync5(logoPath).toString("base64");
|
|
15819
15958
|
this.defaultLogoUrl = `data:image/png;base64,${logoBase64}`;
|
|
15820
15959
|
} else {
|
|
15821
15960
|
this.defaultLogoUrl = "";
|
|
@@ -15907,7 +16046,7 @@ var init_mail_service = __esm({
|
|
|
15907
16046
|
});
|
|
15908
16047
|
|
|
15909
16048
|
// src/modules/mail/mail.entity.ts
|
|
15910
|
-
import { useIdField as useIdField7, useTextField as useTextField12, useSelectField as
|
|
16049
|
+
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";
|
|
15911
16050
|
import nodemailer2 from "nodemailer";
|
|
15912
16051
|
async function getMailConfigFromDB(ctx) {
|
|
15913
16052
|
const configService = ctx.services["config"];
|
|
@@ -15976,12 +16115,12 @@ var init_mail_entity = __esm({
|
|
|
15976
16115
|
nullable: false,
|
|
15977
16116
|
hint: { en: "Default: SMTP_HOST env var", es: "Por defecto: variable SMTP_HOST" }
|
|
15978
16117
|
}),
|
|
15979
|
-
port:
|
|
16118
|
+
port: useNumberField6({
|
|
15980
16119
|
label: { en: "Port", es: "Puerto" },
|
|
15981
16120
|
nullable: false,
|
|
15982
16121
|
hint: { en: "Default: SMTP_PORT env var", es: "Por defecto: variable SMTP_PORT" }
|
|
15983
16122
|
}),
|
|
15984
|
-
secure:
|
|
16123
|
+
secure: useSwitchField3({
|
|
15985
16124
|
label: { en: "TLS/SSL", es: "TLS/SSL" },
|
|
15986
16125
|
hint: { en: "Default: SMTP_SECURE env var", es: "Por defecto: variable SMTP_SECURE" }
|
|
15987
16126
|
}),
|
|
@@ -16184,7 +16323,7 @@ var init_mail_entity = __esm({
|
|
|
16184
16323
|
meta: { searchable: true, sortable: true }
|
|
16185
16324
|
}),
|
|
16186
16325
|
status: {
|
|
16187
|
-
...
|
|
16326
|
+
...useSelectField11({
|
|
16188
16327
|
label: { en: "Status", es: "Estado" },
|
|
16189
16328
|
options: [
|
|
16190
16329
|
{ value: "pending", label: { en: "Pending", es: "Pendiente" } },
|
|
@@ -16208,7 +16347,7 @@ var init_mail_entity = __esm({
|
|
|
16208
16347
|
label: { en: "Error", es: "Error" },
|
|
16209
16348
|
nullable: true
|
|
16210
16349
|
}),
|
|
16211
|
-
sent_by:
|
|
16350
|
+
sent_by: useSelectField11({
|
|
16212
16351
|
label: { en: "Sent by", es: "Enviado por" },
|
|
16213
16352
|
table: "users",
|
|
16214
16353
|
column: "id",
|
|
@@ -16814,7 +16953,7 @@ var init_toggle_plugin_action = __esm({
|
|
|
16814
16953
|
});
|
|
16815
16954
|
|
|
16816
16955
|
// src/modules/plugins/plugins.entity.ts
|
|
16817
|
-
import { useTextField as useTextField13, useSelectField as
|
|
16956
|
+
import { useTextField as useTextField13, useSelectField as useSelectField12, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
|
|
16818
16957
|
import { OFFICIAL_PLUGINS } from "@gzl10/nexus-sdk";
|
|
16819
16958
|
var allowPluginManagement, pluginsEntity;
|
|
16820
16959
|
var init_plugins_entity = __esm({
|
|
@@ -16856,7 +16995,7 @@ var init_plugins_entity = __esm({
|
|
|
16856
16995
|
size: 20,
|
|
16857
16996
|
nullable: false
|
|
16858
16997
|
}),
|
|
16859
|
-
category:
|
|
16998
|
+
category: useSelectField12({
|
|
16860
16999
|
label: { en: "Category", es: "Categor\xEDa" },
|
|
16861
17000
|
options: [
|
|
16862
17001
|
{ value: "content", label: { en: "Content", es: "Contenido" } },
|
|
@@ -16935,7 +17074,7 @@ var init_plugins_entity = __esm({
|
|
|
16935
17074
|
|
|
16936
17075
|
// src/modules/plugins/plugins.routes.ts
|
|
16937
17076
|
import { Router } from "express";
|
|
16938
|
-
import { existsSync as
|
|
17077
|
+
import { existsSync as existsSync9 } from "fs";
|
|
16939
17078
|
function createPluginRoutes(ctx) {
|
|
16940
17079
|
const router = Router();
|
|
16941
17080
|
let imageMap = null;
|
|
@@ -16943,7 +17082,7 @@ function createPluginRoutes(ctx) {
|
|
|
16943
17082
|
if (!imageMap) {
|
|
16944
17083
|
const discovered = await ctx.core.plugins.discover();
|
|
16945
17084
|
imageMap = new Map(
|
|
16946
|
-
discovered.filter((p) => p.image &&
|
|
17085
|
+
discovered.filter((p) => p.image && existsSync9(p.image)).map((p) => [p.code, p.image])
|
|
16947
17086
|
);
|
|
16948
17087
|
}
|
|
16949
17088
|
return imageMap;
|
|
@@ -16998,7 +17137,7 @@ var init_plugins = __esm({
|
|
|
16998
17137
|
import {
|
|
16999
17138
|
useIdField as useIdField8,
|
|
17000
17139
|
useTextField as useTextField14,
|
|
17001
|
-
useSelectField as
|
|
17140
|
+
useSelectField as useSelectField13,
|
|
17002
17141
|
useTextareaField as useTextareaField4,
|
|
17003
17142
|
useJsonField as useJsonField3,
|
|
17004
17143
|
useDatetimeField as useDatetimeField7,
|
|
@@ -17040,7 +17179,7 @@ var init_audit_entity = __esm({
|
|
|
17040
17179
|
}),
|
|
17041
17180
|
validation: { min: 1, max: 100 }
|
|
17042
17181
|
},
|
|
17043
|
-
actor_id:
|
|
17182
|
+
actor_id: useSelectField13({
|
|
17044
17183
|
label: { en: "Actor", es: "Actor" },
|
|
17045
17184
|
table: "users",
|
|
17046
17185
|
column: "id",
|
|
@@ -17327,7 +17466,11 @@ import { Server as SocketServer } from "socket.io";
|
|
|
17327
17466
|
import jwt3 from "jsonwebtoken";
|
|
17328
17467
|
import { DEFAULT_TENANT_ID, entityRoom } from "@gzl10/nexus-sdk";
|
|
17329
17468
|
function initSocketIO(httpServer, options) {
|
|
17330
|
-
|
|
17469
|
+
try {
|
|
17470
|
+
jwtSecret = options?.jwtSecret ?? getAuthSecret();
|
|
17471
|
+
} catch {
|
|
17472
|
+
jwtSecret = null;
|
|
17473
|
+
}
|
|
17331
17474
|
if (!jwtSecret) {
|
|
17332
17475
|
logger.warn("Socket.IO authentication disabled: AUTH_SECRET not configured. All connections will be treated as guests.");
|
|
17333
17476
|
}
|
|
@@ -17572,6 +17715,7 @@ var init_socket = __esm({
|
|
|
17572
17715
|
"use strict";
|
|
17573
17716
|
init_emitter();
|
|
17574
17717
|
init_logger();
|
|
17718
|
+
init_secret_resolver();
|
|
17575
17719
|
io = null;
|
|
17576
17720
|
jwtSecret = null;
|
|
17577
17721
|
userSockets = /* @__PURE__ */ new Map();
|
|
@@ -18093,70 +18237,6 @@ var init_validate_middleware = __esm({
|
|
|
18093
18237
|
}
|
|
18094
18238
|
});
|
|
18095
18239
|
|
|
18096
|
-
// src/config/env.ts
|
|
18097
|
-
import { z as z8 } from "zod";
|
|
18098
|
-
function resolveConfig() {
|
|
18099
|
-
env = envSchema.parse(process.env);
|
|
18100
|
-
process.env["TZ"] = env.TZ;
|
|
18101
|
-
resolvedConfig = {
|
|
18102
|
-
nodeEnv: env.NODE_ENV,
|
|
18103
|
-
port: env.PORT,
|
|
18104
|
-
host: "0.0.0.0",
|
|
18105
|
-
ui: {
|
|
18106
|
-
enabled: env.NEXUS_UI_ENABLED,
|
|
18107
|
-
base: env.NEXUS_UI_BASE,
|
|
18108
|
-
path: env.NEXUS_UI_PATH
|
|
18109
|
-
},
|
|
18110
|
-
corsOrigin: env.CORS_ORIGIN,
|
|
18111
|
-
databaseUrl: env.DATABASE_URL,
|
|
18112
|
-
adminEmail: env.ADMIN_EMAIL,
|
|
18113
|
-
adminPassword: env.ADMIN_PASSWORD,
|
|
18114
|
-
timezone: env.TZ
|
|
18115
|
-
};
|
|
18116
|
-
return resolvedConfig;
|
|
18117
|
-
}
|
|
18118
|
-
function getConfig() {
|
|
18119
|
-
if (!resolvedConfig) {
|
|
18120
|
-
return resolveConfig();
|
|
18121
|
-
}
|
|
18122
|
-
return resolvedConfig;
|
|
18123
|
-
}
|
|
18124
|
-
function resetConfig() {
|
|
18125
|
-
resolvedConfig = null;
|
|
18126
|
-
}
|
|
18127
|
-
var envSchema, env, resolvedConfig;
|
|
18128
|
-
var init_env = __esm({
|
|
18129
|
-
"src/config/env.ts"() {
|
|
18130
|
-
"use strict";
|
|
18131
|
-
envSchema = z8.object({
|
|
18132
|
-
NODE_ENV: z8.enum(["development", "production", "test"]).default("development"),
|
|
18133
|
-
PORT: z8.coerce.number().default(3e3),
|
|
18134
|
-
CORS_ORIGIN: z8.string().default("*"),
|
|
18135
|
-
BACKEND_URL: z8.string().optional(),
|
|
18136
|
-
DATABASE_URL: z8.string().default("file:./dev.db"),
|
|
18137
|
-
REDIS_URL: z8.string().url().optional(),
|
|
18138
|
-
REDIS_PREFIX: z8.string().default("nexus"),
|
|
18139
|
-
ADMIN_EMAIL: z8.string().email().optional(),
|
|
18140
|
-
ADMIN_PASSWORD: z8.string().min(6).optional(),
|
|
18141
|
-
COOKIE_DOMAIN: z8.string().optional(),
|
|
18142
|
-
TZ: z8.string().default("UTC"),
|
|
18143
|
-
TRUST_PROXY: z8.coerce.boolean().default(false),
|
|
18144
|
-
NEXUS_UI_ENABLED: z8.coerce.boolean().default(true),
|
|
18145
|
-
NEXUS_UI_BASE: z8.string().default("/"),
|
|
18146
|
-
NEXUS_UI_PATH: z8.string().default("../ui/dist"),
|
|
18147
|
-
NEXUS_CHECK_DRIFT: z8.coerce.boolean().optional(),
|
|
18148
|
-
NEXUS_FAIL_ON_DRIFT: z8.coerce.boolean().optional(),
|
|
18149
|
-
FRPC_SERVER: z8.string().optional(),
|
|
18150
|
-
FRPC_SERVER_PORT: z8.coerce.number().default(7e3),
|
|
18151
|
-
FRPC_TOKEN: z8.string().optional(),
|
|
18152
|
-
FRPC_SUBDOMAIN: z8.string().optional()
|
|
18153
|
-
});
|
|
18154
|
-
env = envSchema.parse(process.env);
|
|
18155
|
-
process.env["TZ"] = env.TZ;
|
|
18156
|
-
resolvedConfig = null;
|
|
18157
|
-
}
|
|
18158
|
-
});
|
|
18159
|
-
|
|
18160
18240
|
// src/core/middleware/error.middleware.ts
|
|
18161
18241
|
import { ZodError } from "zod";
|
|
18162
18242
|
import { ForbiddenError as CASLForbiddenError2 } from "@casl/ability";
|
|
@@ -18334,20 +18414,17 @@ var init_hash = __esm({
|
|
|
18334
18414
|
});
|
|
18335
18415
|
|
|
18336
18416
|
// src/core/crypto/symmetric.ts
|
|
18337
|
-
import { createCipheriv, createDecipheriv, randomBytes as
|
|
18417
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, hkdfSync } from "crypto";
|
|
18338
18418
|
function getKey() {
|
|
18339
18419
|
if (derivedKey) return derivedKey;
|
|
18340
|
-
const secret =
|
|
18341
|
-
if (!secret) {
|
|
18342
|
-
throw new Error("AUTH_SECRET not configured. Required for symmetric encryption.");
|
|
18343
|
-
}
|
|
18420
|
+
const secret = getAuthSecret();
|
|
18344
18421
|
const keyBytes = hkdfSync("sha256", secret, "", HKDF_INFO, KEY_LENGTH);
|
|
18345
18422
|
derivedKey = Buffer.from(keyBytes);
|
|
18346
18423
|
return derivedKey;
|
|
18347
18424
|
}
|
|
18348
18425
|
function encrypt(plaintext) {
|
|
18349
18426
|
const key = getKey();
|
|
18350
|
-
const iv =
|
|
18427
|
+
const iv = randomBytes3(IV_LENGTH);
|
|
18351
18428
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
18352
18429
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
18353
18430
|
const authTag = cipher.getAuthTag();
|
|
@@ -18371,6 +18448,7 @@ var ALGORITHM, IV_LENGTH, KEY_LENGTH, HKDF_INFO, derivedKey;
|
|
|
18371
18448
|
var init_symmetric = __esm({
|
|
18372
18449
|
"src/core/crypto/symmetric.ts"() {
|
|
18373
18450
|
"use strict";
|
|
18451
|
+
init_secret_resolver();
|
|
18374
18452
|
ALGORITHM = "aes-256-gcm";
|
|
18375
18453
|
IV_LENGTH = 12;
|
|
18376
18454
|
KEY_LENGTH = 32;
|
|
@@ -18596,8 +18674,8 @@ var init_safe_json = __esm({
|
|
|
18596
18674
|
|
|
18597
18675
|
// src/core/spa-handler.ts
|
|
18598
18676
|
import express from "express";
|
|
18599
|
-
import { resolve, join as
|
|
18600
|
-
import { existsSync as
|
|
18677
|
+
import { resolve, join as join11 } from "path";
|
|
18678
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
18601
18679
|
function createServeSPA(app, httpServer) {
|
|
18602
18680
|
return async (endpoint, distPath, options = {}) => {
|
|
18603
18681
|
const {
|
|
@@ -18619,7 +18697,7 @@ function createServeSPA(app, httpServer) {
|
|
|
18619
18697
|
registeredEndpoints.add(endpoint);
|
|
18620
18698
|
if (env.NODE_ENV === "development" && viteSrc) {
|
|
18621
18699
|
const srcPath = resolve(getProjectPath(), viteSrc);
|
|
18622
|
-
if (!
|
|
18700
|
+
if (!existsSync10(srcPath)) {
|
|
18623
18701
|
logger.warn({ endpoint, viteSrc, resolved: srcPath }, "Vite source not found \u2014 falling back to static");
|
|
18624
18702
|
} else {
|
|
18625
18703
|
const mounted = await mountViteDevMiddleware(app, endpoint, srcPath, httpServer);
|
|
@@ -18633,6 +18711,7 @@ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
|
|
|
18633
18711
|
try {
|
|
18634
18712
|
const vite = await import("vite");
|
|
18635
18713
|
const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
|
|
18714
|
+
const singletonDeps = ["vue", "vue-router", "pinia", "naive-ui"];
|
|
18636
18715
|
const server2 = await vite.createServer({
|
|
18637
18716
|
root: srcPath,
|
|
18638
18717
|
server: {
|
|
@@ -18640,6 +18719,12 @@ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
|
|
|
18640
18719
|
allowedHosts: true,
|
|
18641
18720
|
hmr: httpServer ? { server: httpServer } : true
|
|
18642
18721
|
},
|
|
18722
|
+
resolve: {
|
|
18723
|
+
dedupe: singletonDeps
|
|
18724
|
+
},
|
|
18725
|
+
optimizeDeps: {
|
|
18726
|
+
include: singletonDeps
|
|
18727
|
+
},
|
|
18643
18728
|
plugins: [{
|
|
18644
18729
|
name: "nexus-config-inject",
|
|
18645
18730
|
transformIndexHtml(html) {
|
|
@@ -18675,24 +18760,24 @@ function mountStaticSPA(app, endpoint, distPath, options) {
|
|
|
18675
18760
|
resolvedPath = distPath;
|
|
18676
18761
|
} else {
|
|
18677
18762
|
const projectPath2 = resolve(getProjectPath(), distPath);
|
|
18678
|
-
if (
|
|
18763
|
+
if (existsSync10(projectPath2)) {
|
|
18679
18764
|
resolvedPath = projectPath2;
|
|
18680
18765
|
} else {
|
|
18681
18766
|
resolvedPath = resolve(getLibPath(), distPath);
|
|
18682
18767
|
}
|
|
18683
18768
|
}
|
|
18684
|
-
if (!
|
|
18769
|
+
if (!existsSync10(resolvedPath)) {
|
|
18685
18770
|
logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
|
|
18686
18771
|
return;
|
|
18687
18772
|
}
|
|
18688
|
-
const indexPath =
|
|
18689
|
-
if (!
|
|
18773
|
+
const indexPath = join11(resolvedPath, index);
|
|
18774
|
+
if (!existsSync10(indexPath)) {
|
|
18690
18775
|
logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
|
|
18691
18776
|
}
|
|
18692
18777
|
app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
|
|
18693
18778
|
let injectedHtml = "";
|
|
18694
|
-
if (
|
|
18695
|
-
const rawHtml =
|
|
18779
|
+
if (existsSync10(indexPath)) {
|
|
18780
|
+
const rawHtml = readFileSync6(indexPath, "utf-8");
|
|
18696
18781
|
const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
|
|
18697
18782
|
const nexusConfig = JSON.stringify({ apiUrl });
|
|
18698
18783
|
injectedHtml = rawHtml.replace(
|
|
@@ -19267,11 +19352,7 @@ var init_cache = __esm({
|
|
|
19267
19352
|
// src/core/jwt/index.ts
|
|
19268
19353
|
import jwt4 from "jsonwebtoken";
|
|
19269
19354
|
function getSecret() {
|
|
19270
|
-
|
|
19271
|
-
if (!secret) {
|
|
19272
|
-
throw new Error("AUTH_SECRET not configured. Set AUTH_SECRET env var.");
|
|
19273
|
-
}
|
|
19274
|
-
return secret;
|
|
19355
|
+
return getAuthSecret();
|
|
19275
19356
|
}
|
|
19276
19357
|
function verifyAccessToken(token) {
|
|
19277
19358
|
return jwt4.verify(token, getSecret());
|
|
@@ -19279,6 +19360,7 @@ function verifyAccessToken(token) {
|
|
|
19279
19360
|
var init_jwt = __esm({
|
|
19280
19361
|
"src/core/jwt/index.ts"() {
|
|
19281
19362
|
"use strict";
|
|
19363
|
+
init_secret_resolver();
|
|
19282
19364
|
}
|
|
19283
19365
|
});
|
|
19284
19366
|
|
|
@@ -19388,91 +19470,6 @@ var init_schema_helpers = __esm({
|
|
|
19388
19470
|
}
|
|
19389
19471
|
});
|
|
19390
19472
|
|
|
19391
|
-
// src/db/sql-utils.ts
|
|
19392
|
-
function extractTableFromSql(sql, type2) {
|
|
19393
|
-
const match = sql.match(SQL_PATTERNS[type2]);
|
|
19394
|
-
return match?.[1];
|
|
19395
|
-
}
|
|
19396
|
-
function extractTableFromSelect(sql) {
|
|
19397
|
-
return extractTableFromSql(sql, "select");
|
|
19398
|
-
}
|
|
19399
|
-
function extractTableFromInsert(sql) {
|
|
19400
|
-
return extractTableFromSql(sql, "insert");
|
|
19401
|
-
}
|
|
19402
|
-
function extractTableFromUpdate(sql) {
|
|
19403
|
-
return extractTableFromSql(sql, "update");
|
|
19404
|
-
}
|
|
19405
|
-
function extractTableFromDelete(sql) {
|
|
19406
|
-
return extractTableFromSql(sql, "delete");
|
|
19407
|
-
}
|
|
19408
|
-
var SQL_PATTERNS;
|
|
19409
|
-
var init_sql_utils = __esm({
|
|
19410
|
-
"src/db/sql-utils.ts"() {
|
|
19411
|
-
"use strict";
|
|
19412
|
-
SQL_PATTERNS = {
|
|
19413
|
-
select: /from\s+["'`]?(\w+)["'`]?/i,
|
|
19414
|
-
insert: /insert into\s+["'`]?(\w+)["'`]?/i,
|
|
19415
|
-
update: /update\s+["'`]?(\w+)["'`]?/i,
|
|
19416
|
-
delete: /delete from\s+["'`]?(\w+)["'`]?/i
|
|
19417
|
-
};
|
|
19418
|
-
}
|
|
19419
|
-
});
|
|
19420
|
-
|
|
19421
|
-
// src/db/sqlite-compat.ts
|
|
19422
|
-
function createSqliteBooleanProcessor() {
|
|
19423
|
-
const booleanColumns = /* @__PURE__ */ new Map();
|
|
19424
|
-
function registerBooleanColumn2(table, column) {
|
|
19425
|
-
if (!booleanColumns.has(table)) {
|
|
19426
|
-
booleanColumns.set(table, /* @__PURE__ */ new Set());
|
|
19427
|
-
}
|
|
19428
|
-
booleanColumns.get(table).add(column);
|
|
19429
|
-
}
|
|
19430
|
-
function convertBooleans2(table, row) {
|
|
19431
|
-
const columns = booleanColumns.get(table);
|
|
19432
|
-
if (!columns || columns.size === 0) return row;
|
|
19433
|
-
const result = { ...row };
|
|
19434
|
-
for (const column of columns) {
|
|
19435
|
-
if (column in result) {
|
|
19436
|
-
const value = result[column];
|
|
19437
|
-
if (value === 0 || value === 1) {
|
|
19438
|
-
result[column] = value === 1;
|
|
19439
|
-
}
|
|
19440
|
-
}
|
|
19441
|
-
}
|
|
19442
|
-
return result;
|
|
19443
|
-
}
|
|
19444
|
-
function postProcess(result, queryContext) {
|
|
19445
|
-
const sql = queryContext?.sql?.toLowerCase() ?? "";
|
|
19446
|
-
if (!sql.startsWith("select")) return result;
|
|
19447
|
-
const table = extractTableFromSelect(sql);
|
|
19448
|
-
if (!table) return result;
|
|
19449
|
-
if (Array.isArray(result)) {
|
|
19450
|
-
return result.map(
|
|
19451
|
-
(row) => typeof row === "object" && row !== null ? convertBooleans2(table, row) : row
|
|
19452
|
-
);
|
|
19453
|
-
}
|
|
19454
|
-
if (typeof result === "object" && result !== null) {
|
|
19455
|
-
return convertBooleans2(table, result);
|
|
19456
|
-
}
|
|
19457
|
-
return result;
|
|
19458
|
-
}
|
|
19459
|
-
function clear() {
|
|
19460
|
-
booleanColumns.clear();
|
|
19461
|
-
}
|
|
19462
|
-
return { registerBooleanColumn: registerBooleanColumn2, convertBooleans: convertBooleans2, postProcess, clear };
|
|
19463
|
-
}
|
|
19464
|
-
var defaultProcessor, registerBooleanColumn, convertBooleans, sqlitePostProcess;
|
|
19465
|
-
var init_sqlite_compat = __esm({
|
|
19466
|
-
"src/db/sqlite-compat.ts"() {
|
|
19467
|
-
"use strict";
|
|
19468
|
-
init_sql_utils();
|
|
19469
|
-
defaultProcessor = createSqliteBooleanProcessor();
|
|
19470
|
-
registerBooleanColumn = defaultProcessor.registerBooleanColumn;
|
|
19471
|
-
convertBooleans = defaultProcessor.convertBooleans;
|
|
19472
|
-
sqlitePostProcess = defaultProcessor.postProcess;
|
|
19473
|
-
}
|
|
19474
|
-
});
|
|
19475
|
-
|
|
19476
19473
|
// src/runtime/types.ts
|
|
19477
19474
|
var init_types = __esm({
|
|
19478
19475
|
"src/runtime/types.ts"() {
|
|
@@ -23743,8 +23740,8 @@ var init_runtime = __esm({
|
|
|
23743
23740
|
});
|
|
23744
23741
|
|
|
23745
23742
|
// src/db/seed-runner.ts
|
|
23746
|
-
import { existsSync as
|
|
23747
|
-
import { join as
|
|
23743
|
+
import { existsSync as existsSync11 } from "fs";
|
|
23744
|
+
import { join as join12 } from "path";
|
|
23748
23745
|
import { pathToFileURL } from "url";
|
|
23749
23746
|
async function runModuleSeed(mod, ctx) {
|
|
23750
23747
|
let seeded = false;
|
|
@@ -23752,8 +23749,8 @@ async function runModuleSeed(mod, ctx) {
|
|
|
23752
23749
|
await mod.seed(ctx);
|
|
23753
23750
|
seeded = true;
|
|
23754
23751
|
} else {
|
|
23755
|
-
const seedPath =
|
|
23756
|
-
if (
|
|
23752
|
+
const seedPath = join12(getLibPath(), "dist", "modules", mod.name, `${mod.name}.seed.js`);
|
|
23753
|
+
if (existsSync11(seedPath)) {
|
|
23757
23754
|
const seedModule = await import(pathToFileURL(seedPath).href);
|
|
23758
23755
|
if (typeof seedModule.seed === "function") {
|
|
23759
23756
|
await seedModule.seed(ctx);
|
|
@@ -23907,94 +23904,6 @@ var init_migration_sources = __esm({
|
|
|
23907
23904
|
}
|
|
23908
23905
|
});
|
|
23909
23906
|
|
|
23910
|
-
// src/config/database.ts
|
|
23911
|
-
import { join as join11, dirname as dirname6, isAbsolute as isAbsolute2 } from "path";
|
|
23912
|
-
import { mkdirSync as mkdirSync4 } from "fs";
|
|
23913
|
-
function getDatabaseConfig() {
|
|
23914
|
-
const url = getConfig().databaseUrl;
|
|
23915
|
-
if (IN_MEMORY_RE.test(url)) {
|
|
23916
|
-
return {
|
|
23917
|
-
client: "better-sqlite3",
|
|
23918
|
-
connection: { filename: ":memory:" },
|
|
23919
|
-
useNullAsDefault: true,
|
|
23920
|
-
postProcessResponse: sqlitePostProcess,
|
|
23921
|
-
pool: { min: 0, max: 1 }
|
|
23922
|
-
};
|
|
23923
|
-
}
|
|
23924
|
-
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
23925
|
-
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
23926
|
-
if (!isAbsolute2(filename)) {
|
|
23927
|
-
filename = join11(getProjectPath(), "data", filename);
|
|
23928
|
-
}
|
|
23929
|
-
mkdirSync4(dirname6(filename), { recursive: true });
|
|
23930
|
-
return {
|
|
23931
|
-
client: "better-sqlite3",
|
|
23932
|
-
connection: { filename },
|
|
23933
|
-
useNullAsDefault: true,
|
|
23934
|
-
postProcessResponse: sqlitePostProcess
|
|
23935
|
-
};
|
|
23936
|
-
}
|
|
23937
|
-
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) {
|
|
23938
|
-
return {
|
|
23939
|
-
client: "pg",
|
|
23940
|
-
connection: url,
|
|
23941
|
-
pool: { min: 2, max: 10 }
|
|
23942
|
-
};
|
|
23943
|
-
}
|
|
23944
|
-
if (url.startsWith("mysql://")) {
|
|
23945
|
-
const offsetMinutes = (/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
23946
|
-
const offsetHours = Math.abs(Math.floor(offsetMinutes / 60));
|
|
23947
|
-
const offsetMins = Math.abs(offsetMinutes % 60);
|
|
23948
|
-
const sign = offsetMinutes <= 0 ? "+" : "-";
|
|
23949
|
-
const tzOffset = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMins).padStart(2, "0")}`;
|
|
23950
|
-
return {
|
|
23951
|
-
client: "mysql2",
|
|
23952
|
-
connection: {
|
|
23953
|
-
uri: url,
|
|
23954
|
-
timezone: tzOffset
|
|
23955
|
-
// mysql2 requiere formato "+HH:MM"
|
|
23956
|
-
},
|
|
23957
|
-
pool: { min: 2, max: 10 }
|
|
23958
|
-
};
|
|
23959
|
-
}
|
|
23960
|
-
throw new Error(`Unsupported database URL: ${url}`);
|
|
23961
|
-
}
|
|
23962
|
-
function getDatabaseType() {
|
|
23963
|
-
const url = getConfig().databaseUrl;
|
|
23964
|
-
if (IN_MEMORY_RE.test(url) || url.startsWith("file:") || url.startsWith("sqlite:")) return "sqlite";
|
|
23965
|
-
if (url.startsWith("postgresql://") || url.startsWith("postgres://")) return "postgresql";
|
|
23966
|
-
if (url.startsWith("mysql://")) return "mysql";
|
|
23967
|
-
return "sqlite";
|
|
23968
|
-
}
|
|
23969
|
-
function getDatabasePath() {
|
|
23970
|
-
const url = getConfig().databaseUrl;
|
|
23971
|
-
if (IN_MEMORY_RE.test(url)) return ":memory:";
|
|
23972
|
-
if (url.startsWith("file:") || url.startsWith("sqlite:")) {
|
|
23973
|
-
let filename = url.replace(/^(file:|sqlite:)/, "");
|
|
23974
|
-
if (!isAbsolute2(filename)) {
|
|
23975
|
-
filename = join11(getProjectPath(), "data", filename);
|
|
23976
|
-
}
|
|
23977
|
-
return filename;
|
|
23978
|
-
}
|
|
23979
|
-
try {
|
|
23980
|
-
const parsed = new URL(url);
|
|
23981
|
-
parsed.password = "***";
|
|
23982
|
-
return parsed.toString();
|
|
23983
|
-
} catch {
|
|
23984
|
-
return url.replace(/:\/\/[^@]+@/, "://***@");
|
|
23985
|
-
}
|
|
23986
|
-
}
|
|
23987
|
-
var IN_MEMORY_RE;
|
|
23988
|
-
var init_database = __esm({
|
|
23989
|
-
"src/config/database.ts"() {
|
|
23990
|
-
"use strict";
|
|
23991
|
-
init_env();
|
|
23992
|
-
init_paths();
|
|
23993
|
-
init_sqlite_compat();
|
|
23994
|
-
IN_MEMORY_RE = /^((file:|sqlite:)?:memory:?)$/;
|
|
23995
|
-
}
|
|
23996
|
-
});
|
|
23997
|
-
|
|
23998
23907
|
// src/db/query-interceptor.ts
|
|
23999
23908
|
function setupQueryInterceptor(knexInstance) {
|
|
24000
23909
|
const queryTimings = /* @__PURE__ */ new Map();
|
|
@@ -24635,7 +24544,7 @@ var init_migration_helpers = __esm({
|
|
|
24635
24544
|
// src/db/migration-generator.ts
|
|
24636
24545
|
import path2 from "path";
|
|
24637
24546
|
import fs2 from "fs/promises";
|
|
24638
|
-
import { readFileSync as
|
|
24547
|
+
import { readFileSync as readFileSync7, mkdirSync as mkdirSync5, realpathSync } from "fs";
|
|
24639
24548
|
function getColumnIndexBytes(field) {
|
|
24640
24549
|
if (!field?.db) return 255 * MYSQL_BYTES_PER_CHAR;
|
|
24641
24550
|
const size = field.db.size ?? 255;
|
|
@@ -26549,8 +26458,8 @@ var init_db = __esm({
|
|
|
26549
26458
|
});
|
|
26550
26459
|
|
|
26551
26460
|
// src/config/load-config.ts
|
|
26552
|
-
import { join as
|
|
26553
|
-
import { existsSync as
|
|
26461
|
+
import { join as join13 } from "path";
|
|
26462
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, readdirSync, statSync } from "fs";
|
|
26554
26463
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
26555
26464
|
function isPluginManifest(value) {
|
|
26556
26465
|
if (!value || typeof value !== "object" || typeof value === "function") return false;
|
|
@@ -26566,25 +26475,25 @@ function extractPluginManifest(mod) {
|
|
|
26566
26475
|
return null;
|
|
26567
26476
|
}
|
|
26568
26477
|
function resolvePluginEntry(projectPath2, pkgName) {
|
|
26569
|
-
const pkgDir =
|
|
26570
|
-
const pkgJsonPath =
|
|
26571
|
-
if (!
|
|
26478
|
+
const pkgDir = join13(projectPath2, "node_modules", pkgName);
|
|
26479
|
+
const pkgJsonPath = join13(pkgDir, "package.json");
|
|
26480
|
+
if (!existsSync12(pkgJsonPath)) return null;
|
|
26572
26481
|
try {
|
|
26573
|
-
const pluginPkg = JSON.parse(
|
|
26482
|
+
const pluginPkg = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
|
|
26574
26483
|
const exports = pluginPkg.exports;
|
|
26575
26484
|
const importEntry = exports?.["."]?.import;
|
|
26576
26485
|
const entry = (typeof importEntry === "string" ? importEntry : importEntry?.default) ?? (typeof exports === "string" ? exports : null) ?? pluginPkg.main ?? pluginPkg.module ?? "index.js";
|
|
26577
|
-
const entryPath =
|
|
26578
|
-
return
|
|
26486
|
+
const entryPath = join13(pkgDir, entry);
|
|
26487
|
+
return existsSync12(entryPath) ? entryPath : null;
|
|
26579
26488
|
} catch {
|
|
26580
26489
|
return null;
|
|
26581
26490
|
}
|
|
26582
26491
|
}
|
|
26583
26492
|
async function discoverPlugins(projectPath2) {
|
|
26584
|
-
const pkgPath =
|
|
26493
|
+
const pkgPath = join13(projectPath2, "package.json");
|
|
26585
26494
|
let pkg2;
|
|
26586
26495
|
try {
|
|
26587
|
-
pkg2 = JSON.parse(
|
|
26496
|
+
pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
26588
26497
|
} catch {
|
|
26589
26498
|
return [];
|
|
26590
26499
|
}
|
|
@@ -26606,21 +26515,21 @@ async function discoverPlugins(projectPath2) {
|
|
|
26606
26515
|
const manifest = extractPluginManifest(mod);
|
|
26607
26516
|
if (manifest) {
|
|
26608
26517
|
if (!manifest.migrationsDir) {
|
|
26609
|
-
const defaultMigrationsDir =
|
|
26610
|
-
if (
|
|
26518
|
+
const defaultMigrationsDir = join13(projectPath2, "node_modules", pkgName, "migrations");
|
|
26519
|
+
if (existsSync12(defaultMigrationsDir)) {
|
|
26611
26520
|
manifest.migrationsDir = defaultMigrationsDir;
|
|
26612
26521
|
}
|
|
26613
26522
|
}
|
|
26614
26523
|
if (!manifest.image) {
|
|
26615
|
-
const imagePath =
|
|
26616
|
-
if (
|
|
26524
|
+
const imagePath = join13(projectPath2, "node_modules", pkgName, "image.png");
|
|
26525
|
+
if (existsSync12(imagePath)) {
|
|
26617
26526
|
manifest.image = imagePath;
|
|
26618
26527
|
}
|
|
26619
26528
|
}
|
|
26620
26529
|
if (!manifest.llms) {
|
|
26621
|
-
const llmsPath =
|
|
26622
|
-
if (
|
|
26623
|
-
manifest.llms =
|
|
26530
|
+
const llmsPath = join13(projectPath2, "node_modules", pkgName, "llms.txt");
|
|
26531
|
+
if (existsSync12(llmsPath)) {
|
|
26532
|
+
manifest.llms = readFileSync8(llmsPath, "utf-8");
|
|
26624
26533
|
}
|
|
26625
26534
|
}
|
|
26626
26535
|
discovered.push(manifest);
|
|
@@ -26656,8 +26565,8 @@ function extractModuleManifests(mod) {
|
|
|
26656
26565
|
return manifests;
|
|
26657
26566
|
}
|
|
26658
26567
|
async function discoverModules(projectPath2) {
|
|
26659
|
-
const modulesDir = process.env["MODULES_DIR"] ||
|
|
26660
|
-
if (!
|
|
26568
|
+
const modulesDir = process.env["MODULES_DIR"] || join13(projectPath2, "src", "modules");
|
|
26569
|
+
if (!existsSync12(modulesDir)) return [];
|
|
26661
26570
|
let entries;
|
|
26662
26571
|
try {
|
|
26663
26572
|
entries = readdirSync(modulesDir);
|
|
@@ -26666,7 +26575,7 @@ async function discoverModules(projectPath2) {
|
|
|
26666
26575
|
}
|
|
26667
26576
|
const discovered = [];
|
|
26668
26577
|
for (const entry of entries) {
|
|
26669
|
-
const entryPath =
|
|
26578
|
+
const entryPath = join13(modulesDir, entry);
|
|
26670
26579
|
try {
|
|
26671
26580
|
if (!statSync(entryPath).isDirectory()) continue;
|
|
26672
26581
|
} catch {
|
|
@@ -26674,8 +26583,8 @@ async function discoverModules(projectPath2) {
|
|
|
26674
26583
|
}
|
|
26675
26584
|
let indexPath = null;
|
|
26676
26585
|
for (const ext of ["index.js", "index.ts"]) {
|
|
26677
|
-
const candidate =
|
|
26678
|
-
if (
|
|
26586
|
+
const candidate = join13(entryPath, ext);
|
|
26587
|
+
if (existsSync12(candidate)) {
|
|
26679
26588
|
indexPath = candidate;
|
|
26680
26589
|
break;
|
|
26681
26590
|
}
|
|
@@ -26789,6 +26698,49 @@ var init_events_api = __esm({
|
|
|
26789
26698
|
}
|
|
26790
26699
|
});
|
|
26791
26700
|
|
|
26701
|
+
// src/engine/capabilities-registry.ts
|
|
26702
|
+
var CapabilitiesRegistry;
|
|
26703
|
+
var init_capabilities_registry = __esm({
|
|
26704
|
+
"src/engine/capabilities-registry.ts"() {
|
|
26705
|
+
"use strict";
|
|
26706
|
+
CapabilitiesRegistry = class {
|
|
26707
|
+
resolvers = /* @__PURE__ */ new Map();
|
|
26708
|
+
logger;
|
|
26709
|
+
constructor(logger2) {
|
|
26710
|
+
this.logger = logger2;
|
|
26711
|
+
}
|
|
26712
|
+
register(key, resolver) {
|
|
26713
|
+
if (this.resolvers.has(key)) {
|
|
26714
|
+
this.logger?.warn({ key }, "Capability key already registered, overwriting");
|
|
26715
|
+
}
|
|
26716
|
+
this.resolvers.set(key, resolver);
|
|
26717
|
+
}
|
|
26718
|
+
async resolve() {
|
|
26719
|
+
const entries = Array.from(this.resolvers.entries());
|
|
26720
|
+
const results = await Promise.allSettled(
|
|
26721
|
+
entries.map(async ([key, resolver]) => {
|
|
26722
|
+
const value = await resolver();
|
|
26723
|
+
return [key, value];
|
|
26724
|
+
})
|
|
26725
|
+
);
|
|
26726
|
+
const capabilities = {};
|
|
26727
|
+
for (const result of results) {
|
|
26728
|
+
if (result.status === "fulfilled") {
|
|
26729
|
+
const [key, value] = result.value;
|
|
26730
|
+
capabilities[key] = value;
|
|
26731
|
+
} else {
|
|
26732
|
+
this.logger?.warn(
|
|
26733
|
+
{ err: result.reason?.message ?? String(result.reason) },
|
|
26734
|
+
"Capability resolver failed"
|
|
26735
|
+
);
|
|
26736
|
+
}
|
|
26737
|
+
}
|
|
26738
|
+
return capabilities;
|
|
26739
|
+
}
|
|
26740
|
+
};
|
|
26741
|
+
}
|
|
26742
|
+
});
|
|
26743
|
+
|
|
26792
26744
|
// src/engine/context.ts
|
|
26793
26745
|
import { ForbiddenError as CASLForbiddenError3, subject } from "@casl/ability";
|
|
26794
26746
|
import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2, DEFAULT_LOCALES } from "@gzl10/nexus-sdk";
|
|
@@ -27062,6 +27014,9 @@ function createModuleContext() {
|
|
|
27062
27014
|
return name in adaptersRegistry;
|
|
27063
27015
|
}
|
|
27064
27016
|
};
|
|
27017
|
+
const capabilitiesRegistry = new CapabilitiesRegistry(
|
|
27018
|
+
logger.child({ service: "capabilities" })
|
|
27019
|
+
);
|
|
27065
27020
|
const ctx = {
|
|
27066
27021
|
tenantId: DEFAULT_TENANT_ID2,
|
|
27067
27022
|
core: coreContext,
|
|
@@ -27072,6 +27027,7 @@ function createModuleContext() {
|
|
|
27072
27027
|
engine: engineContext,
|
|
27073
27028
|
services: servicesContext,
|
|
27074
27029
|
adapters: adaptersContext,
|
|
27030
|
+
capabilities: capabilitiesRegistry,
|
|
27075
27031
|
// Root-level shortcuts for frequently used utilities
|
|
27076
27032
|
events: createEventsApi(nexusEvents, logger),
|
|
27077
27033
|
createRouter: () => createRouter(),
|
|
@@ -27085,6 +27041,9 @@ function createModuleContext() {
|
|
|
27085
27041
|
createEntityRouter: (controller, def) => createEntityRouter(controller, def, ctx)
|
|
27086
27042
|
};
|
|
27087
27043
|
ctx.db.seedModule = (mod) => runModuleSeed(mod, ctx);
|
|
27044
|
+
capabilitiesRegistry.register("version", () => getCoreManifest().version);
|
|
27045
|
+
capabilitiesRegistry.register("plugins", () => getPlugins().map((p) => p.code));
|
|
27046
|
+
capabilitiesRegistry.register("locales", () => platformLocales);
|
|
27088
27047
|
return ctx;
|
|
27089
27048
|
}
|
|
27090
27049
|
var platformLocales, sharedCacheManager, sharedTempAdapter;
|
|
@@ -27100,6 +27059,7 @@ var init_context = __esm({
|
|
|
27100
27059
|
init_plugin_ops();
|
|
27101
27060
|
init_load_config();
|
|
27102
27061
|
init_events_api();
|
|
27062
|
+
init_capabilities_registry();
|
|
27103
27063
|
init_seed_runner();
|
|
27104
27064
|
init_cache_manager();
|
|
27105
27065
|
platformLocales = DEFAULT_LOCALES;
|
|
@@ -27119,6 +27079,7 @@ var init_engine = __esm({
|
|
|
27119
27079
|
init_subject_extractor();
|
|
27120
27080
|
init_definition_extractors();
|
|
27121
27081
|
init_context();
|
|
27082
|
+
init_capabilities_registry();
|
|
27122
27083
|
}
|
|
27123
27084
|
});
|
|
27124
27085
|
|
|
@@ -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);
|
|
@@ -28362,6 +28323,12 @@ async function runMigrationsAndSeeds(config3) {
|
|
|
28362
28323
|
loadCoreModules();
|
|
28363
28324
|
logger.info({ type: getDatabaseType(), path: getDatabasePath() }, "Database ready");
|
|
28364
28325
|
await ensureSystemTables(getDb());
|
|
28326
|
+
setSecretResolverDb(getDb());
|
|
28327
|
+
await initAuthSecret(logger);
|
|
28328
|
+
const isPM2Cluster = !!(process.env["PM2_HOME"] && process.env["NODE_APP_INSTANCE"]);
|
|
28329
|
+
if (isPM2Cluster && getDatabaseType() === "sqlite") {
|
|
28330
|
+
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.");
|
|
28331
|
+
}
|
|
28365
28332
|
const fileConfig = await loadNexusConfig();
|
|
28366
28333
|
const effectiveConfig = { ...config3 };
|
|
28367
28334
|
const programmaticPluginNames = new Set((config3?.plugins ?? []).map((p) => p.name));
|
|
@@ -28616,6 +28583,7 @@ async function stop() {
|
|
|
28616
28583
|
resetConfigCache();
|
|
28617
28584
|
clearCustomCaslRules();
|
|
28618
28585
|
clearSeedPermissions();
|
|
28586
|
+
_resetSecretResolver();
|
|
28619
28587
|
await resetServeSPA();
|
|
28620
28588
|
return;
|
|
28621
28589
|
}
|
|
@@ -28651,6 +28619,7 @@ async function stop() {
|
|
|
28651
28619
|
resetConfigCache();
|
|
28652
28620
|
clearCustomCaslRules();
|
|
28653
28621
|
clearSeedPermissions();
|
|
28622
|
+
_resetSecretResolver();
|
|
28654
28623
|
await resetServeSPA();
|
|
28655
28624
|
currentConfig = void 0;
|
|
28656
28625
|
server = null;
|
|
@@ -28720,6 +28689,7 @@ var init_server = __esm({
|
|
|
28720
28689
|
init_ensure_system_tables();
|
|
28721
28690
|
init_load_config();
|
|
28722
28691
|
init_tunnel();
|
|
28692
|
+
init_secret_resolver();
|
|
28723
28693
|
server = null;
|
|
28724
28694
|
gracefulShutdownRegistered = false;
|
|
28725
28695
|
setupGracefulShutdown();
|