@qwickapps/server 1.2.0 → 1.3.1

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 (299) hide show
  1. package/README.md +392 -0
  2. package/dist/core/control-panel.d.ts +7 -2
  3. package/dist/core/control-panel.d.ts.map +1 -1
  4. package/dist/core/control-panel.js +120 -54
  5. package/dist/core/control-panel.js.map +1 -1
  6. package/dist/core/gateway.d.ts +159 -79
  7. package/dist/core/gateway.d.ts.map +1 -1
  8. package/dist/core/gateway.js +679 -319
  9. package/dist/core/gateway.js.map +1 -1
  10. package/dist/core/index.d.ts +3 -1
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +2 -0
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/plugin-registry.d.ts +307 -0
  15. package/dist/core/plugin-registry.d.ts.map +1 -0
  16. package/dist/core/plugin-registry.js +352 -0
  17. package/dist/core/plugin-registry.js.map +1 -0
  18. package/dist/core/types.d.ts +16 -33
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/dist/index.d.ts +8 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -7
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
  25. package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
  26. package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
  27. package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
  28. package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
  29. package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
  30. package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
  31. package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
  32. package/dist/plugins/auth/adapters/index.d.ts +10 -0
  33. package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
  34. package/dist/plugins/auth/adapters/index.js +10 -0
  35. package/dist/plugins/auth/adapters/index.js.map +1 -0
  36. package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
  37. package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
  38. package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
  39. package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
  40. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts +18 -0
  41. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
  42. package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
  43. package/dist/plugins/auth/adapters/supertokens-adapter.js.map +1 -0
  44. package/dist/plugins/auth/auth-plugin.d.ts +40 -0
  45. package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
  46. package/dist/plugins/auth/auth-plugin.js +255 -0
  47. package/dist/plugins/auth/auth-plugin.js.map +1 -0
  48. package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
  49. package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
  50. package/dist/plugins/auth/auth-plugin.test.js +147 -0
  51. package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
  52. package/dist/plugins/auth/env-config.d.ts +88 -0
  53. package/dist/plugins/auth/env-config.d.ts.map +1 -0
  54. package/dist/plugins/auth/env-config.js +489 -0
  55. package/dist/plugins/auth/env-config.js.map +1 -0
  56. package/dist/plugins/auth/index.d.ts +14 -0
  57. package/dist/plugins/auth/index.d.ts.map +1 -0
  58. package/dist/plugins/auth/index.js +16 -0
  59. package/dist/plugins/auth/index.js.map +1 -0
  60. package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
  61. package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
  62. package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
  63. package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
  64. package/dist/plugins/auth/types.d.ts +218 -0
  65. package/dist/plugins/auth/types.d.ts.map +1 -0
  66. package/dist/plugins/auth/types.js +14 -0
  67. package/dist/plugins/auth/types.js.map +1 -0
  68. package/dist/plugins/bans/bans-plugin.d.ts +59 -0
  69. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
  70. package/dist/plugins/bans/bans-plugin.js +428 -0
  71. package/dist/plugins/bans/bans-plugin.js.map +1 -0
  72. package/dist/plugins/bans/index.d.ts +9 -0
  73. package/dist/plugins/bans/index.d.ts.map +1 -0
  74. package/dist/plugins/bans/index.js +10 -0
  75. package/dist/plugins/bans/index.js.map +1 -0
  76. package/dist/plugins/bans/stores/index.d.ts +7 -0
  77. package/dist/plugins/bans/stores/index.d.ts.map +1 -0
  78. package/dist/plugins/bans/stores/index.js +7 -0
  79. package/dist/plugins/bans/stores/index.js.map +1 -0
  80. package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
  81. package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
  82. package/dist/plugins/bans/stores/postgres-store.js +132 -0
  83. package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
  84. package/dist/plugins/bans/types.d.ts +128 -0
  85. package/dist/plugins/bans/types.d.ts.map +1 -0
  86. package/dist/plugins/bans/types.js +11 -0
  87. package/dist/plugins/bans/types.js.map +1 -0
  88. package/dist/plugins/cache-plugin.d.ts +14 -3
  89. package/dist/plugins/cache-plugin.d.ts.map +1 -1
  90. package/dist/plugins/cache-plugin.js +27 -7
  91. package/dist/plugins/cache-plugin.js.map +1 -1
  92. package/dist/plugins/cache-plugin.test.js +99 -32
  93. package/dist/plugins/cache-plugin.test.js.map +1 -1
  94. package/dist/plugins/config-plugin.d.ts +3 -2
  95. package/dist/plugins/config-plugin.d.ts.map +1 -1
  96. package/dist/plugins/config-plugin.js +17 -10
  97. package/dist/plugins/config-plugin.js.map +1 -1
  98. package/dist/plugins/diagnostics-plugin.d.ts +2 -2
  99. package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
  100. package/dist/plugins/diagnostics-plugin.js +17 -10
  101. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  102. package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
  103. package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
  104. package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
  105. package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
  106. package/dist/plugins/entitlements/index.d.ts +12 -0
  107. package/dist/plugins/entitlements/index.d.ts.map +1 -0
  108. package/dist/plugins/entitlements/index.js +16 -0
  109. package/dist/plugins/entitlements/index.js.map +1 -0
  110. package/dist/plugins/entitlements/sources/index.d.ts +9 -0
  111. package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
  112. package/dist/plugins/entitlements/sources/index.js +9 -0
  113. package/dist/plugins/entitlements/sources/index.js.map +1 -0
  114. package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
  115. package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
  116. package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
  117. package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
  118. package/dist/plugins/entitlements/types.d.ts +232 -0
  119. package/dist/plugins/entitlements/types.d.ts.map +1 -0
  120. package/dist/plugins/entitlements/types.js +11 -0
  121. package/dist/plugins/entitlements/types.js.map +1 -0
  122. package/dist/plugins/frontend-app-plugin.d.ts +9 -3
  123. package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
  124. package/dist/plugins/frontend-app-plugin.js +14 -9
  125. package/dist/plugins/frontend-app-plugin.js.map +1 -1
  126. package/dist/plugins/health-plugin.d.ts +5 -2
  127. package/dist/plugins/health-plugin.d.ts.map +1 -1
  128. package/dist/plugins/health-plugin.js +20 -5
  129. package/dist/plugins/health-plugin.js.map +1 -1
  130. package/dist/plugins/index.d.ts +10 -2
  131. package/dist/plugins/index.d.ts.map +1 -1
  132. package/dist/plugins/index.js +10 -2
  133. package/dist/plugins/index.js.map +1 -1
  134. package/dist/plugins/logs-plugin.d.ts +3 -2
  135. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  136. package/dist/plugins/logs-plugin.js +21 -12
  137. package/dist/plugins/logs-plugin.js.map +1 -1
  138. package/dist/plugins/postgres-plugin.d.ts +3 -3
  139. package/dist/plugins/postgres-plugin.d.ts.map +1 -1
  140. package/dist/plugins/postgres-plugin.js +9 -7
  141. package/dist/plugins/postgres-plugin.js.map +1 -1
  142. package/dist/plugins/postgres-plugin.test.js +50 -29
  143. package/dist/plugins/postgres-plugin.test.js.map +1 -1
  144. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
  145. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
  146. package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
  147. package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
  148. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
  149. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
  150. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
  151. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
  152. package/dist/plugins/preferences/index.d.ts +12 -0
  153. package/dist/plugins/preferences/index.d.ts.map +1 -0
  154. package/dist/plugins/preferences/index.js +13 -0
  155. package/dist/plugins/preferences/index.js.map +1 -0
  156. package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
  157. package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
  158. package/dist/plugins/preferences/preferences-plugin.js +226 -0
  159. package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
  160. package/dist/plugins/preferences/stores/index.d.ts +9 -0
  161. package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
  162. package/dist/plugins/preferences/stores/index.js +9 -0
  163. package/dist/plugins/preferences/stores/index.js.map +1 -0
  164. package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
  165. package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
  166. package/dist/plugins/preferences/stores/postgres-store.js +181 -0
  167. package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
  168. package/dist/plugins/preferences/types.d.ts +91 -0
  169. package/dist/plugins/preferences/types.d.ts.map +1 -0
  170. package/dist/plugins/preferences/types.js +10 -0
  171. package/dist/plugins/preferences/types.js.map +1 -0
  172. package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
  173. package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
  174. package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
  175. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
  176. package/dist/plugins/users/index.d.ts +12 -0
  177. package/dist/plugins/users/index.d.ts.map +1 -0
  178. package/dist/plugins/users/index.js +13 -0
  179. package/dist/plugins/users/index.js.map +1 -0
  180. package/dist/plugins/users/stores/index.d.ts +7 -0
  181. package/dist/plugins/users/stores/index.d.ts.map +1 -0
  182. package/dist/plugins/users/stores/index.js +7 -0
  183. package/dist/plugins/users/stores/index.js.map +1 -0
  184. package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
  185. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
  186. package/dist/plugins/users/stores/postgres-store.js +157 -0
  187. package/dist/plugins/users/stores/postgres-store.js.map +1 -0
  188. package/dist/plugins/users/types.d.ts +225 -0
  189. package/dist/plugins/users/types.d.ts.map +1 -0
  190. package/dist/plugins/users/types.js +12 -0
  191. package/dist/plugins/users/types.js.map +1 -0
  192. package/dist/plugins/users/users-plugin.d.ts +45 -0
  193. package/dist/plugins/users/users-plugin.d.ts.map +1 -0
  194. package/dist/plugins/users/users-plugin.js +359 -0
  195. package/dist/plugins/users/users-plugin.js.map +1 -0
  196. package/dist-ui/assets/index-BY8OxNgO.js +465 -0
  197. package/dist-ui/assets/index-BY8OxNgO.js.map +1 -0
  198. package/dist-ui/index.html +1 -1
  199. package/dist-ui-lib/api/controlPanelApi.d.ts +278 -0
  200. package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
  201. package/dist-ui-lib/components/index.d.ts +18 -0
  202. package/dist-ui-lib/config/AppConfig.d.ts +7 -0
  203. package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
  204. package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
  205. package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
  206. package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +48 -0
  207. package/dist-ui-lib/dashboard/builtInWidgets.d.ts +25 -0
  208. package/dist-ui-lib/dashboard/index.d.ts +13 -0
  209. package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
  210. package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
  211. package/dist-ui-lib/index.js +5172 -0
  212. package/dist-ui-lib/index.js.map +1 -0
  213. package/dist-ui-lib/pages/AuthPage.d.ts +1 -0
  214. package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
  215. package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
  216. package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
  217. package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
  218. package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
  219. package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
  220. package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
  221. package/dist-ui-lib/pages/PluginsPage.d.ts +1 -0
  222. package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
  223. package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
  224. package/package.json +24 -7
  225. package/src/core/control-panel.ts +145 -61
  226. package/src/core/gateway.ts +863 -403
  227. package/src/core/index.ts +21 -2
  228. package/src/core/plugin-registry.ts +716 -0
  229. package/src/core/types.ts +31 -37
  230. package/src/index.ts +125 -19
  231. package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
  232. package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
  233. package/src/plugins/auth/adapters/index.ts +10 -0
  234. package/src/plugins/auth/adapters/supabase-adapter.ts +149 -0
  235. package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
  236. package/src/plugins/auth/auth-plugin.test.ts +176 -0
  237. package/src/plugins/auth/auth-plugin.ts +303 -0
  238. package/src/plugins/auth/env-config.ts +572 -0
  239. package/src/plugins/auth/index.ts +42 -0
  240. package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
  241. package/src/plugins/auth/types.ts +245 -0
  242. package/src/plugins/bans/bans-plugin.ts +485 -0
  243. package/src/plugins/bans/index.ts +31 -0
  244. package/src/plugins/bans/stores/index.ts +7 -0
  245. package/src/plugins/bans/stores/postgres-store.ts +195 -0
  246. package/src/plugins/bans/types.ts +141 -0
  247. package/src/plugins/cache-plugin.test.ts +108 -32
  248. package/src/plugins/cache-plugin.ts +40 -9
  249. package/src/plugins/config-plugin.ts +23 -12
  250. package/src/plugins/diagnostics-plugin.ts +22 -12
  251. package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
  252. package/src/plugins/entitlements/index.ts +51 -0
  253. package/src/plugins/entitlements/sources/index.ts +9 -0
  254. package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
  255. package/src/plugins/entitlements/types.ts +256 -0
  256. package/src/plugins/frontend-app-plugin.ts +24 -12
  257. package/src/plugins/health-plugin.ts +27 -7
  258. package/src/plugins/index.ts +132 -4
  259. package/src/plugins/logs-plugin.ts +28 -14
  260. package/src/plugins/postgres-plugin.test.ts +52 -29
  261. package/src/plugins/postgres-plugin.ts +11 -9
  262. package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
  263. package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
  264. package/src/plugins/preferences/index.ts +30 -0
  265. package/src/plugins/preferences/preferences-plugin.ts +270 -0
  266. package/src/plugins/preferences/stores/index.ts +9 -0
  267. package/src/plugins/preferences/stores/postgres-store.ts +252 -0
  268. package/src/plugins/preferences/types.ts +100 -0
  269. package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
  270. package/src/plugins/users/index.ts +38 -0
  271. package/src/plugins/users/stores/index.ts +7 -0
  272. package/src/plugins/users/stores/postgres-store.ts +225 -0
  273. package/src/plugins/users/types.ts +247 -0
  274. package/src/plugins/users/users-plugin.ts +418 -0
  275. package/ui/src/App.tsx +188 -31
  276. package/ui/src/api/controlPanelApi.ts +453 -1
  277. package/ui/src/components/ControlPanelApp.tsx +212 -0
  278. package/ui/src/components/index.ts +62 -0
  279. package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
  280. package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
  281. package/ui/src/dashboard/PluginWidgetRenderer.tsx +118 -0
  282. package/ui/src/dashboard/WidgetComponentRegistry.tsx +120 -0
  283. package/ui/src/dashboard/builtInWidgets.tsx +35 -0
  284. package/ui/src/dashboard/index.ts +35 -0
  285. package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
  286. package/ui/src/dashboard/widgets/index.ts +7 -0
  287. package/ui/src/pages/AuthPage.tsx +259 -0
  288. package/ui/src/pages/DashboardPage.tsx +28 -149
  289. package/ui/src/pages/EntitlementsPage.tsx +557 -0
  290. package/ui/src/pages/LogsPage.tsx +174 -8
  291. package/ui/src/pages/PluginPage.tsx +148 -0
  292. package/ui/src/pages/PluginsPage.tsx +394 -0
  293. package/ui/src/pages/SystemPage.tsx +445 -0
  294. package/ui/src/pages/UsersPage.tsx +837 -0
  295. package/ui/tsconfig.lib.json +11 -0
  296. package/ui/vite.lib.config.ts +56 -0
  297. package/dist-ui/assets/index-CW1BviRn.js +0 -465
  298. package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
  299. package/ui/src/pages/HealthPage.tsx +0 -204
@@ -0,0 +1,485 @@
1
+ /**
2
+ * Bans Plugin
3
+ *
4
+ * User ban management plugin for @qwickapps/server.
5
+ * Bans are always on USER entities (by user_id), not emails.
6
+ *
7
+ * This plugin depends on the Users Plugin for user resolution.
8
+ * Use `isEmailBanned()` convenience function to check bans by email,
9
+ * which internally resolves email → user_id → ban status.
10
+ *
11
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
12
+ */
13
+
14
+ import type { Request, Response } from 'express';
15
+ import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
16
+ import type {
17
+ BansPluginConfig,
18
+ BanStore,
19
+ Ban,
20
+ CreateBanInput,
21
+ RemoveBanInput,
22
+ } from './types.js';
23
+ import type { AuthenticatedRequest } from '../auth/types.js';
24
+ import { getUserByEmail, getUserById } from '../users/users-plugin.js';
25
+
26
+ // Store instance for helper access
27
+ let currentStore: BanStore | null = null;
28
+ let banCleanupInterval: NodeJS.Timeout | null = null;
29
+ let pluginConfig: BansPluginConfig | null = null;
30
+
31
+ /**
32
+ * Create the Bans plugin
33
+ */
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 || '/bans';
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 || '');
43
+ }
44
+ }
45
+
46
+ return {
47
+ id: 'bans',
48
+ name: 'Bans',
49
+ version: '1.0.0',
50
+
51
+ async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
52
+ log('Starting bans plugin');
53
+
54
+ // Check for users plugin dependency
55
+ if (!registry.hasPlugin('users')) {
56
+ throw new Error('Bans plugin requires Users plugin to be loaded first');
57
+ }
58
+
59
+ // Initialize the store (creates tables if needed)
60
+ await config.store.initialize();
61
+ log('Bans plugin migrations complete');
62
+
63
+ // Store references for helper access
64
+ currentStore = config.store;
65
+ pluginConfig = config;
66
+
67
+ // Start ban cleanup interval if temporary bans are supported
68
+ if (config.supportTemporary) {
69
+ banCleanupInterval = setInterval(async () => {
70
+ try {
71
+ const cleaned = await config.store.cleanupExpiredBans();
72
+ if (cleaned > 0) {
73
+ log('Cleaned up expired bans', { count: cleaned });
74
+ }
75
+ } catch (error) {
76
+ console.error('[BansPlugin] Ban cleanup error:', error);
77
+ }
78
+ }, 60 * 1000); // Check every minute
79
+ }
80
+
81
+ // Register health check
82
+ registry.registerHealthCheck({
83
+ name: 'bans-store',
84
+ type: 'custom',
85
+ check: async () => {
86
+ try {
87
+ // Simple health check - list with limit 0
88
+ await config.store.listActiveBans({ limit: 0 });
89
+ return { healthy: true };
90
+ } catch {
91
+ return { healthy: false };
92
+ }
93
+ },
94
+ });
95
+
96
+ // Add API routes if enabled
97
+ if (apiEnabled) {
98
+ // List active bans
99
+ registry.addRoute({
100
+ method: 'get',
101
+ path: apiPrefix,
102
+ pluginId: 'bans',
103
+ handler: async (req: Request, res: Response) => {
104
+ try {
105
+ const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
106
+ const offset = parseInt(req.query.offset as string) || 0;
107
+
108
+ const result = await config.store.listActiveBans({ limit, offset });
109
+ res.json(result);
110
+ } catch (error) {
111
+ console.error('[BansPlugin] List bans error:', error);
112
+ res.status(500).json({ error: 'Failed to list bans' });
113
+ }
114
+ },
115
+ });
116
+
117
+ // Get user ban status by user ID
118
+ registry.addRoute({
119
+ method: 'get',
120
+ path: `${apiPrefix}/user/:userId`,
121
+ pluginId: 'bans',
122
+ handler: async (req: Request, res: Response) => {
123
+ try {
124
+ const ban = await config.store.getActiveBan(req.params.userId);
125
+ res.json({
126
+ isBanned: ban !== null,
127
+ ban,
128
+ });
129
+ } catch (error) {
130
+ console.error('[BansPlugin] Get ban status error:', error);
131
+ res.status(500).json({ error: 'Failed to get ban status' });
132
+ }
133
+ },
134
+ });
135
+
136
+ // Get user ban history
137
+ registry.addRoute({
138
+ method: 'get',
139
+ path: `${apiPrefix}/user/:userId/history`,
140
+ pluginId: 'bans',
141
+ handler: async (req: Request, res: Response) => {
142
+ try {
143
+ const bans = await config.store.listBans(req.params.userId);
144
+ res.json({ bans });
145
+ } catch (error) {
146
+ console.error('[BansPlugin] Get ban history error:', error);
147
+ res.status(500).json({ error: 'Failed to get ban history' });
148
+ }
149
+ },
150
+ });
151
+
152
+ // Ban user by user ID
153
+ registry.addRoute({
154
+ method: 'post',
155
+ path: `${apiPrefix}/user/:userId`,
156
+ pluginId: 'bans',
157
+ handler: async (req: Request, res: Response) => {
158
+ try {
159
+ const authReq = req as AuthenticatedRequest;
160
+ const bannedBy = authReq.auth?.user?.id || authReq.auth?.user?.email || 'system';
161
+
162
+ const input: CreateBanInput = {
163
+ user_id: req.params.userId,
164
+ reason: req.body.reason || 'No reason provided',
165
+ banned_by: bannedBy,
166
+ duration: config.supportTemporary ? req.body.duration : undefined,
167
+ metadata: req.body.metadata,
168
+ };
169
+
170
+ // Verify user exists via Users Plugin
171
+ const user = await getUserById(req.params.userId);
172
+ if (!user) {
173
+ return res.status(404).json({ error: 'User not found' });
174
+ }
175
+
176
+ const ban = await config.store.createBan(input);
177
+
178
+ // Call onBan callback if provided
179
+ if (config.callbacks?.onBan) {
180
+ try {
181
+ await config.callbacks.onBan(user, ban);
182
+ } catch (callbackError) {
183
+ console.error('[BansPlugin] onBan callback error:', callbackError);
184
+ }
185
+ }
186
+
187
+ log('User banned', { userId: req.params.userId, reason: input.reason });
188
+ res.status(201).json(ban);
189
+ } catch (error) {
190
+ console.error('[BansPlugin] Ban user error:', error);
191
+ res.status(500).json({ error: 'Failed to ban user' });
192
+ }
193
+ },
194
+ });
195
+
196
+ // Unban user by user ID
197
+ registry.addRoute({
198
+ method: 'delete',
199
+ path: `${apiPrefix}/user/:userId`,
200
+ pluginId: 'bans',
201
+ handler: async (req: Request, res: Response) => {
202
+ try {
203
+ const authReq = req as AuthenticatedRequest;
204
+ const removedBy = authReq.auth?.user?.id || authReq.auth?.user?.email || 'system';
205
+
206
+ // Verify user exists via Users Plugin
207
+ const user = await getUserById(req.params.userId);
208
+ if (!user) {
209
+ return res.status(404).json({ error: 'User not found' });
210
+ }
211
+
212
+ const input: RemoveBanInput = {
213
+ user_id: req.params.userId,
214
+ removed_by: removedBy,
215
+ note: req.body?.note,
216
+ };
217
+
218
+ const removed = await config.store.removeBan(input);
219
+ if (!removed) {
220
+ return res.status(404).json({ error: 'No active ban found' });
221
+ }
222
+
223
+ // Call onUnban callback if provided
224
+ if (config.callbacks?.onUnban) {
225
+ try {
226
+ await config.callbacks.onUnban(user);
227
+ } catch (callbackError) {
228
+ console.error('[BansPlugin] onUnban callback error:', callbackError);
229
+ }
230
+ }
231
+
232
+ log('User unbanned', { userId: req.params.userId });
233
+ res.status(204).send();
234
+ } catch (error) {
235
+ console.error('[BansPlugin] Unban user error:', error);
236
+ res.status(500).json({ error: 'Failed to unban user' });
237
+ }
238
+ },
239
+ });
240
+
241
+ // Check ban status by email (convenience endpoint)
242
+ registry.addRoute({
243
+ method: 'get',
244
+ path: `${apiPrefix}/email/:email`,
245
+ pluginId: 'bans',
246
+ handler: async (req: Request, res: Response) => {
247
+ try {
248
+ const email = decodeURIComponent(req.params.email);
249
+ const isBanned = await isEmailBanned(email);
250
+ res.json({ email, isBanned });
251
+ } catch (error) {
252
+ console.error('[BansPlugin] Check email ban error:', error);
253
+ res.status(500).json({ error: 'Failed to check ban status' });
254
+ }
255
+ },
256
+ });
257
+
258
+ // Ban user by email (convenience endpoint)
259
+ registry.addRoute({
260
+ method: 'post',
261
+ path: `${apiPrefix}/email/:email`,
262
+ pluginId: 'bans',
263
+ handler: async (req: Request, res: Response) => {
264
+ try {
265
+ const email = decodeURIComponent(req.params.email);
266
+ const authReq = req as AuthenticatedRequest;
267
+ const bannedBy = authReq.auth?.user?.id || authReq.auth?.user?.email || 'system';
268
+
269
+ // Resolve email to user via Users Plugin
270
+ const user = await getUserByEmail(email);
271
+ if (!user) {
272
+ return res.status(404).json({ error: 'User not found' });
273
+ }
274
+
275
+ const input: CreateBanInput = {
276
+ user_id: user.id,
277
+ reason: req.body.reason || 'No reason provided',
278
+ banned_by: bannedBy,
279
+ duration: config.supportTemporary ? req.body.duration : undefined,
280
+ metadata: req.body.metadata,
281
+ };
282
+
283
+ const ban = await config.store.createBan(input);
284
+
285
+ // Call onBan callback if provided
286
+ if (config.callbacks?.onBan) {
287
+ try {
288
+ await config.callbacks.onBan(user, ban);
289
+ } catch (callbackError) {
290
+ console.error('[BansPlugin] onBan callback error:', callbackError);
291
+ }
292
+ }
293
+
294
+ log('User banned by email', { email, userId: user.id, reason: input.reason });
295
+ res.status(201).json(ban);
296
+ } catch (error) {
297
+ console.error('[BansPlugin] Ban user by email error:', error);
298
+ res.status(500).json({ error: 'Failed to ban user' });
299
+ }
300
+ },
301
+ });
302
+
303
+ // Unban user by email (convenience endpoint)
304
+ registry.addRoute({
305
+ method: 'delete',
306
+ path: `${apiPrefix}/email/:email`,
307
+ pluginId: 'bans',
308
+ handler: async (req: Request, res: Response) => {
309
+ try {
310
+ const email = decodeURIComponent(req.params.email);
311
+ const authReq = req as AuthenticatedRequest;
312
+ const removedBy = authReq.auth?.user?.id || authReq.auth?.user?.email || 'system';
313
+
314
+ // Resolve email to user via Users Plugin
315
+ const user = await getUserByEmail(email);
316
+ if (!user) {
317
+ return res.status(404).json({ error: 'User not found' });
318
+ }
319
+
320
+ const input: RemoveBanInput = {
321
+ user_id: user.id,
322
+ removed_by: removedBy,
323
+ note: req.body?.note,
324
+ };
325
+
326
+ const removed = await config.store.removeBan(input);
327
+ if (!removed) {
328
+ return res.status(404).json({ error: 'No active ban found' });
329
+ }
330
+
331
+ // Call onUnban callback if provided
332
+ if (config.callbacks?.onUnban) {
333
+ try {
334
+ await config.callbacks.onUnban(user);
335
+ } catch (callbackError) {
336
+ console.error('[BansPlugin] onUnban callback error:', callbackError);
337
+ }
338
+ }
339
+
340
+ log('User unbanned by email', { email, userId: user.id });
341
+ res.status(204).send();
342
+ } catch (error) {
343
+ console.error('[BansPlugin] Unban user by email error:', error);
344
+ res.status(500).json({ error: 'Failed to unban user' });
345
+ }
346
+ },
347
+ });
348
+ }
349
+
350
+ log('Bans plugin started');
351
+ },
352
+
353
+ async onStop(): Promise<void> {
354
+ log('Stopping bans plugin');
355
+
356
+ if (banCleanupInterval) {
357
+ clearInterval(banCleanupInterval);
358
+ banCleanupInterval = null;
359
+ }
360
+
361
+ await config.store.shutdown();
362
+ currentStore = null;
363
+ pluginConfig = null;
364
+
365
+ log('Bans plugin stopped');
366
+ },
367
+ };
368
+ }
369
+
370
+ // ========================================
371
+ // Helper Functions
372
+ // ========================================
373
+
374
+ /**
375
+ * Get the current ban store instance
376
+ */
377
+ export function getBanStore(): BanStore | null {
378
+ return currentStore;
379
+ }
380
+
381
+ /**
382
+ * Check if a user is banned by user ID
383
+ */
384
+ export async function isUserBanned(userId: string): Promise<boolean> {
385
+ if (!currentStore) {
386
+ throw new Error('Bans plugin not initialized');
387
+ }
388
+ return currentStore.isBanned(userId);
389
+ }
390
+
391
+ /**
392
+ * Check if a user is banned by email
393
+ *
394
+ * This is a convenience function that:
395
+ * 1. Resolves email → user via Users Plugin
396
+ * 2. Checks ban status by user_id
397
+ *
398
+ * Returns false if user doesn't exist (unknown user = not banned)
399
+ */
400
+ export async function isEmailBanned(email: string): Promise<boolean> {
401
+ if (!currentStore) {
402
+ throw new Error('Bans plugin not initialized');
403
+ }
404
+
405
+ // Resolve email to user via Users Plugin
406
+ const user = await getUserByEmail(email);
407
+ if (!user) {
408
+ return false; // Unknown user = not banned
409
+ }
410
+
411
+ return currentStore.isBanned(user.id);
412
+ }
413
+
414
+ /**
415
+ * Get active ban for a user
416
+ */
417
+ export async function getActiveBan(userId: string): Promise<Ban | null> {
418
+ if (!currentStore) {
419
+ throw new Error('Bans plugin not initialized');
420
+ }
421
+ return currentStore.getActiveBan(userId);
422
+ }
423
+
424
+ /**
425
+ * Ban a user
426
+ */
427
+ export async function banUser(input: CreateBanInput): Promise<Ban> {
428
+ if (!currentStore) {
429
+ throw new Error('Bans plugin not initialized');
430
+ }
431
+
432
+ const ban = await currentStore.createBan(input);
433
+
434
+ // Call onBan callback if configured
435
+ if (pluginConfig?.callbacks?.onBan) {
436
+ const user = await getUserById(input.user_id);
437
+ if (user) {
438
+ try {
439
+ await pluginConfig.callbacks.onBan(user, ban);
440
+ } catch (error) {
441
+ console.error('[BansPlugin] onBan callback error:', error);
442
+ }
443
+ }
444
+ }
445
+
446
+ return ban;
447
+ }
448
+
449
+ /**
450
+ * Unban a user
451
+ */
452
+ export async function unbanUser(input: RemoveBanInput): Promise<boolean> {
453
+ if (!currentStore) {
454
+ throw new Error('Bans plugin not initialized');
455
+ }
456
+
457
+ const removed = await currentStore.removeBan(input);
458
+
459
+ // Call onUnban callback if configured
460
+ if (removed && pluginConfig?.callbacks?.onUnban) {
461
+ const user = await getUserById(input.user_id);
462
+ if (user) {
463
+ try {
464
+ await pluginConfig.callbacks.onUnban(user);
465
+ } catch (error) {
466
+ console.error('[BansPlugin] onUnban callback error:', error);
467
+ }
468
+ }
469
+ }
470
+
471
+ return removed;
472
+ }
473
+
474
+ /**
475
+ * List all active bans
476
+ */
477
+ export async function listActiveBans(options?: { limit?: number; offset?: number }): Promise<{
478
+ bans: Ban[];
479
+ total: number;
480
+ }> {
481
+ if (!currentStore) {
482
+ throw new Error('Bans plugin not initialized');
483
+ }
484
+ return currentStore.listActiveBans(options);
485
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Bans Plugin Index
3
+ *
4
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
5
+ */
6
+
7
+ // Main plugin
8
+ export {
9
+ createBansPlugin,
10
+ getBanStore,
11
+ isUserBanned,
12
+ isEmailBanned,
13
+ getActiveBan,
14
+ banUser,
15
+ unbanUser,
16
+ listActiveBans,
17
+ } from './bans-plugin.js';
18
+
19
+ // Types
20
+ export type {
21
+ BansPluginConfig,
22
+ BanStore,
23
+ Ban,
24
+ CreateBanInput,
25
+ RemoveBanInput,
26
+ BanCallbacks,
27
+ PostgresBanStoreConfig,
28
+ } from './types.js';
29
+
30
+ // Stores
31
+ export { postgresBanStore } from './stores/index.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Ban Stores Index
3
+ *
4
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
5
+ */
6
+
7
+ export { postgresBanStore } from './postgres-store.js';