@qwickapps/server 1.7.1 → 1.8.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.
Files changed (259) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/src/core/control-panel.js +5 -5
  3. package/dist/src/core/control-panel.js.map +1 -1
  4. package/dist/src/core/gateway.d.ts.map +1 -1
  5. package/dist/src/core/gateway.js +117 -15
  6. package/dist/src/core/gateway.js.map +1 -1
  7. package/dist/src/core/plugin-registry.d.ts +70 -0
  8. package/dist/src/core/plugin-registry.d.ts.map +1 -1
  9. package/dist/src/core/plugin-registry.js +94 -0
  10. package/dist/src/core/plugin-registry.js.map +1 -1
  11. package/dist/src/index.d.ts +1 -1
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/plugins/api-keys/api-keys-plugin.d.ts +5 -2
  14. package/dist/src/plugins/api-keys/api-keys-plugin.d.ts.map +1 -1
  15. package/dist/src/plugins/api-keys/api-keys-plugin.js +113 -19
  16. package/dist/src/plugins/api-keys/api-keys-plugin.js.map +1 -1
  17. package/dist/src/plugins/api-keys/index.d.ts +1 -5
  18. package/dist/src/plugins/api-keys/index.d.ts.map +1 -1
  19. package/dist/src/plugins/api-keys/index.js +2 -3
  20. package/dist/src/plugins/api-keys/index.js.map +1 -1
  21. package/dist/src/plugins/api-keys/stores/postgres-store.d.ts.map +1 -1
  22. package/dist/src/plugins/api-keys/stores/postgres-store.js +83 -65
  23. package/dist/src/plugins/api-keys/stores/postgres-store.js.map +1 -1
  24. package/dist/src/plugins/api-keys/types.d.ts +22 -4
  25. package/dist/src/plugins/api-keys/types.d.ts.map +1 -1
  26. package/dist/src/plugins/api-keys/types.js.map +1 -1
  27. package/dist/src/plugins/auth/index.d.ts +0 -4
  28. package/dist/src/plugins/auth/index.d.ts.map +1 -1
  29. package/dist/src/plugins/auth/index.js +2 -3
  30. package/dist/src/plugins/auth/index.js.map +1 -1
  31. package/dist/src/plugins/bans/bans-plugin.d.ts +5 -2
  32. package/dist/src/plugins/bans/bans-plugin.d.ts.map +1 -1
  33. package/dist/src/plugins/bans/bans-plugin.js +71 -25
  34. package/dist/src/plugins/bans/bans-plugin.js.map +1 -1
  35. package/dist/src/plugins/bans/index.d.ts +0 -4
  36. package/dist/src/plugins/bans/index.d.ts.map +1 -1
  37. package/dist/src/plugins/bans/index.js +2 -3
  38. package/dist/src/plugins/bans/index.js.map +1 -1
  39. package/dist/src/plugins/bans/types.d.ts +13 -6
  40. package/dist/src/plugins/bans/types.d.ts.map +1 -1
  41. package/dist/src/plugins/devices/devices-plugin.d.ts +5 -2
  42. package/dist/src/plugins/devices/devices-plugin.d.ts.map +1 -1
  43. package/dist/src/plugins/devices/devices-plugin.js +62 -26
  44. package/dist/src/plugins/devices/devices-plugin.js.map +1 -1
  45. package/dist/src/plugins/devices/index.d.ts +0 -4
  46. package/dist/src/plugins/devices/index.d.ts.map +1 -1
  47. package/dist/src/plugins/devices/index.js +2 -3
  48. package/dist/src/plugins/devices/index.js.map +1 -1
  49. package/dist/src/plugins/diagnostics-plugin.d.ts.map +1 -1
  50. package/dist/src/plugins/diagnostics-plugin.js +73 -0
  51. package/dist/src/plugins/diagnostics-plugin.js.map +1 -1
  52. package/dist/src/plugins/entitlements/entitlements-plugin.d.ts +5 -2
  53. package/dist/src/plugins/entitlements/entitlements-plugin.d.ts.map +1 -1
  54. package/dist/src/plugins/entitlements/entitlements-plugin.js +78 -41
  55. package/dist/src/plugins/entitlements/entitlements-plugin.js.map +1 -1
  56. package/dist/src/plugins/entitlements/index.d.ts +0 -4
  57. package/dist/src/plugins/entitlements/index.d.ts.map +1 -1
  58. package/dist/src/plugins/entitlements/index.js +2 -3
  59. package/dist/src/plugins/entitlements/index.js.map +1 -1
  60. package/dist/src/plugins/entitlements/types.d.ts +9 -2
  61. package/dist/src/plugins/entitlements/types.d.ts.map +1 -1
  62. package/dist/src/plugins/index.d.ts +1 -1
  63. package/dist/src/plugins/index.d.ts.map +1 -1
  64. package/dist/src/plugins/maintenance/SeedExecutor.d.ts +2 -0
  65. package/dist/src/plugins/maintenance/SeedExecutor.d.ts.map +1 -1
  66. package/dist/src/plugins/maintenance/SeedExecutor.js +6 -2
  67. package/dist/src/plugins/maintenance/SeedExecutor.js.map +1 -1
  68. package/dist/src/plugins/maintenance/SeedList.d.ts +2 -2
  69. package/dist/src/plugins/maintenance/SeedList.d.ts.map +1 -1
  70. package/dist/src/plugins/maintenance/SeedList.js +39 -14
  71. package/dist/src/plugins/maintenance/SeedList.js.map +1 -1
  72. package/dist/src/plugins/maintenance/SeedManagementPage.d.ts +1 -1
  73. package/dist/src/plugins/maintenance/SeedManagementPage.d.ts.map +1 -1
  74. package/dist/src/plugins/maintenance/SeedManagementPage.js +9 -5
  75. package/dist/src/plugins/maintenance/SeedManagementPage.js.map +1 -1
  76. package/dist/src/plugins/maintenance/seed-executor.d.ts +6 -4
  77. package/dist/src/plugins/maintenance/seed-executor.d.ts.map +1 -1
  78. package/dist/src/plugins/maintenance/seed-executor.js +53 -17
  79. package/dist/src/plugins/maintenance/seed-executor.js.map +1 -1
  80. package/dist/src/plugins/maintenance-plugin.d.ts +24 -0
  81. package/dist/src/plugins/maintenance-plugin.d.ts.map +1 -1
  82. package/dist/src/plugins/maintenance-plugin.js +222 -34
  83. package/dist/src/plugins/maintenance-plugin.js.map +1 -1
  84. package/dist/src/plugins/notifications/index.d.ts +0 -4
  85. package/dist/src/plugins/notifications/index.d.ts.map +1 -1
  86. package/dist/src/plugins/notifications/index.js +2 -3
  87. package/dist/src/plugins/notifications/index.js.map +1 -1
  88. package/dist/src/plugins/notifications/notifications-plugin.d.ts +5 -2
  89. package/dist/src/plugins/notifications/notifications-plugin.d.ts.map +1 -1
  90. package/dist/src/plugins/notifications/notifications-plugin.js +45 -13
  91. package/dist/src/plugins/notifications/notifications-plugin.js.map +1 -1
  92. package/dist/src/plugins/parental/index.d.ts +0 -4
  93. package/dist/src/plugins/parental/index.d.ts.map +1 -1
  94. package/dist/src/plugins/parental/index.js +2 -3
  95. package/dist/src/plugins/parental/index.js.map +1 -1
  96. package/dist/src/plugins/parental/parental-plugin.d.ts +5 -2
  97. package/dist/src/plugins/parental/parental-plugin.d.ts.map +1 -1
  98. package/dist/src/plugins/parental/parental-plugin.js +60 -24
  99. package/dist/src/plugins/parental/parental-plugin.js.map +1 -1
  100. package/dist/src/plugins/postgres-plugin.d.ts +12 -0
  101. package/dist/src/plugins/postgres-plugin.d.ts.map +1 -1
  102. package/dist/src/plugins/postgres-plugin.js +319 -5
  103. package/dist/src/plugins/postgres-plugin.js.map +1 -1
  104. package/dist/src/plugins/preferences/index.d.ts +0 -4
  105. package/dist/src/plugins/preferences/index.d.ts.map +1 -1
  106. package/dist/src/plugins/preferences/index.js +2 -3
  107. package/dist/src/plugins/preferences/index.js.map +1 -1
  108. package/dist/src/plugins/preferences/preferences-plugin.d.ts +5 -2
  109. package/dist/src/plugins/preferences/preferences-plugin.d.ts.map +1 -1
  110. package/dist/src/plugins/preferences/preferences-plugin.js +63 -19
  111. package/dist/src/plugins/preferences/preferences-plugin.js.map +1 -1
  112. package/dist/src/plugins/profiles/index.d.ts +0 -4
  113. package/dist/src/plugins/profiles/index.d.ts.map +1 -1
  114. package/dist/src/plugins/profiles/index.js +2 -3
  115. package/dist/src/plugins/profiles/index.js.map +1 -1
  116. package/dist/src/plugins/profiles/profiles-plugin.d.ts +5 -2
  117. package/dist/src/plugins/profiles/profiles-plugin.d.ts.map +1 -1
  118. package/dist/src/plugins/profiles/profiles-plugin.js +60 -26
  119. package/dist/src/plugins/profiles/profiles-plugin.js.map +1 -1
  120. package/dist/src/plugins/profiles/types.d.ts +9 -2
  121. package/dist/src/plugins/profiles/types.d.ts.map +1 -1
  122. package/dist/src/plugins/qwickbrain/index.d.ts +0 -4
  123. package/dist/src/plugins/qwickbrain/index.d.ts.map +1 -1
  124. package/dist/src/plugins/qwickbrain/index.js +2 -3
  125. package/dist/src/plugins/qwickbrain/index.js.map +1 -1
  126. package/dist/src/plugins/qwickbrain/qwickbrain-plugin.d.ts.map +1 -1
  127. package/dist/src/plugins/qwickbrain/qwickbrain-plugin.js +117 -0
  128. package/dist/src/plugins/qwickbrain/qwickbrain-plugin.js.map +1 -1
  129. package/dist/src/plugins/rate-limit/index.d.ts +0 -4
  130. package/dist/src/plugins/rate-limit/index.d.ts.map +1 -1
  131. package/dist/src/plugins/rate-limit/index.js +2 -3
  132. package/dist/src/plugins/rate-limit/index.js.map +1 -1
  133. package/dist/src/plugins/subscriptions/index.d.ts +0 -4
  134. package/dist/src/plugins/subscriptions/index.d.ts.map +1 -1
  135. package/dist/src/plugins/subscriptions/index.js +2 -3
  136. package/dist/src/plugins/subscriptions/index.js.map +1 -1
  137. package/dist/src/plugins/subscriptions/subscriptions-plugin.d.ts +5 -2
  138. package/dist/src/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -1
  139. package/dist/src/plugins/subscriptions/subscriptions-plugin.js +63 -29
  140. package/dist/src/plugins/subscriptions/subscriptions-plugin.js.map +1 -1
  141. package/dist/src/plugins/subscriptions/types.d.ts +8 -2
  142. package/dist/src/plugins/subscriptions/types.d.ts.map +1 -1
  143. package/dist/src/plugins/tenants/tenants-plugin.d.ts +5 -2
  144. package/dist/src/plugins/tenants/tenants-plugin.d.ts.map +1 -1
  145. package/dist/src/plugins/tenants/tenants-plugin.js +91 -58
  146. package/dist/src/plugins/tenants/tenants-plugin.js.map +1 -1
  147. package/dist/src/plugins/tenants/types.d.ts +8 -2
  148. package/dist/src/plugins/tenants/types.d.ts.map +1 -1
  149. package/dist/src/plugins/usage/index.d.ts +0 -4
  150. package/dist/src/plugins/usage/index.d.ts.map +1 -1
  151. package/dist/src/plugins/usage/index.js +2 -3
  152. package/dist/src/plugins/usage/index.js.map +1 -1
  153. package/dist/src/plugins/usage/usage-plugin.d.ts +5 -2
  154. package/dist/src/plugins/usage/usage-plugin.d.ts.map +1 -1
  155. package/dist/src/plugins/usage/usage-plugin.js +57 -23
  156. package/dist/src/plugins/usage/usage-plugin.js.map +1 -1
  157. package/dist/src/plugins/users/types.d.ts +7 -2
  158. package/dist/src/plugins/users/types.d.ts.map +1 -1
  159. package/dist/src/plugins/users/users-plugin.d.ts +5 -2
  160. package/dist/src/plugins/users/users-plugin.d.ts.map +1 -1
  161. package/dist/src/plugins/users/users-plugin.js +56 -23
  162. package/dist/src/plugins/users/users-plugin.js.map +1 -1
  163. package/dist/ui/src/components/ControlPanelApp.d.ts.map +1 -1
  164. package/dist/ui/src/components/ControlPanelApp.js +4 -3
  165. package/dist/ui/src/components/ControlPanelApp.js.map +1 -1
  166. package/dist/ui/src/dashboard/builtInWidgets.d.ts.map +1 -1
  167. package/dist/ui/src/dashboard/builtInWidgets.js +3 -1
  168. package/dist/ui/src/dashboard/builtInWidgets.js.map +1 -1
  169. package/dist/ui/src/dashboard/widgets/CMSMaintenanceWidget.d.ts.map +1 -1
  170. package/dist/ui/src/dashboard/widgets/CMSMaintenanceWidget.js +17 -4
  171. package/dist/ui/src/dashboard/widgets/CMSMaintenanceWidget.js.map +1 -1
  172. package/dist/ui/src/dashboard/widgets/CMSStatusWidget.d.ts.map +1 -1
  173. package/dist/ui/src/dashboard/widgets/CMSStatusWidget.js +5 -1
  174. package/dist/ui/src/dashboard/widgets/CMSStatusWidget.js.map +1 -1
  175. package/dist/ui/src/dashboard/widgets/CacheMaintenanceWidget.d.ts.map +1 -1
  176. package/dist/ui/src/dashboard/widgets/CacheMaintenanceWidget.js +4 -2
  177. package/dist/ui/src/dashboard/widgets/CacheMaintenanceWidget.js.map +1 -1
  178. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.d.ts +12 -0
  179. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.d.ts.map +1 -0
  180. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.js +174 -0
  181. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.js.map +1 -0
  182. package/dist/ui/src/dashboard/widgets/LogsMaintenanceWidget.d.ts.map +1 -1
  183. package/dist/ui/src/dashboard/widgets/LogsMaintenanceWidget.js +6 -3
  184. package/dist/ui/src/dashboard/widgets/LogsMaintenanceWidget.js.map +1 -1
  185. package/dist/ui/src/dashboard/widgets/SeedManagementWidget.d.ts +1 -1
  186. package/dist/ui/src/dashboard/widgets/SeedManagementWidget.d.ts.map +1 -1
  187. package/dist/ui/src/dashboard/widgets/SeedManagementWidget.js +256 -16
  188. package/dist/ui/src/dashboard/widgets/SeedManagementWidget.js.map +1 -1
  189. package/dist/ui/src/dashboard/widgets/index.d.ts +1 -0
  190. package/dist/ui/src/dashboard/widgets/index.d.ts.map +1 -1
  191. package/dist/ui/src/dashboard/widgets/index.js +1 -0
  192. package/dist/ui/src/dashboard/widgets/index.js.map +1 -1
  193. package/dist-ui/assets/index-BkGp7ZKd.js +529 -0
  194. package/dist-ui/assets/index-BkGp7ZKd.js.map +1 -0
  195. package/dist-ui/index.html +1 -1
  196. package/dist-ui-lib/index.js +3735 -3187
  197. package/dist-ui-lib/index.js.map +1 -1
  198. package/dist-ui-lib/src/dashboard/widgets/DatabaseOperationsWidget.d.ts +11 -0
  199. package/dist-ui-lib/src/dashboard/widgets/SeedManagementWidget.d.ts +1 -1
  200. package/dist-ui-lib/src/dashboard/widgets/index.d.ts +1 -0
  201. package/package.json +8 -5
  202. package/src/core/control-panel.ts +5 -5
  203. package/src/core/gateway.ts +135 -15
  204. package/src/core/plugin-registry.ts +171 -0
  205. package/src/index.ts +2 -0
  206. package/src/plugins/api-keys/api-keys-plugin.ts +121 -20
  207. package/src/plugins/api-keys/index.ts +3 -5
  208. package/src/plugins/api-keys/stores/postgres-store.ts +90 -67
  209. package/src/plugins/api-keys/types.ts +23 -4
  210. package/src/plugins/auth/index.ts +3 -5
  211. package/src/plugins/bans/bans-plugin.ts +71 -26
  212. package/src/plugins/bans/index.ts +3 -5
  213. package/src/plugins/bans/types.ts +13 -6
  214. package/src/plugins/devices/devices-plugin.ts +62 -27
  215. package/src/plugins/devices/index.ts +3 -5
  216. package/src/plugins/diagnostics-plugin.ts +77 -0
  217. package/src/plugins/entitlements/entitlements-plugin.ts +81 -43
  218. package/src/plugins/entitlements/index.ts +3 -5
  219. package/src/plugins/entitlements/types.ts +9 -2
  220. package/src/plugins/index.ts +1 -1
  221. package/src/plugins/maintenance/SeedExecutor.tsx +9 -1
  222. package/src/plugins/maintenance/SeedList.tsx +85 -38
  223. package/src/plugins/maintenance/SeedManagementPage.tsx +10 -4
  224. package/src/plugins/maintenance/seed-executor.ts +56 -17
  225. package/src/plugins/maintenance-plugin.ts +267 -36
  226. package/src/plugins/notifications/index.ts +3 -5
  227. package/src/plugins/notifications/notifications-plugin.ts +48 -19
  228. package/src/plugins/parental/index.ts +3 -5
  229. package/src/plugins/parental/parental-plugin.ts +63 -25
  230. package/src/plugins/postgres-plugin.ts +410 -5
  231. package/src/plugins/preferences/index.ts +3 -5
  232. package/src/plugins/preferences/preferences-plugin.ts +66 -20
  233. package/src/plugins/profiles/index.ts +3 -5
  234. package/src/plugins/profiles/profiles-plugin.ts +60 -27
  235. package/src/plugins/profiles/types.ts +9 -2
  236. package/src/plugins/qwickbrain/index.ts +3 -5
  237. package/src/plugins/qwickbrain/qwickbrain-plugin.ts +135 -0
  238. package/src/plugins/rate-limit/index.ts +3 -5
  239. package/src/plugins/subscriptions/index.ts +3 -5
  240. package/src/plugins/subscriptions/subscriptions-plugin.ts +63 -30
  241. package/src/plugins/subscriptions/types.ts +8 -2
  242. package/src/plugins/tenants/tenants-plugin.ts +95 -60
  243. package/src/plugins/tenants/types.ts +8 -2
  244. package/src/plugins/usage/index.ts +3 -5
  245. package/src/plugins/usage/usage-plugin.ts +60 -26
  246. package/src/plugins/users/types.ts +7 -2
  247. package/src/plugins/users/users-plugin.ts +56 -24
  248. package/ui/src/App.tsx +3 -3
  249. package/ui/src/components/ControlPanelApp.tsx +4 -3
  250. package/ui/src/dashboard/builtInWidgets.tsx +3 -0
  251. package/ui/src/dashboard/widgets/CMSMaintenanceWidget.tsx +17 -4
  252. package/ui/src/dashboard/widgets/CMSStatusWidget.tsx +5 -1
  253. package/ui/src/dashboard/widgets/CacheMaintenanceWidget.tsx +4 -2
  254. package/ui/src/dashboard/widgets/DatabaseOperationsWidget.tsx +410 -0
  255. package/ui/src/dashboard/widgets/LogsMaintenanceWidget.tsx +6 -3
  256. package/ui/src/dashboard/widgets/SeedManagementWidget.tsx +533 -49
  257. package/ui/src/dashboard/widgets/index.ts +1 -0
  258. package/dist-ui/assets/index-8y0jDGcd.js +0 -528
  259. package/dist-ui/assets/index-8y0jDGcd.js.map +0 -1
@@ -10,13 +10,103 @@
10
10
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
11
11
  */
12
12
 
13
- import { readdirSync, statSync } from 'fs';
14
- import { resolve } from 'path';
13
+ import { readdirSync, statSync, readFileSync } from 'fs';
14
+ import { resolve, join, relative } from 'path';
15
15
  import type { Request, Response } from 'express';
16
16
  import type { Plugin, PluginConfig, PluginRegistry } from '../core/plugin-registry.js';
17
17
  import { SeedExecutor, validateScriptPath } from './maintenance/seed-executor.js';
18
18
  import { getPostgres, hasPostgres } from './postgres-plugin.js';
19
19
 
20
+ /**
21
+ * Extract description from JSDoc comment at top of file
22
+ */
23
+ function extractDescription(filePath: string): string | undefined {
24
+ try {
25
+ const content = readFileSync(filePath, 'utf-8');
26
+ // Match JSDoc comment at start of file (after any whitespace)
27
+ const jsdocMatch = content.match(/^\s*\/\*\*\s*\n([\s\S]*?)\*\//);
28
+
29
+ if (jsdocMatch) {
30
+ // Extract lines and clean up asterisks and whitespace
31
+ const lines = jsdocMatch[1]
32
+ .split('\n')
33
+ .map(line => line.replace(/^\s*\*\s?/, '').trim())
34
+ .filter(line => line.length > 0);
35
+
36
+ // Skip the first line if it's just the title (will be shown separately)
37
+ // Return subsequent lines as the description
38
+ return lines.slice(1).join(' ').trim() || undefined;
39
+ }
40
+ } catch (err) {
41
+ // Failed to read file or parse description
42
+ }
43
+
44
+ return undefined;
45
+ }
46
+
47
+ /**
48
+ * Recursively scan directory for .mjs files
49
+ */
50
+ function scanSeedScripts(dir: string, basePath: string = dir): any[] {
51
+ const results: any[] = [];
52
+
53
+ try {
54
+ const entries = readdirSync(dir, { withFileTypes: true });
55
+
56
+ for (const entry of entries) {
57
+ const fullPath = join(dir, entry.name);
58
+
59
+ if (entry.isDirectory()) {
60
+ // Recursively scan subdirectories
61
+ results.push(...scanSeedScripts(fullPath, basePath));
62
+ } else if (entry.isFile() && entry.name.endsWith('.mjs')) {
63
+ // Found a .mjs file
64
+ const stats = statSync(fullPath);
65
+ const relativePath = relative(basePath, fullPath);
66
+ const description = extractDescription(fullPath);
67
+
68
+ results.push({
69
+ type: 'file',
70
+ name: entry.name,
71
+ path: relativePath, // Relative path from scripts directory
72
+ fullPath: fullPath, // Absolute path
73
+ size: stats.size,
74
+ createdAt: stats.birthtime,
75
+ modifiedAt: stats.mtime,
76
+ description, // Add description from JSDoc
77
+ });
78
+ }
79
+ }
80
+ } catch (err) {
81
+ // Directory not accessible, return empty array
82
+ }
83
+
84
+ return results;
85
+ }
86
+
87
+ /**
88
+ * Custom seed task handler
89
+ */
90
+ export interface SeedTaskHandler {
91
+ (options?: Record<string, any>, res?: Response): Promise<void>;
92
+ }
93
+
94
+ /**
95
+ * Custom seed task definition
96
+ */
97
+ export interface SeedTask {
98
+ /** Unique task identifier */
99
+ id: string;
100
+ /** Display name */
101
+ name: string;
102
+ /** Description of what this task does */
103
+ description: string;
104
+ /** Task handler function */
105
+ handler: SeedTaskHandler;
106
+ /** Optional task options/parameters */
107
+ options?: Record<string, any>;
108
+ }
109
+
20
110
  export interface MaintenancePluginConfig {
21
111
  /** Path to scripts directory (default: './scripts') */
22
112
  scriptsPath?: string;
@@ -41,6 +131,9 @@ export interface MaintenancePluginConfig {
41
131
 
42
132
  /** Enable database operations (default: true) */
43
133
  enableDatabaseOps?: boolean;
134
+
135
+ /** Custom seed tasks */
136
+ customTasks?: SeedTask[];
44
137
  }
45
138
 
46
139
  /**
@@ -60,6 +153,38 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
60
153
  const logger = registry.getLogger('maintenance');
61
154
  logger.info('Maintenance plugin starting...');
62
155
 
156
+ // Initialize seed_executions table if PostgreSQL is available
157
+ if (hasPostgres()) {
158
+ try {
159
+ const db = getPostgres();
160
+ await db.queryRaw(`
161
+ CREATE TABLE IF NOT EXISTS seed_executions (
162
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
163
+ name TEXT NOT NULL,
164
+ status TEXT NOT NULL CHECK (status IN ('running', 'completed', 'failed')),
165
+ started_at TIMESTAMPTZ NOT NULL,
166
+ completed_at TIMESTAMPTZ,
167
+ exit_code INTEGER,
168
+ output TEXT,
169
+ error TEXT,
170
+ duration_ms INTEGER,
171
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
172
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
173
+ )
174
+ `);
175
+
176
+ // Create index on status for faster queries
177
+ await db.queryRaw(`
178
+ CREATE INDEX IF NOT EXISTS idx_seed_executions_status
179
+ ON seed_executions(status)
180
+ `);
181
+
182
+ logger.debug('Seed executions table initialized');
183
+ } catch (error) {
184
+ logger.error('Failed to initialize seed_executions table', { error });
185
+ }
186
+ }
187
+
63
188
  // Clean up orphaned executions from previous crashes
64
189
  if (hasPostgres()) {
65
190
  try {
@@ -112,7 +237,7 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
112
237
  logger.warn('Seed management requires PostgreSQL plugin for execution history');
113
238
  }
114
239
 
115
- // GET /seeds/discover - List available seed scripts
240
+ // GET /seeds/discover - List available seed scripts and custom tasks
116
241
  registry.addRoute({
117
242
  method: 'get',
118
243
  path: '/seeds/discover',
@@ -120,25 +245,30 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
120
245
  handler: (_req: Request, res: Response) => {
121
246
  try {
122
247
  const resolvedPath = resolve(scriptsPath);
123
- const files = readdirSync(resolvedPath);
124
-
125
- // Filter for seed-*.mjs files
126
- const seedFiles = files
127
- .filter((file) => /^seed-[a-z0-9-]+\.mjs$/.test(file))
128
- .map((file) => {
129
- const filePath = resolve(resolvedPath, file);
130
- const stats = statSync(filePath);
131
- return {
132
- name: file,
133
- path: filePath,
134
- size: stats.size,
135
- createdAt: stats.birthtime,
136
- modifiedAt: stats.mtime,
137
- };
138
- })
139
- .sort((a, b) => a.name.localeCompare(b.name));
140
-
141
- res.json({ seeds: seedFiles });
248
+ let seedFiles: any[] = [];
249
+
250
+ try {
251
+ // Recursively scan for .mjs files
252
+ seedFiles = scanSeedScripts(resolvedPath);
253
+
254
+ // Sort by relative path (natural ordering respects numbered prefixes)
255
+ // Example: "01-Setup/001.init.mjs" comes before "02-Production/001.seed.mjs"
256
+ seedFiles.sort((a, b) => a.path.localeCompare(b.path, undefined, { numeric: true }));
257
+ } catch (err) {
258
+ // Scripts directory may not exist, which is fine if we have custom tasks
259
+ logger.debug('Scripts directory not found or not readable');
260
+ }
261
+
262
+ // Add custom tasks
263
+ const customTasks = (config.customTasks || []).map((task) => ({
264
+ type: 'task',
265
+ id: task.id,
266
+ name: task.name,
267
+ description: task.description,
268
+ options: task.options,
269
+ }));
270
+
271
+ res.json({ seeds: [...seedFiles, ...customTasks] });
142
272
  } catch (error) {
143
273
  logger.error('Failed to discover seed scripts', { error });
144
274
  res.status(500).json({
@@ -149,23 +279,45 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
149
279
  },
150
280
  });
151
281
 
152
- // POST /seeds/execute - Execute a seed script
282
+ // POST /seeds/execute - Execute a seed script or custom task
153
283
  registry.addRoute({
154
284
  method: 'post',
155
285
  path: '/seeds/execute',
156
286
  pluginId: 'maintenance',
157
287
  handler: async (req: Request, res: Response) => {
158
288
  try {
159
- const { name } = req.body;
289
+ const { name, type, options } = req.body;
160
290
 
161
291
  if (!name || typeof name !== 'string') {
162
292
  return res.status(400).json({ error: 'Script name is required' });
163
293
  }
164
294
 
165
- // Validate script path
166
- const scriptPath = validateScriptPath(name, scriptsPath);
167
- if (!scriptPath) {
168
- return res.status(400).json({ error: 'Invalid script name or file not found' });
295
+ // Ensure seed_executions table exists (lazy initialization)
296
+ if (hasPostgres()) {
297
+ const db = getPostgres();
298
+ try {
299
+ await db.queryRaw(`
300
+ CREATE TABLE IF NOT EXISTS seed_executions (
301
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
302
+ name TEXT NOT NULL,
303
+ status TEXT NOT NULL CHECK (status IN ('running', 'completed', 'failed')),
304
+ started_at TIMESTAMPTZ NOT NULL,
305
+ completed_at TIMESTAMPTZ,
306
+ exit_code INTEGER,
307
+ output TEXT,
308
+ error TEXT,
309
+ duration_ms INTEGER,
310
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
311
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
312
+ )
313
+ `);
314
+ await db.queryRaw(`
315
+ CREATE INDEX IF NOT EXISTS idx_seed_executions_status
316
+ ON seed_executions(status)
317
+ `);
318
+ } catch (err) {
319
+ logger.debug('Table initialization check', { err });
320
+ }
169
321
  }
170
322
 
171
323
  // Check for concurrent execution
@@ -204,10 +356,39 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
204
356
  executionId = result?.id || null;
205
357
  }
206
358
 
207
- // Execute seed script
208
- const executor = new SeedExecutor();
359
+ const startTime = Date.now();
360
+ let exitCode = 0;
361
+ let output = '';
362
+ let error = '';
363
+
209
364
  try {
210
- const result = await executor.execute(scriptPath, res);
365
+ // Execute based on type
366
+ if (type === 'task') {
367
+ // Find custom task
368
+ const task = (config.customTasks || []).find((t) => t.id === name);
369
+ if (!task) {
370
+ throw new Error(`Custom task not found: ${name}`);
371
+ }
372
+
373
+ // Execute custom task handler with SSE streaming
374
+ await task.handler(options || {}, res);
375
+ } else {
376
+ // Execute file-based seed script
377
+ const scriptPath = validateScriptPath(name, scriptsPath);
378
+ if (!scriptPath) {
379
+ throw new Error('Invalid script name or file not found');
380
+ }
381
+
382
+ const executor = new SeedExecutor();
383
+ // Project root is one level up from scripts directory
384
+ const projectRoot = resolve(scriptsPath, '..');
385
+ const result = await executor.execute(scriptPath, res, config.databaseUrl, projectRoot);
386
+ exitCode = result.exitCode;
387
+ output = result.output;
388
+ error = result.error;
389
+ }
390
+
391
+ const duration = Date.now() - startTime;
211
392
 
212
393
  // Update execution record
213
394
  if (hasPostgres() && executionId) {
@@ -218,11 +399,11 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
218
399
  output = $3, error = $4, duration_ms = $5, updated_at = NOW()
219
400
  WHERE id = $6`,
220
401
  [
221
- result.exitCode === 0 ? 'completed' : 'failed',
222
- result.exitCode,
223
- result.output,
224
- result.error,
225
- result.duration,
402
+ exitCode === 0 ? 'completed' : 'failed',
403
+ exitCode,
404
+ output,
405
+ error,
406
+ duration,
226
407
  executionId,
227
408
  ]
228
409
  );
@@ -262,6 +443,56 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
262
443
  },
263
444
  });
264
445
 
446
+ // POST /database/reset - Drop and recreate database schema (local/dev only)
447
+ registry.addRoute({
448
+ method: 'post',
449
+ path: '/database/reset',
450
+ pluginId: 'maintenance',
451
+ handler: async (req: Request, res: Response) => {
452
+ try {
453
+ // Security: Only allow in local or development environments
454
+ const nodeEnv = process.env.NODE_ENV?.toLowerCase();
455
+ if (nodeEnv !== 'local' && nodeEnv !== 'development') {
456
+ return res.status(403).json({
457
+ error: 'Database reset is only available in local or development environments',
458
+ currentEnv: nodeEnv || 'production',
459
+ });
460
+ }
461
+
462
+ if (!hasPostgres()) {
463
+ return res.status(503).json({
464
+ error: 'PostgreSQL plugin required for database reset',
465
+ });
466
+ }
467
+
468
+ const db = getPostgres();
469
+
470
+ // Drop and recreate public schema (removes all tables, data, etc.)
471
+ await db.queryRaw('DROP SCHEMA IF EXISTS public CASCADE');
472
+ await db.queryRaw('CREATE SCHEMA public');
473
+ await db.queryRaw('GRANT ALL ON SCHEMA public TO public');
474
+ await db.queryRaw('GRANT ALL ON SCHEMA public TO postgres');
475
+ await db.queryRaw('GRANT ALL ON SCHEMA public TO qwickapps');
476
+
477
+ // Grant default privileges for future tables and sequences
478
+ await db.queryRaw('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO qwickapps');
479
+ await db.queryRaw('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO qwickapps');
480
+
481
+ res.json({
482
+ success: true,
483
+ message: 'Database schema has been reset. All tables and data have been deleted.',
484
+ timestamp: new Date().toISOString(),
485
+ });
486
+ } catch (error) {
487
+ logger.error('Database reset failed', { error });
488
+ res.status(500).json({
489
+ error: 'Failed to reset database',
490
+ message: error instanceof Error ? error.message : String(error),
491
+ });
492
+ }
493
+ },
494
+ });
495
+
265
496
  // GET /seeds/history - List execution history
266
497
  registry.addRoute({
267
498
  method: 'get',
@@ -90,8 +90,6 @@ export type {
90
90
  NotificationsManagerInterface,
91
91
  } from './types.js';
92
92
 
93
- // UI Components
94
- export { NotificationsStatusWidget } from './NotificationsStatusWidget.js';
95
- export { NotificationsManagementPage } from './NotificationsManagementPage.js';
96
- export type { NotificationsStatusWidgetProps } from './NotificationsStatusWidget.js';
97
- export type { NotificationsManagementPageProps } from './NotificationsManagementPage.js';
93
+ // UI Components are exported from main package index (@qwickapps/server)
94
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
95
+
@@ -96,7 +96,10 @@ function validateId(id: string | undefined, paramName: string): { valid: boolean
96
96
  }
97
97
 
98
98
  /**
99
- * Create the Notifications plugin
99
+ * Create the Notifications plugin with smart defaults
100
+ *
101
+ * Config is optional - plugin will use defaults and get dependencies from registry.
102
+ * Gracefully handles missing dependencies with clear log messages.
100
103
  *
101
104
  * @param config Plugin configuration
102
105
  * @returns Plugin instance
@@ -112,11 +115,7 @@ function validateId(id: string | undefined, paramName: string): { valid: boolean
112
115
  * });
113
116
  * ```
114
117
  */
115
- export function createNotificationsPlugin(config: NotificationsPluginConfig): Plugin {
116
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /notifications prefix automatically
117
- const streamEnabled = config.api?.stream !== false;
118
- const statsEnabled = config.api?.stats !== false;
119
-
118
+ export function createNotificationsPlugin(config: Partial<NotificationsPluginConfig> = {}): Plugin {
120
119
  let manager: NotificationsManager | null = null;
121
120
 
122
121
  return {
@@ -129,10 +128,19 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
129
128
 
130
129
  // Check for postgres plugin dependency
131
130
  if (!hasPostgres()) {
132
- throw new Error(
133
- 'Notifications plugin requires postgres plugin. ' +
134
- 'Please add createPostgresPlugin() before createNotificationsPlugin().'
135
- );
131
+ logger.warn('No Database! Notifications plugin disabled.');
132
+ registry.registerHealthCheck({
133
+ name: 'notifications',
134
+ type: 'custom',
135
+ check: async () => ({
136
+ healthy: false,
137
+ details: {
138
+ error: 'PostgreSQL not available',
139
+ state: 'disabled',
140
+ },
141
+ }),
142
+ });
143
+ return;
136
144
  }
137
145
 
138
146
  // Get database connection string from postgres plugin
@@ -150,22 +158,43 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
150
158
  }
151
159
 
152
160
  if (!connectionString) {
153
- throw new Error(
154
- 'Could not determine PostgreSQL connection string. ' +
155
- 'Ensure DATABASE_URL is set or postgres plugin was configured with a URL.'
156
- );
161
+ logger.warn('Could not determine PostgreSQL connection string. Notifications plugin disabled.');
162
+ registry.registerHealthCheck({
163
+ name: 'notifications',
164
+ type: 'custom',
165
+ check: async () => ({
166
+ healthy: false,
167
+ details: {
168
+ error: 'Connection string not available',
169
+ state: 'disabled',
170
+ },
171
+ }),
172
+ });
173
+ return;
157
174
  }
158
175
 
176
+ // Smart defaults
177
+ const channels = config.channels ?? [];
178
+ const apiPrefix = config.api?.prefix ?? '/'; // Framework adds /notifications prefix automatically
179
+ const streamEnabled = config.api?.stream ?? true;
180
+ const statsEnabled = config.api?.stats ?? true;
181
+
159
182
  logger.debug('Initializing notifications manager', {
160
- channels: config.channels,
183
+ channels: channels,
161
184
  heartbeatInterval: config.heartbeat?.interval,
162
185
  });
163
186
 
187
+ // Create full config with defaults for required fields
188
+ const fullConfig: NotificationsPluginConfig = {
189
+ ...config,
190
+ channels, // Required field with default
191
+ };
192
+
164
193
  // Create and initialize manager
165
194
  manager = new NotificationsManager(
166
195
  connectionString,
167
- config.channels,
168
- config,
196
+ channels,
197
+ fullConfig,
169
198
  logger
170
199
  );
171
200
 
@@ -182,7 +211,7 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
182
211
  healthy: health?.isHealthy ?? false,
183
212
  details: {
184
213
  connected: health?.isConnected,
185
- channels: config.channels,
214
+ channels: channels,
186
215
  activeClients: manager?.getStats().currentConnections ?? 0,
187
216
  lastEventAt: health?.lastEventAt?.toISOString(),
188
217
  isReconnecting: health?.isReconnecting,
@@ -273,7 +302,7 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
273
302
  const stats = manager.getStats();
274
303
  res.json({
275
304
  ...stats,
276
- channels: config.channels,
305
+ channels: channels,
277
306
  lastEventAt: stats.connectionHealth.lastEventAt?.toISOString(),
278
307
  lastReconnectionAt: stats.lastReconnectionAt?.toISOString(),
279
308
  });
@@ -54,8 +54,6 @@ export { postgresParentalStore } from './stores/index.js';
54
54
  export { kidsAdapter } from './adapters/index.js';
55
55
  export type { KidsAdapterConfig } from './adapters/index.js';
56
56
 
57
- // UI Components
58
- export { ParentalStatusWidget } from './ParentalStatusWidget.js';
59
- export type { ParentalStatusWidgetProps } from './ParentalStatusWidget.js';
60
- export { ParentalManagementPage } from './ParentalManagementPage.js';
61
- export type { ParentalManagementPageProps } from './ParentalManagementPage.js';
57
+ // UI Components are exported from main package index (@qwickapps/server)
58
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
59
+