@qwickapps/server 1.8.2 → 1.9.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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Control Panel</title>
7
- <script type="module" crossorigin src="/assets/index-Cez_jyhl.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-DnEQCOGR.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-De-dCl_t.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@qwickapps/server",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "description": "Plugin-based application server framework for building websites, APIs, admin dashboards, and full-stack products",
5
+ "type": "module",
5
6
  "main": "dist/src/index.js",
6
7
  "types": "dist/src/index.d.ts",
7
8
  "exports": {
@@ -29,6 +30,29 @@
29
30
  "ui",
30
31
  "CHANGELOG.md"
31
32
  ],
33
+ "scripts": {
34
+ "build": "npm run build:ui-lib && npm run build:server && npm run build:ui",
35
+ "build:server": "tsc",
36
+ "build:ui": "cd ui && vite build",
37
+ "build:ui-lib": "cd ui && vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json",
38
+ "build:clean": "rm -rf dist dist-ui dist-ui-lib && npm run build",
39
+ "dev": "tsc --watch",
40
+ "dev:ui": "cd ui && vite",
41
+ "clean": "rm -rf dist dist-ui dist-ui-lib node_modules",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "test:coverage": "vitest run --coverage",
45
+ "test:e2e": "playwright test",
46
+ "test:e2e:ui": "playwright test --ui",
47
+ "test:e2e:headed": "playwright test --headed",
48
+ "demo": "npm run build:server && pnpm tsx examples/demo-server.ts",
49
+ "demo:gateway": "npm run build:server && pnpm tsx examples/demo-gateway.ts",
50
+ "demo:all": "npm run build:server && pnpm tsx examples/demo-all.ts",
51
+ "type-check": "tsc --noEmit",
52
+ "type-check:ui": "cd ui && tsc --noEmit",
53
+ "validate:clean-install": "./qa/clean-install/validate.sh",
54
+ "prepublishOnly": "npm run test && npm run build && npm run validate:clean-install"
55
+ },
32
56
  "dependencies": {
33
57
  "@qwickapps/logging": "^1.0.2",
34
58
  "compression": "^1.7.4",
@@ -120,27 +144,5 @@
120
144
  "homepage": "https://github.com/qwickapps/server#readme",
121
145
  "publishConfig": {
122
146
  "access": "public"
123
- },
124
- "scripts": {
125
- "build": "npm run build:ui-lib && npm run build:server && npm run build:ui",
126
- "build:server": "tsc",
127
- "build:ui": "cd ui && vite build",
128
- "build:ui-lib": "cd ui && vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json",
129
- "build:clean": "rm -rf dist dist-ui dist-ui-lib && npm run build",
130
- "dev": "tsc --watch",
131
- "dev:ui": "cd ui && vite",
132
- "clean": "rm -rf dist dist-ui dist-ui-lib node_modules",
133
- "test": "vitest run",
134
- "test:watch": "vitest",
135
- "test:coverage": "vitest run --coverage",
136
- "test:e2e": "playwright test",
137
- "test:e2e:ui": "playwright test --ui",
138
- "test:e2e:headed": "playwright test --headed",
139
- "demo": "npm run build:server && pnpm tsx examples/demo-server.ts",
140
- "demo:gateway": "npm run build:server && pnpm tsx examples/demo-gateway.ts",
141
- "demo:all": "npm run build:server && pnpm tsx examples/demo-all.ts",
142
- "type-check": "tsc --noEmit",
143
- "type-check:ui": "cd ui && tsc --noEmit",
144
- "validate:clean-install": "./qa/clean-install/validate.sh"
145
147
  }
146
- }
148
+ }
@@ -47,14 +47,12 @@ export function supertokensAdapter(config: SupertokensAdapterConfig): AuthAdapte
47
47
  // Store response on request for later use in getUser()
48
48
  (req as SupertokensExtendedRequest)[REQUEST_RES_KEY] = res;
49
49
 
50
- // Skip if already initialized with error
50
+ // Skip if already initialized with error — let auth-checking middleware
51
+ // decide whether to block the request based on authRequired config.
52
+ // Returning 500 here blocks ALL routes (including /auth/config/status)
53
+ // even when authRequired is false.
51
54
  if (initializationError) {
52
- return res.status(500).json({
53
- error: 'Auth Configuration Error',
54
- message:
55
- 'Supertokens is not properly configured. Install supertokens-node package: npm install supertokens-node',
56
- details: initializationError.message,
57
- });
55
+ return next();
58
56
  }
59
57
 
60
58
  // Lazy initialize Supertokens
@@ -163,12 +161,10 @@ export function supertokensAdapter(config: SupertokensAdapterConfig): AuthAdapte
163
161
  initializationError =
164
162
  error instanceof Error ? error : new Error('Failed to initialize Supertokens');
165
163
  console.error('[SupertokensAdapter] Initialization error:', error);
166
- return res.status(500).json({
167
- error: 'Auth Configuration Error',
168
- message:
169
- 'Supertokens is not properly configured. Install supertokens-node package: npm install supertokens-node',
170
- details: initializationError.message,
171
- });
164
+ // Let the auth-checking middleware decide whether to block this request.
165
+ // Non-auth routes (e.g. /auth/config/status) must remain accessible
166
+ // so the UI can display the configuration error state.
167
+ return next();
172
168
  }
173
169
  }
174
170
 
@@ -73,13 +73,14 @@ export function createAuthPlugin(config: AuthPluginConfig): Plugin {
73
73
  }
74
74
  }
75
75
 
76
- // Register SuperTokens middleware on router for /auth/* paths
77
- // This ensures Gateway forwards ALL /api/auth/* requests to control panel
78
- // where SuperTokens can handle them dynamically
76
+ // Register SuperTokens middleware on router for auth paths
77
+ // Uses config.apiBasePath so Gateway forwards requests to the correct path
78
+ // e.g. SUPERTOKENS_API_BASE_PATH=/qapi/auth router.use('/qapi/auth', ...)
79
+ const authBasePath = config.apiBasePath ?? '/auth';
79
80
  if (Array.isArray(primaryMiddleware)) {
80
- router.use('/auth', ...primaryMiddleware);
81
+ router.use(authBasePath, ...primaryMiddleware);
81
82
  } else {
82
- router.use('/auth', primaryMiddleware);
83
+ router.use(authBasePath, primaryMiddleware);
83
84
  }
84
85
 
85
86
  // Add the auth checking middleware to router (not app)
@@ -402,6 +402,7 @@ export function createAuthPluginFromEnv(options?: AuthEnvPluginOptions): Plugin
402
402
  excludePaths,
403
403
  authRequired,
404
404
  debug,
405
+ apiBasePath: getEnv('SUPERTOKENS_API_BASE_PATH') ?? getEnv('AUTH_API_BASE_PATH'),
405
406
  onUnauthorized: options?.onUnauthorized,
406
407
  onAuthenticated: options?.onAuthenticated,
407
408
  };
@@ -180,6 +180,8 @@ export interface AuthPluginConfig {
180
180
  excludePaths?: string[];
181
181
  /** Whether auth is required for all routes (default: true) */
182
182
  authRequired?: boolean;
183
+ /** API base path for auth routes registered on the Express router (default: '/auth') */
184
+ apiBasePath?: string;
183
185
  /** Custom unauthorized handler */
184
186
  onUnauthorized?: (req: Request, res: Response) => void;
185
187
  /**
@@ -106,11 +106,12 @@ export class SeedExecutor {
106
106
  this.outputSize = 0;
107
107
 
108
108
  return new Promise((resolvePromise, rejectPromise) => {
109
- // Determine if we need tsx (for .ts/.mts files that import TS modules)
110
- // Use tsx for .mjs files too since they might import from TS source
111
- const needsTsx = scriptPath.match(/\.(mjs|ts|mts)$/);
109
+ // .ts/.mts files require tsx for TypeScript compilation.
110
+ // .mjs files are native ESM and run directly with node — tsx is not
111
+ // available in the system PATH in Docker production builds.
112
+ const needsTsx = scriptPath.match(/\.(ts|mts)$/);
112
113
  const execCommand = needsTsx ? 'tsx' : process.execPath;
113
- const execArgs = needsTsx ? [scriptPath] : [scriptPath];
114
+ const execArgs = [scriptPath];
114
115
 
115
116
  // Spawn process with TypeScript support if needed
116
117
  this.child = spawn(execCommand, execArgs, {
@@ -416,6 +416,8 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
416
416
  } catch (error) {
417
417
  logger.error('Seed execution failed', { name, error });
418
418
 
419
+ const duration = Date.now() - startTime;
420
+
419
421
  // Send error event via SSE to notify client
420
422
  res.write(`data: ${JSON.stringify({
421
423
  type: 'error',
@@ -423,6 +425,13 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
423
425
  timestamp: new Date().toISOString()
424
426
  })}\n\n`);
425
427
 
428
+ // Send exit event so the UI can transition out of the "Running..." state
429
+ res.write(`data: ${JSON.stringify({
430
+ type: 'exit',
431
+ data: JSON.stringify({ exitCode: 1, duration }),
432
+ timestamp: new Date().toISOString()
433
+ })}\n\n`);
434
+
426
435
  // Update execution record as failed
427
436
  if (hasPostgres() && executionId) {
428
437
  const db = getPostgres();
@@ -476,16 +485,31 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
476
485
 
477
486
  const db = getPostgres();
478
487
 
488
+ // Derive the DB role from the connection URL so GRANT statements
489
+ // work regardless of which user owns the schema.
490
+ let dbRole = 'qwickapps';
491
+ if (config.databaseUrl) {
492
+ try {
493
+ const parsedUrl = new URL(config.databaseUrl);
494
+ const urlUser = parsedUrl.username;
495
+ if (urlUser && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(urlUser)) {
496
+ dbRole = urlUser;
497
+ }
498
+ } catch {
499
+ // Keep default if URL is unparseable
500
+ }
501
+ }
502
+
479
503
  // Drop and recreate public schema (removes all tables, data, etc.)
480
504
  await db.queryRaw('DROP SCHEMA IF EXISTS public CASCADE');
481
505
  await db.queryRaw('CREATE SCHEMA public');
482
506
  await db.queryRaw('GRANT ALL ON SCHEMA public TO public');
483
507
  await db.queryRaw('GRANT ALL ON SCHEMA public TO postgres');
484
- await db.queryRaw('GRANT ALL ON SCHEMA public TO qwickapps');
508
+ await db.queryRaw(`GRANT ALL ON SCHEMA public TO ${dbRole}`);
485
509
 
486
510
  // Grant default privileges for future tables and sequences
487
- await db.queryRaw('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO qwickapps');
488
- await db.queryRaw('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO qwickapps');
511
+ await db.queryRaw(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${dbRole}`);
512
+ await db.queryRaw(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ${dbRole}`);
489
513
 
490
514
  res.json({
491
515
  success: true,
@@ -12,14 +12,9 @@ import { Box, Typography, Chip, CircularProgress, Alert } from '@mui/material';
12
12
  import CheckCircleIcon from '@mui/icons-material/CheckCircle';
13
13
  import ErrorIcon from '@mui/icons-material/Error';
14
14
  import BlockIcon from '@mui/icons-material/Block';
15
- import { api } from '../../api/controlPanelApi';
15
+ import { api, type AuthConfigStatus } from '../../api/controlPanelApi';
16
16
 
17
- interface AuthStatus {
18
- state: 'enabled' | 'disabled' | 'error';
19
- adapter: string | null;
20
- error?: string;
21
- missingVars?: string[];
22
- }
17
+ type AuthStatus = AuthConfigStatus;
23
18
 
24
19
  const adapterLabels: Record<string, string> = {
25
20
  supertokens: 'SuperTokens',
@@ -36,7 +31,7 @@ export function AuthStatusWidget() {
36
31
  useEffect(() => {
37
32
  const fetchStatus = async () => {
38
33
  try {
39
- const data = await api.fetch<AuthStatus>('/auth/config/status');
34
+ const data = await api.getAuthConfigStatus();
40
35
  setStatus(data);
41
36
  } catch (err) {
42
37
  setError(err instanceof Error ? err.message : 'Failed to fetch auth status');