@qwickapps/server 1.4.0 → 1.5.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/CHANGELOG.md +507 -0
- package/README.md +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
- package/dist/plugins/bans/bans-plugin.js +12 -3
- package/dist/plugins/bans/bans-plugin.js.map +1 -1
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
- package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
- package/dist/plugins/devices/adapters/index.d.ts +12 -0
- package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/index.js +10 -0
- package/dist/plugins/devices/adapters/index.js.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
- package/dist/plugins/devices/devices-plugin.d.ts +70 -0
- package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
- package/dist/plugins/devices/devices-plugin.js +453 -0
- package/dist/plugins/devices/devices-plugin.js.map +1 -0
- package/dist/plugins/devices/index.d.ts +18 -0
- package/dist/plugins/devices/index.d.ts.map +1 -0
- package/dist/plugins/devices/index.js +18 -0
- package/dist/plugins/devices/index.js.map +1 -0
- package/dist/plugins/devices/stores/index.d.ts +9 -0
- package/dist/plugins/devices/stores/index.d.ts.map +1 -0
- package/dist/plugins/devices/stores/index.js +9 -0
- package/dist/plugins/devices/stores/index.js.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.js +199 -0
- package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
- package/dist/plugins/devices/token-utils.d.ts +100 -0
- package/dist/plugins/devices/token-utils.d.ts.map +1 -0
- package/dist/plugins/devices/token-utils.js +162 -0
- package/dist/plugins/devices/token-utils.js.map +1 -0
- package/dist/plugins/devices/types.d.ts +307 -0
- package/dist/plugins/devices/types.d.ts.map +1 -0
- package/dist/plugins/devices/types.js +10 -0
- package/dist/plugins/devices/types.js.map +1 -0
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +13 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
- package/dist/plugins/notifications/index.d.ts +71 -0
- package/dist/plugins/notifications/index.d.ts.map +1 -0
- package/dist/plugins/notifications/index.js +72 -0
- package/dist/plugins/notifications/index.js.map +1 -0
- package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
- package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-manager.js +610 -0
- package/dist/plugins/notifications/notifications-manager.js.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.js +337 -0
- package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
- package/dist/plugins/notifications/types.d.ts +164 -0
- package/dist/plugins/notifications/types.d.ts.map +1 -0
- package/dist/plugins/notifications/types.js +9 -0
- package/dist/plugins/notifications/types.js.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
- package/dist/plugins/parental/adapters/index.d.ts +8 -0
- package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/index.js +7 -0
- package/dist/plugins/parental/adapters/index.js.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
- package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
- package/dist/plugins/parental/index.d.ts +14 -0
- package/dist/plugins/parental/index.d.ts.map +1 -0
- package/dist/plugins/parental/index.js +15 -0
- package/dist/plugins/parental/index.js.map +1 -0
- package/dist/plugins/parental/parental-plugin.d.ts +88 -0
- package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
- package/dist/plugins/parental/parental-plugin.js +666 -0
- package/dist/plugins/parental/parental-plugin.js.map +1 -0
- package/dist/plugins/parental/stores/index.d.ts +7 -0
- package/dist/plugins/parental/stores/index.d.ts.map +1 -0
- package/dist/plugins/parental/stores/index.js +7 -0
- package/dist/plugins/parental/stores/index.js.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.js +209 -0
- package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
- package/dist/plugins/parental/types.d.ts +154 -0
- package/dist/plugins/parental/types.d.ts.map +1 -0
- package/dist/plugins/parental/types.js +10 -0
- package/dist/plugins/parental/types.js.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
- package/dist/plugins/profiles/index.d.ts +12 -0
- package/dist/plugins/profiles/index.d.ts.map +1 -0
- package/dist/plugins/profiles/index.js +13 -0
- package/dist/plugins/profiles/index.js.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.js +481 -0
- package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
- package/dist/plugins/profiles/stores/index.d.ts +9 -0
- package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/index.js +9 -0
- package/dist/plugins/profiles/stores/index.js.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.js +310 -0
- package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
- package/dist/plugins/profiles/types.d.ts +289 -0
- package/dist/plugins/profiles/types.d.ts.map +1 -0
- package/dist/plugins/profiles/types.js +10 -0
- package/dist/plugins/profiles/types.js.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
- package/dist/plugins/subscriptions/index.d.ts +12 -0
- package/dist/plugins/subscriptions/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/index.js +13 -0
- package/dist/plugins/subscriptions/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
- package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/index.js +9 -0
- package/dist/plugins/subscriptions/stores/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
- package/dist/plugins/subscriptions/types.d.ts +308 -0
- package/dist/plugins/subscriptions/types.d.ts.map +1 -0
- package/dist/plugins/subscriptions/types.js +10 -0
- package/dist/plugins/subscriptions/types.js.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
- package/dist/plugins/usage/index.d.ts +12 -0
- package/dist/plugins/usage/index.d.ts.map +1 -0
- package/dist/plugins/usage/index.js +13 -0
- package/dist/plugins/usage/index.js.map +1 -0
- package/dist/plugins/usage/stores/index.d.ts +9 -0
- package/dist/plugins/usage/stores/index.d.ts.map +1 -0
- package/dist/plugins/usage/stores/index.js +9 -0
- package/dist/plugins/usage/stores/index.js.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.js +146 -0
- package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
- package/dist/plugins/usage/types.d.ts +195 -0
- package/dist/plugins/usage/types.d.ts.map +1 -0
- package/dist/plugins/usage/types.js +10 -0
- package/dist/plugins/usage/types.js.map +1 -0
- package/dist/plugins/usage/usage-plugin.d.ts +51 -0
- package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
- package/dist/plugins/usage/usage-plugin.js +412 -0
- package/dist/plugins/usage/usage-plugin.js.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
- package/dist/plugins/users/index.d.ts +2 -2
- package/dist/plugins/users/index.d.ts.map +1 -1
- package/dist/plugins/users/index.js +1 -1
- package/dist/plugins/users/index.js.map +1 -1
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
- package/dist/plugins/users/stores/postgres-store.js +76 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -1
- package/dist/plugins/users/types.d.ts +74 -6
- package/dist/plugins/users/types.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.d.ts +15 -1
- package/dist/plugins/users/users-plugin.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.js +29 -0
- package/dist/plugins/users/users-plugin.js.map +1 -1
- package/dist-ui/assets/index-CynOqPkb.js +469 -0
- package/dist-ui/assets/index-CynOqPkb.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +46 -0
- package/dist-ui-lib/components/StatCard.d.ts +16 -0
- package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +1 -0
- package/dist-ui-lib/index.js +1822 -1611
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
- package/dist-ui-lib/utils/formatters.d.ts +19 -0
- package/package.json +3 -2
- package/src/index.ts +178 -0
- package/src/plugins/bans/bans-plugin.ts +15 -3
- package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
- package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
- package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
- package/src/plugins/devices/adapters/index.ts +13 -0
- package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
- package/src/plugins/devices/devices-plugin.ts +538 -0
- package/src/plugins/devices/index.ts +69 -0
- package/src/plugins/devices/stores/index.ts +9 -0
- package/src/plugins/devices/stores/postgres-store.ts +304 -0
- package/src/plugins/devices/token-utils.ts +213 -0
- package/src/plugins/devices/types.ts +351 -0
- package/src/plugins/index.ts +218 -0
- package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
- package/src/plugins/notifications/index.ts +91 -0
- package/src/plugins/notifications/notifications-manager.ts +773 -0
- package/src/plugins/notifications/notifications-plugin.ts +398 -0
- package/src/plugins/notifications/types.ts +207 -0
- package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
- package/src/plugins/parental/adapters/index.ts +8 -0
- package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
- package/src/plugins/parental/index.ts +55 -0
- package/src/plugins/parental/parental-plugin.ts +759 -0
- package/src/plugins/parental/stores/index.ts +7 -0
- package/src/plugins/parental/stores/postgres-store.ts +304 -0
- package/src/plugins/parental/types.ts +180 -0
- package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
- package/src/plugins/profiles/index.ts +49 -0
- package/src/plugins/profiles/profiles-plugin.ts +546 -0
- package/src/plugins/profiles/stores/index.ts +9 -0
- package/src/plugins/profiles/stores/postgres-store.ts +439 -0
- package/src/plugins/profiles/types.ts +338 -0
- package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
- package/src/plugins/subscriptions/index.ts +51 -0
- package/src/plugins/subscriptions/stores/index.ts +9 -0
- package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
- package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
- package/src/plugins/subscriptions/types.ts +355 -0
- package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
- package/src/plugins/usage/index.ts +39 -0
- package/src/plugins/usage/stores/index.ts +9 -0
- package/src/plugins/usage/stores/postgres-store.ts +213 -0
- package/src/plugins/usage/types.ts +222 -0
- package/src/plugins/usage/usage-plugin.ts +484 -0
- package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
- package/src/plugins/users/index.ts +6 -0
- package/src/plugins/users/stores/postgres-store.ts +104 -0
- package/src/plugins/users/types.ts +82 -6
- package/src/plugins/users/users-plugin.ts +37 -0
- package/ui/src/App.tsx +5 -1
- package/ui/src/api/controlPanelApi.ts +103 -6
- package/ui/src/components/StatCard.tsx +58 -0
- package/ui/src/dashboard/builtInWidgets.tsx +3 -1
- package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
- package/ui/src/dashboard/widgets/index.ts +1 -0
- package/ui/src/pages/NotificationsPage.tsx +417 -0
- package/ui/src/utils/formatters.ts +33 -0
- package/dist-ui/assets/index-D7DoZ9rL.js +0 -478
- package/dist-ui/assets/index-D7DoZ9rL.js.map +0 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Plugin
|
|
3
|
+
*
|
|
4
|
+
* Usage tracking plugin for @qwickapps/server.
|
|
5
|
+
* Tracks daily/monthly feature usage and enforces limits.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
// Import subscription helpers if available
|
|
10
|
+
let getFeatureLimitFn = null;
|
|
11
|
+
// Store instance for helper access
|
|
12
|
+
let currentStore = null;
|
|
13
|
+
let currentConfig = null;
|
|
14
|
+
let cleanupIntervalId = null;
|
|
15
|
+
/**
|
|
16
|
+
* Get current date in YYYY-MM-DD format
|
|
17
|
+
*/
|
|
18
|
+
function getCurrentDate() {
|
|
19
|
+
return new Date().toISOString().split('T')[0];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get tomorrow's date at midnight (for reset time)
|
|
23
|
+
*/
|
|
24
|
+
function getTomorrowMidnight() {
|
|
25
|
+
const tomorrow = new Date();
|
|
26
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
27
|
+
tomorrow.setHours(0, 0, 0, 0);
|
|
28
|
+
return tomorrow;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create the Usage plugin
|
|
32
|
+
*/
|
|
33
|
+
export function createUsagePlugin(config) {
|
|
34
|
+
const debug = config.debug || false;
|
|
35
|
+
const apiPrefix = config.api?.prefix || '/usage';
|
|
36
|
+
function log(message, data) {
|
|
37
|
+
if (debug) {
|
|
38
|
+
console.log(`[UsagePlugin] ${message}`, data || '');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
id: 'usage',
|
|
43
|
+
name: 'Usage',
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
async onStart(_pluginConfig, registry) {
|
|
46
|
+
log('Starting usage plugin');
|
|
47
|
+
// Initialize the store (creates tables if needed)
|
|
48
|
+
await config.store.initialize();
|
|
49
|
+
log('Usage plugin migrations complete');
|
|
50
|
+
// Store references for helper access
|
|
51
|
+
currentStore = config.store;
|
|
52
|
+
currentConfig = config;
|
|
53
|
+
// Try to get the feature limit function from subscriptions plugin
|
|
54
|
+
try {
|
|
55
|
+
const subscriptions = await import('../subscriptions/index.js');
|
|
56
|
+
getFeatureLimitFn = subscriptions.getFeatureLimit;
|
|
57
|
+
log('Subscriptions plugin integration enabled');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
log('Subscriptions plugin not available, limits will not be enforced');
|
|
61
|
+
}
|
|
62
|
+
// Register health check
|
|
63
|
+
registry.registerHealthCheck({
|
|
64
|
+
name: 'usage-store',
|
|
65
|
+
type: 'custom',
|
|
66
|
+
check: async () => {
|
|
67
|
+
try {
|
|
68
|
+
// Simple health check
|
|
69
|
+
return { healthy: true };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { healthy: false };
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
// Run cleanup on startup if configured
|
|
77
|
+
if (config.cleanup?.runOnStartup) {
|
|
78
|
+
const dailyDays = config.cleanup.dailyRetentionDays || 90;
|
|
79
|
+
const monthlyMonths = config.cleanup.monthlyRetentionMonths || 24;
|
|
80
|
+
const dailyDeleted = await config.store.cleanupOldDaily(dailyDays);
|
|
81
|
+
const monthlyDeleted = await config.store.cleanupOldMonthly(monthlyMonths);
|
|
82
|
+
log('Startup cleanup complete', { dailyDeleted, monthlyDeleted });
|
|
83
|
+
}
|
|
84
|
+
// Set up periodic cleanup if configured
|
|
85
|
+
if (config.cleanup?.cleanupIntervalHours && config.cleanup.cleanupIntervalHours > 0) {
|
|
86
|
+
const intervalMs = config.cleanup.cleanupIntervalHours * 60 * 60 * 1000;
|
|
87
|
+
cleanupIntervalId = setInterval(async () => {
|
|
88
|
+
try {
|
|
89
|
+
const dailyDays = config.cleanup?.dailyRetentionDays || 90;
|
|
90
|
+
const monthlyMonths = config.cleanup?.monthlyRetentionMonths || 24;
|
|
91
|
+
const dailyDeleted = await config.store.cleanupOldDaily(dailyDays);
|
|
92
|
+
const monthlyDeleted = await config.store.cleanupOldMonthly(monthlyMonths);
|
|
93
|
+
log('Periodic cleanup complete', { dailyDeleted, monthlyDeleted });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error('[UsagePlugin] Cleanup error:', error);
|
|
97
|
+
}
|
|
98
|
+
}, intervalMs);
|
|
99
|
+
}
|
|
100
|
+
// Add API routes if enabled
|
|
101
|
+
if (config.api?.enabled !== false) {
|
|
102
|
+
// Get daily usage summary
|
|
103
|
+
registry.addRoute({
|
|
104
|
+
method: 'get',
|
|
105
|
+
path: `${apiPrefix}/user/:userId/daily`,
|
|
106
|
+
pluginId: 'usage',
|
|
107
|
+
handler: async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const { userId } = req.params;
|
|
110
|
+
const date = req.query.date || getCurrentDate();
|
|
111
|
+
const summary = await getDailyUsageSummary(userId, date);
|
|
112
|
+
res.json(summary);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('[UsagePlugin] Get daily usage error:', error);
|
|
116
|
+
res.status(500).json({ error: 'Failed to get daily usage' });
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
// Get usage for a specific feature
|
|
121
|
+
registry.addRoute({
|
|
122
|
+
method: 'get',
|
|
123
|
+
path: `${apiPrefix}/user/:userId/feature/:featureCode`,
|
|
124
|
+
pluginId: 'usage',
|
|
125
|
+
handler: async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
const { userId, featureCode } = req.params;
|
|
128
|
+
const status = await getFeatureUsageStatus(userId, featureCode);
|
|
129
|
+
res.json(status);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error('[UsagePlugin] Get feature usage error:', error);
|
|
133
|
+
res.status(500).json({ error: 'Failed to get feature usage' });
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
// Increment usage (with limit check)
|
|
138
|
+
registry.addRoute({
|
|
139
|
+
method: 'post',
|
|
140
|
+
path: `${apiPrefix}/user/:userId/feature/:featureCode/increment`,
|
|
141
|
+
pluginId: 'usage',
|
|
142
|
+
handler: async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const { userId, featureCode } = req.params;
|
|
145
|
+
const amount = parseInt(req.body.amount) || 1;
|
|
146
|
+
const result = await incrementUsage(userId, featureCode, amount);
|
|
147
|
+
res.json(result);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('[UsagePlugin] Increment usage error:', error);
|
|
151
|
+
res.status(500).json({ error: 'Failed to increment usage' });
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
// Check if usage is within limit (without incrementing)
|
|
156
|
+
registry.addRoute({
|
|
157
|
+
method: 'get',
|
|
158
|
+
path: `${apiPrefix}/user/:userId/feature/:featureCode/check`,
|
|
159
|
+
pluginId: 'usage',
|
|
160
|
+
handler: async (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
const { userId, featureCode } = req.params;
|
|
163
|
+
const amount = parseInt(req.query.amount) || 1;
|
|
164
|
+
const result = await checkUsageLimit(userId, featureCode, amount);
|
|
165
|
+
res.json(result);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error('[UsagePlugin] Check usage error:', error);
|
|
169
|
+
res.status(500).json({ error: 'Failed to check usage' });
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
log('Usage plugin started');
|
|
175
|
+
},
|
|
176
|
+
async onStop() {
|
|
177
|
+
log('Stopping usage plugin');
|
|
178
|
+
// Clear cleanup interval
|
|
179
|
+
if (cleanupIntervalId) {
|
|
180
|
+
clearInterval(cleanupIntervalId);
|
|
181
|
+
cleanupIntervalId = null;
|
|
182
|
+
}
|
|
183
|
+
await config.store.shutdown();
|
|
184
|
+
currentStore = null;
|
|
185
|
+
currentConfig = null;
|
|
186
|
+
log('Usage plugin stopped');
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
191
|
+
// Helper Functions
|
|
192
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
193
|
+
/**
|
|
194
|
+
* Get the current usage store instance
|
|
195
|
+
*/
|
|
196
|
+
export function getUsageStore() {
|
|
197
|
+
return currentStore;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get current daily usage for a feature
|
|
201
|
+
*/
|
|
202
|
+
export async function getDailyUsage(userId, featureCode) {
|
|
203
|
+
if (!currentStore) {
|
|
204
|
+
throw new Error('Usage plugin not initialized');
|
|
205
|
+
}
|
|
206
|
+
const usage = await currentStore.getDailyUsage(userId, featureCode);
|
|
207
|
+
return usage?.count || 0;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Increment usage and check limit
|
|
211
|
+
*/
|
|
212
|
+
export async function incrementUsage(userId, featureCode, amount = 1) {
|
|
213
|
+
if (!currentStore) {
|
|
214
|
+
throw new Error('Usage plugin not initialized');
|
|
215
|
+
}
|
|
216
|
+
// Get current usage
|
|
217
|
+
const currentUsage = await getDailyUsage(userId, featureCode);
|
|
218
|
+
// Get limit from subscriptions plugin
|
|
219
|
+
let limit = null;
|
|
220
|
+
if (getFeatureLimitFn) {
|
|
221
|
+
limit = await getFeatureLimitFn(userId, featureCode);
|
|
222
|
+
}
|
|
223
|
+
// Check if increment is allowed
|
|
224
|
+
// limit = -1 means unlimited, null means no subscription/feature disabled
|
|
225
|
+
if (limit === null) {
|
|
226
|
+
return {
|
|
227
|
+
allowed: false,
|
|
228
|
+
current_count: currentUsage,
|
|
229
|
+
limit: null,
|
|
230
|
+
remaining: null,
|
|
231
|
+
reason: 'Feature not available in your subscription',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (limit === 0) {
|
|
235
|
+
return {
|
|
236
|
+
allowed: false,
|
|
237
|
+
current_count: currentUsage,
|
|
238
|
+
limit: 0,
|
|
239
|
+
remaining: 0,
|
|
240
|
+
reason: 'Feature is disabled',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// Check if within limit (-1 = unlimited)
|
|
244
|
+
if (limit !== -1 && currentUsage + amount > limit) {
|
|
245
|
+
return {
|
|
246
|
+
allowed: false,
|
|
247
|
+
current_count: currentUsage,
|
|
248
|
+
limit,
|
|
249
|
+
remaining: Math.max(0, limit - currentUsage),
|
|
250
|
+
resets_at: getTomorrowMidnight(),
|
|
251
|
+
reason: 'Daily limit reached',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// Increment usage
|
|
255
|
+
const newCount = await currentStore.incrementDaily(userId, featureCode, amount);
|
|
256
|
+
return {
|
|
257
|
+
allowed: true,
|
|
258
|
+
current_count: newCount,
|
|
259
|
+
limit,
|
|
260
|
+
remaining: limit === -1 ? -1 : Math.max(0, limit - newCount),
|
|
261
|
+
resets_at: getTomorrowMidnight(),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Check if usage is within limit (without incrementing)
|
|
266
|
+
*/
|
|
267
|
+
export async function checkUsageLimit(userId, featureCode, amount = 1) {
|
|
268
|
+
if (!currentStore) {
|
|
269
|
+
throw new Error('Usage plugin not initialized');
|
|
270
|
+
}
|
|
271
|
+
const currentUsage = await getDailyUsage(userId, featureCode);
|
|
272
|
+
// Get limit from subscriptions plugin
|
|
273
|
+
let limit = null;
|
|
274
|
+
if (getFeatureLimitFn) {
|
|
275
|
+
limit = await getFeatureLimitFn(userId, featureCode);
|
|
276
|
+
}
|
|
277
|
+
if (limit === null) {
|
|
278
|
+
return {
|
|
279
|
+
allowed: false,
|
|
280
|
+
current_count: currentUsage,
|
|
281
|
+
limit: null,
|
|
282
|
+
remaining: null,
|
|
283
|
+
reason: 'Feature not available in your subscription',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (limit === 0) {
|
|
287
|
+
return {
|
|
288
|
+
allowed: false,
|
|
289
|
+
current_count: currentUsage,
|
|
290
|
+
limit: 0,
|
|
291
|
+
remaining: 0,
|
|
292
|
+
reason: 'Feature is disabled',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Check if within limit
|
|
296
|
+
const wouldExceed = limit !== -1 && currentUsage + amount > limit;
|
|
297
|
+
return {
|
|
298
|
+
allowed: !wouldExceed,
|
|
299
|
+
current_count: currentUsage,
|
|
300
|
+
limit,
|
|
301
|
+
remaining: limit === -1 ? -1 : Math.max(0, limit - currentUsage),
|
|
302
|
+
resets_at: getTomorrowMidnight(),
|
|
303
|
+
reason: wouldExceed ? 'Would exceed daily limit' : undefined,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get usage status for a specific feature
|
|
308
|
+
*/
|
|
309
|
+
export async function getFeatureUsageStatus(userId, featureCode) {
|
|
310
|
+
if (!currentStore) {
|
|
311
|
+
throw new Error('Usage plugin not initialized');
|
|
312
|
+
}
|
|
313
|
+
const currentUsage = await getDailyUsage(userId, featureCode);
|
|
314
|
+
// Get limit from subscriptions plugin
|
|
315
|
+
let limit = null;
|
|
316
|
+
if (getFeatureLimitFn) {
|
|
317
|
+
limit = await getFeatureLimitFn(userId, featureCode);
|
|
318
|
+
}
|
|
319
|
+
let remaining = null;
|
|
320
|
+
let percentageUsed = null;
|
|
321
|
+
if (limit !== null) {
|
|
322
|
+
if (limit === -1) {
|
|
323
|
+
remaining = -1; // Unlimited
|
|
324
|
+
}
|
|
325
|
+
else if (limit > 0) {
|
|
326
|
+
remaining = Math.max(0, limit - currentUsage);
|
|
327
|
+
percentageUsed = Math.min(100, Math.round((currentUsage / limit) * 100));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
remaining = 0;
|
|
331
|
+
percentageUsed = 100;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
feature_code: featureCode,
|
|
336
|
+
current: currentUsage,
|
|
337
|
+
limit,
|
|
338
|
+
remaining,
|
|
339
|
+
resets_at: getTomorrowMidnight(),
|
|
340
|
+
percentage_used: percentageUsed,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get daily usage summary for all features
|
|
345
|
+
*/
|
|
346
|
+
export async function getDailyUsageSummary(userId, date) {
|
|
347
|
+
if (!currentStore) {
|
|
348
|
+
throw new Error('Usage plugin not initialized');
|
|
349
|
+
}
|
|
350
|
+
const targetDate = date || getCurrentDate();
|
|
351
|
+
const dailyUsages = await currentStore.getAllDailyUsage(userId, targetDate);
|
|
352
|
+
// Get status for each feature
|
|
353
|
+
const features = await Promise.all(dailyUsages.map(async (usage) => {
|
|
354
|
+
let limit = null;
|
|
355
|
+
if (getFeatureLimitFn) {
|
|
356
|
+
limit = await getFeatureLimitFn(userId, usage.feature_code);
|
|
357
|
+
}
|
|
358
|
+
let remaining = null;
|
|
359
|
+
let percentageUsed = null;
|
|
360
|
+
if (limit !== null) {
|
|
361
|
+
if (limit === -1) {
|
|
362
|
+
remaining = -1;
|
|
363
|
+
}
|
|
364
|
+
else if (limit > 0) {
|
|
365
|
+
remaining = Math.max(0, limit - usage.count);
|
|
366
|
+
percentageUsed = Math.min(100, Math.round((usage.count / limit) * 100));
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
remaining = 0;
|
|
370
|
+
percentageUsed = 100;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
feature_code: usage.feature_code,
|
|
375
|
+
current: usage.count,
|
|
376
|
+
limit,
|
|
377
|
+
remaining,
|
|
378
|
+
resets_at: getTomorrowMidnight(),
|
|
379
|
+
percentage_used: percentageUsed,
|
|
380
|
+
};
|
|
381
|
+
}));
|
|
382
|
+
return {
|
|
383
|
+
user_id: userId,
|
|
384
|
+
period: 'daily',
|
|
385
|
+
period_value: targetDate,
|
|
386
|
+
features,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Reset usage for a feature (admin function)
|
|
391
|
+
*/
|
|
392
|
+
export async function resetUsage(userId, featureCode) {
|
|
393
|
+
if (!currentStore) {
|
|
394
|
+
throw new Error('Usage plugin not initialized');
|
|
395
|
+
}
|
|
396
|
+
await currentStore.resetDailyUsage(userId, featureCode);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get remaining quota for a feature
|
|
400
|
+
*/
|
|
401
|
+
export async function getRemainingQuota(userId, featureCode) {
|
|
402
|
+
const status = await getFeatureUsageStatus(userId, featureCode);
|
|
403
|
+
return status.remaining;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Check if user can use a feature (has remaining quota)
|
|
407
|
+
*/
|
|
408
|
+
export async function canUseFeature(userId, featureCode, amount = 1) {
|
|
409
|
+
const result = await checkUsageLimit(userId, featureCode, amount);
|
|
410
|
+
return result.allowed;
|
|
411
|
+
}
|
|
412
|
+
//# sourceMappingURL=usage-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-plugin.js","sourceRoot":"","sources":["../../../src/plugins/usage/usage-plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL User Store Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for the PostgreSQL user store implementation,
|
|
5
|
+
* focusing on getByIdentifier() and linkIdentifiers() methods.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=postgres-store.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-store.test.d.ts","sourceRoot":"","sources":["../../../../src/plugins/users/__tests__/postgres-store.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL User Store Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for the PostgreSQL user store implementation,
|
|
5
|
+
* focusing on getByIdentifier() and linkIdentifiers() methods.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
10
|
+
import { postgresUserStore } from '../stores/postgres-store.js';
|
|
11
|
+
// Mock user data
|
|
12
|
+
const mockUser = {
|
|
13
|
+
id: 'test-user-id-123',
|
|
14
|
+
email: 'test@example.com',
|
|
15
|
+
name: 'Test User',
|
|
16
|
+
external_id: 'auth0|abc123',
|
|
17
|
+
provider: 'auth0',
|
|
18
|
+
picture: 'https://example.com/avatar.jpg',
|
|
19
|
+
metadata: {
|
|
20
|
+
identifiers: {
|
|
21
|
+
auth0_user_id: 'auth0|abc123',
|
|
22
|
+
wp_user_id: 42,
|
|
23
|
+
keap_contact_id: 12345,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
created_at: new Date('2025-01-01'),
|
|
27
|
+
updated_at: new Date('2025-01-01'),
|
|
28
|
+
last_login_at: new Date('2025-12-13'),
|
|
29
|
+
};
|
|
30
|
+
// Mock pg pool
|
|
31
|
+
const createMockPool = () => {
|
|
32
|
+
const mockQuery = vi.fn();
|
|
33
|
+
return {
|
|
34
|
+
query: mockQuery,
|
|
35
|
+
_mockQuery: mockQuery, // Expose for test assertions
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
describe('PostgreSQL User Store', () => {
|
|
39
|
+
let mockPool;
|
|
40
|
+
let store;
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
mockPool = createMockPool();
|
|
43
|
+
store = postgresUserStore({
|
|
44
|
+
pool: mockPool,
|
|
45
|
+
autoCreateTables: false, // Skip table creation in tests
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('getByIdentifier()', () => {
|
|
49
|
+
it('should throw error when no identifier is provided (UT-001)', async () => {
|
|
50
|
+
const emptyIdentifiers = {};
|
|
51
|
+
await expect(store.getByIdentifier(emptyIdentifiers)).rejects.toThrow('At least one identifier must be provided');
|
|
52
|
+
// Should not make any DB queries
|
|
53
|
+
expect(mockPool._mockQuery).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
it('should find user by email first (priority 1) (UT-002)', async () => {
|
|
56
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] });
|
|
57
|
+
const identifiers = {
|
|
58
|
+
email: 'test@example.com',
|
|
59
|
+
auth0_user_id: 'auth0|abc123',
|
|
60
|
+
wp_user_id: 42,
|
|
61
|
+
};
|
|
62
|
+
const result = await store.getByIdentifier(identifiers);
|
|
63
|
+
expect(result).toEqual(mockUser);
|
|
64
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(mockPool._mockQuery).toHaveBeenCalledWith(expect.stringContaining('LOWER(email) = LOWER($1)'), ['test@example.com']);
|
|
66
|
+
});
|
|
67
|
+
it('should find user by auth0_user_id if email not found (priority 2) (UT-003)', async () => {
|
|
68
|
+
// Email query returns no results
|
|
69
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [] });
|
|
70
|
+
// auth0_user_id query in metadata returns user
|
|
71
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] });
|
|
72
|
+
const identifiers = {
|
|
73
|
+
email: 'nonexistent@example.com',
|
|
74
|
+
auth0_user_id: 'auth0|abc123',
|
|
75
|
+
};
|
|
76
|
+
const result = await store.getByIdentifier(identifiers);
|
|
77
|
+
expect(result).toEqual(mockUser);
|
|
78
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(2);
|
|
79
|
+
expect(mockPool._mockQuery).toHaveBeenNthCalledWith(2, expect.stringContaining("metadata->'identifiers'->>'auth0_user_id'"), ['auth0|abc123']);
|
|
80
|
+
});
|
|
81
|
+
it('should check legacy external_id for auth0 users (UT-004)', async () => {
|
|
82
|
+
// No email provided, so skip email check
|
|
83
|
+
// auth0_user_id in metadata returns no results
|
|
84
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [] });
|
|
85
|
+
// Legacy external_id query returns user
|
|
86
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] });
|
|
87
|
+
const identifiers = {
|
|
88
|
+
auth0_user_id: 'auth0|abc123',
|
|
89
|
+
};
|
|
90
|
+
const result = await store.getByIdentifier(identifiers);
|
|
91
|
+
expect(result).toEqual(mockUser);
|
|
92
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(2);
|
|
93
|
+
expect(mockPool._mockQuery).toHaveBeenNthCalledWith(2, expect.stringContaining('external_id = $1'), ['auth0|abc123']);
|
|
94
|
+
});
|
|
95
|
+
it('should find user by wp_user_id (priority 3) (UT-005)', async () => {
|
|
96
|
+
// All higher priority queries return no results
|
|
97
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [] }); // email
|
|
98
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [] }); // auth0 metadata
|
|
99
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [] }); // auth0 external_id
|
|
100
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] }); // wp_user_id
|
|
101
|
+
const identifiers = {
|
|
102
|
+
email: 'nonexistent@example.com',
|
|
103
|
+
auth0_user_id: 'auth0|xyz',
|
|
104
|
+
wp_user_id: 42,
|
|
105
|
+
};
|
|
106
|
+
const result = await store.getByIdentifier(identifiers);
|
|
107
|
+
expect(result).toEqual(mockUser);
|
|
108
|
+
expect(mockPool._mockQuery).toHaveBeenNthCalledWith(4, expect.stringContaining("(metadata->'identifiers'->>'wp_user_id')::int"), [42]);
|
|
109
|
+
});
|
|
110
|
+
it('should find user by keap_contact_id (priority 4) (UT-006)', async () => {
|
|
111
|
+
// All higher priority queries return no results
|
|
112
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [] }); // wp_user_id
|
|
113
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] }); // keap_contact_id
|
|
114
|
+
const identifiers = {
|
|
115
|
+
wp_user_id: 999,
|
|
116
|
+
keap_contact_id: 12345,
|
|
117
|
+
};
|
|
118
|
+
const result = await store.getByIdentifier(identifiers);
|
|
119
|
+
expect(result).toEqual(mockUser);
|
|
120
|
+
expect(mockPool._mockQuery).toHaveBeenNthCalledWith(2, expect.stringContaining("(metadata->'identifiers'->>'keap_contact_id')::int"), [12345]);
|
|
121
|
+
});
|
|
122
|
+
it('should return null if no user found by any identifier (UT-007)', async () => {
|
|
123
|
+
// All queries return no results
|
|
124
|
+
mockPool._mockQuery.mockResolvedValue({ rows: [] });
|
|
125
|
+
const identifiers = {
|
|
126
|
+
email: 'nonexistent@example.com',
|
|
127
|
+
auth0_user_id: 'auth0|nonexistent',
|
|
128
|
+
wp_user_id: 99999,
|
|
129
|
+
keap_contact_id: 99999,
|
|
130
|
+
};
|
|
131
|
+
const result = await store.getByIdentifier(identifiers);
|
|
132
|
+
expect(result).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
it('should handle wp_user_id of 0 as valid identifier (UT-008)', async () => {
|
|
135
|
+
// Only wp_user_id provided (no email or auth0_user_id)
|
|
136
|
+
// So only 1 query for wp_user_id should be made
|
|
137
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] });
|
|
138
|
+
const identifiers = {
|
|
139
|
+
wp_user_id: 0,
|
|
140
|
+
};
|
|
141
|
+
const result = await store.getByIdentifier(identifiers);
|
|
142
|
+
expect(result).toEqual(mockUser);
|
|
143
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
144
|
+
expect(mockPool._mockQuery).toHaveBeenCalledWith(expect.stringContaining("(metadata->'identifiers'->>'wp_user_id')::int"), [0]);
|
|
145
|
+
});
|
|
146
|
+
it('should handle keap_contact_id of 0 as valid identifier (UT-009)', async () => {
|
|
147
|
+
// Only keap_contact_id provided (no email, auth0_user_id, or wp_user_id)
|
|
148
|
+
// So only 1 query for keap_contact_id should be made
|
|
149
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [mockUser] });
|
|
150
|
+
const identifiers = {
|
|
151
|
+
keap_contact_id: 0,
|
|
152
|
+
};
|
|
153
|
+
const result = await store.getByIdentifier(identifiers);
|
|
154
|
+
expect(result).toEqual(mockUser);
|
|
155
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
156
|
+
expect(mockPool._mockQuery).toHaveBeenCalledWith(expect.stringContaining("(metadata->'identifiers'->>'keap_contact_id')::int"), [0]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('linkIdentifiers()', () => {
|
|
160
|
+
it('should not make DB query when no identifiers provided (UT-010)', async () => {
|
|
161
|
+
await store.linkIdentifiers('user-123', {});
|
|
162
|
+
expect(mockPool._mockQuery).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
it('should update metadata with single identifier (UT-011)', async () => {
|
|
165
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 });
|
|
166
|
+
await store.linkIdentifiers('user-123', {
|
|
167
|
+
wp_user_id: 42,
|
|
168
|
+
});
|
|
169
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(mockPool._mockQuery).toHaveBeenCalledWith(expect.stringContaining('jsonb_set'), [JSON.stringify({ wp_user_id: 42 }), 'user-123']);
|
|
171
|
+
});
|
|
172
|
+
it('should update metadata with multiple identifiers (UT-012)', async () => {
|
|
173
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 });
|
|
174
|
+
await store.linkIdentifiers('user-123', {
|
|
175
|
+
wp_user_id: 42,
|
|
176
|
+
auth0_user_id: 'auth0|abc123',
|
|
177
|
+
keap_contact_id: 12345,
|
|
178
|
+
});
|
|
179
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
180
|
+
const [, args] = mockPool._mockQuery.mock.calls[0];
|
|
181
|
+
const identifiersJson = JSON.parse(args[0]);
|
|
182
|
+
expect(identifiersJson).toEqual({
|
|
183
|
+
wp_user_id: 42,
|
|
184
|
+
auth0_user_id: 'auth0|abc123',
|
|
185
|
+
keap_contact_id: 12345,
|
|
186
|
+
});
|
|
187
|
+
expect(args[1]).toBe('user-123');
|
|
188
|
+
});
|
|
189
|
+
it('should preserve existing identifiers (uses COALESCE) (UT-013)', async () => {
|
|
190
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 });
|
|
191
|
+
await store.linkIdentifiers('user-123', {
|
|
192
|
+
wp_user_id: 42,
|
|
193
|
+
});
|
|
194
|
+
// Verify the query uses COALESCE to preserve existing values
|
|
195
|
+
expect(mockPool._mockQuery).toHaveBeenCalledWith(expect.stringContaining("COALESCE(metadata->'identifiers', '{}'::jsonb)"), expect.any(Array));
|
|
196
|
+
});
|
|
197
|
+
it('should handle undefined values correctly (UT-014)', async () => {
|
|
198
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 });
|
|
199
|
+
await store.linkIdentifiers('user-123', {
|
|
200
|
+
wp_user_id: undefined,
|
|
201
|
+
auth0_user_id: 'auth0|abc123',
|
|
202
|
+
keap_contact_id: undefined,
|
|
203
|
+
});
|
|
204
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
205
|
+
const [, args] = mockPool._mockQuery.mock.calls[0];
|
|
206
|
+
const identifiersJson = JSON.parse(args[0]);
|
|
207
|
+
// Should only include defined values
|
|
208
|
+
expect(identifiersJson).toEqual({
|
|
209
|
+
auth0_user_id: 'auth0|abc123',
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe('getByIds()', () => {
|
|
214
|
+
it('should return empty array for empty input (UT-015)', async () => {
|
|
215
|
+
const result = await store.getByIds([]);
|
|
216
|
+
expect(result).toEqual([]);
|
|
217
|
+
expect(mockPool._mockQuery).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
it('should batch query multiple users (UT-016)', async () => {
|
|
220
|
+
const mockUsers = [mockUser, { ...mockUser, id: 'user-2' }];
|
|
221
|
+
mockPool._mockQuery.mockResolvedValueOnce({ rows: mockUsers });
|
|
222
|
+
const result = await store.getByIds(['user-1', 'user-2']);
|
|
223
|
+
expect(result).toEqual(mockUsers);
|
|
224
|
+
expect(mockPool._mockQuery).toHaveBeenCalledTimes(1);
|
|
225
|
+
expect(mockPool._mockQuery).toHaveBeenCalledWith(expect.stringContaining('id = ANY($1)'), [['user-1', 'user-2']]);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
//# sourceMappingURL=postgres-store.test.js.map
|