thatcher 1.0.6 → 1.0.33
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 +29 -88
- 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 +14 -5
- 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,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database Migrations - Custom SQL migrations and schema evolution
|
|
3
|
-
* Runs after auto-migration completes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Run custom migrations
|
|
8
|
-
* @param {object} db - better-sqlite3 database instance
|
|
9
|
-
*/
|
|
10
|
-
export function runMigrations(db) {
|
|
11
|
-
// All migrations defined here are idempotent
|
|
12
|
-
const migrations = [
|
|
13
|
-
createTimestampTriggers,
|
|
14
|
-
createActivityLogTable,
|
|
15
|
-
createNotificationTable,
|
|
16
|
-
createSessionsTable,
|
|
17
|
-
createChatTables,
|
|
18
|
-
createAuditLogsTable,
|
|
19
|
-
createRfiTables,
|
|
20
|
-
createPasswordResetTokensTable,
|
|
21
|
-
addHighlightColumns,
|
|
22
|
-
migrateRfiSectionTable,
|
|
23
|
-
createSystemSettingsTable,
|
|
24
|
-
createRecreationLogTable,
|
|
25
|
-
createBugReportTable,
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
for (const migration of migrations) {
|
|
29
|
-
try {
|
|
30
|
-
migration(db);
|
|
31
|
-
} catch (err) {
|
|
32
|
-
console.error('[Migration] Failed:', err.message);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Create triggers to auto-update updated_at on row changes
|
|
39
|
-
* @param {object} db
|
|
40
|
-
*/
|
|
41
|
-
function createTimestampTriggers(db) {
|
|
42
|
-
const tables = ['users', 'engagements', 'rfis', 'reviews']; // Could be dynamic
|
|
43
|
-
|
|
44
|
-
for (const table of tables) {
|
|
45
|
-
try {
|
|
46
|
-
db.exec(`
|
|
47
|
-
CREATE TRIGGER IF NOT EXISTS set_timestamp_${table}
|
|
48
|
-
AFTER UPDATE ON ${table}
|
|
49
|
-
FOR EACH ROW
|
|
50
|
-
BEGIN
|
|
51
|
-
UPDATE ${table} SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
52
|
-
END;
|
|
53
|
-
`);
|
|
54
|
-
} catch (e) {
|
|
55
|
-
// Table might not exist yet
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Create activity_log table for audit trail
|
|
62
|
-
* @param {object} db
|
|
63
|
-
*/
|
|
64
|
-
function createActivityLogTable(db) {
|
|
65
|
-
db.exec(`
|
|
66
|
-
CREATE TABLE IF NOT EXISTS activity_log (
|
|
67
|
-
id TEXT PRIMARY KEY,
|
|
68
|
-
entity_type TEXT NOT NULL,
|
|
69
|
-
entity_id TEXT NOT NULL,
|
|
70
|
-
action TEXT NOT NULL,
|
|
71
|
-
message TEXT,
|
|
72
|
-
details TEXT,
|
|
73
|
-
user_id TEXT,
|
|
74
|
-
created_at INTEGER NOT NULL,
|
|
75
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
76
|
-
);
|
|
77
|
-
`);
|
|
78
|
-
|
|
79
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_log_entity ON activity_log(entity_type, entity_id)`);
|
|
80
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_log_user ON activity_log(user_id)`);
|
|
81
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_log_created ON activity_log(created_at)`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Create notification table
|
|
86
|
-
* @param {object} db
|
|
87
|
-
*/
|
|
88
|
-
function createNotificationTable(db) {
|
|
89
|
-
db.exec(`
|
|
90
|
-
CREATE TABLE IF NOT EXISTS notification (
|
|
91
|
-
id TEXT PRIMARY KEY,
|
|
92
|
-
type TEXT NOT NULL,
|
|
93
|
-
user_id TEXT NOT NULL,
|
|
94
|
-
title TEXT NOT NULL,
|
|
95
|
-
message TEXT,
|
|
96
|
-
data TEXT,
|
|
97
|
-
entity_type TEXT,
|
|
98
|
-
entity_id TEXT,
|
|
99
|
-
created_at INTEGER NOT NULL,
|
|
100
|
-
read_at INTEGER,
|
|
101
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
CREATE INDEX IF NOT EXISTS idx_notification_user ON notification(user_id, read_at);
|
|
105
|
-
`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Create sessions table (Lucia auth)
|
|
110
|
-
* @param {object} db
|
|
111
|
-
*/
|
|
112
|
-
function createSessionsTable(db) {
|
|
113
|
-
db.exec(`
|
|
114
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
115
|
-
id TEXT PRIMARY KEY,
|
|
116
|
-
user_id TEXT NOT NULL,
|
|
117
|
-
expires_at INTEGER NOT NULL,
|
|
118
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
119
|
-
);
|
|
120
|
-
`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Create chat tables (messages + mentions)
|
|
125
|
-
* @param {object} db
|
|
126
|
-
*/
|
|
127
|
-
function createChatTables(db) {
|
|
128
|
-
db.exec(`
|
|
129
|
-
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
130
|
-
id TEXT PRIMARY KEY,
|
|
131
|
-
rfi_id TEXT,
|
|
132
|
-
user_id TEXT,
|
|
133
|
-
content TEXT,
|
|
134
|
-
attachments TEXT,
|
|
135
|
-
reactions TEXT DEFAULT '{}',
|
|
136
|
-
mentions TEXT DEFAULT '[]',
|
|
137
|
-
created_at INTEGER NOT NULL,
|
|
138
|
-
updated_at INTEGER,
|
|
139
|
-
FOREIGN KEY (rfi_id) REFERENCES rfi(id),
|
|
140
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
CREATE TABLE IF NOT EXISTS chat_mentions (
|
|
144
|
-
id TEXT PRIMARY KEY,
|
|
145
|
-
message_id TEXT NOT NULL,
|
|
146
|
-
user_id TEXT,
|
|
147
|
-
resolved BOOLEAN DEFAULT 0,
|
|
148
|
-
created_at INTEGER NOT NULL,
|
|
149
|
-
FOREIGN KEY (message_id) REFERENCES chat_messages(id),
|
|
150
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
CREATE INDEX IF NOT EXISTS idx_chat_messages_rfi ON chat_messages(rfi_id);
|
|
154
|
-
CREATE INDEX IF NOT EXISTS idx_chat_messages_user ON chat_messages(user_id);
|
|
155
|
-
CREATE INDEX IF NOT EXISTS idx_chat_mentions_message ON chat_mentions(message_id);
|
|
156
|
-
CREATE INDEX IF NOT EXISTS idx_chat_mentions_user ON chat_mentions(user_id);
|
|
157
|
-
`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Create audit_logs table (before/after state audit trail)
|
|
162
|
-
* Distinct from activity_log; preserved as an additive table.
|
|
163
|
-
* @param {object} db
|
|
164
|
-
*/
|
|
165
|
-
function createAuditLogsTable(db) {
|
|
166
|
-
db.exec(`
|
|
167
|
-
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
168
|
-
id TEXT PRIMARY KEY,
|
|
169
|
-
entity_type TEXT NOT NULL,
|
|
170
|
-
entity_id TEXT NOT NULL,
|
|
171
|
-
action TEXT NOT NULL,
|
|
172
|
-
user_id TEXT,
|
|
173
|
-
before_state TEXT,
|
|
174
|
-
after_state TEXT,
|
|
175
|
-
created_at INTEGER NOT NULL,
|
|
176
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
|
|
180
|
-
CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs(user_id);
|
|
181
|
-
CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at);
|
|
182
|
-
`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Create RFI tables (rfis, questions, responses)
|
|
187
|
-
* @param {object} db
|
|
188
|
-
*/
|
|
189
|
-
function createRfiTables(db) {
|
|
190
|
-
db.exec(`
|
|
191
|
-
CREATE TABLE IF NOT EXISTS rfis (
|
|
192
|
-
id TEXT PRIMARY KEY,
|
|
193
|
-
engagement_id TEXT NOT NULL,
|
|
194
|
-
status TEXT DEFAULT 'draft',
|
|
195
|
-
created_at INTEGER NOT NULL,
|
|
196
|
-
updated_at INTEGER NOT NULL,
|
|
197
|
-
FOREIGN KEY (engagement_id) REFERENCES engagement(id)
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
CREATE TABLE IF NOT EXISTS rfi_questions (
|
|
201
|
-
id TEXT PRIMARY KEY,
|
|
202
|
-
rfi_id TEXT NOT NULL,
|
|
203
|
-
question TEXT NOT NULL,
|
|
204
|
-
category TEXT,
|
|
205
|
-
assigned_to TEXT,
|
|
206
|
-
due_date TEXT,
|
|
207
|
-
status TEXT DEFAULT 'pending',
|
|
208
|
-
created_at INTEGER NOT NULL,
|
|
209
|
-
updated_at INTEGER NOT NULL,
|
|
210
|
-
FOREIGN KEY (rfi_id) REFERENCES rfis(id),
|
|
211
|
-
FOREIGN KEY (assigned_to) REFERENCES users(id)
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
CREATE TABLE IF NOT EXISTS rfi_responses (
|
|
215
|
-
id TEXT PRIMARY KEY,
|
|
216
|
-
question_id TEXT NOT NULL,
|
|
217
|
-
response TEXT,
|
|
218
|
-
attachments TEXT,
|
|
219
|
-
created_at INTEGER NOT NULL,
|
|
220
|
-
updated_at INTEGER NOT NULL,
|
|
221
|
-
FOREIGN KEY (question_id) REFERENCES rfi_questions(id)
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
CREATE INDEX IF NOT EXISTS idx_rfis_engagement ON rfis(engagement_id);
|
|
225
|
-
CREATE INDEX IF NOT EXISTS idx_rfis_status ON rfis(status);
|
|
226
|
-
CREATE INDEX IF NOT EXISTS idx_rfi_questions_rfi ON rfi_questions(rfi_id);
|
|
227
|
-
CREATE INDEX IF NOT EXISTS idx_rfi_questions_status ON rfi_questions(status);
|
|
228
|
-
CREATE INDEX IF NOT EXISTS idx_rfi_responses_question ON rfi_responses(question_id);
|
|
229
|
-
`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Create password_reset_tokens table
|
|
234
|
-
* @param {object} db
|
|
235
|
-
*/
|
|
236
|
-
function createPasswordResetTokensTable(db) {
|
|
237
|
-
db.exec(`
|
|
238
|
-
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
|
239
|
-
id TEXT PRIMARY KEY,
|
|
240
|
-
user_id TEXT NOT NULL,
|
|
241
|
-
token TEXT NOT NULL UNIQUE,
|
|
242
|
-
expires_at INTEGER NOT NULL,
|
|
243
|
-
used INTEGER DEFAULT 0,
|
|
244
|
-
created_at INTEGER NOT NULL,
|
|
245
|
-
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_token ON password_reset_tokens(token);
|
|
249
|
-
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user ON password_reset_tokens(user_id);
|
|
250
|
-
`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Add flags/tags columns to highlight table (idempotent — ignores duplicate-column error)
|
|
255
|
-
* @param {object} db
|
|
256
|
-
*/
|
|
257
|
-
function addHighlightColumns(db) {
|
|
258
|
-
for (const col of ['flags', 'tags']) {
|
|
259
|
-
try {
|
|
260
|
-
db.exec(`ALTER TABLE highlight ADD COLUMN ${col} TEXT`);
|
|
261
|
-
} catch (e) {
|
|
262
|
-
// Column already exists or table not present yet
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Rebuild rfi_section table if it lacks an id column (legacy shape)
|
|
269
|
-
* @param {object} db
|
|
270
|
-
*/
|
|
271
|
-
function migrateRfiSectionTable(db) {
|
|
272
|
-
let cols = [];
|
|
273
|
-
try {
|
|
274
|
-
cols = db.prepare('PRAGMA table_info(rfi_section)').all().map(c => c.name);
|
|
275
|
-
} catch (e) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (!cols.includes('id')) {
|
|
279
|
-
db.exec(`DROP TABLE IF EXISTS rfi_section`);
|
|
280
|
-
db.exec(`
|
|
281
|
-
CREATE TABLE IF NOT EXISTS rfi_section (
|
|
282
|
-
id TEXT PRIMARY KEY,
|
|
283
|
-
engagement_id TEXT NOT NULL,
|
|
284
|
-
name TEXT NOT NULL DEFAULT '',
|
|
285
|
-
sort_order INTEGER DEFAULT 0,
|
|
286
|
-
created_at INTEGER,
|
|
287
|
-
FOREIGN KEY (engagement_id) REFERENCES engagement(id)
|
|
288
|
-
);
|
|
289
|
-
CREATE INDEX IF NOT EXISTS idx_rfi_section_engagement ON rfi_section(engagement_id);
|
|
290
|
-
`);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create system_settings key/value table
|
|
296
|
-
* @param {object} db
|
|
297
|
-
*/
|
|
298
|
-
function createSystemSettingsTable(db) {
|
|
299
|
-
db.exec(`
|
|
300
|
-
CREATE TABLE IF NOT EXISTS system_settings (
|
|
301
|
-
key TEXT PRIMARY KEY,
|
|
302
|
-
value TEXT NOT NULL,
|
|
303
|
-
updated_at INTEGER
|
|
304
|
-
);
|
|
305
|
-
`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Create recreation_log table
|
|
310
|
-
* @param {object} db
|
|
311
|
-
*/
|
|
312
|
-
function createRecreationLogTable(db) {
|
|
313
|
-
db.exec(`
|
|
314
|
-
CREATE TABLE IF NOT EXISTS recreation_log (
|
|
315
|
-
id TEXT PRIMARY KEY,
|
|
316
|
-
engagement_id TEXT,
|
|
317
|
-
client_id TEXT,
|
|
318
|
-
engagement_type_id TEXT,
|
|
319
|
-
status TEXT,
|
|
320
|
-
details TEXT,
|
|
321
|
-
error TEXT,
|
|
322
|
-
created_at INTEGER NOT NULL
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
CREATE INDEX IF NOT EXISTS idx_recreation_log_created ON recreation_log(created_at);
|
|
326
|
-
CREATE INDEX IF NOT EXISTS idx_recreation_log_status ON recreation_log(status);
|
|
327
|
-
`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Create bug_report table
|
|
332
|
-
* @param {object} db
|
|
333
|
-
*/
|
|
334
|
-
function createBugReportTable(db) {
|
|
335
|
-
db.exec(`
|
|
336
|
-
CREATE TABLE IF NOT EXISTS bug_report (
|
|
337
|
-
id TEXT PRIMARY KEY,
|
|
338
|
-
user_id TEXT,
|
|
339
|
-
summary TEXT,
|
|
340
|
-
description TEXT,
|
|
341
|
-
url TEXT,
|
|
342
|
-
user_agent TEXT,
|
|
343
|
-
viewport TEXT,
|
|
344
|
-
status TEXT DEFAULT 'open',
|
|
345
|
-
created_at INTEGER NOT NULL
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
CREATE INDEX IF NOT EXISTS idx_bug_report_created ON bug_report(created_at);
|
|
349
|
-
`);
|
|
350
|
-
}
|
package/src/lib/db-backup.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import Database from 'better-sqlite3';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_DB_PATH = path.join(process.cwd(), 'data', 'app.db');
|
|
6
|
-
const DEFAULT_BACKUP_DIR = path.join(process.cwd(), 'data', 'backups');
|
|
7
|
-
|
|
8
|
-
export function createBackup(options = {}) {
|
|
9
|
-
const dbPath = options.dbPath || DEFAULT_DB_PATH;
|
|
10
|
-
const backupDir = options.backupDir || DEFAULT_BACKUP_DIR;
|
|
11
|
-
const label = options.label || 'backup';
|
|
12
|
-
|
|
13
|
-
if (!fs.existsSync(dbPath)) {
|
|
14
|
-
throw new Error(`Database not found: ${dbPath}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!fs.existsSync(backupDir)) {
|
|
18
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
22
|
-
const backupPath = path.join(backupDir, `app-${label}-${timestamp}.db`);
|
|
23
|
-
|
|
24
|
-
const db = new Database(dbPath);
|
|
25
|
-
try {
|
|
26
|
-
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
27
|
-
fs.copyFileSync(dbPath, backupPath);
|
|
28
|
-
} finally {
|
|
29
|
-
db.close();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const sourceSize = fs.statSync(dbPath).size;
|
|
33
|
-
const backupSize = fs.statSync(backupPath).size;
|
|
34
|
-
|
|
35
|
-
if (sourceSize !== backupSize) {
|
|
36
|
-
throw new Error(`Backup size mismatch: source=${sourceSize}, backup=${backupSize}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { path: backupPath, size: backupSize, timestamp };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function restoreBackup(backupPath, options = {}) {
|
|
43
|
-
const dbPath = options.dbPath || DEFAULT_DB_PATH;
|
|
44
|
-
|
|
45
|
-
if (!fs.existsSync(backupPath)) {
|
|
46
|
-
throw new Error(`Backup not found: ${backupPath}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const verifyDb = new Database(backupPath, { readonly: true });
|
|
50
|
-
try {
|
|
51
|
-
const result = verifyDb.pragma('integrity_check');
|
|
52
|
-
if (result[0]?.integrity_check !== 'ok') {
|
|
53
|
-
throw new Error(`Backup integrity check failed: ${JSON.stringify(result)}`);
|
|
54
|
-
}
|
|
55
|
-
} finally {
|
|
56
|
-
verifyDb.close();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const walPath = dbPath + '-wal';
|
|
60
|
-
const shmPath = dbPath + '-shm';
|
|
61
|
-
|
|
62
|
-
fs.copyFileSync(backupPath, dbPath);
|
|
63
|
-
if (fs.existsSync(walPath)) fs.unlinkSync(walPath);
|
|
64
|
-
if (fs.existsSync(shmPath)) fs.unlinkSync(shmPath);
|
|
65
|
-
|
|
66
|
-
return { restored: true, from: backupPath, to: dbPath };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function listBackups(options = {}) {
|
|
70
|
-
const backupDir = options.backupDir || DEFAULT_BACKUP_DIR;
|
|
71
|
-
|
|
72
|
-
if (!fs.existsSync(backupDir)) return [];
|
|
73
|
-
|
|
74
|
-
return fs.readdirSync(backupDir)
|
|
75
|
-
.filter(f => f.endsWith('.db') && f.startsWith('app-'))
|
|
76
|
-
.map(f => ({
|
|
77
|
-
name: f,
|
|
78
|
-
path: path.join(backupDir, f),
|
|
79
|
-
size: fs.statSync(path.join(backupDir, f)).size,
|
|
80
|
-
created: fs.statSync(path.join(backupDir, f)).mtime,
|
|
81
|
-
}))
|
|
82
|
-
.sort((a, b) => b.created - a.created);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function pruneBackups(options = {}) {
|
|
86
|
-
const maxBackups = options.maxBackups || 10;
|
|
87
|
-
const backups = listBackups(options);
|
|
88
|
-
|
|
89
|
-
if (backups.length <= maxBackups) return { pruned: 0 };
|
|
90
|
-
|
|
91
|
-
const toDelete = backups.slice(maxBackups);
|
|
92
|
-
for (const backup of toDelete) {
|
|
93
|
-
fs.unlinkSync(backup.path);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return { pruned: toDelete.length };
|
|
97
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
|
|
2
|
-
|
|
3
|
-
const errorBoundaryState = new Map();
|
|
4
|
-
|
|
5
|
-
export class ErrorBoundary {
|
|
6
|
-
constructor(id, options = {}) {
|
|
7
|
-
this.id = id;
|
|
8
|
-
this.fallback = options.fallback || this.defaultFallback.bind(this);
|
|
9
|
-
this.onError = options.onError;
|
|
10
|
-
this.reset = this.reset.bind(this);
|
|
11
|
-
|
|
12
|
-
errorBoundaryState.set(id, {
|
|
13
|
-
hasError: false,
|
|
14
|
-
error: null,
|
|
15
|
-
errorInfo: null,
|
|
16
|
-
retryCount: 0
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getState() {
|
|
21
|
-
return errorBoundaryState.get(this.id);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
setState(updates) {
|
|
25
|
-
const current = this.getState();
|
|
26
|
-
errorBoundaryState.set(this.id, { ...current, ...updates });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
catchError(error, errorInfo = {}) {
|
|
30
|
-
const state = this.getState();
|
|
31
|
-
|
|
32
|
-
console.error(`[ErrorBoundary:${this.id}] Caught error:`, error);
|
|
33
|
-
|
|
34
|
-
this.setState({
|
|
35
|
-
hasError: true,
|
|
36
|
-
error,
|
|
37
|
-
errorInfo,
|
|
38
|
-
retryCount: state.retryCount + 1
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
if (this.onError) {
|
|
42
|
-
this.onError(error, errorInfo);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
reset() {
|
|
47
|
-
this.setState({
|
|
48
|
-
hasError: false,
|
|
49
|
-
error: null,
|
|
50
|
-
errorInfo: null
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
defaultFallback(error, retry) {
|
|
55
|
-
const state = this.getState();
|
|
56
|
-
|
|
57
|
-
return _jsxs('div', {
|
|
58
|
-
className: 'p-6 bg-error-content border border-error rounded-lg',
|
|
59
|
-
children: [
|
|
60
|
-
_jsx('h2', {
|
|
61
|
-
className: 'text-xl font-bold mb-2 text-error',
|
|
62
|
-
children: 'Something went wrong'
|
|
63
|
-
}),
|
|
64
|
-
_jsx('p', {
|
|
65
|
-
className: 'text-sm mb-4',
|
|
66
|
-
children: String(error?.message || 'An unexpected error occurred')
|
|
67
|
-
}),
|
|
68
|
-
state.retryCount < 3 && _jsx('button', {
|
|
69
|
-
onClick: retry,
|
|
70
|
-
className: 'btn btn-primary btn-sm',
|
|
71
|
-
children: 'Try again'
|
|
72
|
-
}),
|
|
73
|
-
state.retryCount >= 3 && _jsx('p', {
|
|
74
|
-
className: 'text-sm text-error',
|
|
75
|
-
children: 'Please refresh the page or contact support'
|
|
76
|
-
})
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
render(children) {
|
|
82
|
-
const state = this.getState();
|
|
83
|
-
|
|
84
|
-
if (state.hasError) {
|
|
85
|
-
return this.fallback(state.error, this.reset);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return children;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function createErrorBoundary(id, options = {}) {
|
|
93
|
-
return new ErrorBoundary(id, options);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function wrapWithErrorBoundary(component, id, options = {}) {
|
|
97
|
-
const boundary = createErrorBoundary(id, options);
|
|
98
|
-
|
|
99
|
-
return (...args) => {
|
|
100
|
-
try {
|
|
101
|
-
const result = component(...args);
|
|
102
|
-
return boundary.render(result);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
boundary.catchError(error);
|
|
105
|
-
return boundary.render(null);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function tryCatch(fn, fallback = null) {
|
|
111
|
-
try {
|
|
112
|
-
return await fn();
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error('[tryCatch] Error caught:', error);
|
|
115
|
-
return fallback;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function safeRender(component, props = {}, fallback = null) {
|
|
120
|
-
try {
|
|
121
|
-
return component(props);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error('[safeRender] Render error:', error);
|
|
124
|
-
return fallback || _jsx('div', {
|
|
125
|
-
className: 'p-4 text-error',
|
|
126
|
-
children: 'Failed to render component'
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (typeof global !== 'undefined') {
|
|
132
|
-
global.errorBoundaryState = errorBoundaryState;
|
|
133
|
-
global.createErrorBoundary = createErrorBoundary;
|
|
134
|
-
}
|
package/src/lib/errors.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { HTTP } from '@/config/constants';
|
|
2
|
-
|
|
3
|
-
export class AppError extends Error {
|
|
4
|
-
constructor(message, code = 'ERROR', statusCode = HTTP.INTERNAL_ERROR, context = {}) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = 'AppError';
|
|
7
|
-
this.code = code;
|
|
8
|
-
this.statusCode = statusCode;
|
|
9
|
-
this.context = context;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
toJSON() {
|
|
13
|
-
return {
|
|
14
|
-
status: 'error',
|
|
15
|
-
message: this.message,
|
|
16
|
-
code: this.code,
|
|
17
|
-
statusCode: this.statusCode,
|
|
18
|
-
context: this.context,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class ValidationError extends AppError {
|
|
24
|
-
constructor(message, errors = {}) {
|
|
25
|
-
super(message, 'VALIDATION_ERROR', HTTP.BAD_REQUEST);
|
|
26
|
-
this.errors = errors;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
toJSON() {
|
|
30
|
-
return {
|
|
31
|
-
status: 'error',
|
|
32
|
-
message: this.message,
|
|
33
|
-
code: this.code,
|
|
34
|
-
statusCode: this.statusCode,
|
|
35
|
-
errors: this.errors,
|
|
36
|
-
context: this.context,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export class AuthError extends AppError {
|
|
42
|
-
constructor(message = 'Authentication required') {
|
|
43
|
-
super(message, 'UNAUTHORIZED', HTTP.UNAUTHORIZED);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export class ForbiddenError extends AppError {
|
|
48
|
-
constructor(message = 'Permission denied') {
|
|
49
|
-
super(message, 'FORBIDDEN', HTTP.FORBIDDEN);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export class NotFoundError extends AppError {
|
|
54
|
-
constructor(message = 'Not found') {
|
|
55
|
-
super(message, 'NOT_FOUND', HTTP.NOT_FOUND);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class ConflictError extends AppError {
|
|
60
|
-
constructor(message = 'Conflict') {
|
|
61
|
-
super(message, 'CONFLICT', HTTP.CONFLICT);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export class ServerError extends AppError {
|
|
66
|
-
constructor(message = 'Server error', code = 'SERVER_ERROR') {
|
|
67
|
-
super(message, code, HTTP.INTERNAL_ERROR);
|
|
68
|
-
}
|
|
69
|
-
}
|