thatcher 1.0.7 → 1.0.34
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/README.md +4 -16
- package/package.json +12 -18
- package/src/adapters/google-auth.js +4 -43
- package/src/adapters/google-drive.js +12 -67
- package/src/app/api/audit/dashboard/route.js +14 -11
- package/src/app/api/audit/logs/route.js +2 -2
- package/src/app/api/audit/permissions/[id]/route.js +6 -3
- package/src/app/api/audit/permissions/route.js +10 -7
- package/src/app/api/audit/permissions/stats/route.js +8 -5
- package/src/app/api/audit/route.js +4 -4
- package/src/app/api/audit/stats/route.js +1 -1
- package/src/app/api/auth/google/callback/route.js +5 -2
- package/src/app/api/auth/google/route.js +4 -1
- package/src/app/api/auth/login/route.js +5 -2
- package/src/app/api/auth/mwr-bridge/route.js +11 -21
- package/src/app/api/auth/password-reset/route.js +15 -14
- package/src/app/api/cron/trigger/route.js +4 -1
- package/src/app/api/debug/[[...path]]/route.js +6 -5
- package/src/app/api/debug/sqlite/route.js +7 -12
- package/src/app/api/debug/sync/route.js +5 -5
- package/src/app/api/domains/route.js +4 -1
- package/src/app/api/email/allocate/batch/route.js +19 -29
- package/src/app/api/email/allocate/route.js +16 -17
- package/src/app/api/email/receive/route.js +31 -42
- package/src/app/api/email/send/route.js +28 -16
- package/src/app/api/email/unallocated/route.js +10 -13
- package/src/app/api/files/[id]/route.js +4 -1
- package/src/app/api/formula/[[...path]]/route.js +0 -4
- package/src/app/api/health/route.js +10 -9
- package/src/app/api/metrics/route.js +1 -1
- package/src/cli.js +4 -4
- package/src/config/config-loader.js +1 -21
- package/src/config/constants.js +0 -35
- package/src/config/env.js +0 -30
- package/src/config/spec-helpers.js +6 -95
- package/src/engine.js +23 -0
- package/src/engine.server.js +8 -78
- package/src/index.js +42 -67
- package/src/lib/alert-manager.js +7 -3
- package/src/lib/api-helpers.js +4 -3
- package/src/lib/api.js +10 -10
- package/src/lib/audit-logger-enhanced.js +77 -83
- package/src/lib/auth-middleware.js +1 -32
- package/src/lib/auth-route-helpers.js +5 -0
- package/src/lib/busybase-adapter.js +0 -5
- package/src/lib/busybase-audit-reads.js +142 -0
- package/src/lib/busybase-audit.js +41 -0
- package/src/lib/busybase-lucia-adapter.js +72 -0
- package/src/lib/busybase-store.js +338 -0
- package/src/lib/config-field-helpers.js +0 -17
- package/src/lib/config-generator-engine.js +19 -36
- package/src/lib/config-helpers.js +0 -21
- package/src/lib/crud-action-helpers.js +2 -2
- package/src/lib/crud-factory.js +1 -16
- package/src/lib/crud-handlers.js +17 -52
- package/src/lib/csrf-protection.js +5 -2
- package/src/lib/date-utils.js +3 -127
- package/src/lib/debug-registry.js +0 -5
- package/src/lib/domain-loader.js +4 -1
- package/src/lib/email-sender.js +25 -23
- package/src/lib/error-handler.js +5 -41
- package/src/lib/error-recovery.js +10 -7
- package/src/lib/error-resilience.js +7 -5
- package/src/lib/events-engine.js +9 -6
- package/src/lib/export-sink.js +0 -5
- package/src/lib/field-iterator.js +7 -76
- package/src/lib/field-registry.js +0 -22
- package/src/lib/generic-crud-handler.js +0 -16
- package/src/lib/hook-engine.js +5 -53
- package/src/lib/hot-reload/checkpoint.js +5 -2
- package/src/lib/hot-reload/promise-container.js +7 -3
- package/src/lib/hot-reload/route-wrapper.js +4 -1
- package/src/lib/hot-reload/supervisor.js +6 -3
- package/src/lib/hyperformula-service.js +0 -5
- package/src/lib/id-helpers.js +10 -0
- package/src/lib/index.js +4 -7
- package/src/lib/log-aggregator.js +0 -1
- package/src/lib/logger.js +0 -15
- package/src/lib/metrics-collector.js +2 -46
- package/src/lib/next-compat.js +15 -1
- package/src/lib/observability-bootstrap.js +0 -5
- package/src/lib/perf-monitor.js +4 -1
- package/src/lib/perf-profiler.js +0 -5
- package/src/lib/query-string-adapter.js +5 -47
- package/src/lib/realtime-server.js +4 -26
- package/src/lib/request-tracing.js +0 -5
- package/src/lib/resource-monitor.js +4 -1
- package/src/lib/response-formatter.js +0 -38
- package/src/lib/route-helpers.js +3 -1
- package/src/lib/safe-json.js +5 -1
- package/src/lib/server-bootstrap.js +10 -9
- package/src/lib/stage-pipeline.js +2 -2
- package/src/lib/state-transport-client.js +4 -1
- package/src/lib/static-server.js +2 -2
- package/src/lib/status-helpers.js +2 -51
- package/src/lib/tracing.js +0 -5
- package/src/lib/utils.js +6 -67
- package/src/lib/validate.js +5 -88
- package/src/lib/validators.js +0 -38
- package/src/lib/with-audit-logging.js +1 -1
- package/src/lib/workflow-engine.js +10 -77
- package/src/lib/xstate-workflow-engine.js +3 -9
- package/src/plugins/index.js +5 -3
- package/src/server/server.js +26 -27
- package/src/services/collaborator-role.service.js +21 -77
- package/src/services/email-sender.js +7 -34
- package/src/services/notification-engine.js +12 -33
- package/src/services/permission.service.js +10 -73
- package/src/ui/advanced-search-renderer.js +3 -2
- package/src/ui/advanced-widgets.js +1 -1
- package/src/ui/auth-pages.js +10 -9
- package/src/ui/auth-styles.js +1 -1
- package/src/ui/client.js +1 -1
- package/src/ui/component-engine.js +1 -1
- package/src/ui/dashboard-renderer.js +5 -5
- package/src/ui/dialog-engine.js +6 -9
- package/src/ui/dialog-factory.js +2 -2
- package/src/ui/engagement-cards.js +7 -7
- package/src/ui/engagement-dialogs.js +2 -3
- package/src/ui/engagement-grid-renderer.js +20 -19
- package/src/ui/entity-renderer.js +56 -42
- package/src/ui/file-dialogs.js +25 -9
- package/src/ui/flexup-report-renderer.js +21 -18
- package/src/ui/format-helpers.js +27 -28
- package/src/ui/highlight-threading-renderer.js +9 -9
- package/src/ui/job-management-renderer.js +18 -16
- package/src/ui/layout.js +51 -25
- package/src/ui/ml-console-renderer.js +15 -16
- package/src/ui/monitoring-dashboard-client.js +9 -8
- package/src/ui/notifications-renderer.js +11 -10
- package/src/ui/page-handler-admin.js +32 -23
- package/src/ui/page-handler-helpers.js +27 -23
- package/src/ui/page-handler-reviews.js +71 -35
- package/src/ui/page-handler-rfi.js +28 -14
- package/src/ui/page-handler.js +65 -48
- package/src/ui/picker-dialogs.js +26 -19
- package/src/ui/render-helpers.js +84 -46
- package/src/ui/review-comparison-renderer.js +8 -7
- package/src/ui/review-detail-panels.js +8 -9
- package/src/ui/review-detail-renderer.js +3 -3
- package/src/ui/review-detail-script.js +13 -12
- package/src/ui/review-mwr-renderer.js +6 -5
- package/src/ui/review-renderer.js +7 -6
- package/src/ui/review-widgets.js +3 -3
- package/src/ui/rfi-detail-renderer.js +37 -20
- package/src/ui/rfi-report-renderer.js +14 -9
- package/src/ui/settings-renderer-advanced.js +21 -18
- package/src/ui/settings-renderer-advanced2.js +20 -19
- package/src/ui/settings-renderer-teams.js +4 -4
- package/src/ui/settings-renderer.js +4 -4
- package/src/ui/spacing-system.js +5 -2
- package/src/ui/standalone-login.js +2 -2
- package/src/ui/styles.css +2 -1
- package/src/ui/styles2.css +75 -5
- package/src/ui/test-page.js +4 -4
- package/src/ui/widgets.js +20 -15
- package/src/lib/audit-logger.js +0 -193
- package/src/lib/database-core.js +0 -255
- package/src/lib/database-migrations.js +0 -350
- package/src/lib/db-backup.js +0 -97
- package/src/lib/error-boundary.js +0 -134
- package/src/lib/errors.js +0 -69
- package/src/lib/health-monitor.js +0 -134
- package/src/lib/index-optimizer.js +0 -96
- package/src/lib/query-engine-write.js +0 -221
- package/src/lib/query-engine.js +0 -446
- package/src/ui/collaboration-dialogs.js +0 -31
- package/src/ui/global-tags.js +0 -144
- package/src/ui/idle-logout.js +0 -156
- package/src/ui/letter-dialogs.js +0 -37
- package/src/ui/password-reset-page.js +0 -146
- package/src/ui/perf-renderer.js +0 -71
- package/src/ui/rfi-renderer.js +0 -194
- package/src/ui/validation-ui.js +0 -147
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from '@/lib/next-polyfills';
|
|
2
|
-
import {
|
|
2
|
+
import { createLogger } from '@/lib/logger.js';
|
|
3
|
+
import { list } from '@/engine';
|
|
4
|
+
|
|
5
|
+
const log = createLogger('[EmailUnallocated]');
|
|
3
6
|
|
|
4
7
|
export async function GET(request) {
|
|
5
8
|
try {
|
|
@@ -11,17 +14,11 @@ export async function GET(request) {
|
|
|
11
14
|
const limit = parseInt(searchParams.get('limit') || '50', 10);
|
|
12
15
|
const offset = parseInt(searchParams.get('offset') || '0', 10);
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
WHERE allocated = 0
|
|
20
|
-
ORDER BY received_at DESC
|
|
21
|
-
LIMIT ? OFFSET ?
|
|
22
|
-
`).all(limit, offset);
|
|
23
|
-
|
|
24
|
-
const total = db.prepare('SELECT COUNT(*) as count FROM email WHERE allocated = 0').get();
|
|
17
|
+
// Unallocated emails, newest first, paginated (busybase + in-memory slice).
|
|
18
|
+
const all = (await list('email', {}, { sort: { field: 'received_at', dir: 'DESC' } }))
|
|
19
|
+
.filter(e => !e.allocated || e.allocated === 0);
|
|
20
|
+
const total = { count: all.length };
|
|
21
|
+
const emails = all.slice(offset, offset + limit);
|
|
25
22
|
|
|
26
23
|
const emailsWithParsedAttachments = emails.map(email => ({
|
|
27
24
|
...email,
|
|
@@ -38,7 +35,7 @@ export async function GET(request) {
|
|
|
38
35
|
});
|
|
39
36
|
|
|
40
37
|
} catch (error) {
|
|
41
|
-
|
|
38
|
+
log.error('error:', { message: error.message });
|
|
42
39
|
return NextResponse.json(
|
|
43
40
|
{ error: 'Internal server error', message: error.message },
|
|
44
41
|
{ status: 500 }
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from '@/lib/next-polyfills';
|
|
2
|
+
import { createLogger } from '@/lib/logger.js';
|
|
3
|
+
|
|
4
|
+
const log = createLogger('[FilesAPI]');
|
|
2
5
|
import { requireUser } from '@/engine.server';
|
|
3
6
|
import { permissionService } from '@/services/permission.service';
|
|
4
7
|
import { get } from '@/engine';
|
|
@@ -32,7 +35,7 @@ export async function GET(request, { params }) {
|
|
|
32
35
|
},
|
|
33
36
|
});
|
|
34
37
|
} catch (error) {
|
|
35
|
-
|
|
38
|
+
log.error('file download error:', { message: error.message });
|
|
36
39
|
return NextResponse.json({ error: error.message }, { status: HTTP.INTERNAL_ERROR });
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { createLogger } from '@/lib/logger.js';
|
|
1
2
|
import fs from 'fs'
|
|
3
|
+
|
|
4
|
+
const log = createLogger('[Health]');
|
|
2
5
|
import path from 'path'
|
|
3
|
-
import {
|
|
6
|
+
import { count } from '@/engine'
|
|
4
7
|
import { getAllMetrics } from '@/lib/metrics-collector.js'
|
|
5
8
|
import { getDatabaseStats } from '@/lib/db-monitor.js'
|
|
6
9
|
import { getCurrentResources } from '@/lib/resource-monitor.js'
|
|
@@ -19,11 +22,10 @@ function getLastSyncAt() {
|
|
|
19
22
|
|
|
20
23
|
export const GET = async (request) => {
|
|
21
24
|
try {
|
|
22
|
-
const db = getDatabase()
|
|
23
25
|
const start = process.hrtime.bigint()
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// busybase liveness probe (replaces the SQLite SELECT 1 + wal_checkpoint).
|
|
28
|
+
try { await count('user', {}); } catch { /* table may not exist yet; still alive */ }
|
|
27
29
|
|
|
28
30
|
const dbLatency = Number(process.hrtime.bigint() - start) / 1000000
|
|
29
31
|
const url = new URL(request.url)
|
|
@@ -48,7 +50,7 @@ export const GET = async (request) => {
|
|
|
48
50
|
const user = await getUser()
|
|
49
51
|
if (user && (user.role === 'admin' || user.role === 'partner')) {
|
|
50
52
|
health.metrics = getAllMetrics()
|
|
51
|
-
health.database.stats = getDatabaseStats()
|
|
53
|
+
try { health.database.stats = getDatabaseStats() } catch { health.database.stats = null }
|
|
52
54
|
health.resources = getCurrentResources()
|
|
53
55
|
health.alerts = getRecentAlerts(10)
|
|
54
56
|
health.memory = {
|
|
@@ -68,7 +70,7 @@ export const GET = async (request) => {
|
|
|
68
70
|
}
|
|
69
71
|
)
|
|
70
72
|
} catch (error) {
|
|
71
|
-
|
|
73
|
+
log.error('check failed:', { message: error.message })
|
|
72
74
|
return new Response(
|
|
73
75
|
JSON.stringify({
|
|
74
76
|
status: 'error',
|
|
@@ -85,11 +87,10 @@ export const GET = async (request) => {
|
|
|
85
87
|
|
|
86
88
|
export const HEAD = async (request) => {
|
|
87
89
|
try {
|
|
88
|
-
|
|
89
|
-
db.prepare('SELECT 1').get()
|
|
90
|
+
await count('user', {})
|
|
90
91
|
return new Response(null, { status: 200 })
|
|
91
92
|
} catch (error) {
|
|
92
|
-
|
|
93
|
+
log.error('check failed:', { message: error.message })
|
|
93
94
|
return new Response(null, { status: 503 })
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -27,7 +27,7 @@ export const GET = async (request) => {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (type === 'all' || type === 'database') {
|
|
30
|
-
data.database = getDatabaseStats()
|
|
30
|
+
try { data.database = getDatabaseStats() } catch { data.database = null }
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
if (type === 'all' || type === 'resources') {
|
package/src/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ async function main() {
|
|
|
39
39
|
console.log('[Thatcher] Running migrations...');
|
|
40
40
|
const t1 = new Thatcher({});
|
|
41
41
|
await t1.init();
|
|
42
|
-
console.log('
|
|
42
|
+
console.log('[Thatcher] Migrations complete');
|
|
43
43
|
break;
|
|
44
44
|
|
|
45
45
|
case 'validate':
|
|
@@ -47,11 +47,11 @@ async function main() {
|
|
|
47
47
|
try {
|
|
48
48
|
const t2 = new Thatcher({});
|
|
49
49
|
await t2.init();
|
|
50
|
-
console.log('
|
|
50
|
+
console.log('[Thatcher] Configuration is valid');
|
|
51
51
|
console.log(` Entities: ${t2.getAllEntities().length}`);
|
|
52
52
|
console.log(` Workflows: ${Object.keys(t2.config?.workflows || {}).length}`);
|
|
53
53
|
} catch (err) {
|
|
54
|
-
console.error('
|
|
54
|
+
console.error('[Thatcher] Configuration error:', err.message);
|
|
55
55
|
process.exit(1);
|
|
56
56
|
}
|
|
57
57
|
break;
|
|
@@ -200,7 +200,7 @@ thresholds:
|
|
|
200
200
|
`;
|
|
201
201
|
|
|
202
202
|
fs.writeFileSync(path.join(exampleDir, 'thatcher.config.yml'), config);
|
|
203
|
-
console.log(
|
|
203
|
+
console.log(`[Thatcher] Example config written to ${exampleDir}/`);
|
|
204
204
|
console.log(' cd', exampleDir);
|
|
205
205
|
console.log(' thatcher start');
|
|
206
206
|
}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
* Configuration loading and management
|
|
3
|
-
* Adapted from moonlanding/src/config/
|
|
4
|
-
*/
|
|
1
|
+
// Adapted from moonlanding/src/config/
|
|
5
2
|
|
|
6
3
|
import fs from 'fs';
|
|
7
4
|
import path from 'path';
|
|
8
5
|
import yaml from 'js-yaml';
|
|
9
6
|
|
|
10
|
-
/**
|
|
11
|
-
* Load configuration from YAML file or object
|
|
12
|
-
*/
|
|
13
7
|
export async function loadConfig(configSource) {
|
|
14
8
|
let config;
|
|
15
9
|
|
|
16
10
|
if (typeof configSource === 'string') {
|
|
17
|
-
// File path
|
|
18
11
|
const configPath = path.resolve(process.cwd(), configSource);
|
|
19
12
|
if (!fs.existsSync(configPath)) {
|
|
20
13
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
@@ -22,7 +15,6 @@ export async function loadConfig(configSource) {
|
|
|
22
15
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
23
16
|
config = yaml.load(content);
|
|
24
17
|
} else if (typeof configSource === 'object') {
|
|
25
|
-
// Raw object
|
|
26
18
|
config = configSource;
|
|
27
19
|
} else {
|
|
28
20
|
throw new Error('Config must be a file path or object');
|
|
@@ -31,9 +23,6 @@ export async function loadConfig(configSource) {
|
|
|
31
23
|
return config;
|
|
32
24
|
}
|
|
33
25
|
|
|
34
|
-
/**
|
|
35
|
-
* Validate required configuration sections
|
|
36
|
-
*/
|
|
37
26
|
export function validateConfig(config) {
|
|
38
27
|
const errors = [];
|
|
39
28
|
|
|
@@ -42,7 +31,6 @@ export function validateConfig(config) {
|
|
|
42
31
|
return errors;
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
// Check required sections
|
|
46
34
|
const requiredSections = ['entities', 'roles', 'permission_templates'];
|
|
47
35
|
for (const section of requiredSections) {
|
|
48
36
|
if (!config[section]) {
|
|
@@ -50,7 +38,6 @@ export function validateConfig(config) {
|
|
|
50
38
|
}
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
// Validate roles
|
|
54
41
|
if (config.roles) {
|
|
55
42
|
const roleNames = Object.keys(config.roles);
|
|
56
43
|
if (roleNames.length === 0) {
|
|
@@ -58,7 +45,6 @@ export function validateConfig(config) {
|
|
|
58
45
|
}
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
// Validate entities
|
|
62
48
|
if (config.entities) {
|
|
63
49
|
for (const [entityName, entityDef] of Object.entries(config.entities)) {
|
|
64
50
|
if (!entityDef.fields && !entityDef.children) {
|
|
@@ -70,9 +56,6 @@ export function validateConfig(config) {
|
|
|
70
56
|
return errors;
|
|
71
57
|
}
|
|
72
58
|
|
|
73
|
-
/**
|
|
74
|
-
* Get configuration with defaults applied
|
|
75
|
-
*/
|
|
76
59
|
export function getConfigWithDefaults(userConfig) {
|
|
77
60
|
const defaults = {
|
|
78
61
|
system: {
|
|
@@ -96,9 +79,6 @@ export function getConfigWithDefaults(userConfig) {
|
|
|
96
79
|
return deepMerge(defaults, userConfig);
|
|
97
80
|
}
|
|
98
81
|
|
|
99
|
-
/**
|
|
100
|
-
* Deep merge utility
|
|
101
|
-
*/
|
|
102
82
|
function deepMerge(target, source) {
|
|
103
83
|
const result = { ...target };
|
|
104
84
|
for (const key of Object.keys(source)) {
|
package/src/config/constants.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core constants for Thatcher SDK
|
|
3
|
-
* HTTP codes, status values, display limits, etc.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* HTTP status codes
|
|
8
|
-
*/
|
|
9
1
|
export const HTTP = {
|
|
10
2
|
OK: 200,
|
|
11
3
|
CREATED: 201,
|
|
@@ -21,9 +13,6 @@ export const HTTP = {
|
|
|
21
13
|
SERVICE_UNAVAILABLE: 503,
|
|
22
14
|
};
|
|
23
15
|
|
|
24
|
-
/**
|
|
25
|
-
* Error message templates
|
|
26
|
-
*/
|
|
27
16
|
export const ERROR_MESSAGES = {
|
|
28
17
|
notFound: (entity = 'Resource') => `${entity} not found`,
|
|
29
18
|
invalidRequest: (reason = 'Invalid request') => reason,
|
|
@@ -33,9 +22,6 @@ export const ERROR_MESSAGES = {
|
|
|
33
22
|
},
|
|
34
23
|
};
|
|
35
24
|
|
|
36
|
-
/**
|
|
37
|
-
* Success message templates
|
|
38
|
-
*/
|
|
39
25
|
export const SUCCESS_MESSAGES = {
|
|
40
26
|
created: (entity = 'Item') => `${entity} created successfully`,
|
|
41
27
|
updated: (entity = 'Item') => `${entity} updated successfully`,
|
|
@@ -43,23 +29,14 @@ export const SUCCESS_MESSAGES = {
|
|
|
43
29
|
saved: 'Changes saved successfully',
|
|
44
30
|
};
|
|
45
31
|
|
|
46
|
-
/**
|
|
47
|
-
* Record status constants
|
|
48
|
-
*/
|
|
49
32
|
export const RECORD_STATUS = {
|
|
50
33
|
ACTIVE: 'active',
|
|
51
34
|
DELETED: 'deleted',
|
|
52
35
|
ARCHIVED: 'archived',
|
|
53
36
|
};
|
|
54
37
|
|
|
55
|
-
/**
|
|
56
|
-
* Email status
|
|
57
|
-
*/
|
|
58
38
|
export const EMAIL_STATUS = 'pending';
|
|
59
39
|
|
|
60
|
-
/**
|
|
61
|
-
* Display/UI constants
|
|
62
|
-
*/
|
|
63
40
|
export const DISPLAY = {
|
|
64
41
|
MAX_API_CALLS_HISTORY: 100,
|
|
65
42
|
API_TIMEOUT_MS: 30000,
|
|
@@ -74,17 +51,11 @@ export const DISPLAY = {
|
|
|
74
51
|
DEBOUNCE_FORM_CHANGE_MS: 500,
|
|
75
52
|
};
|
|
76
53
|
|
|
77
|
-
/**
|
|
78
|
-
* Validation patterns
|
|
79
|
-
*/
|
|
80
54
|
export const VALIDATION = {
|
|
81
55
|
EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
82
56
|
PASSWORD_MIN_LENGTH: 8,
|
|
83
57
|
};
|
|
84
58
|
|
|
85
|
-
/**
|
|
86
|
-
* Log prefixes for structured logging
|
|
87
|
-
*/
|
|
88
59
|
export const LOG_PREFIXES = {
|
|
89
60
|
API: '[API]',
|
|
90
61
|
DB: '[DB]',
|
|
@@ -97,9 +68,6 @@ export const LOG_PREFIXES = {
|
|
|
97
68
|
database: '[DB]',
|
|
98
69
|
};
|
|
99
70
|
|
|
100
|
-
/**
|
|
101
|
-
* SQL type mapping
|
|
102
|
-
*/
|
|
103
71
|
export const SQL_TYPES = {
|
|
104
72
|
id: 'TEXT PRIMARY KEY',
|
|
105
73
|
text: 'TEXT',
|
|
@@ -116,9 +84,6 @@ export const SQL_TYPES = {
|
|
|
116
84
|
enum: 'TEXT',
|
|
117
85
|
};
|
|
118
86
|
|
|
119
|
-
/**
|
|
120
|
-
* Authentication scopes (Google, etc.)
|
|
121
|
-
*/
|
|
122
87
|
export const AUTH_SCOPES = {
|
|
123
88
|
google: [
|
|
124
89
|
'https://www.googleapis.com/auth/userinfo.email',
|
package/src/config/env.js
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment configuration
|
|
3
|
-
* Provides typed access to environment variables with defaults
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get environment variable with optional default
|
|
8
|
-
*/
|
|
9
1
|
function getEnv(key, defaultValue = null) {
|
|
10
2
|
const val = process.env[key];
|
|
11
3
|
if (val === undefined || val === null) return defaultValue;
|
|
12
4
|
return val;
|
|
13
5
|
}
|
|
14
6
|
|
|
15
|
-
/**
|
|
16
|
-
* Get boolean from env
|
|
17
|
-
*/
|
|
18
7
|
function getEnvBool(key, defaultValue = false) {
|
|
19
8
|
const val = getEnv(key);
|
|
20
9
|
if (val === null) return defaultValue;
|
|
21
10
|
return val === 'true' || val === '1';
|
|
22
11
|
}
|
|
23
12
|
|
|
24
|
-
/**
|
|
25
|
-
* Get number from env
|
|
26
|
-
*/
|
|
27
13
|
function getEnvInt(key, defaultValue = 0) {
|
|
28
14
|
const val = getEnv(key);
|
|
29
15
|
if (val === null) return defaultValue;
|
|
@@ -31,9 +17,6 @@ function getEnvInt(key, defaultValue = 0) {
|
|
|
31
17
|
return isNaN(num) ? defaultValue : num;
|
|
32
18
|
}
|
|
33
19
|
|
|
34
|
-
/**
|
|
35
|
-
* Configuration object builder
|
|
36
|
-
*/
|
|
37
20
|
export function buildConfig(overrides = {}) {
|
|
38
21
|
const config = {
|
|
39
22
|
db: {
|
|
@@ -107,34 +90,21 @@ export function buildConfig(overrides = {}) {
|
|
|
107
90
|
return config;
|
|
108
91
|
}
|
|
109
92
|
|
|
110
|
-
/**
|
|
111
|
-
* Check if Google OAuth is configured
|
|
112
|
-
*/
|
|
113
93
|
export function hasGoogleAuth(config) {
|
|
114
94
|
const cfg = config?.auth?.google || buildConfig().auth.google;
|
|
115
95
|
return !!(cfg.clientId && cfg.clientSecret);
|
|
116
96
|
}
|
|
117
97
|
|
|
118
|
-
/**
|
|
119
|
-
* Check if Drive integration is configured
|
|
120
|
-
*/
|
|
121
98
|
export function hasDriveConfig(config) {
|
|
122
99
|
const cfg = config?.drive || buildConfig().drive;
|
|
123
100
|
return !!cfg.rootFolderId;
|
|
124
101
|
}
|
|
125
102
|
|
|
126
|
-
/**
|
|
127
|
-
* Check if email sending is configured
|
|
128
|
-
*/
|
|
129
103
|
export function hasEmailConfig(config) {
|
|
130
104
|
const cfg = config?.email || buildConfig().email;
|
|
131
105
|
return !!(cfg.smtp.user && cfg.smtp.password);
|
|
132
106
|
}
|
|
133
107
|
|
|
134
|
-
/**
|
|
135
|
-
* Validate environment configuration
|
|
136
|
-
* Returns array of warnings (non-fatal)
|
|
137
|
-
*/
|
|
138
108
|
export function validateEnv(config = buildConfig()) {
|
|
139
109
|
const warnings = [];
|
|
140
110
|
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get entity spec by name
|
|
8
|
-
* @param {string} name - Entity name
|
|
9
|
-
* @param {object} configEngine - Config engine instance
|
|
10
|
-
* @returns {object|null} Entity specification or null if not found
|
|
11
|
-
*/
|
|
1
|
+
import { createLogger } from '../lib/logger.js';
|
|
2
|
+
|
|
3
|
+
const log = createLogger('[SpecHelpers]');
|
|
4
|
+
|
|
12
5
|
export function getSpec(name, configEngine) {
|
|
13
6
|
// Callers across the framework invoke getSpec(name) without threading the engine
|
|
14
7
|
// (moon's getSpec self-resolved it). Fall back to the singleton when omitted.
|
|
@@ -26,11 +19,6 @@ export function getSpec(name, configEngine) {
|
|
|
26
19
|
}
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
/**
|
|
30
|
-
* Get all navigable entities (top-level, non-embedded)
|
|
31
|
-
* @param {object} configEngine
|
|
32
|
-
* @returns {Array} Sorted array of entity specs
|
|
33
|
-
*/
|
|
34
22
|
export function getNavItems(configEngine) {
|
|
35
23
|
try {
|
|
36
24
|
const allEntities = configEngine.getAllEntities();
|
|
@@ -45,16 +33,11 @@ export function getNavItems(configEngine) {
|
|
|
45
33
|
href: `/${s.name}`,
|
|
46
34
|
}));
|
|
47
35
|
} catch (error) {
|
|
48
|
-
|
|
36
|
+
log.error('getNavItems error:', { message: error.message });
|
|
49
37
|
return [];
|
|
50
38
|
}
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
/**
|
|
54
|
-
* Get child entity definitions for a parent spec
|
|
55
|
-
* @param {object} spec - Parent entity spec
|
|
56
|
-
* @returns {Array} Child entity configs
|
|
57
|
-
*/
|
|
58
41
|
export function getChildEntities(spec) {
|
|
59
42
|
if (!spec.children) return [];
|
|
60
43
|
return Object.entries(spec.children).map(([key, child]) => ({
|
|
@@ -67,57 +50,26 @@ export function getChildEntities(spec) {
|
|
|
67
50
|
}));
|
|
68
51
|
}
|
|
69
52
|
|
|
70
|
-
/**
|
|
71
|
-
* Get parent entity name
|
|
72
|
-
* @param {object} spec
|
|
73
|
-
* @returns {string|null}
|
|
74
|
-
*/
|
|
75
53
|
export function getParentEntity(spec) {
|
|
76
54
|
return spec.parent || null;
|
|
77
55
|
}
|
|
78
56
|
|
|
79
|
-
/**
|
|
80
|
-
* Get default sort configuration
|
|
81
|
-
* @param {object} spec
|
|
82
|
-
* @returns {{field: string, dir: string}}
|
|
83
|
-
*/
|
|
84
57
|
export function getDefaultSort(spec) {
|
|
85
58
|
return spec.list?.defaultSort || { field: 'created_at', dir: 'desc' };
|
|
86
59
|
}
|
|
87
60
|
|
|
88
|
-
/**
|
|
89
|
-
* Get available filters for entity
|
|
90
|
-
* @param {object} spec
|
|
91
|
-
* @returns {Array}
|
|
92
|
-
*/
|
|
93
61
|
export function getAvailableFilters(spec) {
|
|
94
62
|
return spec.list?.filters || [];
|
|
95
63
|
}
|
|
96
64
|
|
|
97
|
-
/**
|
|
98
|
-
* Get page size for entity
|
|
99
|
-
* @param {object} spec
|
|
100
|
-
* @returns {number}
|
|
101
|
-
*/
|
|
102
65
|
export function getPageSize(spec, systemPagination = { default_page_size: 20 }) {
|
|
103
66
|
return spec.list?.pageSize || systemPagination.default_page_size;
|
|
104
67
|
}
|
|
105
68
|
|
|
106
|
-
/**
|
|
107
|
-
* Get entity label (singular or plural)
|
|
108
|
-
* @param {object} spec
|
|
109
|
-
* @param {boolean} plural
|
|
110
|
-
* @returns {string}
|
|
111
|
-
*/
|
|
112
69
|
export function getEntityLabel(spec, plural = false) {
|
|
113
70
|
return plural ? (spec.labelPlural || spec.label) : spec.label;
|
|
114
71
|
}
|
|
115
72
|
|
|
116
|
-
/**
|
|
117
|
-
* Get default initial state for entity (empty record with defaults)
|
|
118
|
-
* @param {object} spec
|
|
119
|
-
* @returns {object}
|
|
120
|
-
*/
|
|
121
73
|
export function getInitialState(spec) {
|
|
122
74
|
const state = {};
|
|
123
75
|
for (const [key, field] of Object.entries(spec.fields)) {
|
|
@@ -139,73 +91,32 @@ export function getInitialState(spec) {
|
|
|
139
91
|
return state;
|
|
140
92
|
}
|
|
141
93
|
|
|
142
|
-
/**
|
|
143
|
-
* Check if entity is embedded (used in parent contexts)
|
|
144
|
-
* @param {object} spec
|
|
145
|
-
* @returns {boolean}
|
|
146
|
-
*/
|
|
147
94
|
export function isEmbeddedEntity(spec) {
|
|
148
95
|
return spec.embedded === true;
|
|
149
96
|
}
|
|
150
97
|
|
|
151
|
-
/**
|
|
152
|
-
* Check if entity is a top-level parent
|
|
153
|
-
* @param {object} spec
|
|
154
|
-
* @returns {boolean}
|
|
155
|
-
*/
|
|
156
98
|
export function isParentEntity(spec) {
|
|
157
99
|
return !spec.embedded && !spec.parent;
|
|
158
100
|
}
|
|
159
101
|
|
|
160
|
-
/**
|
|
161
|
-
* Check if entity has child relationships
|
|
162
|
-
* @param {object} spec
|
|
163
|
-
* @returns {boolean}
|
|
164
|
-
*/
|
|
165
102
|
export function hasChildRelationships(spec) {
|
|
166
103
|
return !!spec.children && Object.keys(spec.children).length > 0;
|
|
167
104
|
}
|
|
168
105
|
|
|
169
|
-
/**
|
|
170
|
-
* Get entity options (enums, picklists)
|
|
171
|
-
* @param {object} spec
|
|
172
|
-
* @param {string} optionKey
|
|
173
|
-
* @returns {Array}
|
|
174
|
-
*/
|
|
175
106
|
export function getOptions(spec, optionKey) {
|
|
176
107
|
return spec.options?.[optionKey] || [];
|
|
177
108
|
}
|
|
178
109
|
|
|
179
|
-
/**
|
|
180
|
-
* Get display label for an option value
|
|
181
|
-
* @param {object} spec
|
|
182
|
-
* @param {string} optionKey
|
|
183
|
-
* @param {string} value
|
|
184
|
-
* @returns {string}
|
|
185
|
-
*/
|
|
186
110
|
export function getOptionLabel(spec, optionKey, value) {
|
|
187
111
|
const option = getOptions(spec, optionKey).find(o => o.value === value);
|
|
188
112
|
return option?.label || String(value);
|
|
189
113
|
}
|
|
190
114
|
|
|
191
|
-
/**
|
|
192
|
-
* Get color for an option value (for UI badges)
|
|
193
|
-
* @param {object} spec
|
|
194
|
-
* @param {string} optionKey
|
|
195
|
-
* @param {string} value
|
|
196
|
-
* @returns {string}
|
|
197
|
-
*/
|
|
198
115
|
export function getOptionColor(spec, optionKey, value) {
|
|
199
116
|
const option = getOptions(spec, optionKey).find(o => o.value === value);
|
|
200
117
|
return option?.color || 'gray';
|
|
201
118
|
}
|
|
202
119
|
|
|
203
|
-
/**
|
|
204
|
-
* Build navigation structure for menus
|
|
205
|
-
* @param {object} configEngine
|
|
206
|
-
* @param {object} user - Optional user for permission filtering
|
|
207
|
-
* @returns {Array} Navigation items
|
|
208
|
-
*/
|
|
209
120
|
export function buildNavigation(configEngine, user = null) {
|
|
210
121
|
try {
|
|
211
122
|
const allEntities = configEngine.getAllEntities();
|
|
@@ -232,7 +143,7 @@ export function buildNavigation(configEngine, user = null) {
|
|
|
232
143
|
|
|
233
144
|
return items;
|
|
234
145
|
} catch (error) {
|
|
235
|
-
|
|
146
|
+
log.error('buildNavigation error:', { message: error.message });
|
|
236
147
|
return [];
|
|
237
148
|
}
|
|
238
149
|
}
|
package/src/engine.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Previously `@/engine` resolved to a non-existent module; this barrel makes it
|
|
2
|
+
// concrete and points the data layer at busybase-store.
|
|
3
|
+
|
|
4
|
+
// Data layer (busybase)
|
|
5
|
+
export {
|
|
6
|
+
list, get, getBy, count, listWithPagination, searchWithPagination, search,
|
|
7
|
+
create, update, remove, bulkCreate, getChildren, batchGetChildren,
|
|
8
|
+
setBusyBaseClient,
|
|
9
|
+
} from '@/lib/busybase-store.js';
|
|
10
|
+
|
|
11
|
+
// IDs / time
|
|
12
|
+
export { genId, now } from '@/lib/id-helpers.js';
|
|
13
|
+
|
|
14
|
+
// Auth helpers
|
|
15
|
+
export { hashPassword, verifyPassword } from '@/engine.server.js';
|
|
16
|
+
|
|
17
|
+
// Audit
|
|
18
|
+
export { logAction } from '@/lib/busybase-audit.js';
|
|
19
|
+
|
|
20
|
+
// Legacy SQLite shims (data layer is busybase now). busybase is schemaless so there
|
|
21
|
+
// is no migration step; migrate() is a no-op kept for callers that still invoke it.
|
|
22
|
+
export function migrate() { /* no-op: busybase creates tables lazily on first insert */ }
|
|
23
|
+
|