@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
@@ -17,6 +17,8 @@ import type {
17
17
  UsageStatus,
18
18
  UsageSummary,
19
19
  } from './types.js';
20
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
21
+ import { postgresUsageStore } from './stores/index.js';
20
22
 
21
23
  // Import subscription helpers if available
22
24
  let getFeatureLimitFn: ((userId: string, featureCode: string) => Promise<number | null>) | null = null;
@@ -44,15 +46,18 @@ function getTomorrowMidnight(): Date {
44
46
  }
45
47
 
46
48
  /**
47
- * Create the Usage plugin
49
+ * Create the Usage plugin with smart defaults
50
+ *
51
+ * Config is optional - plugin will use defaults and get dependencies from registry.
52
+ * Gracefully handles missing dependencies with clear log messages.
48
53
  */
49
- export function createUsagePlugin(config: UsagePluginConfig): Plugin {
50
- const debug = config.debug || false;
51
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /usage prefix automatically
52
-
53
- function log(message: string, data?: Record<string, unknown>) {
54
- if (debug) {
55
- console.log(`[UsagePlugin] ${message}`, data || '');
54
+ export function createUsagePlugin(config: Partial<UsagePluginConfig> = {}): Plugin {
55
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
56
+ const prefix = '[UsagePlugin]';
57
+ if (isError) {
58
+ console.error(`${prefix} ${message}`, data || '');
59
+ } else if (config.debug) {
60
+ console.log(`${prefix} ${message}`, data || '');
56
61
  }
57
62
  }
58
63
 
@@ -62,15 +67,48 @@ export function createUsagePlugin(config: UsagePluginConfig): Plugin {
62
67
  version: '1.0.0',
63
68
 
64
69
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
70
+ const logger = registry.getLogger('usage');
71
+
72
+ // Check for postgres in registry
73
+ if (!hasPostgres()) {
74
+ logger.warn('No Database! Usage plugin disabled.');
75
+ registry.registerHealthCheck({
76
+ name: 'usage-store',
77
+ type: 'custom',
78
+ check: async () => ({
79
+ healthy: false,
80
+ details: {
81
+ error: 'PostgreSQL not available',
82
+ state: 'disabled',
83
+ },
84
+ }),
85
+ });
86
+ return;
87
+ }
88
+
89
+ // Smart defaults - get dependencies from registry
90
+ const store = config.store ?? postgresUsageStore({
91
+ pool: () => getPostgres().getPool(),
92
+ autoCreateTables: true,
93
+ });
94
+
95
+ const debug = config.debug ?? false;
96
+ const apiPrefix = config.api?.prefix ?? '/'; // Framework adds /usage prefix automatically
97
+ const apiEnabled = config.api?.enabled ?? true;
98
+ const dailyRetentionDays = config.cleanup?.dailyRetentionDays ?? 90;
99
+ const monthlyRetentionMonths = config.cleanup?.monthlyRetentionMonths ?? 24;
100
+ const runOnStartup = config.cleanup?.runOnStartup ?? false;
101
+ const cleanupIntervalHours = config.cleanup?.cleanupIntervalHours ?? 0;
102
+
65
103
  log('Starting usage plugin');
66
104
 
67
105
  // Initialize the store (creates tables if needed)
68
- await config.store.initialize();
106
+ await store.initialize();
69
107
  log('Usage plugin migrations complete');
70
108
 
71
109
  // Store references for helper access
72
- currentStore = config.store;
73
- currentConfig = config;
110
+ currentStore = store;
111
+ currentConfig = { ...config, store, debug };
74
112
 
75
113
  // Try to get the feature limit function from subscriptions plugin
76
114
  try {
@@ -96,25 +134,19 @@ export function createUsagePlugin(config: UsagePluginConfig): Plugin {
96
134
  });
97
135
 
98
136
  // Run cleanup on startup if configured
99
- if (config.cleanup?.runOnStartup) {
100
- const dailyDays = config.cleanup.dailyRetentionDays || 90;
101
- const monthlyMonths = config.cleanup.monthlyRetentionMonths || 24;
102
-
103
- const dailyDeleted = await config.store.cleanupOldDaily(dailyDays);
104
- const monthlyDeleted = await config.store.cleanupOldMonthly(monthlyMonths);
137
+ if (runOnStartup) {
138
+ const dailyDeleted = await store.cleanupOldDaily(dailyRetentionDays);
139
+ const monthlyDeleted = await store.cleanupOldMonthly(monthlyRetentionMonths);
105
140
  log('Startup cleanup complete', { dailyDeleted, monthlyDeleted });
106
141
  }
107
142
 
108
143
  // Set up periodic cleanup if configured
109
- if (config.cleanup?.cleanupIntervalHours && config.cleanup.cleanupIntervalHours > 0) {
110
- const intervalMs = config.cleanup.cleanupIntervalHours * 60 * 60 * 1000;
144
+ if (cleanupIntervalHours > 0) {
145
+ const intervalMs = cleanupIntervalHours * 60 * 60 * 1000;
111
146
  cleanupIntervalId = setInterval(async () => {
112
147
  try {
113
- const dailyDays = config.cleanup?.dailyRetentionDays || 90;
114
- const monthlyMonths = config.cleanup?.monthlyRetentionMonths || 24;
115
-
116
- const dailyDeleted = await config.store.cleanupOldDaily(dailyDays);
117
- const monthlyDeleted = await config.store.cleanupOldMonthly(monthlyMonths);
148
+ const dailyDeleted = await store.cleanupOldDaily(dailyRetentionDays);
149
+ const monthlyDeleted = await store.cleanupOldMonthly(monthlyRetentionMonths);
118
150
  log('Periodic cleanup complete', { dailyDeleted, monthlyDeleted });
119
151
  } catch (error) {
120
152
  console.error('[UsagePlugin] Cleanup error:', error);
@@ -123,7 +155,7 @@ export function createUsagePlugin(config: UsagePluginConfig): Plugin {
123
155
  }
124
156
 
125
157
  // Add API routes if enabled
126
- if (config.api?.enabled !== false) {
158
+ if (apiEnabled) {
127
159
  // Get daily usage summary
128
160
  registry.addRoute({
129
161
  method: 'get',
@@ -211,7 +243,9 @@ export function createUsagePlugin(config: UsagePluginConfig): Plugin {
211
243
  cleanupIntervalId = null;
212
244
  }
213
245
 
214
- await config.store.shutdown();
246
+ if (currentStore) {
247
+ await currentStore.shutdown();
248
+ }
215
249
  currentStore = null;
216
250
  currentConfig = null;
217
251
  log('Usage plugin stopped');
@@ -269,10 +269,15 @@ export interface UsersUiConfig {
269
269
 
270
270
  /**
271
271
  * Users plugin configuration
272
+ *
273
+ * All properties are optional - plugin will use smart defaults:
274
+ * - store: Postgres user store using registry's postgres instance
275
+ * - api.prefix: '/users'
276
+ * - debug: false
272
277
  */
273
278
  export interface UsersPluginConfig {
274
- /** User storage backend */
275
- store: UserStore;
279
+ /** User storage backend (default: postgres user store from registry) */
280
+ store?: UserStore;
276
281
  /** Sync configuration (optional) */
277
282
  sync?: UserSyncConfig;
278
283
  /** API configuration */
@@ -30,22 +30,26 @@ import { getEntitlements } from '../entitlements/entitlements-plugin.js';
30
30
  import { getPreferences } from '../preferences/preferences-plugin.js';
31
31
  import { getActiveBan } from '../bans/bans-plugin.js';
32
32
  import { autoCreateUserTenant } from '../tenants/index.js';
33
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
34
+ import { postgresUserStore } from './stores/index.js';
33
35
 
34
36
  // Store instance for helper access
35
37
  let currentStore: UserStore | null = null;
36
38
  let currentRegistry: PluginRegistry | null = null;
37
39
 
38
40
  /**
39
- * Create the Users plugin
41
+ * Create the Users plugin with smart defaults
42
+ *
43
+ * Config is optional - plugin will use defaults and get dependencies from registry.
44
+ * Gracefully handles missing dependencies with clear log messages.
40
45
  */
41
- export function createUsersPlugin(config: UsersPluginConfig): Plugin {
42
- const debug = config.debug || false;
43
- // Routes are mounted under /api by the control panel, so don't include /api in prefix
44
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /users prefix automatically
45
-
46
- function log(message: string, data?: Record<string, unknown>) {
47
- if (debug) {
48
- console.log(`[UsersPlugin] ${message}`, data || '');
46
+ export function createUsersPlugin(config: Partial<UsersPluginConfig> = {}): Plugin {
47
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
48
+ const prefix = '[UsersPlugin]';
49
+ if (isError) {
50
+ console.error(`${prefix} ${message}`, data || '');
51
+ } else if (config.debug) {
52
+ console.log(`${prefix} ${message}`, data || '');
49
53
  }
50
54
  }
51
55
 
@@ -55,14 +59,42 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
55
59
  version: '1.0.0',
56
60
 
57
61
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
62
+ const logger = registry.getLogger('users');
63
+
64
+ // Check for postgres in registry
65
+ if (!hasPostgres()) {
66
+ logger.warn('No Database! Users plugin disabled.');
67
+ registry.registerHealthCheck({
68
+ name: 'users-store',
69
+ type: 'custom',
70
+ check: async () => ({
71
+ healthy: false,
72
+ details: {
73
+ error: 'PostgreSQL not available',
74
+ state: 'disabled',
75
+ },
76
+ }),
77
+ });
78
+ return;
79
+ }
80
+
81
+ // Smart defaults - get dependencies from registry
82
+ const store = config.store ?? postgresUserStore({
83
+ pool: () => getPostgres().getPool(),
84
+ autoCreateTables: true,
85
+ });
86
+
87
+ const debug = config.debug ?? false;
88
+ const apiPrefix = config.api?.prefix ?? '/users';
89
+
58
90
  log('Starting users plugin');
59
91
 
60
92
  // Initialize the store (creates tables if needed)
61
- await config.store.initialize();
93
+ await store.initialize();
62
94
  log('Users plugin migrations complete');
63
95
 
64
96
  // Store references for helper access
65
- currentStore = config.store;
97
+ currentStore = store;
66
98
  currentRegistry = registry;
67
99
 
68
100
  // Register health check
@@ -72,7 +104,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
72
104
  check: async () => {
73
105
  try {
74
106
  // Simple health check - try to search with limit 1
75
- await config.store.search({ limit: 1 });
107
+ await store.search({ limit: 1 });
76
108
  return { healthy: true };
77
109
  } catch {
78
110
  return { healthy: false };
@@ -98,7 +130,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
98
130
  sortOrder: (req.query.sortOrder as UserSearchParams['sortOrder']) || 'desc',
99
131
  };
100
132
 
101
- const result = await config.store.search(params);
133
+ const result = await store.search(params);
102
134
  res.json(result);
103
135
  } catch (error) {
104
136
  console.error('[UsersPlugin] Search error:', error);
@@ -114,7 +146,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
114
146
  pluginId: 'users',
115
147
  handler: async (req: Request, res: Response) => {
116
148
  try {
117
- const user = await config.store.getById(req.params.id);
149
+ const user = await store.getById(req.params.id);
118
150
  if (!user) {
119
151
  return res.status(404).json({ error: 'User not found' });
120
152
  }
@@ -147,12 +179,12 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
147
179
  }
148
180
 
149
181
  // Check if user already exists
150
- const existing = await config.store.getByEmail(input.email);
182
+ const existing = await store.getByEmail(input.email);
151
183
  if (existing) {
152
184
  return res.status(409).json({ error: 'User with this email already exists' });
153
185
  }
154
186
 
155
- const user = await config.store.create(input);
187
+ const user = await store.create(input);
156
188
 
157
189
  // Auto-create personal tenant if tenants plugin available
158
190
  if (registry.hasPlugin('tenants')) {
@@ -187,7 +219,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
187
219
  }
188
220
 
189
221
  // Check if user already exists
190
- const existing = await config.store.getByEmail(email);
222
+ const existing = await store.getByEmail(email);
191
223
  if (existing) {
192
224
  return res.status(409).json({ error: 'User with this email already exists' });
193
225
  }
@@ -200,7 +232,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
200
232
  expiresAt.setDate(expiresAt.getDate() + 7);
201
233
 
202
234
  // Create user with invited status
203
- const user = await config.store.create({
235
+ const user = await store.create({
204
236
  email,
205
237
  name,
206
238
  metadata: role ? { role } : undefined,
@@ -251,7 +283,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
251
283
  return res.status(400).json({ error: 'Invitation token is required' });
252
284
  }
253
285
 
254
- const user = await config.store.acceptInvitation(token);
286
+ const user = await store.acceptInvitation(token);
255
287
 
256
288
  if (!user) {
257
289
  return res.status(404).json({
@@ -290,7 +322,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
290
322
  metadata: req.body.metadata,
291
323
  };
292
324
 
293
- const user = await config.store.update(req.params.id, input);
325
+ const user = await store.update(req.params.id, input);
294
326
  if (!user) {
295
327
  return res.status(404).json({ error: 'User not found' });
296
328
  }
@@ -309,7 +341,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
309
341
  pluginId: 'users',
310
342
  handler: async (req: Request, res: Response) => {
311
343
  try {
312
- const deleted = await config.store.delete(req.params.id);
344
+ const deleted = await store.delete(req.params.id);
313
345
  if (!deleted) {
314
346
  return res.status(404).json({ error: 'User not found' });
315
347
  }
@@ -328,7 +360,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
328
360
  pluginId: 'users',
329
361
  handler: async (req: Request, res: Response) => {
330
362
  try {
331
- const user = await config.store.getById(req.params.id);
363
+ const user = await store.getById(req.params.id);
332
364
  if (!user) {
333
365
  return res.status(404).json({ error: 'User not found' });
334
366
  }
@@ -391,7 +423,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
391
423
  pluginId: 'users',
392
424
  handler: async (_req: Request, res: Response) => {
393
425
  try {
394
- const allUsers = await config.store.search({ limit: 10000 });
426
+ const allUsers = await store.search({ limit: 10000 });
395
427
  const now = Date.now();
396
428
  const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1000;
397
429
 
@@ -419,7 +451,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
419
451
 
420
452
  async onStop(): Promise<void> {
421
453
  log('Stopping users plugin');
422
- await config.store.shutdown();
454
+ if (currentStore) { await currentStore.shutdown(); };
423
455
  currentStore = null;
424
456
  currentRegistry = null;
425
457
  log('Users plugin stopped');
package/ui/src/App.tsx CHANGED
@@ -71,9 +71,9 @@ declare global {
71
71
  */
72
72
  const basePath = window.__APP_BASE_PATH__ ?? '';
73
73
 
74
- // API routes are always at '/api' regardless of control panel mount path
75
- // The control panel might be mounted at /cpanel, but API is always at /api
76
- api.setBaseUrl('');
74
+ // Set API base URL to match the control panel mount path
75
+ // When proxied through a gateway at /cpanel, API calls need to go to /cpanel/api
76
+ api.setBaseUrl(basePath);
77
77
 
78
78
  // Footer content with QwickApps Server branding
79
79
  const footerContent = (
@@ -147,9 +147,10 @@ export function ControlPanelApp({
147
147
  // Combine built-in widget components with custom ones
148
148
  const allWidgetComponents = [...getBuiltInWidgetComponents(), ...widgetComponents];
149
149
 
150
- // Configure API base URL - API routes are always at '/api' regardless of control panel mount path
151
- // The control panel might be mounted at /cpanel, but API is always at /api (not /cpanel/api)
152
- const apiBasePath = '';
150
+ // Configure API base URL - read from injected __APP_BASE_PATH__ if available
151
+ // Server injects window.__APP_BASE_PATH__ when control panel is mounted at a base path
152
+ // Example: mounted at /cpanel → API calls go to /cpanel/api (not /api)
153
+ const apiBasePath = (window as any).__APP_BASE_PATH__ || '';
153
154
  api.setBaseUrl(apiBasePath);
154
155
 
155
156
  // Fetch version from API
@@ -21,6 +21,7 @@ import {
21
21
  ServiceControlWidget,
22
22
  EnvironmentConfigWidget,
23
23
  DatabaseOpsWidget,
24
+ DatabaseOperationsWidget,
24
25
  LogsMaintenanceWidget,
25
26
  CacheMaintenanceWidget,
26
27
  } from './widgets';
@@ -42,6 +43,7 @@ export const builtInWidgetComponents: Record<string, React.ComponentType> = {
42
43
  ServiceControlWidget: ServiceControlWidget,
43
44
  EnvironmentConfigWidget: EnvironmentConfigWidget,
44
45
  DatabaseOpsWidget: DatabaseOpsWidget,
46
+ DatabaseOperationsWidget: DatabaseOperationsWidget,
45
47
  LogsMaintenanceWidget: LogsMaintenanceWidget,
46
48
  CacheMaintenanceWidget: CacheMaintenanceWidget,
47
49
  PreferencesPage: PreferencesPage,
@@ -66,6 +68,7 @@ export function getBuiltInWidgetComponents(): WidgetComponent[] {
66
68
  { name: 'ServiceControlWidget', component: ServiceControlWidget },
67
69
  { name: 'EnvironmentConfigWidget', component: EnvironmentConfigWidget },
68
70
  { name: 'DatabaseOpsWidget', component: DatabaseOpsWidget },
71
+ { name: 'DatabaseOperationsWidget', component: DatabaseOperationsWidget },
69
72
  { name: 'LogsMaintenanceWidget', component: LogsMaintenanceWidget },
70
73
  { name: 'CacheMaintenanceWidget', component: CacheMaintenanceWidget },
71
74
  { name: 'PreferencesPage', component: PreferencesPage },
@@ -49,7 +49,11 @@ export function CMSMaintenanceWidget() {
49
49
 
50
50
  const fetchStatus = async () => {
51
51
  try {
52
- const response = await fetch('/api/cms/status');
52
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
53
+ const response = await fetch(`${basePath}/api/cms/status`);
54
+ if (!response.ok) {
55
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
56
+ }
53
57
  const data = await response.json();
54
58
  setStatus(data);
55
59
  } catch (err) {
@@ -59,7 +63,11 @@ export function CMSMaintenanceWidget() {
59
63
 
60
64
  const fetchSeeds = async () => {
61
65
  try {
62
- const response = await fetch('/api/cms/seeds');
66
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
67
+ const response = await fetch(`${basePath}/api/cms/seeds`);
68
+ if (!response.ok) {
69
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
70
+ }
63
71
  const data = await response.json();
64
72
  setSeeds(data.seeds || []);
65
73
  } catch (err) {
@@ -80,7 +88,8 @@ export function CMSMaintenanceWidget() {
80
88
  setError(null);
81
89
  setSuccess(null);
82
90
  try {
83
- const response = await fetch('/api/cms/restart', { method: 'POST' });
91
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
92
+ const response = await fetch(`${basePath}/api/cms/restart`, { method: 'POST' });
84
93
  const data = await response.json();
85
94
 
86
95
  if (response.ok) {
@@ -100,9 +109,13 @@ export function CMSMaintenanceWidget() {
100
109
  setSuccess(null);
101
110
 
102
111
  try {
103
- const response = await fetch(`/api/cms/seeds/${seedName}/execute`, {
112
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
113
+ const response = await fetch(`${basePath}/api/cms/seeds/${seedName}/execute`, {
104
114
  method: 'POST',
105
115
  });
116
+ if (!response.ok) {
117
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
118
+ }
106
119
  const data = await response.json();
107
120
 
108
121
  if (data.success) {
@@ -35,7 +35,11 @@ export function CMSStatusWidget() {
35
35
 
36
36
  const fetchStatus = async () => {
37
37
  try {
38
- const response = await fetch('/api/cms/status');
38
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
39
+ const response = await fetch(`${basePath}/api/cms/status`);
40
+ if (!response.ok) {
41
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
42
+ }
39
43
  const data = await response.json();
40
44
  setStatus(data);
41
45
  setError(null);
@@ -47,7 +47,8 @@ export function CacheMaintenanceWidget() {
47
47
  setLoading(true);
48
48
  setError(null);
49
49
  try {
50
- const response = await fetch('/api/cache:default/stats');
50
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
51
+ const response = await fetch(`${basePath}/api/cache:default/stats`);
51
52
  if (!response.ok) {
52
53
  if (response.status === 404) {
53
54
  throw new Error('Cache plugin not configured');
@@ -75,7 +76,8 @@ export function CacheMaintenanceWidget() {
75
76
  setSuccess(null);
76
77
 
77
78
  try {
78
- const response = await fetch('/api/cache:default/flush', {
79
+ const basePath = (window as any).__APP_BASE_PATH__ || '';
80
+ const response = await fetch(`${basePath}/api/cache:default/flush`, {
79
81
  method: 'POST',
80
82
  headers: { 'Content-Type': 'application/json' },
81
83
  });