@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
@@ -22,6 +22,8 @@ import type {
22
22
  } from './types.js';
23
23
  import type { AuthenticatedRequest } from '../auth/types.js';
24
24
  import { getUserByEmail, getUserById, getUsersByIds } from '../users/users-plugin.js';
25
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
26
+ import { postgresBanStore } from './stores/index.js';
25
27
 
26
28
  // Store instance for helper access
27
29
  let currentStore: BanStore | null = null;
@@ -29,17 +31,18 @@ let banCleanupInterval: NodeJS.Timeout | null = null;
29
31
  let pluginConfig: BansPluginConfig | null = null;
30
32
 
31
33
  /**
32
- * Create the Bans plugin
34
+ * Create the Bans plugin with smart defaults
35
+ *
36
+ * Config is optional - plugin will use defaults and get dependencies from registry.
37
+ * Gracefully handles missing dependencies with clear log messages.
33
38
  */
34
- export function createBansPlugin(config: BansPluginConfig): Plugin {
35
- const debug = config.debug || false;
36
- // Routes are mounted under /api by the control panel, so don't include /api in prefix
37
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /bans prefix automatically
38
- const apiEnabled = config.api?.enabled !== false;
39
-
40
- function log(message: string, data?: Record<string, unknown>) {
41
- if (debug) {
42
- console.log(`[BansPlugin] ${message}`, data || '');
39
+ export function createBansPlugin(config: Partial<BansPluginConfig> = {}): Plugin {
40
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
41
+ const prefix = '[BansPlugin]';
42
+ if (isError) {
43
+ console.error(`${prefix} ${message}`, data || '');
44
+ } else if (config.debug) {
45
+ console.log(`${prefix} ${message}`, data || '');
43
46
  }
44
47
  }
45
48
 
@@ -49,26 +52,68 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
49
52
  version: '1.0.0',
50
53
 
51
54
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
52
- log('Starting bans plugin');
55
+ const logger = registry.getLogger('bans');
53
56
 
54
57
  // Check for users plugin dependency
55
58
  if (!registry.hasPlugin('users')) {
56
- throw new Error('Bans plugin requires Users plugin to be loaded first');
59
+ logger.warn('Users plugin not loaded! Bans plugin disabled.');
60
+ registry.registerHealthCheck({
61
+ name: 'bans-store',
62
+ type: 'custom',
63
+ check: async () => ({
64
+ healthy: false,
65
+ details: {
66
+ error: 'Users plugin not available',
67
+ state: 'disabled',
68
+ },
69
+ }),
70
+ });
71
+ return;
72
+ }
73
+
74
+ // Check for postgres in registry
75
+ if (!hasPostgres()) {
76
+ logger.warn('No Database! Bans plugin disabled.');
77
+ registry.registerHealthCheck({
78
+ name: 'bans-store',
79
+ type: 'custom',
80
+ check: async () => ({
81
+ healthy: false,
82
+ details: {
83
+ error: 'PostgreSQL not available',
84
+ state: 'disabled',
85
+ },
86
+ }),
87
+ });
88
+ return;
57
89
  }
58
90
 
91
+ // Smart defaults - get dependencies from registry
92
+ const store = config.store ?? postgresBanStore({
93
+ pool: () => getPostgres().getPool(),
94
+ autoCreateTables: true,
95
+ });
96
+
97
+ const debug = config.debug ?? false;
98
+ const apiPrefix = config.api?.prefix ?? '/bans';
99
+ const apiEnabled = config.api?.enabled ?? true;
100
+ const supportTemporary = config.supportTemporary ?? true;
101
+
102
+ log('Starting bans plugin');
103
+
59
104
  // Initialize the store (creates tables if needed)
60
- await config.store.initialize();
105
+ await store.initialize();
61
106
  log('Bans plugin migrations complete');
62
107
 
63
108
  // Store references for helper access
64
- currentStore = config.store;
65
- pluginConfig = config;
109
+ currentStore = store;
110
+ pluginConfig = { ...config, store, debug, supportTemporary };
66
111
 
67
112
  // Start ban cleanup interval if temporary bans are supported
68
- if (config.supportTemporary) {
113
+ if (supportTemporary) {
69
114
  banCleanupInterval = setInterval(async () => {
70
115
  try {
71
- const cleaned = await config.store.cleanupExpiredBans();
116
+ const cleaned = await store.cleanupExpiredBans();
72
117
  if (cleaned > 0) {
73
118
  log('Cleaned up expired bans', { count: cleaned });
74
119
  }
@@ -85,7 +130,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
85
130
  check: async () => {
86
131
  try {
87
132
  // Simple health check - list with limit 0
88
- await config.store.listActiveBans({ limit: 0 });
133
+ await store.listActiveBans({ limit: 0 });
89
134
  return { healthy: true };
90
135
  } catch {
91
136
  return { healthy: false };
@@ -105,7 +150,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
105
150
  const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
106
151
  const offset = parseInt(req.query.offset as string) || 0;
107
152
 
108
- const result = await config.store.listActiveBans({ limit, offset });
153
+ const result = await store.listActiveBans({ limit, offset });
109
154
 
110
155
  // Batch fetch users for all bans (single query instead of N queries)
111
156
  const userIds = [...new Set(result.bans.map((ban) => ban.user_id))];
@@ -133,7 +178,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
133
178
  pluginId: 'bans',
134
179
  handler: async (req: Request, res: Response) => {
135
180
  try {
136
- const ban = await config.store.getActiveBan(req.params.userId);
181
+ const ban = await store.getActiveBan(req.params.userId);
137
182
  res.json({
138
183
  isBanned: ban !== null,
139
184
  ban,
@@ -152,7 +197,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
152
197
  pluginId: 'bans',
153
198
  handler: async (req: Request, res: Response) => {
154
199
  try {
155
- const bans = await config.store.listBans(req.params.userId);
200
+ const bans = await store.listBans(req.params.userId);
156
201
  res.json({ bans });
157
202
  } catch (error) {
158
203
  console.error('[BansPlugin] Get ban history error:', error);
@@ -185,7 +230,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
185
230
  return res.status(404).json({ error: 'User not found' });
186
231
  }
187
232
 
188
- const ban = await config.store.createBan(input);
233
+ const ban = await store.createBan(input);
189
234
 
190
235
  // Call onBan callback if provided
191
236
  if (config.callbacks?.onBan) {
@@ -227,7 +272,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
227
272
  note: req.body?.note,
228
273
  };
229
274
 
230
- const removed = await config.store.removeBan(input);
275
+ const removed = await store.removeBan(input);
231
276
  if (!removed) {
232
277
  return res.status(404).json({ error: 'No active ban found' });
233
278
  }
@@ -292,7 +337,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
292
337
  metadata: req.body.metadata,
293
338
  };
294
339
 
295
- const ban = await config.store.createBan(input);
340
+ const ban = await store.createBan(input);
296
341
 
297
342
  // Call onBan callback if provided
298
343
  if (config.callbacks?.onBan) {
@@ -335,7 +380,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
335
380
  note: req.body?.note,
336
381
  };
337
382
 
338
- const removed = await config.store.removeBan(input);
383
+ const removed = await store.removeBan(input);
339
384
  if (!removed) {
340
385
  return res.status(404).json({ error: 'No active ban found' });
341
386
  }
@@ -370,7 +415,7 @@ export function createBansPlugin(config: BansPluginConfig): Plugin {
370
415
  banCleanupInterval = null;
371
416
  }
372
417
 
373
- await config.store.shutdown();
418
+ if (currentStore) { await currentStore.shutdown(); };
374
419
  currentStore = null;
375
420
  pluginConfig = null;
376
421
 
@@ -30,8 +30,6 @@ export type {
30
30
  // Stores
31
31
  export { postgresBanStore, inMemoryBanStore } from './stores/index.js';
32
32
 
33
- // UI Components
34
- export { BansStatusWidget } from './BansStatusWidget.js';
35
- export type { BansStatusWidgetProps } from './BansStatusWidget.js';
36
- export { BansManagementPage } from './BansManagementPage.js';
37
- export type { BansManagementPageProps } from './BansManagementPage.js';
33
+ // UI Components are exported from main package index (@qwickapps/server)
34
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
35
+
@@ -107,22 +107,29 @@ export interface BanCallbacks {
107
107
 
108
108
  /**
109
109
  * Bans plugin configuration
110
+ *
111
+ * All properties are optional - plugin will use smart defaults:
112
+ * - store: Postgres ban store using registry's postgres instance
113
+ * - supportTemporary: true
114
+ * - api.prefix: '/bans'
115
+ * - api.enabled: true
116
+ * - debug: false
110
117
  */
111
118
  export interface BansPluginConfig {
112
- /** Ban storage backend */
113
- store: BanStore;
114
- /** Support temporary bans (with expiration) */
119
+ /** Ban storage backend (default: postgres ban store from registry) */
120
+ store?: BanStore;
121
+ /** Support temporary bans (with expiration) (default: true) */
115
122
  supportTemporary?: boolean;
116
123
  /** Callbacks */
117
124
  callbacks?: BanCallbacks;
118
125
  /** API configuration */
119
126
  api?: {
120
- /** API route prefix (default: '/api/bans') */
127
+ /** API route prefix (default: '/bans') */
121
128
  prefix?: string;
122
- /** Enable API endpoints */
129
+ /** Enable API endpoints (default: true) */
123
130
  enabled?: boolean;
124
131
  };
125
- /** Enable debug logging */
132
+ /** Enable debug logging (default: false) */
126
133
  debug?: boolean;
127
134
  }
128
135
 
@@ -28,6 +28,9 @@ import {
28
28
  isTokenExpired,
29
29
  getTokenExpiration,
30
30
  } from './token-utils.js';
31
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
32
+ import { postgresDeviceStore } from './stores/index.js';
33
+ import { computeDeviceAdapter } from './adapters/index.js';
31
34
 
32
35
  // Store instances for helper access
33
36
  let currentStore: DeviceStore | null = null;
@@ -35,16 +38,18 @@ let currentAdapter: DeviceAdapter | null = null;
35
38
  let currentConfig: DevicesPluginConfig | null = null;
36
39
 
37
40
  /**
38
- * Create the Devices plugin
41
+ * Create the Devices 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.
39
45
  */
40
- export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
41
- const debug = config.debug || false;
42
- const defaultTokenValidityDays = config.defaultTokenValidityDays || 90;
43
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /devices prefix automatically
44
-
45
- function log(message: string, data?: Record<string, unknown>) {
46
- if (debug) {
47
- console.log(`[DevicesPlugin] ${message}`, data || '');
46
+ export function createDevicesPlugin(config: Partial<DevicesPluginConfig> = {}): Plugin {
47
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
48
+ const prefix = '[DevicesPlugin]';
49
+ if (isError) {
50
+ console.error(`${prefix} ${message}`, data || '');
51
+ } else if (config.debug) {
52
+ console.log(`${prefix} ${message}`, data || '');
48
53
  }
49
54
  }
50
55
 
@@ -54,16 +59,46 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
54
59
  version: '1.0.0',
55
60
 
56
61
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
57
- log('Starting devices plugin', { adapter: config.adapter.name });
62
+ const logger = registry.getLogger('devices');
63
+
64
+ // Check for postgres in registry
65
+ if (!hasPostgres()) {
66
+ logger.warn('No Database! Devices plugin disabled.');
67
+ registry.registerHealthCheck({
68
+ name: 'devices-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 ?? postgresDeviceStore({
83
+ pool: () => getPostgres().getPool(),
84
+ autoCreateTables: true,
85
+ });
86
+
87
+ const adapter = config.adapter ?? computeDeviceAdapter();
88
+ const debug = config.debug ?? false;
89
+ const defaultTokenValidityDays = config.defaultTokenValidityDays ?? 90;
90
+ const apiPrefix = config.api?.prefix ?? '/devices';
91
+
92
+ log('Starting devices plugin', { adapter: adapter.name });
58
93
 
59
94
  // Initialize the store (creates tables if needed)
60
- await config.store.initialize();
95
+ await store.initialize();
61
96
  log('Devices plugin migrations complete');
62
97
 
63
98
  // Store references for helper access
64
- currentStore = config.store;
65
- currentAdapter = config.adapter;
66
- currentConfig = config;
99
+ currentStore = store;
100
+ currentAdapter = adapter;
101
+ currentConfig = { ...config, store, adapter, debug, defaultTokenValidityDays };
67
102
 
68
103
  // Register health check
69
104
  registry.registerHealthCheck({
@@ -71,12 +106,12 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
71
106
  type: 'custom',
72
107
  check: async () => {
73
108
  try {
74
- await config.store.search({ limit: 1 });
109
+ await store.search({ limit: 1 });
75
110
  return {
76
111
  healthy: true,
77
112
  details: {
78
- adapter: config.adapter.name,
79
- tokenPrefix: config.adapter.tokenPrefix,
113
+ adapter: adapter.name,
114
+ tokenPrefix: adapter.tokenPrefix,
80
115
  },
81
116
  };
82
117
  } catch {
@@ -106,7 +141,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
106
141
  sortOrder: (req.query.sortOrder as DeviceSearchParams['sortOrder']) || 'desc',
107
142
  };
108
143
 
109
- const result = await config.store.search(params);
144
+ const result = await store.search(params);
110
145
  res.json(result);
111
146
  } catch (error) {
112
147
  console.error('[DevicesPlugin] Search error:', error);
@@ -122,7 +157,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
122
157
  pluginId: 'devices',
123
158
  handler: async (req: Request, res: Response) => {
124
159
  try {
125
- const device = await config.store.getById(req.params.id);
160
+ const device = await store.getById(req.params.id);
126
161
  if (!device) {
127
162
  return res.status(404).json({ error: 'Device not found' });
128
163
  }
@@ -150,7 +185,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
150
185
  };
151
186
 
152
187
  // Validate using adapter
153
- const validation = config.adapter.validateDeviceInput(input);
188
+ const validation = adapter.validateDeviceInput(input);
154
189
  if (!validation.valid) {
155
190
  return res.status(400).json({
156
191
  error: 'Validation failed',
@@ -181,7 +216,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
181
216
  metadata: req.body.metadata,
182
217
  };
183
218
 
184
- const device = await config.store.update(req.params.id, input);
219
+ const device = await store.update(req.params.id, input);
185
220
  if (!device) {
186
221
  return res.status(404).json({ error: 'Device not found' });
187
222
  }
@@ -200,19 +235,19 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
200
235
  pluginId: 'devices',
201
236
  handler: async (req: Request, res: Response) => {
202
237
  try {
203
- const device = await config.store.getById(req.params.id);
238
+ const device = await store.getById(req.params.id);
204
239
  if (!device) {
205
240
  return res.status(404).json({ error: 'Device not found' });
206
241
  }
207
242
 
208
- const deleted = await config.store.delete(req.params.id);
243
+ const deleted = await store.delete(req.params.id);
209
244
  if (!deleted) {
210
245
  return res.status(404).json({ error: 'Device not found' });
211
246
  }
212
247
 
213
248
  // Call adapter hook
214
- if (config.adapter.onDeviceDeleted) {
215
- await config.adapter.onDeviceDeleted(device);
249
+ if (adapter.onDeviceDeleted) {
250
+ await adapter.onDeviceDeleted(device);
216
251
  }
217
252
 
218
253
  res.status(204).send();
@@ -230,7 +265,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
230
265
  pluginId: 'devices',
231
266
  handler: async (req: Request, res: Response) => {
232
267
  try {
233
- const device = await config.store.getById(req.params.id);
268
+ const device = await store.getById(req.params.id);
234
269
  if (!device) {
235
270
  return res.status(404).json({ error: 'Device not found' });
236
271
  }
@@ -295,7 +330,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
295
330
 
296
331
  async onStop(): Promise<void> {
297
332
  log('Stopping devices plugin');
298
- await config.store.shutdown();
333
+ if (currentStore) { await currentStore.shutdown(); };
299
334
  currentStore = null;
300
335
  currentAdapter = null;
301
336
  currentConfig = null;
@@ -68,8 +68,6 @@ export {
68
68
  } from './token-utils.js';
69
69
  export type { DeviceTokenPair } from './token-utils.js';
70
70
 
71
- // UI Components
72
- export { DevicesStatusWidget } from './DevicesStatusWidget.js';
73
- export type { DevicesStatusWidgetProps } from './DevicesStatusWidget.js';
74
- export { DevicesManagementPage } from './DevicesManagementPage.js';
75
- export type { DevicesManagementPageProps } from './DevicesManagementPage.js';
71
+ // UI Components are exported from main package index (@qwickapps/server)
72
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
73
+
@@ -148,6 +148,83 @@ export function createDiagnosticsPlugin(config: DiagnosticsPluginConfig = {}): P
148
148
  },
149
149
  });
150
150
 
151
+ // Register maintenance listing endpoint (GET /diagnostics/maintenance)
152
+ registry.addRoute({
153
+ method: 'get',
154
+ path: '/maintenance',
155
+ pluginId: 'diagnostics',
156
+ handler: (_req: Request, res: Response) => {
157
+ try {
158
+ const pluginsNeedingMaintenance = registry.getPluginsNeedingMaintenance();
159
+
160
+ res.json({
161
+ timestamp: new Date().toISOString(),
162
+ count: pluginsNeedingMaintenance.length,
163
+ plugins: pluginsNeedingMaintenance.map(info => ({
164
+ pluginId: info.pluginId,
165
+ error: info.error,
166
+ recommendedAction: info.recommendedAction,
167
+ actions: info.actions.map(action => ({
168
+ id: action.id,
169
+ name: action.name,
170
+ description: action.description,
171
+ destructive: action.destructive,
172
+ })),
173
+ })),
174
+ });
175
+ } catch (error) {
176
+ res.status(500).json({
177
+ error: 'Failed to retrieve maintenance info',
178
+ message: error instanceof Error ? error.message : String(error),
179
+ });
180
+ }
181
+ },
182
+ });
183
+
184
+ // Register maintenance action endpoint (POST /diagnostics/maintenance/:pluginId/:actionId)
185
+ registry.addRoute({
186
+ method: 'post',
187
+ path: '/maintenance/:pluginId/:actionId',
188
+ pluginId: 'diagnostics',
189
+ handler: async (req: Request, res: Response) => {
190
+ try {
191
+ const { pluginId, actionId } = req.params;
192
+
193
+ if (!pluginId || !actionId) {
194
+ return res.status(400).json({
195
+ error: 'Missing required parameters',
196
+ message: 'Both pluginId and actionId are required',
197
+ });
198
+ }
199
+
200
+ logger.info(`Executing maintenance action ${actionId} for plugin ${pluginId}`);
201
+
202
+ const result = await registry.runMaintenanceAction(pluginId, actionId);
203
+
204
+ if (result.success) {
205
+ res.json({
206
+ success: true,
207
+ message: result.message || 'Maintenance action completed successfully',
208
+ pluginId,
209
+ actionId,
210
+ });
211
+ } else {
212
+ res.status(500).json({
213
+ success: false,
214
+ error: result.error || 'Maintenance action failed',
215
+ pluginId,
216
+ actionId,
217
+ });
218
+ }
219
+ } catch (error) {
220
+ res.status(500).json({
221
+ error: 'Failed to execute maintenance action',
222
+ message: error instanceof Error ? error.message : String(error),
223
+ });
224
+ }
225
+ },
226
+ });
227
+
151
228
  logger.debug('Diagnostics plugin initialized');
152
229
  },
153
230