@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
@@ -23,22 +23,25 @@ import {
23
23
  CreateApiKeySchema,
24
24
  UpdateApiKeySchema,
25
25
  } from './types.js';
26
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
27
+ import { postgresApiKeyStore } from './stores/index.js';
26
28
 
27
29
  // Store instance for helper access
28
30
  let currentStore: ApiKeyStore | null = null;
29
31
 
30
32
  /**
31
- * Create the API Keys plugin
33
+ * Create the API Keys plugin with smart defaults
34
+ *
35
+ * Config is optional - plugin will use defaults and get dependencies from registry.
36
+ * Gracefully handles missing dependencies with clear log messages.
32
37
  */
33
- export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
34
- const debug = config.debug || false;
35
- // Framework automatically prefixes routes with plugin slug, so use root path
36
- const apiPrefix = config.api?.prefix || '/';
37
- const apiEnabled = config.api?.enabled !== false;
38
-
39
- function log(message: string, data?: Record<string, unknown>) {
40
- if (debug) {
41
- console.log(`[ApiKeysPlugin] ${message}`, data || '');
38
+ export function createApiKeysPlugin(config: Partial<ApiKeysPluginConfig> = {}): Plugin {
39
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
40
+ const prefix = '[ApiKeysPlugin]';
41
+ if (isError) {
42
+ console.error(`${prefix} ${message}`, data || '');
43
+ } else if (config.debug) {
44
+ console.log(`${prefix} ${message}`, data || '');
42
45
  }
43
46
  }
44
47
 
@@ -48,15 +51,113 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
48
51
  version: '1.0.0',
49
52
 
50
53
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
51
- log('Starting API keys plugin');
54
+ const logger = registry.getLogger('api-keys');
52
55
 
53
56
  // Check for users plugin dependency
54
57
  if (!registry.hasPlugin('users')) {
55
- throw new Error('API Keys plugin requires Users plugin to be loaded first');
58
+ logger.warn('Users plugin not loaded! API Keys plugin disabled.');
59
+ registry.registerHealthCheck({
60
+ name: 'api-keys-store',
61
+ type: 'custom',
62
+ check: async () => ({
63
+ healthy: false,
64
+ details: {
65
+ error: 'Users plugin not available',
66
+ state: 'disabled',
67
+ },
68
+ }),
69
+ });
70
+ return;
56
71
  }
57
72
 
73
+ // Check for postgres in registry
74
+ if (!hasPostgres()) {
75
+ logger.warn('No Database! API Keys plugin disabled.');
76
+ registry.registerHealthCheck({
77
+ name: 'api-keys-store',
78
+ type: 'custom',
79
+ check: async () => ({
80
+ healthy: false,
81
+ details: {
82
+ error: 'PostgreSQL not available',
83
+ state: 'disabled',
84
+ },
85
+ }),
86
+ });
87
+ return;
88
+ }
89
+
90
+ // Smart defaults - get dependencies from registry
91
+ const store = config.store ?? postgresApiKeyStore({
92
+ pool: () => getPostgres().getPool(),
93
+ autoCreateTables: true,
94
+ });
95
+
96
+ const debug = config.debug ?? false;
97
+ const apiPrefix = config.api?.prefix ?? '/api-keys';
98
+ const apiEnabled = config.api?.enabled ?? true;
99
+
100
+ log('Starting API keys plugin');
101
+
58
102
  // Initialize the store (creates tables and RLS policies if needed)
59
- await config.store.initialize();
103
+ const initResult = await store.initialize();
104
+
105
+ if (!initResult.success) {
106
+ logger.error('API keys store initialization failed', { error: initResult.error });
107
+
108
+ // If migration is required, register maintenance actions
109
+ if (initResult.requiresMaintenance) {
110
+ registry.registerMaintenance({
111
+ pluginId: 'api-keys',
112
+ error: initResult.error || 'Store initialization failed',
113
+ actions: [
114
+ {
115
+ id: 'truncate-api-keys-table',
116
+ name: 'Clear API Keys Table',
117
+ description: 'Delete all API keys from the database. This allows the migration to proceed by removing old data.',
118
+ destructive: true,
119
+ handler: async () => {
120
+ try {
121
+ const pool = getPostgres().getPool();
122
+ await pool.query('TRUNCATE TABLE "public"."api_keys" CASCADE');
123
+ logger.info('API keys table truncated successfully');
124
+ return {
125
+ success: true,
126
+ message: 'API keys table cleared. Plugin will retry initialization.',
127
+ };
128
+ } catch (error) {
129
+ const errorMessage = error instanceof Error ? error.message : String(error);
130
+ logger.error('Failed to truncate API keys table', { error: errorMessage });
131
+ return {
132
+ success: false,
133
+ error: `Failed to truncate table: ${errorMessage}`,
134
+ };
135
+ }
136
+ },
137
+ },
138
+ ],
139
+ recommendedAction: 'truncate-api-keys-table',
140
+ });
141
+
142
+ logger.warn('API keys plugin requires maintenance - registered recovery actions');
143
+ }
144
+
145
+ // Register unhealthy health check
146
+ registry.registerHealthCheck({
147
+ name: 'api-keys-store',
148
+ type: 'custom',
149
+ check: async () => ({
150
+ healthy: false,
151
+ details: {
152
+ error: initResult.error || 'Initialization failed',
153
+ requiresMaintenance: initResult.requiresMaintenance,
154
+ },
155
+ }),
156
+ });
157
+
158
+ return; // Don't continue startup
159
+ }
160
+
60
161
  log('API keys store initialized');
61
162
 
62
163
  // Initialize optional Phase 2 stores
@@ -71,7 +172,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
71
172
  }
72
173
 
73
174
  // Store reference for helper access
74
- currentStore = config.store;
175
+ currentStore = store;
75
176
 
76
177
  // Register health check
77
178
  registry.registerHealthCheck({
@@ -117,7 +218,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
117
218
  };
118
219
 
119
220
  // Create the API key
120
- const apiKey = await config.store.create(params);
221
+ const apiKey = await store.create(params);
121
222
 
122
223
  // Return the key with plaintext (ONLY time plaintext is accessible)
123
224
  res.status(201).json({
@@ -152,7 +253,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
152
253
  return res.status(401).json({ error: 'Authentication required' });
153
254
  }
154
255
 
155
- const keys = await config.store.list(userId);
256
+ const keys = await store.list(userId);
156
257
 
157
258
  // Remove sensitive fields from response
158
259
  const sanitized = keys.map(key => ({
@@ -191,7 +292,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
191
292
  }
192
293
 
193
294
  const { id } = req.params;
194
- const key = await config.store.get(userId, id);
295
+ const key = await store.get(userId, id);
195
296
 
196
297
  if (!key) {
197
298
  return res.status(404).json({ error: 'API key not found' });
@@ -244,7 +345,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
244
345
  const { id } = req.params;
245
346
  const params: UpdateApiKeyParams = validation.data;
246
347
 
247
- const updated = await config.store.update(userId, id, params);
348
+ const updated = await store.update(userId, id, params);
248
349
 
249
350
  if (!updated) {
250
351
  return res.status(404).json({ error: 'API key not found' });
@@ -287,7 +388,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
287
388
  }
288
389
 
289
390
  const { id } = req.params;
290
- const deleted = await config.store.delete(userId, id);
391
+ const deleted = await store.delete(userId, id);
291
392
 
292
393
  if (!deleted) {
293
394
  return res.status(404).json({ error: 'API key not found' });
@@ -357,7 +458,7 @@ export function createApiKeysPlugin(config: ApiKeysPluginConfig): Plugin {
357
458
  const { id: keyId } = req.params;
358
459
 
359
460
  // Verify key belongs to user
360
- const key = await config.store.get(userId, keyId);
461
+ const key = await store.get(userId, keyId);
361
462
  if (!key) {
362
463
  return res.status(404).json({ error: 'API key not found' });
363
464
  }
@@ -31,6 +31,7 @@ export type {
31
31
  UpdateApiKeyParams,
32
32
  PostgresApiKeyStoreConfig,
33
33
  ApiKeysApiConfig,
34
+ StoreInitializationResult,
34
35
  } from './types.js';
35
36
 
36
37
  // Zod schemas
@@ -48,8 +49,5 @@ export { postgresApiKeyStore } from './stores/index.js';
48
49
  // Middleware
49
50
  export { bearerTokenAuth } from './middleware/index.js';
50
51
 
51
- // UI Components
52
- export { ApiKeysStatusWidget } from './ApiKeysStatusWidget.js';
53
- export type { ApiKeysStatusWidgetProps } from './ApiKeysStatusWidget.js';
54
- export { ApiKeysManagementPage } from './ApiKeysManagementPage.js';
55
- export type { ApiKeysManagementPageProps } from './ApiKeysManagementPage.js';
52
+ // UI Components are exported from main package index (@qwickapps/server)
53
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
@@ -21,6 +21,7 @@ import type {
21
21
  UpdateApiKeyParams,
22
22
  ApiKey,
23
23
  ApiKeyWithPlaintext,
24
+ StoreInitializationResult,
24
25
  } from '../types.js';
25
26
 
26
27
  // Pool interface (from pg package)
@@ -239,82 +240,104 @@ export function postgresApiKeyStore(config: PostgresApiKeyStoreConfig): ApiKeySt
239
240
  return {
240
241
  name: 'postgres',
241
242
 
242
- async initialize(): Promise<void> {
243
- if (!autoCreateTables) return;
243
+ async initialize(): Promise<StoreInitializationResult> {
244
+ if (!autoCreateTables) {
245
+ return { success: true };
246
+ }
244
247
 
245
- const pool = getPool();
248
+ try {
249
+ const pool = getPool();
246
250
 
247
- // Create table with foreign key to users
248
- await pool.query(`
249
- CREATE TABLE IF NOT EXISTS ${tableFullName} (
250
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
251
- user_id UUID NOT NULL REFERENCES "public"."users"(id) ON DELETE CASCADE,
252
- name VARCHAR(255) NOT NULL,
253
- key_hash VARCHAR(64) NOT NULL,
254
- key_prefix VARCHAR(12) NOT NULL,
255
- key_type VARCHAR(10) NOT NULL CHECK (key_type IN ('m2m', 'pat')),
256
- scopes TEXT[] NOT NULL DEFAULT '{}',
257
- last_used_at TIMESTAMPTZ,
258
- expires_at TIMESTAMPTZ,
259
- is_active BOOLEAN NOT NULL DEFAULT true,
260
- created_at TIMESTAMPTZ DEFAULT NOW(),
261
- updated_at TIMESTAMPTZ DEFAULT NOW()
262
- );
251
+ // Create table with foreign key to users
252
+ await pool.query(`
253
+ CREATE TABLE IF NOT EXISTS ${tableFullName} (
254
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
255
+ user_id UUID NOT NULL REFERENCES "public"."users"(id) ON DELETE CASCADE,
256
+ name VARCHAR(255) NOT NULL,
257
+ key_hash VARCHAR(64) NOT NULL,
258
+ key_prefix VARCHAR(12) NOT NULL,
259
+ key_type VARCHAR(10) NOT NULL CHECK (key_type IN ('m2m', 'pat')),
260
+ scopes TEXT[] NOT NULL DEFAULT '{}',
261
+ last_used_at TIMESTAMPTZ,
262
+ expires_at TIMESTAMPTZ,
263
+ is_active BOOLEAN NOT NULL DEFAULT true,
264
+ created_at TIMESTAMPTZ DEFAULT NOW(),
265
+ updated_at TIMESTAMPTZ DEFAULT NOW()
266
+ );
267
+
268
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableFullName}(user_id);
269
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_key_prefix ON ${tableFullName}(key_prefix);
270
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_key_hash ON ${tableFullName}(key_hash);
271
+ `);
263
272
 
264
- CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableFullName}(user_id);
265
- CREATE INDEX IF NOT EXISTS idx_${tableName}_key_prefix ON ${tableFullName}(key_prefix);
266
- CREATE INDEX IF NOT EXISTS idx_${tableName}_key_hash ON ${tableFullName}(key_hash);
267
- `);
268
-
269
- // Migration: Add user_id column if it doesn't exist (for existing installations)
270
- await pool.query(`
271
- DO $$
272
- DECLARE
273
- row_count INTEGER;
274
- BEGIN
275
- IF NOT EXISTS (
276
- SELECT 1 FROM information_schema.columns
277
- WHERE table_schema = '${schema}'
278
- AND table_name = '${tableName}'
279
- AND column_name = 'user_id'
280
- ) THEN
281
- -- Check if table has data
282
- EXECUTE 'SELECT COUNT(*) FROM ${tableFullName}' INTO row_count;
283
-
284
- IF row_count > 0 THEN
285
- -- If table has data, cannot add NOT NULL column
286
- -- This requires manual migration or data cleanup
287
- RAISE EXCEPTION 'Cannot add user_id column: table ${tableFullName} contains % rows. Please migrate data or clear the table first.', row_count;
288
- ELSE
289
- -- Table is empty, safe to add NOT NULL column
273
+ // Migration: Check if table needs user_id column migration
274
+ const needsMigration = await pool.query(`
275
+ SELECT
276
+ NOT EXISTS (
277
+ SELECT 1 FROM information_schema.columns
278
+ WHERE table_schema = '${schema}'
279
+ AND table_name = '${tableName}'
280
+ AND column_name = 'user_id'
281
+ ) AS missing_column,
282
+ (SELECT COUNT(*) FROM ${tableFullName}) AS row_count
283
+ `);
284
+
285
+ const { missing_column, row_count } = needsMigration.rows[0] as { missing_column: boolean; row_count: number };
286
+
287
+ if (missing_column) {
288
+ if (row_count > 0) {
289
+ // Table has data but is missing user_id column - requires maintenance
290
+ logger.warn(
291
+ `Table ${tableFullName} has ${row_count} rows but is missing user_id column. ` +
292
+ `Requires maintenance action to migrate or clear data.`
293
+ );
294
+ return {
295
+ success: false,
296
+ error: `Table ${tableFullName} contains ${row_count} rows without user_id column. Migration required.`,
297
+ requiresMaintenance: true,
298
+ };
299
+ } else {
300
+ // Table is empty, safe to add NOT NULL column
301
+ await pool.query(`
290
302
  ALTER TABLE ${tableFullName}
291
303
  ADD COLUMN user_id UUID NOT NULL REFERENCES "public"."users"(id) ON DELETE CASCADE;
292
304
 
293
305
  CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableFullName}(user_id);
294
- END IF;
295
- END IF;
296
- END $$;
297
- `);
298
-
299
- // Enable RLS if configured
300
- if (enableRLS) {
301
- await pool.query(`
302
- ALTER TABLE ${tableFullName} ENABLE ROW LEVEL SECURITY;
303
- ALTER TABLE ${tableFullName} FORCE ROW LEVEL SECURITY;
304
- `);
306
+ `);
307
+ logger.info(`Added user_id column to empty table ${tableFullName}`);
308
+ }
309
+ }
305
310
 
306
- // Create or replace the RLS policy
307
- await pool.query(`
308
- DROP POLICY IF EXISTS "${tableName}_owner" ON ${tableFullName};
309
- `);
311
+ // Enable RLS if configured
312
+ if (enableRLS) {
313
+ await pool.query(`
314
+ ALTER TABLE ${tableFullName} ENABLE ROW LEVEL SECURITY;
315
+ ALTER TABLE ${tableFullName} FORCE ROW LEVEL SECURITY;
316
+ `);
317
+
318
+ // Create or replace the RLS policy
319
+ await pool.query(`
320
+ DROP POLICY IF EXISTS "${tableName}_owner" ON ${tableFullName};
321
+ `);
322
+
323
+ // RLS policy: users can only access their own keys
324
+ await pool.query(`
325
+ CREATE POLICY "${tableName}_owner" ON ${tableFullName}
326
+ FOR ALL
327
+ USING (user_id::text = current_setting('app.current_user_id', true))
328
+ WITH CHECK (user_id::text = current_setting('app.current_user_id', true));
329
+ `);
330
+ }
310
331
 
311
- // RLS policy: users can only access their own keys
312
- await pool.query(`
313
- CREATE POLICY "${tableName}_owner" ON ${tableFullName}
314
- FOR ALL
315
- USING (user_id::text = current_setting('app.current_user_id', true))
316
- WITH CHECK (user_id::text = current_setting('app.current_user_id', true));
317
- `);
332
+ logger.info(`API keys store initialized successfully (table: ${tableFullName})`);
333
+ return { success: true };
334
+ } catch (error) {
335
+ const errorMessage = error instanceof Error ? error.message : String(error);
336
+ logger.error(`Failed to initialize API keys store: ${errorMessage}`, { error });
337
+ return {
338
+ success: false,
339
+ error: `Database initialization failed: ${errorMessage}`,
340
+ };
318
341
  }
319
342
  },
320
343
 
@@ -140,6 +140,18 @@ export interface ApiKeyWithPlaintext extends ApiKey {
140
140
  plaintext_key: string;
141
141
  }
142
142
 
143
+ /**
144
+ * Result of store initialization
145
+ */
146
+ export interface StoreInitializationResult {
147
+ /** Whether initialization succeeded */
148
+ success: boolean;
149
+ /** Error message if initialization failed */
150
+ error?: string;
151
+ /** Additional details about what needs to be done */
152
+ requiresMaintenance?: boolean;
153
+ }
154
+
143
155
  /**
144
156
  * API key store interface - all storage backends must implement this
145
157
  */
@@ -149,8 +161,9 @@ export interface ApiKeyStore {
149
161
 
150
162
  /**
151
163
  * Initialize the store (create tables, RLS policies, etc.)
164
+ * Returns initialization status instead of throwing
152
165
  */
153
- initialize(): Promise<void>;
166
+ initialize(): Promise<StoreInitializationResult>;
154
167
 
155
168
  /**
156
169
  * Create a new API key
@@ -230,17 +243,23 @@ export interface ApiKeysApiConfig {
230
243
 
231
244
  /**
232
245
  * API keys plugin configuration
246
+ *
247
+ * All properties are optional - plugin will use smart defaults:
248
+ * - store: Postgres API key store using registry's postgres instance
249
+ * - api.prefix: '/api-keys'
250
+ * - api.enabled: true
251
+ * - debug: false
233
252
  */
234
253
  export interface ApiKeysPluginConfig {
235
- /** API key storage backend */
236
- store: ApiKeyStore;
254
+ /** API key storage backend (default: postgres API key store from registry) */
255
+ store?: ApiKeyStore;
237
256
  /** Plugin scope storage backend (optional, for Phase 2) */
238
257
  scopeStore?: import('./stores/plugin-scope-store.js').PluginScopeStore;
239
258
  /** Usage log storage backend (optional, for Phase 2) */
240
259
  usageStore?: import('./stores/usage-log-store.js').UsageLogStore;
241
260
  /** API configuration */
242
261
  api?: ApiKeysApiConfig;
243
- /** Enable debug logging */
262
+ /** Enable debug logging (default: false) */
244
263
  debug?: boolean;
245
264
  }
246
265
 
@@ -65,8 +65,6 @@ export { basicAdapter } from './adapters/basic-adapter.js';
65
65
  export { supabaseAdapter } from './adapters/supabase-adapter.js';
66
66
  export { supertokensAdapter } from './adapters/supertokens-adapter.js';
67
67
 
68
- // UI Components
69
- export { AuthStatusWidget } from './AuthStatusWidget.js';
70
- export type { AuthStatusWidgetProps } from './AuthStatusWidget.js';
71
- export { AuthManagementPage } from './AuthManagementPage.js';
72
- export type { AuthManagementPageProps } from './AuthManagementPage.js';
68
+ // UI Components are exported from main package index (@qwickapps/server)
69
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
70
+