@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
@@ -21,7 +21,9 @@ import type {
21
21
  EntitlementStats,
22
22
  } from './types.js';
23
23
  import type { AuthenticatedRequest } from '../auth/types.js';
24
- import { getCache, type CacheInstance } from '../cache-plugin.js';
24
+ import { getCache, hasCache, type CacheInstance } from '../cache-plugin.js';
25
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
26
+ import { postgresEntitlementSource } from './sources/index.js';
25
27
 
26
28
  // Plugin state
27
29
  let primarySource: EntitlementSource | null = null;
@@ -35,67 +37,103 @@ let cacheEnabled = true;
35
37
  let cacheVersion = 1;
36
38
 
37
39
  /**
38
- * Create the Entitlements plugin
40
+ * Create the Entitlements plugin with smart defaults
41
+ *
42
+ * Config is optional - plugin will use defaults and get dependencies from registry.
43
+ * Gracefully handles missing dependencies with clear log messages.
39
44
  */
40
- export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plugin {
41
- const debug = config.debug || false;
42
- // Routes are mounted under /api by the control panel, so don't include /api in prefix
43
- const apiPrefix = config.api?.prefix || ''; // Framework adds /entitlements prefix automatically (empty string to avoid double slash)
44
- const apiEnabled = config.api?.enabled !== false;
45
- const enableWriteApi = config.api?.enableWrite !== false;
46
-
47
- function log(message: string, data?: Record<string, unknown>) {
48
- if (debug) {
49
- console.log(`[EntitlementsPlugin] ${message}`, data || '');
45
+ export function createEntitlementsPlugin(config: Partial<EntitlementsPluginConfig> = {}): Plugin {
46
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
47
+ const prefix = '[EntitlementsPlugin]';
48
+ if (isError) {
49
+ console.error(`${prefix} ${message}`, data || '');
50
+ } else if (config.debug) {
51
+ console.log(`${prefix} ${message}`, data || '');
50
52
  }
51
53
  }
52
54
 
53
- // Cache key helpers
54
- const keys = {
55
- entitlements: (email: string) => `${cacheKeyPrefix}user:${email.toLowerCase()}`,
56
- mapping: (source: string, id: string) => `${cacheKeyPrefix}mapping:${source}:${id}`,
57
- };
58
-
59
55
  return {
60
56
  id: 'entitlements',
61
57
  name: 'Entitlements',
62
58
  version: '1.0.0',
63
59
 
64
60
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
61
+ const logger = registry.getLogger('entitlements');
62
+
63
+ // Check for postgres in registry (needed for default source)
64
+ if (!hasPostgres()) {
65
+ logger.warn('No Database! Entitlements plugin disabled.');
66
+ registry.registerHealthCheck({
67
+ name: 'entitlements-source',
68
+ type: 'custom',
69
+ check: async () => ({
70
+ healthy: false,
71
+ details: {
72
+ error: 'PostgreSQL not available',
73
+ state: 'disabled',
74
+ },
75
+ }),
76
+ });
77
+ return;
78
+ }
79
+
80
+ // Smart defaults - get dependencies from registry
81
+ const source = config.source ?? postgresEntitlementSource({
82
+ pool: () => getPostgres().getPool(),
83
+ autoCreateTables: true,
84
+ });
85
+
86
+ const debug = config.debug ?? false;
87
+ const apiPrefix = config.api?.prefix ?? '/entitlements';
88
+ const apiEnabled = config.api?.enabled ?? true;
89
+ const enableWriteApi = config.api?.enableWrite ?? true;
90
+
65
91
  log('Starting entitlements plugin');
66
92
 
67
93
  // Initialize primary source
68
- await config.source.initialize();
69
- primarySource = config.source;
70
- log('Primary source initialized', { source: config.source.name });
94
+ await source.initialize();
95
+ primarySource = source;
96
+ log('Primary source initialized', { source: source.name });
71
97
 
72
98
  // Initialize additional sources
73
99
  additionalSources = config.additionalSources || [];
74
- for (const source of additionalSources) {
75
- await source.initialize();
76
- log('Additional source initialized', { source: source.name });
100
+ for (const additionalSource of additionalSources) {
101
+ await additionalSource.initialize();
102
+ log('Additional source initialized', { source: additionalSource.name });
77
103
  }
78
104
 
79
105
  // Store config
80
- pluginConfig = config;
106
+ pluginConfig = { ...config, source, debug };
81
107
 
82
- // Setup caching if enabled
108
+ // Setup caching if enabled and available
83
109
  cacheEnabled = config.cache?.enabled !== false;
84
110
  if (cacheEnabled) {
85
- try {
86
- const instanceName = config.cache?.instanceName || 'default';
87
- cacheInstance = getCache(instanceName);
88
- cacheKeyPrefix = config.cache?.keyPrefix || 'entitlements:';
89
- cacheTtl = config.cache?.ttl || 300;
90
- cacheMappingTtl = config.cache?.mappingTtl || cacheTtl * 2;
91
- log('Cache configured', { instanceName, prefix: cacheKeyPrefix, ttl: cacheTtl });
92
- } catch {
93
- log('Cache not available, running without caching');
111
+ if (hasCache()) {
112
+ try {
113
+ const instanceName = config.cache?.instanceName || 'default';
114
+ cacheInstance = getCache(instanceName);
115
+ cacheKeyPrefix = config.cache?.keyPrefix || 'entitlements:';
116
+ cacheTtl = config.cache?.ttl || 300;
117
+ cacheMappingTtl = config.cache?.mappingTtl || cacheTtl * 2;
118
+ log('Cache configured', { instanceName, prefix: cacheKeyPrefix, ttl: cacheTtl });
119
+ } catch {
120
+ log('Cache instance not available, running without caching');
121
+ cacheEnabled = false;
122
+ cacheInstance = null;
123
+ }
124
+ } else {
125
+ log('Cache plugin not available, running without caching');
94
126
  cacheEnabled = false;
95
127
  cacheInstance = null;
96
128
  }
97
129
  }
98
130
 
131
+ // Cache key helpers
132
+ const keys = {
133
+ entitlements: (email: string) => `${cacheKeyPrefix}user:${email.toLowerCase()}`,
134
+ mapping: (sourceId: string, id: string) => `${cacheKeyPrefix}mapping:${sourceId}:${id}`,
135
+ };
136
+
99
137
  // Register health check
100
138
  registry.registerHealthCheck({
101
139
  name: 'entitlements-source',
@@ -104,8 +142,8 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
104
142
  try {
105
143
  // Use source's isHealthy() method if available (avoids API calls)
106
144
  // Otherwise just check that source is initialized
107
- if (config.source.isHealthy) {
108
- const healthy = await config.source.isHealthy();
145
+ if (source.isHealthy) {
146
+ const healthy = await source.isHealthy();
109
147
  return { healthy };
110
148
  }
111
149
  // Source is healthy if initialized (we got here means it started)
@@ -145,9 +183,9 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
145
183
  try {
146
184
  const sources = [
147
185
  {
148
- name: config.source.name,
149
- description: config.source.description,
150
- readonly: config.source.readonly ?? false,
186
+ name: source.name,
187
+ description: source.description,
188
+ readonly: source.readonly ?? false,
151
189
  primary: true,
152
190
  },
153
191
  ...additionalSources.map((s) => ({
@@ -159,8 +197,8 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
159
197
  ];
160
198
 
161
199
  res.json({
162
- readonly: config.source.readonly ?? false,
163
- writeEnabled: enableWriteApi && !config.source.readonly,
200
+ readonly: source.readonly ?? false,
201
+ writeEnabled: enableWriteApi && !source.readonly,
164
202
  cacheEnabled,
165
203
  cacheTtl,
166
204
  sources,
@@ -311,7 +349,7 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
311
349
  });
312
350
 
313
351
  // Write endpoints (grant/revoke) - only if enabled and source is writable
314
- if (enableWriteApi && !config.source.readonly) {
352
+ if (enableWriteApi && !source.readonly) {
315
353
  // Grant entitlement
316
354
  registry.addRoute({
317
355
  method: 'post',
@@ -50,8 +50,6 @@ export type {
50
50
  EntitlementStats,
51
51
  } from './types.js';
52
52
 
53
- // UI Components
54
- export { EntitlementsStatusWidget } from './EntitlementsStatusWidget.js';
55
- export type { EntitlementsStatusWidgetProps } from './EntitlementsStatusWidget.js';
56
- export { EntitlementsManagementPage } from './EntitlementsManagementPage.js';
57
- export type { EntitlementsManagementPageProps } from './EntitlementsManagementPage.js';
53
+ // UI Components are exported from main package index (@qwickapps/server)
54
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
55
+
@@ -200,10 +200,17 @@ export interface EntitlementsApiConfig {
200
200
 
201
201
  /**
202
202
  * Entitlements plugin configuration
203
+ *
204
+ * All properties are optional - plugin will use smart defaults:
205
+ * - source: Postgres entitlement source using registry's postgres instance
206
+ * - cache: Uses cache from registry if available
207
+ * - api.prefix: '/entitlements'
208
+ * - api.enabled: true
209
+ * - debug: false
203
210
  */
204
211
  export interface EntitlementsPluginConfig {
205
- /** Primary entitlement source */
206
- source: EntitlementSource;
212
+ /** Primary entitlement source (default: postgres source from registry) */
213
+ source?: EntitlementSource;
207
214
 
208
215
  /** Additional sources to query (results are merged) */
209
216
  additionalSources?: EntitlementSource[];
@@ -14,7 +14,7 @@ export { createLogsPlugin } from './logs-plugin.js';
14
14
  export type { LogsPluginConfig } from './logs-plugin.js';
15
15
 
16
16
  export { createMaintenancePlugin } from './maintenance-plugin.js';
17
- export type { MaintenancePluginConfig } from './maintenance-plugin.js';
17
+ export type { MaintenancePluginConfig, SeedTask, SeedTaskHandler } from './maintenance-plugin.js';
18
18
  export {
19
19
  MaintenanceManagementPage,
20
20
  MaintenanceStatusWidget,
@@ -11,6 +11,8 @@ import React, { useEffect, useRef, useState } from 'react';
11
11
  export interface SeedExecutorProps {
12
12
  apiPrefix: string;
13
13
  seedName: string;
14
+ seedType?: string;
15
+ seedOptions?: any;
14
16
  onComplete: () => void;
15
17
  onCancel: () => void;
16
18
  }
@@ -26,6 +28,8 @@ interface OutputLine {
26
28
  export const SeedExecutor: React.FC<SeedExecutorProps> = ({
27
29
  apiPrefix,
28
30
  seedName,
31
+ seedType = 'file',
32
+ seedOptions,
29
33
  onComplete,
30
34
  onCancel,
31
35
  }) => {
@@ -63,7 +67,11 @@ export const SeedExecutor: React.FC<SeedExecutorProps> = ({
63
67
  headers: {
64
68
  'Content-Type': 'application/json',
65
69
  },
66
- body: JSON.stringify({ name: seedName }),
70
+ body: JSON.stringify({
71
+ name: seedName,
72
+ type: seedType,
73
+ options: seedOptions,
74
+ }),
67
75
  });
68
76
 
69
77
  if (!response.ok) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Seed List Component
3
3
  *
4
- * Displays available seed scripts with metadata.
4
+ * Displays available seed scripts and custom tasks with metadata.
5
5
  *
6
6
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
7
  */
@@ -10,10 +10,11 @@ import React, { useEffect, useState } from 'react';
10
10
 
11
11
  export interface SeedListProps {
12
12
  apiPrefix: string;
13
- onExecute: (seedName: string) => void;
13
+ onExecute: (seedName: string, type?: string, options?: any) => void;
14
14
  }
15
15
 
16
16
  interface SeedFile {
17
+ type: 'file';
17
18
  name: string;
18
19
  path: string;
19
20
  size: number;
@@ -21,8 +22,18 @@ interface SeedFile {
21
22
  modifiedAt: string;
22
23
  }
23
24
 
25
+ interface CustomTask {
26
+ type: 'task';
27
+ id: string;
28
+ name: string;
29
+ description: string;
30
+ options?: Record<string, any>;
31
+ }
32
+
33
+ type SeedItem = SeedFile | CustomTask;
34
+
24
35
  export const SeedList: React.FC<SeedListProps> = ({ apiPrefix, onExecute }) => {
25
- const [seeds, setSeeds] = useState<SeedFile[]>([]);
36
+ const [seeds, setSeeds] = useState<SeedItem[]>([]);
26
37
  const [loading, setLoading] = useState(true);
27
38
  const [error, setError] = useState<string | null>(null);
28
39
 
@@ -57,6 +68,14 @@ export const SeedList: React.FC<SeedListProps> = ({ apiPrefix, onExecute }) => {
57
68
  return new Date(dateString).toLocaleString();
58
69
  };
59
70
 
71
+ const isFileType = (item: SeedItem): item is SeedFile => {
72
+ return item.type === 'file';
73
+ };
74
+
75
+ const isTaskType = (item: SeedItem): item is CustomTask => {
76
+ return item.type === 'task';
77
+ };
78
+
60
79
  if (loading) {
61
80
  return <div style={{ padding: '20px' }}>Loading seeds...</div>;
62
81
  }
@@ -72,55 +91,83 @@ export const SeedList: React.FC<SeedListProps> = ({ apiPrefix, onExecute }) => {
72
91
  if (seeds.length === 0) {
73
92
  return (
74
93
  <div style={{ padding: '20px', color: '#666' }}>
75
- No seed scripts found in scripts directory.
94
+ No seed scripts or tasks found.
76
95
  </div>
77
96
  );
78
97
  }
79
98
 
80
99
  return (
81
100
  <div style={{ padding: '20px' }}>
82
- <h3>Available Seed Scripts ({seeds.length})</h3>
101
+ <h3>Available Seeds & Tasks ({seeds.length})</h3>
83
102
  <table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '16px' }}>
84
103
  <thead>
85
104
  <tr style={{ borderBottom: '2px solid #ddd', textAlign: 'left' }}>
105
+ <th style={{ padding: '12px' }}>Type</th>
86
106
  <th style={{ padding: '12px' }}>Name</th>
87
- <th style={{ padding: '12px' }}>Size</th>
88
- <th style={{ padding: '12px' }}>Modified</th>
107
+ <th style={{ padding: '12px' }}>Description</th>
108
+ <th style={{ padding: '12px' }}>Details</th>
89
109
  <th style={{ padding: '12px' }}>Action</th>
90
110
  </tr>
91
111
  </thead>
92
112
  <tbody>
93
- {seeds.map((seed) => (
94
- <tr key={seed.name} style={{ borderBottom: '1px solid #eee' }}>
95
- <td style={{ padding: '12px', fontFamily: 'monospace' }}>
96
- {seed.name}
97
- </td>
98
- <td style={{ padding: '12px' }}>{formatFileSize(seed.size)}</td>
99
- <td style={{ padding: '12px', fontSize: '14px', color: '#666' }}>
100
- {formatDate(seed.modifiedAt)}
101
- </td>
102
- <td style={{ padding: '12px' }}>
103
- <button
104
- onClick={() => {
105
- if (confirm(`Execute ${seed.name}?`)) {
106
- onExecute(seed.name);
107
- }
108
- }}
109
- style={{
110
- padding: '6px 12px',
111
- backgroundColor: '#1976d2',
112
- color: 'white',
113
- border: 'none',
114
- borderRadius: '4px',
115
- cursor: 'pointer',
116
- }}
117
- data-testid={`execute-${seed.name}`}
118
- >
119
- Execute
120
- </button>
121
- </td>
122
- </tr>
123
- ))}
113
+ {seeds.map((seed) => {
114
+ const itemKey = isFileType(seed) ? seed.name : seed.id;
115
+ return (
116
+ <tr key={itemKey} style={{ borderBottom: '1px solid #eee' }}>
117
+ <td style={{ padding: '12px' }}>
118
+ <span
119
+ style={{
120
+ display: 'inline-block',
121
+ padding: '4px 8px',
122
+ borderRadius: '4px',
123
+ fontSize: '12px',
124
+ fontWeight: 'bold',
125
+ backgroundColor: isFileType(seed) ? '#e3f2fd' : '#f3e5f5',
126
+ color: isFileType(seed) ? '#1976d2' : '#7b1fa2',
127
+ }}
128
+ >
129
+ {isFileType(seed) ? 'FILE' : 'TASK'}
130
+ </span>
131
+ </td>
132
+ <td style={{ padding: '12px', fontFamily: 'monospace' }}>
133
+ {isFileType(seed) ? seed.name : seed.name}
134
+ </td>
135
+ <td style={{ padding: '12px', fontSize: '14px', color: '#666' }}>
136
+ {isTaskType(seed) ? seed.description : '-'}
137
+ </td>
138
+ <td style={{ padding: '12px', fontSize: '14px', color: '#666' }}>
139
+ {isFileType(seed)
140
+ ? `${formatFileSize(seed.size)} • ${formatDate(seed.modifiedAt)}`
141
+ : '-'}
142
+ </td>
143
+ <td style={{ padding: '12px' }}>
144
+ <button
145
+ onClick={() => {
146
+ const displayName = isFileType(seed) ? seed.name : seed.name;
147
+ if (confirm(`Execute ${displayName}?`)) {
148
+ if (isFileType(seed)) {
149
+ onExecute(seed.name, 'file');
150
+ } else {
151
+ onExecute(seed.id, 'task', seed.options);
152
+ }
153
+ }
154
+ }}
155
+ style={{
156
+ padding: '6px 12px',
157
+ backgroundColor: '#1976d2',
158
+ color: 'white',
159
+ border: 'none',
160
+ borderRadius: '4px',
161
+ cursor: 'pointer',
162
+ }}
163
+ data-testid={`execute-${itemKey}`}
164
+ >
165
+ Execute
166
+ </button>
167
+ </td>
168
+ </tr>
169
+ );
170
+ })}
124
171
  </tbody>
125
172
  </table>
126
173
  </div>
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Seed Management Page
3
3
  *
4
- * Main page for managing and executing seed scripts.
4
+ * Main page for managing and executing seed scripts and custom tasks.
5
5
  *
6
6
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
7
  */
@@ -23,9 +23,13 @@ export const SeedManagementPage: React.FC<SeedManagementPageProps> = ({
23
23
  }) => {
24
24
  const [activeTab, setActiveTab] = useState<Tab>('list');
25
25
  const [selectedSeed, setSelectedSeed] = useState<string | null>(null);
26
+ const [selectedType, setSelectedType] = useState<string>('file');
27
+ const [selectedOptions, setSelectedOptions] = useState<any>(undefined);
26
28
 
27
- const handleExecute = (seedName: string) => {
29
+ const handleExecute = (seedName: string, type: string = 'file', options?: any) => {
28
30
  setSelectedSeed(seedName);
31
+ setSelectedType(type);
32
+ setSelectedOptions(options);
29
33
  setActiveTab('execute');
30
34
  };
31
35
 
@@ -36,7 +40,7 @@ export const SeedManagementPage: React.FC<SeedManagementPageProps> = ({
36
40
  return (
37
41
  <PluginManagementPage
38
42
  title="Seed Management"
39
- description="Manage and execute database seed scripts"
43
+ description="Manage and execute database seed scripts and custom tasks"
40
44
  >
41
45
  <div style={{ marginBottom: '20px' }}>
42
46
  <button
@@ -51,7 +55,7 @@ export const SeedManagementPage: React.FC<SeedManagementPageProps> = ({
51
55
  cursor: 'pointer',
52
56
  }}
53
57
  >
54
- Available Seeds
58
+ Available Seeds & Tasks
55
59
  </button>
56
60
  <button
57
61
  onClick={() => setActiveTab('history')}
@@ -76,6 +80,8 @@ export const SeedManagementPage: React.FC<SeedManagementPageProps> = ({
76
80
  <SeedExecutor
77
81
  apiPrefix={apiPrefix}
78
82
  seedName={selectedSeed}
83
+ seedType={selectedType}
84
+ seedOptions={selectedOptions}
79
85
  onComplete={handleExecutionComplete}
80
86
  onCancel={() => setActiveTab('list')}
81
87
  />
@@ -40,33 +40,33 @@ const MAX_OUTPUT_SIZE = 100 * 1024;
40
40
  /**
41
41
  * Validate script path to prevent path traversal attacks
42
42
  *
43
- * @param scriptName - Name of the script (e.g., "seed-products.mjs")
43
+ * @param scriptPath - Relative path to the script (e.g., "database/001.init.mjs" or "01-Setup/seed-data.mjs")
44
44
  * @param scriptsPath - Base path for scripts directory
45
- * @returns Resolved path if valid, null if invalid
45
+ * @returns Resolved absolute path if valid, null if invalid
46
46
  */
47
- export function validateScriptPath(scriptName: string, scriptsPath: string): string | null {
48
- // Only allow seed-*.mjs pattern
49
- if (!/^seed-[a-z0-9-]+\.mjs$/.test(scriptName)) {
47
+ export function validateScriptPath(scriptPath: string, scriptsPath: string): string | null {
48
+ // Only allow .mjs files
49
+ if (!scriptPath.endsWith('.mjs')) {
50
50
  return null;
51
51
  }
52
52
 
53
53
  // Resolve paths
54
54
  const basePath = resolve(scriptsPath);
55
- const scriptPath = resolve(basePath, scriptName);
55
+ const resolvedScriptPath = resolve(basePath, scriptPath);
56
56
 
57
57
  // Ensure resolved path is within scriptsPath (prevent path traversal)
58
58
  // Use relative() for platform-agnostic check (works on Windows and Unix)
59
- const relativePath = relative(basePath, scriptPath);
59
+ const relativePath = relative(basePath, resolvedScriptPath);
60
60
  if (relativePath.startsWith('..') || relativePath.includes('../') || relativePath.includes('..\\')) {
61
61
  return null;
62
62
  }
63
63
 
64
64
  // Ensure file exists
65
- if (!existsSync(scriptPath)) {
65
+ if (!existsSync(resolvedScriptPath)) {
66
66
  return null;
67
67
  }
68
68
 
69
- return scriptPath;
69
+ return resolvedScriptPath;
70
70
  }
71
71
 
72
72
  /**
@@ -95,30 +95,64 @@ export class SeedExecutor {
95
95
  *
96
96
  * @param scriptPath - Absolute path to the script
97
97
  * @param res - Express response object (for SSE streaming)
98
+ * @param databaseUrl - Optional database URL to pass to the script
99
+ * @param projectRoot - Optional project root directory (defaults to script's directory)
98
100
  * @returns Promise resolving to execution result
99
101
  */
100
- async execute(scriptPath: string, res: Response): Promise<SeedExecutionResult> {
102
+ async execute(scriptPath: string, res: Response, databaseUrl?: string, projectRoot?: string): Promise<SeedExecutionResult> {
101
103
  this.startTime = Date.now();
102
104
  this.outputBuffer = '';
103
105
  this.errorBuffer = '';
104
106
  this.outputSize = 0;
105
107
 
106
108
  return new Promise((resolvePromise, rejectPromise) => {
107
- // Spawn Node.js process with minimal environment
108
- // Use process.execPath to ensure we use the same node binary as the parent process
109
- this.child = spawn(process.execPath, [scriptPath], {
109
+ // Determine if we need tsx (for .ts/.mts files that import TS modules)
110
+ // Use tsx for .mjs files too since they might import from TS source
111
+ const needsTsx = scriptPath.match(/\.(mjs|ts|mts)$/);
112
+ const execCommand = needsTsx ? 'tsx' : process.execPath;
113
+ const execArgs = needsTsx ? [scriptPath] : [scriptPath];
114
+
115
+ // Spawn process with TypeScript support if needed
116
+ this.child = spawn(execCommand, execArgs, {
110
117
  env: {
118
+ ...process.env, // Inherit all env vars for tsx to work properly
111
119
  NODE_ENV: process.env.NODE_ENV || 'development',
112
- DATABASE_URI: process.env.DATABASE_URI,
113
- DATABASE_URL: process.env.DATABASE_URL,
120
+ DATABASE_URI: databaseUrl || process.env.DATABASE_URI,
121
+ DATABASE_URL: databaseUrl || process.env.DATABASE_URL,
114
122
  PAYLOAD_PUBLIC_SERVER_URL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
115
123
  NEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL,
116
124
  API_URL: process.env.API_URL,
125
+ PAYLOAD_SECRET: process.env.PAYLOAD_SECRET || 'dev-secret-key-change-in-production',
117
126
  },
118
- stdio: ['ignore', 'pipe', 'pipe'], // stdin: ignore, stdout: pipe, stderr: pipe
119
- cwd: resolve(scriptPath, '..'), // Run from scripts directory
127
+ stdio: ['pipe', 'pipe', 'pipe'], // stdin: pipe, stdout: pipe, stderr: pipe
128
+ cwd: projectRoot || resolve(scriptPath, '..'), // Run from project root (or scripts directory if not provided)
120
129
  });
121
130
 
131
+ // Auto-answer interactive prompts (like Drizzle's schema push confirmation)
132
+ // Send 'y\n' every 500ms to handle any prompts that appear
133
+ const stdinInterval = setInterval(() => {
134
+ if (this.child && this.child.stdin && !this.child.stdin.destroyed) {
135
+ try {
136
+ this.child.stdin.write('y\n');
137
+ } catch (err) {
138
+ // Ignore errors - stdin might be closed
139
+ }
140
+ }
141
+ }, 500);
142
+
143
+ // Clear interval when process exits
144
+ const cleanupInterval = () => {
145
+ clearInterval(stdinInterval);
146
+ // Close stdin to signal EOF
147
+ if (this.child && this.child.stdin && !this.child.stdin.destroyed) {
148
+ try {
149
+ this.child.stdin.end();
150
+ } catch (err) {
151
+ // Ignore errors
152
+ }
153
+ }
154
+ };
155
+
122
156
  // Handle stdout
123
157
  this.child.stdout?.on('data', (data: Buffer) => {
124
158
  const text = data.toString();
@@ -165,6 +199,8 @@ export class SeedExecutor {
165
199
 
166
200
  // Handle process exit
167
201
  this.child.on('exit', (code, signal) => {
202
+ cleanupInterval(); // Clean up stdin interval
203
+
168
204
  const exitCode = code ?? (signal ? 1 : 0);
169
205
  const duration = Date.now() - this.startTime;
170
206
 
@@ -188,6 +224,8 @@ export class SeedExecutor {
188
224
 
189
225
  // Handle spawn errors
190
226
  this.child.on('error', (err: Error) => {
227
+ cleanupInterval(); // Clean up stdin interval
228
+
191
229
  sendSSEEvent(res, {
192
230
  type: 'error',
193
231
  data: err.message,
@@ -199,6 +237,7 @@ export class SeedExecutor {
199
237
 
200
238
  // Handle SSE connection close - terminate child process
201
239
  res.on('close', () => {
240
+ cleanupInterval(); // Clean up stdin interval
202
241
  if (this.child && !this.child.killed) {
203
242
  this.child.kill('SIGTERM');
204
243
  }