@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.
- package/README.md +392 -0
- package/dist/core/control-panel.d.ts +7 -2
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +120 -54
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/gateway.d.ts +159 -79
- package/dist/core/gateway.d.ts.map +1 -1
- package/dist/core/gateway.js +679 -319
- package/dist/core/gateway.js.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/plugin-registry.d.ts +307 -0
- package/dist/core/plugin-registry.d.ts.map +1 -0
- package/dist/core/plugin-registry.js +352 -0
- package/dist/core/plugin-registry.js.map +1 -0
- package/dist/core/types.d.ts +16 -33
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
- package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
- package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
- package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
- package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/index.d.ts +10 -0
- package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/index.js +10 -0
- package/dist/plugins/auth/adapters/index.js.map +1 -0
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
- package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.d.ts +18 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.d.ts +40 -0
- package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
- package/dist/plugins/auth/auth-plugin.js +255 -0
- package/dist/plugins/auth/auth-plugin.js.map +1 -0
- package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
- package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
- package/dist/plugins/auth/auth-plugin.test.js +147 -0
- package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
- package/dist/plugins/auth/env-config.d.ts +88 -0
- package/dist/plugins/auth/env-config.d.ts.map +1 -0
- package/dist/plugins/auth/env-config.js +489 -0
- package/dist/plugins/auth/env-config.js.map +1 -0
- package/dist/plugins/auth/index.d.ts +14 -0
- package/dist/plugins/auth/index.d.ts.map +1 -0
- package/dist/plugins/auth/index.js +16 -0
- package/dist/plugins/auth/index.js.map +1 -0
- package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
- package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
- package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
- package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
- package/dist/plugins/auth/types.d.ts +218 -0
- package/dist/plugins/auth/types.d.ts.map +1 -0
- package/dist/plugins/auth/types.js +14 -0
- package/dist/plugins/auth/types.js.map +1 -0
- package/dist/plugins/bans/bans-plugin.d.ts +59 -0
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
- package/dist/plugins/bans/bans-plugin.js +428 -0
- package/dist/plugins/bans/bans-plugin.js.map +1 -0
- package/dist/plugins/bans/index.d.ts +9 -0
- package/dist/plugins/bans/index.d.ts.map +1 -0
- package/dist/plugins/bans/index.js +10 -0
- package/dist/plugins/bans/index.js.map +1 -0
- package/dist/plugins/bans/stores/index.d.ts +7 -0
- package/dist/plugins/bans/stores/index.d.ts.map +1 -0
- package/dist/plugins/bans/stores/index.js +7 -0
- package/dist/plugins/bans/stores/index.js.map +1 -0
- package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
- package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/bans/stores/postgres-store.js +132 -0
- package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
- package/dist/plugins/bans/types.d.ts +128 -0
- package/dist/plugins/bans/types.d.ts.map +1 -0
- package/dist/plugins/bans/types.js +11 -0
- package/dist/plugins/bans/types.js.map +1 -0
- package/dist/plugins/cache-plugin.d.ts +14 -3
- package/dist/plugins/cache-plugin.d.ts.map +1 -1
- package/dist/plugins/cache-plugin.js +27 -7
- package/dist/plugins/cache-plugin.js.map +1 -1
- package/dist/plugins/cache-plugin.test.js +99 -32
- package/dist/plugins/cache-plugin.test.js.map +1 -1
- package/dist/plugins/config-plugin.d.ts +3 -2
- package/dist/plugins/config-plugin.d.ts.map +1 -1
- package/dist/plugins/config-plugin.js +17 -10
- package/dist/plugins/config-plugin.js.map +1 -1
- package/dist/plugins/diagnostics-plugin.d.ts +2 -2
- package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
- package/dist/plugins/diagnostics-plugin.js +17 -10
- package/dist/plugins/diagnostics-plugin.js.map +1 -1
- package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
- package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
- package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
- package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
- package/dist/plugins/entitlements/index.d.ts +12 -0
- package/dist/plugins/entitlements/index.d.ts.map +1 -0
- package/dist/plugins/entitlements/index.js +16 -0
- package/dist/plugins/entitlements/index.js.map +1 -0
- package/dist/plugins/entitlements/sources/index.d.ts +9 -0
- package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
- package/dist/plugins/entitlements/sources/index.js +9 -0
- package/dist/plugins/entitlements/sources/index.js.map +1 -0
- package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
- package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
- package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
- package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
- package/dist/plugins/entitlements/types.d.ts +232 -0
- package/dist/plugins/entitlements/types.d.ts.map +1 -0
- package/dist/plugins/entitlements/types.js +11 -0
- package/dist/plugins/entitlements/types.js.map +1 -0
- package/dist/plugins/frontend-app-plugin.d.ts +9 -3
- package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
- package/dist/plugins/frontend-app-plugin.js +14 -9
- package/dist/plugins/frontend-app-plugin.js.map +1 -1
- package/dist/plugins/health-plugin.d.ts +5 -2
- package/dist/plugins/health-plugin.d.ts.map +1 -1
- package/dist/plugins/health-plugin.js +20 -5
- package/dist/plugins/health-plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +10 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +10 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/logs-plugin.d.ts +3 -2
- package/dist/plugins/logs-plugin.d.ts.map +1 -1
- package/dist/plugins/logs-plugin.js +21 -12
- package/dist/plugins/logs-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.d.ts +3 -3
- package/dist/plugins/postgres-plugin.d.ts.map +1 -1
- package/dist/plugins/postgres-plugin.js +9 -7
- package/dist/plugins/postgres-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.test.js +50 -29
- package/dist/plugins/postgres-plugin.test.js.map +1 -1
- package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
- package/dist/plugins/preferences/index.d.ts +12 -0
- package/dist/plugins/preferences/index.d.ts.map +1 -0
- package/dist/plugins/preferences/index.js +13 -0
- package/dist/plugins/preferences/index.js.map +1 -0
- package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
- package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
- package/dist/plugins/preferences/preferences-plugin.js +226 -0
- package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
- package/dist/plugins/preferences/stores/index.d.ts +9 -0
- package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
- package/dist/plugins/preferences/stores/index.js +9 -0
- package/dist/plugins/preferences/stores/index.js.map +1 -0
- package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
- package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/preferences/stores/postgres-store.js +181 -0
- package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
- package/dist/plugins/preferences/types.d.ts +91 -0
- package/dist/plugins/preferences/types.d.ts.map +1 -0
- package/dist/plugins/preferences/types.js +10 -0
- package/dist/plugins/preferences/types.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
- package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
- package/dist/plugins/users/index.d.ts +12 -0
- package/dist/plugins/users/index.d.ts.map +1 -0
- package/dist/plugins/users/index.js +13 -0
- package/dist/plugins/users/index.js.map +1 -0
- package/dist/plugins/users/stores/index.d.ts +7 -0
- package/dist/plugins/users/stores/index.d.ts.map +1 -0
- package/dist/plugins/users/stores/index.js +7 -0
- package/dist/plugins/users/stores/index.js.map +1 -0
- package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/users/stores/postgres-store.js +157 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -0
- package/dist/plugins/users/types.d.ts +225 -0
- package/dist/plugins/users/types.d.ts.map +1 -0
- package/dist/plugins/users/types.js +12 -0
- package/dist/plugins/users/types.js.map +1 -0
- package/dist/plugins/users/users-plugin.d.ts +45 -0
- package/dist/plugins/users/users-plugin.d.ts.map +1 -0
- package/dist/plugins/users/users-plugin.js +359 -0
- package/dist/plugins/users/users-plugin.js.map +1 -0
- package/dist-ui/assets/index-BY8OxNgO.js +465 -0
- package/dist-ui/assets/index-BY8OxNgO.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +278 -0
- package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
- package/dist-ui-lib/components/index.d.ts +18 -0
- package/dist-ui-lib/config/AppConfig.d.ts +7 -0
- package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
- package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
- package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
- package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +48 -0
- package/dist-ui-lib/dashboard/builtInWidgets.d.ts +25 -0
- package/dist-ui-lib/dashboard/index.d.ts +13 -0
- package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
- package/dist-ui-lib/index.js +5172 -0
- package/dist-ui-lib/index.js.map +1 -0
- package/dist-ui-lib/pages/AuthPage.d.ts +1 -0
- package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
- package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
- package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
- package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
- package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
- package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
- package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
- package/dist-ui-lib/pages/PluginsPage.d.ts +1 -0
- package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
- package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
- package/package.json +24 -7
- package/src/core/control-panel.ts +145 -61
- package/src/core/gateway.ts +863 -403
- package/src/core/index.ts +21 -2
- package/src/core/plugin-registry.ts +716 -0
- package/src/core/types.ts +31 -37
- package/src/index.ts +125 -19
- package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
- package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
- package/src/plugins/auth/adapters/index.ts +10 -0
- package/src/plugins/auth/adapters/supabase-adapter.ts +149 -0
- package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
- package/src/plugins/auth/auth-plugin.test.ts +176 -0
- package/src/plugins/auth/auth-plugin.ts +303 -0
- package/src/plugins/auth/env-config.ts +572 -0
- package/src/plugins/auth/index.ts +42 -0
- package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
- package/src/plugins/auth/types.ts +245 -0
- package/src/plugins/bans/bans-plugin.ts +485 -0
- package/src/plugins/bans/index.ts +31 -0
- package/src/plugins/bans/stores/index.ts +7 -0
- package/src/plugins/bans/stores/postgres-store.ts +195 -0
- package/src/plugins/bans/types.ts +141 -0
- package/src/plugins/cache-plugin.test.ts +108 -32
- package/src/plugins/cache-plugin.ts +40 -9
- package/src/plugins/config-plugin.ts +23 -12
- package/src/plugins/diagnostics-plugin.ts +22 -12
- package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
- package/src/plugins/entitlements/index.ts +51 -0
- package/src/plugins/entitlements/sources/index.ts +9 -0
- package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
- package/src/plugins/entitlements/types.ts +256 -0
- package/src/plugins/frontend-app-plugin.ts +24 -12
- package/src/plugins/health-plugin.ts +27 -7
- package/src/plugins/index.ts +132 -4
- package/src/plugins/logs-plugin.ts +28 -14
- package/src/plugins/postgres-plugin.test.ts +52 -29
- package/src/plugins/postgres-plugin.ts +11 -9
- package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
- package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
- package/src/plugins/preferences/index.ts +30 -0
- package/src/plugins/preferences/preferences-plugin.ts +270 -0
- package/src/plugins/preferences/stores/index.ts +9 -0
- package/src/plugins/preferences/stores/postgres-store.ts +252 -0
- package/src/plugins/preferences/types.ts +100 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
- package/src/plugins/users/index.ts +38 -0
- package/src/plugins/users/stores/index.ts +7 -0
- package/src/plugins/users/stores/postgres-store.ts +225 -0
- package/src/plugins/users/types.ts +247 -0
- package/src/plugins/users/users-plugin.ts +418 -0
- package/ui/src/App.tsx +188 -31
- package/ui/src/api/controlPanelApi.ts +453 -1
- package/ui/src/components/ControlPanelApp.tsx +212 -0
- package/ui/src/components/index.ts +62 -0
- package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
- package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
- package/ui/src/dashboard/PluginWidgetRenderer.tsx +118 -0
- package/ui/src/dashboard/WidgetComponentRegistry.tsx +120 -0
- package/ui/src/dashboard/builtInWidgets.tsx +35 -0
- package/ui/src/dashboard/index.ts +35 -0
- package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
- package/ui/src/dashboard/widgets/index.ts +7 -0
- package/ui/src/pages/AuthPage.tsx +259 -0
- package/ui/src/pages/DashboardPage.tsx +28 -149
- package/ui/src/pages/EntitlementsPage.tsx +557 -0
- package/ui/src/pages/LogsPage.tsx +174 -8
- package/ui/src/pages/PluginPage.tsx +148 -0
- package/ui/src/pages/PluginsPage.tsx +394 -0
- package/ui/src/pages/SystemPage.tsx +445 -0
- package/ui/src/pages/UsersPage.tsx +837 -0
- package/ui/tsconfig.lib.json +11 -0
- package/ui/vite.lib.config.ts +56 -0
- package/dist-ui/assets/index-CW1BviRn.js +0 -465
- package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
- package/ui/src/pages/HealthPage.tsx +0 -204
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Ban Store
|
|
3
|
+
*
|
|
4
|
+
* Ban storage implementation using PostgreSQL.
|
|
5
|
+
* Requires the 'pg' package and the Users plugin to be installed.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
BanStore,
|
|
12
|
+
Ban,
|
|
13
|
+
CreateBanInput,
|
|
14
|
+
RemoveBanInput,
|
|
15
|
+
PostgresBanStoreConfig,
|
|
16
|
+
} from '../types.js';
|
|
17
|
+
|
|
18
|
+
// Pool interface (from pg package)
|
|
19
|
+
interface PgPool {
|
|
20
|
+
query(text: string, values?: unknown[]): Promise<{ rows: unknown[]; rowCount: number | null }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a PostgreSQL ban store
|
|
25
|
+
*
|
|
26
|
+
* @param config Configuration including a pg Pool instance or a function that returns one
|
|
27
|
+
* @returns BanStore implementation
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { Pool } from 'pg';
|
|
32
|
+
* import { postgresBanStore } from '@qwickapps/server';
|
|
33
|
+
*
|
|
34
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
35
|
+
* const store = postgresBanStore({ pool });
|
|
36
|
+
*
|
|
37
|
+
* // Or with lazy initialization:
|
|
38
|
+
* const store = postgresBanStore({ pool: () => getPostgres().getPool() });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function postgresBanStore(config: PostgresBanStoreConfig): BanStore {
|
|
42
|
+
const {
|
|
43
|
+
pool: poolOrFn,
|
|
44
|
+
tableName = 'user_bans',
|
|
45
|
+
schema = 'public',
|
|
46
|
+
autoCreateTables = true,
|
|
47
|
+
} = config;
|
|
48
|
+
|
|
49
|
+
// Helper to get pool (supports lazy initialization via function)
|
|
50
|
+
const getPool = (): PgPool => {
|
|
51
|
+
const pool = typeof poolOrFn === 'function' ? poolOrFn() : poolOrFn;
|
|
52
|
+
return pool as PgPool;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const tableFullName = `"${schema}"."${tableName}"`;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: 'postgres',
|
|
59
|
+
|
|
60
|
+
async initialize(): Promise<void> {
|
|
61
|
+
if (!autoCreateTables) return;
|
|
62
|
+
|
|
63
|
+
// Create bans table
|
|
64
|
+
// Note: This does NOT have a foreign key to users table
|
|
65
|
+
// The relationship is enforced at the application level via Users Plugin
|
|
66
|
+
await getPool().query(`
|
|
67
|
+
CREATE TABLE IF NOT EXISTS ${tableFullName} (
|
|
68
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
69
|
+
user_id UUID NOT NULL,
|
|
70
|
+
reason TEXT NOT NULL,
|
|
71
|
+
banned_by VARCHAR(255) NOT NULL,
|
|
72
|
+
banned_at TIMESTAMPTZ DEFAULT NOW(),
|
|
73
|
+
expires_at TIMESTAMPTZ,
|
|
74
|
+
is_active BOOLEAN DEFAULT TRUE,
|
|
75
|
+
removed_at TIMESTAMPTZ,
|
|
76
|
+
removed_by VARCHAR(255),
|
|
77
|
+
metadata JSONB DEFAULT '{}'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableFullName}(user_id);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_is_active ON ${tableFullName}(is_active);
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_expires_at ON ${tableFullName}(expires_at) WHERE expires_at IS NOT NULL;
|
|
83
|
+
`);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async isBanned(userId: string): Promise<boolean> {
|
|
87
|
+
const ban = await this.getActiveBan(userId);
|
|
88
|
+
return ban !== null;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async getActiveBan(userId: string): Promise<Ban | null> {
|
|
92
|
+
const result = await getPool().query(
|
|
93
|
+
`SELECT * FROM ${tableFullName}
|
|
94
|
+
WHERE user_id = $1 AND is_active = TRUE
|
|
95
|
+
AND (expires_at IS NULL OR expires_at > NOW())`,
|
|
96
|
+
[userId]
|
|
97
|
+
);
|
|
98
|
+
return (result.rows[0] as Ban) || null;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async createBan(input: CreateBanInput): Promise<Ban> {
|
|
102
|
+
const expiresAt = input.duration
|
|
103
|
+
? new Date(Date.now() + input.duration * 1000)
|
|
104
|
+
: null;
|
|
105
|
+
|
|
106
|
+
// Deactivate any existing active bans for this user
|
|
107
|
+
await getPool().query(
|
|
108
|
+
`UPDATE ${tableFullName}
|
|
109
|
+
SET is_active = FALSE, removed_at = NOW(), removed_by = $2
|
|
110
|
+
WHERE user_id = $1 AND is_active = TRUE`,
|
|
111
|
+
[input.user_id, input.banned_by]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Create new ban
|
|
115
|
+
const result = await getPool().query(
|
|
116
|
+
`INSERT INTO ${tableFullName} (user_id, reason, banned_by, expires_at, metadata)
|
|
117
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
118
|
+
RETURNING *`,
|
|
119
|
+
[
|
|
120
|
+
input.user_id,
|
|
121
|
+
input.reason,
|
|
122
|
+
input.banned_by,
|
|
123
|
+
expiresAt,
|
|
124
|
+
JSON.stringify(input.metadata || {}),
|
|
125
|
+
]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return result.rows[0] as Ban;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async removeBan(input: RemoveBanInput): Promise<boolean> {
|
|
132
|
+
const result = await getPool().query(
|
|
133
|
+
`UPDATE ${tableFullName}
|
|
134
|
+
SET is_active = FALSE, removed_at = NOW(), removed_by = $2
|
|
135
|
+
WHERE user_id = $1 AND is_active = TRUE
|
|
136
|
+
RETURNING *`,
|
|
137
|
+
[input.user_id, input.removed_by]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return (result.rowCount ?? 0) > 0;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async listBans(userId: string): Promise<Ban[]> {
|
|
144
|
+
const result = await getPool().query(
|
|
145
|
+
`SELECT * FROM ${tableFullName}
|
|
146
|
+
WHERE user_id = $1
|
|
147
|
+
ORDER BY banned_at DESC`,
|
|
148
|
+
[userId]
|
|
149
|
+
);
|
|
150
|
+
return result.rows as Ban[];
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async listActiveBans(options: { limit?: number; offset?: number } = {}): Promise<{
|
|
154
|
+
bans: Ban[];
|
|
155
|
+
total: number;
|
|
156
|
+
}> {
|
|
157
|
+
const { limit = 50, offset = 0 } = options;
|
|
158
|
+
|
|
159
|
+
// Get total count
|
|
160
|
+
const countResult = await getPool().query(
|
|
161
|
+
`SELECT COUNT(*) FROM ${tableFullName}
|
|
162
|
+
WHERE is_active = TRUE AND (expires_at IS NULL OR expires_at > NOW())`
|
|
163
|
+
);
|
|
164
|
+
const total = parseInt((countResult.rows[0] as { count: string }).count, 10);
|
|
165
|
+
|
|
166
|
+
// Get bans
|
|
167
|
+
const result = await getPool().query(
|
|
168
|
+
`SELECT * FROM ${tableFullName}
|
|
169
|
+
WHERE is_active = TRUE AND (expires_at IS NULL OR expires_at > NOW())
|
|
170
|
+
ORDER BY banned_at DESC
|
|
171
|
+
LIMIT $1 OFFSET $2`,
|
|
172
|
+
[limit, offset]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
bans: result.rows as Ban[],
|
|
177
|
+
total,
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
async cleanupExpiredBans(): Promise<number> {
|
|
182
|
+
const result = await getPool().query(
|
|
183
|
+
`UPDATE ${tableFullName}
|
|
184
|
+
SET is_active = FALSE
|
|
185
|
+
WHERE is_active = TRUE AND expires_at IS NOT NULL AND expires_at <= NOW()`
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return result.rowCount ?? 0;
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
async shutdown(): Promise<void> {
|
|
192
|
+
// Pool is managed externally, nothing to do here
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bans Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for ban management.
|
|
5
|
+
* Bans are always on USER entities (by user_id), not emails.
|
|
6
|
+
* Email is just an identifier to resolve to a user.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { User } from '../users/types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ban record
|
|
15
|
+
*/
|
|
16
|
+
export interface Ban {
|
|
17
|
+
/** Primary key - UUID */
|
|
18
|
+
id: string;
|
|
19
|
+
/** User ID being banned */
|
|
20
|
+
user_id: string;
|
|
21
|
+
/** Reason for the ban */
|
|
22
|
+
reason: string;
|
|
23
|
+
/** Who created the ban (user ID or system) */
|
|
24
|
+
banned_by: string;
|
|
25
|
+
/** When the ban was created */
|
|
26
|
+
banned_at: Date;
|
|
27
|
+
/** When the ban expires (null = permanent) */
|
|
28
|
+
expires_at?: Date;
|
|
29
|
+
/** Whether the ban is currently active */
|
|
30
|
+
is_active: boolean;
|
|
31
|
+
/** When the ban was removed */
|
|
32
|
+
removed_at?: Date;
|
|
33
|
+
/** Who removed the ban */
|
|
34
|
+
removed_by?: string;
|
|
35
|
+
/** Additional metadata */
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ban creation payload
|
|
41
|
+
*/
|
|
42
|
+
export interface CreateBanInput {
|
|
43
|
+
user_id: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
banned_by: string;
|
|
46
|
+
/** Duration in seconds (null = permanent) */
|
|
47
|
+
duration?: number;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Ban removal payload
|
|
53
|
+
*/
|
|
54
|
+
export interface RemoveBanInput {
|
|
55
|
+
user_id: string;
|
|
56
|
+
removed_by: string;
|
|
57
|
+
note?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ban store interface - storage backend for bans
|
|
62
|
+
*/
|
|
63
|
+
export interface BanStore {
|
|
64
|
+
/** Store name (e.g., 'postgres', 'memory') */
|
|
65
|
+
name: string;
|
|
66
|
+
|
|
67
|
+
/** Initialize the store (create tables, etc.) */
|
|
68
|
+
initialize(): Promise<void>;
|
|
69
|
+
|
|
70
|
+
/** Check if user is banned */
|
|
71
|
+
isBanned(userId: string): Promise<boolean>;
|
|
72
|
+
|
|
73
|
+
/** Get active ban for user */
|
|
74
|
+
getActiveBan(userId: string): Promise<Ban | null>;
|
|
75
|
+
|
|
76
|
+
/** Create a ban */
|
|
77
|
+
createBan(input: CreateBanInput): Promise<Ban>;
|
|
78
|
+
|
|
79
|
+
/** Remove a ban */
|
|
80
|
+
removeBan(input: RemoveBanInput): Promise<boolean>;
|
|
81
|
+
|
|
82
|
+
/** List bans for a user (including history) */
|
|
83
|
+
listBans(userId: string): Promise<Ban[]>;
|
|
84
|
+
|
|
85
|
+
/** List all active bans */
|
|
86
|
+
listActiveBans(options?: { limit?: number; offset?: number }): Promise<{
|
|
87
|
+
bans: Ban[];
|
|
88
|
+
total: number;
|
|
89
|
+
}>;
|
|
90
|
+
|
|
91
|
+
/** Cleanup expired bans */
|
|
92
|
+
cleanupExpiredBans(): Promise<number>;
|
|
93
|
+
|
|
94
|
+
/** Shutdown the store */
|
|
95
|
+
shutdown(): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Ban callbacks
|
|
100
|
+
*/
|
|
101
|
+
export interface BanCallbacks {
|
|
102
|
+
/** Called when a user is banned */
|
|
103
|
+
onBan?: (user: User, ban: Ban) => Promise<void>;
|
|
104
|
+
/** Called when a ban is removed */
|
|
105
|
+
onUnban?: (user: User) => Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Bans plugin configuration
|
|
110
|
+
*/
|
|
111
|
+
export interface BansPluginConfig {
|
|
112
|
+
/** Ban storage backend */
|
|
113
|
+
store: BanStore;
|
|
114
|
+
/** Support temporary bans (with expiration) */
|
|
115
|
+
supportTemporary?: boolean;
|
|
116
|
+
/** Callbacks */
|
|
117
|
+
callbacks?: BanCallbacks;
|
|
118
|
+
/** API configuration */
|
|
119
|
+
api?: {
|
|
120
|
+
/** API route prefix (default: '/api/bans') */
|
|
121
|
+
prefix?: string;
|
|
122
|
+
/** Enable API endpoints */
|
|
123
|
+
enabled?: boolean;
|
|
124
|
+
};
|
|
125
|
+
/** Enable debug logging */
|
|
126
|
+
debug?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* PostgreSQL ban store configuration
|
|
131
|
+
*/
|
|
132
|
+
export interface PostgresBanStoreConfig {
|
|
133
|
+
/** PostgreSQL pool instance or a function that returns one (for lazy initialization) */
|
|
134
|
+
pool: unknown | (() => unknown);
|
|
135
|
+
/** Bans table name (default: 'user_bans') */
|
|
136
|
+
tableName?: string;
|
|
137
|
+
/** Schema name (default: 'public') */
|
|
138
|
+
schema?: string;
|
|
139
|
+
/** Auto-create tables on init (default: true) */
|
|
140
|
+
autoCreateTables?: boolean;
|
|
141
|
+
}
|
|
@@ -9,6 +9,27 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
9
9
|
|
|
10
10
|
// Mock ioredis before importing the plugin
|
|
11
11
|
vi.mock('ioredis', () => {
|
|
12
|
+
// Create a mock stream that emits keys in batches
|
|
13
|
+
const createMockScanStream = (keys: string[]) => {
|
|
14
|
+
const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
|
|
15
|
+
return {
|
|
16
|
+
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
|
|
17
|
+
if (!handlers[event]) handlers[event] = [];
|
|
18
|
+
handlers[event].push(handler);
|
|
19
|
+
// Simulate async emission after all handlers are registered
|
|
20
|
+
if (event === 'error') {
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
// Emit data in batches
|
|
23
|
+
handlers['data']?.forEach(h => h(keys));
|
|
24
|
+
// Then emit end
|
|
25
|
+
handlers['end']?.forEach(h => h());
|
|
26
|
+
}, 0);
|
|
27
|
+
}
|
|
28
|
+
return { on: vi.fn() };
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
12
33
|
const mockClient = {
|
|
13
34
|
get: vi.fn().mockResolvedValue(null),
|
|
14
35
|
setex: vi.fn().mockResolvedValue('OK'),
|
|
@@ -19,6 +40,7 @@ vi.mock('ioredis', () => {
|
|
|
19
40
|
incr: vi.fn().mockResolvedValue(1),
|
|
20
41
|
incrby: vi.fn().mockResolvedValue(5),
|
|
21
42
|
keys: vi.fn().mockResolvedValue([]),
|
|
43
|
+
scanStream: vi.fn(() => createMockScanStream(['test:key1', 'test:key2', 'test:key3'])),
|
|
22
44
|
info: vi.fn().mockResolvedValue('used_memory_human:1.5M\n'),
|
|
23
45
|
dbsize: vi.fn().mockResolvedValue(100),
|
|
24
46
|
ping: vi.fn().mockResolvedValue('PONG'),
|
|
@@ -38,6 +60,7 @@ import {
|
|
|
38
60
|
hasCache,
|
|
39
61
|
type CachePluginConfig,
|
|
40
62
|
} from './cache-plugin.js';
|
|
63
|
+
import type { PluginRegistry } from '../core/plugin-registry.js';
|
|
41
64
|
|
|
42
65
|
describe('Cache Plugin', () => {
|
|
43
66
|
const mockConfig: CachePluginConfig = {
|
|
@@ -47,21 +70,42 @@ describe('Cache Plugin', () => {
|
|
|
47
70
|
healthCheck: false, // Disable for unit tests
|
|
48
71
|
};
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
// Create a mock registry that matches the new Plugin interface
|
|
74
|
+
const createMockRegistry = (): PluginRegistry => ({
|
|
75
|
+
hasPlugin: vi.fn().mockReturnValue(false),
|
|
76
|
+
getPlugin: vi.fn().mockReturnValue(null),
|
|
77
|
+
listPlugins: vi.fn().mockReturnValue([]),
|
|
78
|
+
addRoute: vi.fn(),
|
|
79
|
+
addMenuItem: vi.fn(),
|
|
80
|
+
addPage: vi.fn(),
|
|
81
|
+
addWidget: vi.fn(),
|
|
82
|
+
addConfigComponent: vi.fn(),
|
|
83
|
+
getRoutes: vi.fn().mockReturnValue([]),
|
|
84
|
+
getMenuItems: vi.fn().mockReturnValue([]),
|
|
85
|
+
getPages: vi.fn().mockReturnValue([]),
|
|
86
|
+
getWidgets: vi.fn().mockReturnValue([]),
|
|
87
|
+
getConfigComponents: vi.fn().mockReturnValue([]),
|
|
88
|
+
getPluginContributions: vi.fn().mockReturnValue({ routes: [], menuItems: [], pages: [], widgets: [], config: undefined }),
|
|
89
|
+
getConfig: vi.fn().mockReturnValue({}),
|
|
90
|
+
setConfig: vi.fn().mockResolvedValue(undefined),
|
|
91
|
+
subscribe: vi.fn().mockReturnValue(() => {}),
|
|
92
|
+
emit: vi.fn(),
|
|
93
|
+
registerHealthCheck: vi.fn(),
|
|
94
|
+
getApp: vi.fn().mockReturnValue({} as any),
|
|
95
|
+
getRouter: vi.fn().mockReturnValue({} as any),
|
|
96
|
+
getLogger: vi.fn().mockReturnValue({
|
|
55
97
|
debug: vi.fn(),
|
|
56
98
|
info: vi.fn(),
|
|
57
99
|
warn: vi.fn(),
|
|
58
100
|
error: vi.fn(),
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
let mockRegistry: PluginRegistry;
|
|
62
105
|
|
|
63
106
|
beforeEach(() => {
|
|
64
107
|
vi.clearAllMocks();
|
|
108
|
+
mockRegistry = createMockRegistry();
|
|
65
109
|
});
|
|
66
110
|
|
|
67
111
|
afterEach(async () => {
|
|
@@ -75,33 +119,34 @@ describe('Cache Plugin', () => {
|
|
|
75
119
|
describe('createCachePlugin', () => {
|
|
76
120
|
it('should create a plugin with correct name', () => {
|
|
77
121
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
78
|
-
expect(plugin.name).toBe('
|
|
122
|
+
expect(plugin.name).toBe('Redis Cache (test)');
|
|
79
123
|
});
|
|
80
124
|
|
|
81
125
|
it('should use "default" as instance name when not specified', () => {
|
|
82
126
|
const plugin = createCachePlugin(mockConfig);
|
|
83
|
-
expect(plugin.name).toBe('
|
|
127
|
+
expect(plugin.name).toBe('Redis Cache (default)');
|
|
84
128
|
});
|
|
85
129
|
|
|
86
|
-
it('should have
|
|
87
|
-
const plugin = createCachePlugin(mockConfig);
|
|
88
|
-
expect(plugin.
|
|
130
|
+
it('should have correct plugin id', () => {
|
|
131
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
132
|
+
expect(plugin.id).toBe('cache:test');
|
|
89
133
|
});
|
|
90
134
|
});
|
|
91
135
|
|
|
92
|
-
describe('
|
|
136
|
+
describe('onStart', () => {
|
|
93
137
|
it('should register the cache instance', async () => {
|
|
94
138
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
95
|
-
await plugin.
|
|
139
|
+
await plugin.onStart({}, mockRegistry);
|
|
96
140
|
|
|
97
141
|
expect(hasCache('test')).toBe(true);
|
|
98
142
|
});
|
|
99
143
|
|
|
100
144
|
it('should log debug message on successful connection', async () => {
|
|
101
145
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
102
|
-
await plugin.
|
|
146
|
+
await plugin.onStart({}, mockRegistry);
|
|
103
147
|
|
|
104
|
-
|
|
148
|
+
const logger = mockRegistry.getLogger('cache:test');
|
|
149
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
105
150
|
expect.stringContaining('connected')
|
|
106
151
|
);
|
|
107
152
|
});
|
|
@@ -109,9 +154,9 @@ describe('Cache Plugin', () => {
|
|
|
109
154
|
it('should register health check when enabled', async () => {
|
|
110
155
|
const configWithHealth = { ...mockConfig, healthCheck: true };
|
|
111
156
|
const plugin = createCachePlugin(configWithHealth, 'test');
|
|
112
|
-
await plugin.
|
|
157
|
+
await plugin.onStart({}, mockRegistry);
|
|
113
158
|
|
|
114
|
-
expect(
|
|
159
|
+
expect(mockRegistry.registerHealthCheck).toHaveBeenCalledWith(
|
|
115
160
|
expect.objectContaining({
|
|
116
161
|
name: 'redis',
|
|
117
162
|
type: 'custom',
|
|
@@ -126,9 +171,9 @@ describe('Cache Plugin', () => {
|
|
|
126
171
|
healthCheckName: 'custom-cache',
|
|
127
172
|
};
|
|
128
173
|
const plugin = createCachePlugin(configWithCustomName, 'test');
|
|
129
|
-
await plugin.
|
|
174
|
+
await plugin.onStart({}, mockRegistry);
|
|
130
175
|
|
|
131
|
-
expect(
|
|
176
|
+
expect(mockRegistry.registerHealthCheck).toHaveBeenCalledWith(
|
|
132
177
|
expect.objectContaining({
|
|
133
178
|
name: 'custom-cache',
|
|
134
179
|
})
|
|
@@ -139,7 +184,7 @@ describe('Cache Plugin', () => {
|
|
|
139
184
|
describe('getCache', () => {
|
|
140
185
|
it('should return registered instance', async () => {
|
|
141
186
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
142
|
-
await plugin.
|
|
187
|
+
await plugin.onStart({}, mockRegistry);
|
|
143
188
|
|
|
144
189
|
const cache = getCache('test');
|
|
145
190
|
expect(cache).toBeDefined();
|
|
@@ -162,7 +207,7 @@ describe('Cache Plugin', () => {
|
|
|
162
207
|
|
|
163
208
|
it('should return true for registered instance', async () => {
|
|
164
209
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
165
|
-
await plugin.
|
|
210
|
+
await plugin.onStart({}, mockRegistry);
|
|
166
211
|
|
|
167
212
|
expect(hasCache('test')).toBe(true);
|
|
168
213
|
});
|
|
@@ -171,7 +216,7 @@ describe('Cache Plugin', () => {
|
|
|
171
216
|
describe('CacheInstance', () => {
|
|
172
217
|
it('should get value and parse JSON', async () => {
|
|
173
218
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
174
|
-
await plugin.
|
|
219
|
+
await plugin.onStart({}, mockRegistry);
|
|
175
220
|
|
|
176
221
|
const cache = getCache('test');
|
|
177
222
|
// Mock will return null by default
|
|
@@ -181,7 +226,7 @@ describe('Cache Plugin', () => {
|
|
|
181
226
|
|
|
182
227
|
it('should set value with JSON stringification', async () => {
|
|
183
228
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
184
|
-
await plugin.
|
|
229
|
+
await plugin.onStart({}, mockRegistry);
|
|
185
230
|
|
|
186
231
|
const cache = getCache('test');
|
|
187
232
|
await cache.set('key', { foo: 'bar' }, 3600);
|
|
@@ -190,7 +235,7 @@ describe('Cache Plugin', () => {
|
|
|
190
235
|
|
|
191
236
|
it('should return cache stats', async () => {
|
|
192
237
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
193
|
-
await plugin.
|
|
238
|
+
await plugin.onStart({}, mockRegistry);
|
|
194
239
|
|
|
195
240
|
const cache = getCache('test');
|
|
196
241
|
const stats = await cache.getStats();
|
|
@@ -200,7 +245,7 @@ describe('Cache Plugin', () => {
|
|
|
200
245
|
|
|
201
246
|
it('should check if key exists', async () => {
|
|
202
247
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
203
|
-
await plugin.
|
|
248
|
+
await plugin.onStart({}, mockRegistry);
|
|
204
249
|
|
|
205
250
|
const cache = getCache('test');
|
|
206
251
|
const exists = await cache.exists('key');
|
|
@@ -209,7 +254,7 @@ describe('Cache Plugin', () => {
|
|
|
209
254
|
|
|
210
255
|
it('should get TTL for a key', async () => {
|
|
211
256
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
212
|
-
await plugin.
|
|
257
|
+
await plugin.onStart({}, mockRegistry);
|
|
213
258
|
|
|
214
259
|
const cache = getCache('test');
|
|
215
260
|
const ttl = await cache.ttl('key');
|
|
@@ -218,22 +263,53 @@ describe('Cache Plugin', () => {
|
|
|
218
263
|
|
|
219
264
|
it('should increment a value', async () => {
|
|
220
265
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
221
|
-
await plugin.
|
|
266
|
+
await plugin.onStart({}, mockRegistry);
|
|
222
267
|
|
|
223
268
|
const cache = getCache('test');
|
|
224
269
|
const value = await cache.incr('counter');
|
|
225
270
|
expect(typeof value).toBe('number');
|
|
226
271
|
});
|
|
272
|
+
|
|
273
|
+
it('should scan keys using cursor-based iteration', async () => {
|
|
274
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
275
|
+
await plugin.onStart({}, mockRegistry);
|
|
276
|
+
|
|
277
|
+
const cache = getCache('test');
|
|
278
|
+
const keys = await cache.scanKeys('*');
|
|
279
|
+
|
|
280
|
+
// Mock returns ['test:key1', 'test:key2', 'test:key3']
|
|
281
|
+
// After prefix removal, should be ['key1', 'key2', 'key3']
|
|
282
|
+
expect(Array.isArray(keys)).toBe(true);
|
|
283
|
+
expect(keys).toHaveLength(3);
|
|
284
|
+
expect(keys).toContain('key1');
|
|
285
|
+
expect(keys).toContain('key2');
|
|
286
|
+
expect(keys).toContain('key3');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should pass count option to scanStream', async () => {
|
|
290
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
291
|
+
await plugin.onStart({}, mockRegistry);
|
|
292
|
+
|
|
293
|
+
const cache = getCache('test');
|
|
294
|
+
const client = cache.getClient();
|
|
295
|
+
|
|
296
|
+
await cache.scanKeys('*', { count: 500 });
|
|
297
|
+
|
|
298
|
+
expect(client.scanStream).toHaveBeenCalledWith({
|
|
299
|
+
match: 'test:*',
|
|
300
|
+
count: 500,
|
|
301
|
+
});
|
|
302
|
+
});
|
|
227
303
|
});
|
|
228
304
|
|
|
229
|
-
describe('
|
|
305
|
+
describe('onStop', () => {
|
|
230
306
|
it('should close client and unregister instance', async () => {
|
|
231
307
|
const plugin = createCachePlugin(mockConfig, 'test');
|
|
232
|
-
await plugin.
|
|
308
|
+
await plugin.onStart({}, mockRegistry);
|
|
233
309
|
|
|
234
310
|
expect(hasCache('test')).toBe(true);
|
|
235
311
|
|
|
236
|
-
await plugin.
|
|
312
|
+
await plugin.onStop();
|
|
237
313
|
|
|
238
314
|
expect(hasCache('test')).toBe(false);
|
|
239
315
|
});
|