@qwickapps/server 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1 -1
- 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,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devices Plugin
|
|
3
|
+
*
|
|
4
|
+
* Device management plugin for @qwickapps/server.
|
|
5
|
+
* Supports different device types through adapters (compute, mobile, IoT).
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Request, Response } from 'express';
|
|
11
|
+
import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
|
|
12
|
+
import type {
|
|
13
|
+
DevicesPluginConfig,
|
|
14
|
+
DeviceStore,
|
|
15
|
+
DeviceAdapter,
|
|
16
|
+
Device,
|
|
17
|
+
DeviceWithToken,
|
|
18
|
+
CreateDeviceInput,
|
|
19
|
+
UpdateDeviceInput,
|
|
20
|
+
DeviceSearchParams,
|
|
21
|
+
TokenVerificationResult,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
import {
|
|
24
|
+
generateDeviceToken,
|
|
25
|
+
hashToken,
|
|
26
|
+
verifyToken,
|
|
27
|
+
isValidTokenFormat,
|
|
28
|
+
isTokenExpired,
|
|
29
|
+
getTokenExpiration,
|
|
30
|
+
} from './token-utils.js';
|
|
31
|
+
|
|
32
|
+
// Store instances for helper access
|
|
33
|
+
let currentStore: DeviceStore | null = null;
|
|
34
|
+
let currentAdapter: DeviceAdapter | null = null;
|
|
35
|
+
let currentConfig: DevicesPluginConfig | null = null;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create the Devices plugin
|
|
39
|
+
*/
|
|
40
|
+
export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
|
|
41
|
+
const debug = config.debug || false;
|
|
42
|
+
const defaultTokenValidityDays = config.defaultTokenValidityDays || 90;
|
|
43
|
+
const apiPrefix = config.api?.prefix || '/devices';
|
|
44
|
+
|
|
45
|
+
function log(message: string, data?: Record<string, unknown>) {
|
|
46
|
+
if (debug) {
|
|
47
|
+
console.log(`[DevicesPlugin] ${message}`, data || '');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
id: 'devices',
|
|
53
|
+
name: 'Devices',
|
|
54
|
+
version: '1.0.0',
|
|
55
|
+
|
|
56
|
+
async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
|
|
57
|
+
log('Starting devices plugin', { adapter: config.adapter.name });
|
|
58
|
+
|
|
59
|
+
// Initialize the store (creates tables if needed)
|
|
60
|
+
await config.store.initialize();
|
|
61
|
+
log('Devices plugin migrations complete');
|
|
62
|
+
|
|
63
|
+
// Store references for helper access
|
|
64
|
+
currentStore = config.store;
|
|
65
|
+
currentAdapter = config.adapter;
|
|
66
|
+
currentConfig = config;
|
|
67
|
+
|
|
68
|
+
// Register health check
|
|
69
|
+
registry.registerHealthCheck({
|
|
70
|
+
name: 'devices-store',
|
|
71
|
+
type: 'custom',
|
|
72
|
+
check: async () => {
|
|
73
|
+
try {
|
|
74
|
+
await config.store.search({ limit: 1 });
|
|
75
|
+
return {
|
|
76
|
+
healthy: true,
|
|
77
|
+
details: {
|
|
78
|
+
adapter: config.adapter.name,
|
|
79
|
+
tokenPrefix: config.adapter.tokenPrefix,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
} catch {
|
|
83
|
+
return { healthy: false };
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Add API routes if enabled
|
|
89
|
+
if (config.api?.crud !== false) {
|
|
90
|
+
// List/Search devices
|
|
91
|
+
registry.addRoute({
|
|
92
|
+
method: 'get',
|
|
93
|
+
path: apiPrefix,
|
|
94
|
+
pluginId: 'devices',
|
|
95
|
+
handler: async (req: Request, res: Response) => {
|
|
96
|
+
try {
|
|
97
|
+
const params: DeviceSearchParams = {
|
|
98
|
+
org_id: req.query.org_id as string,
|
|
99
|
+
user_id: req.query.user_id as string,
|
|
100
|
+
adapter_type: req.query.adapter_type as string,
|
|
101
|
+
is_active: req.query.is_active === 'true' ? true : req.query.is_active === 'false' ? false : undefined,
|
|
102
|
+
query: req.query.q as string,
|
|
103
|
+
page: parseInt(req.query.page as string) || 1,
|
|
104
|
+
limit: Math.min(parseInt(req.query.limit as string) || 20, 100),
|
|
105
|
+
sortBy: (req.query.sortBy as DeviceSearchParams['sortBy']) || 'created_at',
|
|
106
|
+
sortOrder: (req.query.sortOrder as DeviceSearchParams['sortOrder']) || 'desc',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const result = await config.store.search(params);
|
|
110
|
+
res.json(result);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('[DevicesPlugin] Search error:', error);
|
|
113
|
+
res.status(500).json({ error: 'Failed to search devices' });
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Get device by ID
|
|
119
|
+
registry.addRoute({
|
|
120
|
+
method: 'get',
|
|
121
|
+
path: `${apiPrefix}/:id`,
|
|
122
|
+
pluginId: 'devices',
|
|
123
|
+
handler: async (req: Request, res: Response) => {
|
|
124
|
+
try {
|
|
125
|
+
const device = await config.store.getById(req.params.id);
|
|
126
|
+
if (!device) {
|
|
127
|
+
return res.status(404).json({ error: 'Device not found' });
|
|
128
|
+
}
|
|
129
|
+
res.json(device);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('[DevicesPlugin] Get device error:', error);
|
|
132
|
+
res.status(500).json({ error: 'Failed to get device' });
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Create device
|
|
138
|
+
registry.addRoute({
|
|
139
|
+
method: 'post',
|
|
140
|
+
path: apiPrefix,
|
|
141
|
+
pluginId: 'devices',
|
|
142
|
+
handler: async (req: Request, res: Response) => {
|
|
143
|
+
try {
|
|
144
|
+
const input: CreateDeviceInput = {
|
|
145
|
+
org_id: req.body.org_id,
|
|
146
|
+
user_id: req.body.user_id,
|
|
147
|
+
name: req.body.name,
|
|
148
|
+
token_validity_days: req.body.token_validity_days,
|
|
149
|
+
metadata: req.body.metadata,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Validate using adapter
|
|
153
|
+
const validation = config.adapter.validateDeviceInput(input);
|
|
154
|
+
if (!validation.valid) {
|
|
155
|
+
return res.status(400).json({
|
|
156
|
+
error: 'Validation failed',
|
|
157
|
+
details: validation.errors,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create the device
|
|
162
|
+
const result = await registerDevice(input);
|
|
163
|
+
res.status(201).json(result);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('[DevicesPlugin] Create device error:', error);
|
|
166
|
+
res.status(500).json({ error: 'Failed to create device' });
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Update device
|
|
172
|
+
registry.addRoute({
|
|
173
|
+
method: 'put',
|
|
174
|
+
path: `${apiPrefix}/:id`,
|
|
175
|
+
pluginId: 'devices',
|
|
176
|
+
handler: async (req: Request, res: Response) => {
|
|
177
|
+
try {
|
|
178
|
+
const input: UpdateDeviceInput = {
|
|
179
|
+
name: req.body.name,
|
|
180
|
+
is_active: req.body.is_active,
|
|
181
|
+
metadata: req.body.metadata,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const device = await config.store.update(req.params.id, input);
|
|
185
|
+
if (!device) {
|
|
186
|
+
return res.status(404).json({ error: 'Device not found' });
|
|
187
|
+
}
|
|
188
|
+
res.json(device);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('[DevicesPlugin] Update device error:', error);
|
|
191
|
+
res.status(500).json({ error: 'Failed to update device' });
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Delete device
|
|
197
|
+
registry.addRoute({
|
|
198
|
+
method: 'delete',
|
|
199
|
+
path: `${apiPrefix}/:id`,
|
|
200
|
+
pluginId: 'devices',
|
|
201
|
+
handler: async (req: Request, res: Response) => {
|
|
202
|
+
try {
|
|
203
|
+
const device = await config.store.getById(req.params.id);
|
|
204
|
+
if (!device) {
|
|
205
|
+
return res.status(404).json({ error: 'Device not found' });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const deleted = await config.store.delete(req.params.id);
|
|
209
|
+
if (!deleted) {
|
|
210
|
+
return res.status(404).json({ error: 'Device not found' });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Call adapter hook
|
|
214
|
+
if (config.adapter.onDeviceDeleted) {
|
|
215
|
+
await config.adapter.onDeviceDeleted(device);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
res.status(204).send();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[DevicesPlugin] Delete device error:', error);
|
|
221
|
+
res.status(500).json({ error: 'Failed to delete device' });
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Regenerate token
|
|
227
|
+
registry.addRoute({
|
|
228
|
+
method: 'post',
|
|
229
|
+
path: `${apiPrefix}/:id/regenerate-token`,
|
|
230
|
+
pluginId: 'devices',
|
|
231
|
+
handler: async (req: Request, res: Response) => {
|
|
232
|
+
try {
|
|
233
|
+
const device = await config.store.getById(req.params.id);
|
|
234
|
+
if (!device) {
|
|
235
|
+
return res.status(404).json({ error: 'Device not found' });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const validityDays = req.body.token_validity_days || defaultTokenValidityDays;
|
|
239
|
+
const result = await regenerateToken(req.params.id, validityDays);
|
|
240
|
+
|
|
241
|
+
if (!result) {
|
|
242
|
+
return res.status(500).json({ error: 'Failed to regenerate token' });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
res.json({
|
|
246
|
+
token: result.token,
|
|
247
|
+
expires_at: result.expiresAt,
|
|
248
|
+
message: 'Token regenerated successfully. Store this token securely - it will not be shown again.',
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error('[DevicesPlugin] Regenerate token error:', error);
|
|
252
|
+
res.status(500).json({ error: 'Failed to regenerate token' });
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Token verification endpoint
|
|
259
|
+
if (config.api?.verify !== false) {
|
|
260
|
+
registry.addRoute({
|
|
261
|
+
method: 'post',
|
|
262
|
+
path: `${apiPrefix}/verify`,
|
|
263
|
+
pluginId: 'devices',
|
|
264
|
+
handler: async (req: Request, res: Response) => {
|
|
265
|
+
try {
|
|
266
|
+
const { token } = req.body;
|
|
267
|
+
if (!token) {
|
|
268
|
+
return res.status(400).json({ error: 'Token is required' });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const clientIp = req.ip || req.socket.remoteAddress;
|
|
272
|
+
const result = await verifyDeviceToken(token, clientIp);
|
|
273
|
+
|
|
274
|
+
if (!result.valid) {
|
|
275
|
+
return res.status(401).json({
|
|
276
|
+
valid: false,
|
|
277
|
+
error: result.error,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
res.json({
|
|
282
|
+
valid: true,
|
|
283
|
+
device: result.device,
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error('[DevicesPlugin] Verify token error:', error);
|
|
287
|
+
res.status(500).json({ error: 'Failed to verify token' });
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
log('Devices plugin started');
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
async onStop(): Promise<void> {
|
|
297
|
+
log('Stopping devices plugin');
|
|
298
|
+
await config.store.shutdown();
|
|
299
|
+
currentStore = null;
|
|
300
|
+
currentAdapter = null;
|
|
301
|
+
currentConfig = null;
|
|
302
|
+
log('Devices plugin stopped');
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
308
|
+
// Helper Functions
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get the current device store instance
|
|
313
|
+
*/
|
|
314
|
+
export function getDeviceStore(): DeviceStore | null {
|
|
315
|
+
return currentStore;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get the current device adapter instance
|
|
320
|
+
*/
|
|
321
|
+
export function getDeviceAdapter(): DeviceAdapter | null {
|
|
322
|
+
return currentAdapter;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Register a new device
|
|
327
|
+
*/
|
|
328
|
+
export async function registerDevice(input: CreateDeviceInput): Promise<DeviceWithToken> {
|
|
329
|
+
if (!currentStore || !currentAdapter || !currentConfig) {
|
|
330
|
+
throw new Error('Devices plugin not initialized');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Validate using adapter
|
|
334
|
+
const validation = currentAdapter.validateDeviceInput(input);
|
|
335
|
+
if (!validation.valid) {
|
|
336
|
+
throw new Error(`Validation failed: ${validation.errors?.join(', ')}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Transform metadata using adapter
|
|
340
|
+
const transformedMetadata = currentAdapter.transformForStorage(input);
|
|
341
|
+
|
|
342
|
+
// Generate token
|
|
343
|
+
const tokenValidityDays = input.token_validity_days || currentConfig.defaultTokenValidityDays || 90;
|
|
344
|
+
const { token, hash, prefix } = await generateDeviceToken(currentAdapter.tokenPrefix);
|
|
345
|
+
const expiresAt = getTokenExpiration(tokenValidityDays);
|
|
346
|
+
|
|
347
|
+
// Create device in store
|
|
348
|
+
const device = await currentStore.create({
|
|
349
|
+
...input,
|
|
350
|
+
metadata: transformedMetadata,
|
|
351
|
+
tokenHash: hash,
|
|
352
|
+
tokenPrefix: prefix,
|
|
353
|
+
tokenExpiresAt: expiresAt,
|
|
354
|
+
adapterType: currentAdapter.name,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Call adapter hook
|
|
358
|
+
if (currentAdapter.onDeviceCreated) {
|
|
359
|
+
await currentAdapter.onDeviceCreated(device);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Return device with token (token only shown once)
|
|
363
|
+
return {
|
|
364
|
+
...device,
|
|
365
|
+
token,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Verify a device token
|
|
371
|
+
*/
|
|
372
|
+
export async function verifyDeviceToken(token: string, clientIp?: string): Promise<TokenVerificationResult> {
|
|
373
|
+
if (!currentStore || !currentAdapter) {
|
|
374
|
+
return { valid: false, error: 'Devices plugin not initialized' };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Validate token format
|
|
378
|
+
if (!isValidTokenFormat(token, currentAdapter.tokenPrefix)) {
|
|
379
|
+
return { valid: false, error: 'Invalid token format' };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Hash the token
|
|
383
|
+
const tokenHash = await hashToken(token);
|
|
384
|
+
|
|
385
|
+
// Look up device by token hash
|
|
386
|
+
const device = await currentStore.getByTokenHash(tokenHash);
|
|
387
|
+
if (!device) {
|
|
388
|
+
return { valid: false, error: 'Token not found or expired' };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check if token is expired
|
|
392
|
+
if (isTokenExpired(device.token_expires_at)) {
|
|
393
|
+
return { valid: false, error: 'Token has expired' };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Check if device is active
|
|
397
|
+
if (!device.is_active) {
|
|
398
|
+
return { valid: false, error: 'Device is not active' };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Update last seen
|
|
402
|
+
await currentStore.updateLastSeen(device.id, clientIp);
|
|
403
|
+
|
|
404
|
+
// Call adapter hook
|
|
405
|
+
if (currentAdapter.onDeviceVerified) {
|
|
406
|
+
await currentAdapter.onDeviceVerified(device, clientIp);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { valid: true, device };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get a device by ID
|
|
414
|
+
*/
|
|
415
|
+
export async function getDeviceById(id: string): Promise<Device | null> {
|
|
416
|
+
if (!currentStore) {
|
|
417
|
+
throw new Error('Devices plugin not initialized');
|
|
418
|
+
}
|
|
419
|
+
return currentStore.getById(id);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Update a device
|
|
424
|
+
*/
|
|
425
|
+
export async function updateDevice(id: string, input: UpdateDeviceInput): Promise<Device | null> {
|
|
426
|
+
if (!currentStore) {
|
|
427
|
+
throw new Error('Devices plugin not initialized');
|
|
428
|
+
}
|
|
429
|
+
return currentStore.update(id, input);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Delete a device
|
|
434
|
+
*/
|
|
435
|
+
export async function deleteDevice(id: string): Promise<boolean> {
|
|
436
|
+
if (!currentStore || !currentAdapter) {
|
|
437
|
+
throw new Error('Devices plugin not initialized');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const device = await currentStore.getById(id);
|
|
441
|
+
if (!device) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const deleted = await currentStore.delete(id);
|
|
446
|
+
if (deleted && currentAdapter.onDeviceDeleted) {
|
|
447
|
+
await currentAdapter.onDeviceDeleted(device);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return deleted;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Regenerate token for a device
|
|
455
|
+
*/
|
|
456
|
+
export async function regenerateToken(
|
|
457
|
+
deviceId: string,
|
|
458
|
+
validityDays?: number
|
|
459
|
+
): Promise<{ token: string; expiresAt: Date } | null> {
|
|
460
|
+
if (!currentStore || !currentAdapter || !currentConfig) {
|
|
461
|
+
throw new Error('Devices plugin not initialized');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const device = await currentStore.getById(deviceId);
|
|
465
|
+
if (!device) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const days = validityDays || currentConfig.defaultTokenValidityDays || 90;
|
|
470
|
+
const { token, hash, prefix } = await generateDeviceToken(currentAdapter.tokenPrefix);
|
|
471
|
+
const expiresAt = getTokenExpiration(days);
|
|
472
|
+
|
|
473
|
+
const updated = await currentStore.updateToken(deviceId, hash, prefix, expiresAt);
|
|
474
|
+
if (!updated) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { token, expiresAt };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* List devices for a user
|
|
483
|
+
*/
|
|
484
|
+
export async function listUserDevices(userId: string): Promise<Device[]> {
|
|
485
|
+
if (!currentStore) {
|
|
486
|
+
throw new Error('Devices plugin not initialized');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const result = await currentStore.search({ user_id: userId, limit: 100 });
|
|
490
|
+
return result.devices;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* List devices for an organization
|
|
495
|
+
*/
|
|
496
|
+
export async function listOrgDevices(orgId: string): Promise<Device[]> {
|
|
497
|
+
if (!currentStore) {
|
|
498
|
+
throw new Error('Devices plugin not initialized');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const result = await currentStore.search({ org_id: orgId, limit: 100 });
|
|
502
|
+
return result.devices;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Deactivate a device
|
|
507
|
+
*/
|
|
508
|
+
export async function deactivateDevice(id: string): Promise<boolean> {
|
|
509
|
+
if (!currentStore) {
|
|
510
|
+
throw new Error('Devices plugin not initialized');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const device = await currentStore.update(id, { is_active: false });
|
|
514
|
+
return device !== null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Activate a device
|
|
519
|
+
*/
|
|
520
|
+
export async function activateDevice(id: string): Promise<boolean> {
|
|
521
|
+
if (!currentStore) {
|
|
522
|
+
throw new Error('Devices plugin not initialized');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const device = await currentStore.update(id, { is_active: true });
|
|
526
|
+
return device !== null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Cleanup expired device tokens
|
|
531
|
+
*/
|
|
532
|
+
export async function cleanupExpiredTokens(): Promise<number> {
|
|
533
|
+
if (!currentStore) {
|
|
534
|
+
throw new Error('Devices plugin not initialized');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return currentStore.cleanupExpired();
|
|
538
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devices Plugin
|
|
3
|
+
*
|
|
4
|
+
* Device management plugin with adapter support.
|
|
5
|
+
* Exports all device-related functionality.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Main plugin
|
|
11
|
+
export {
|
|
12
|
+
createDevicesPlugin,
|
|
13
|
+
getDeviceStore,
|
|
14
|
+
getDeviceAdapter,
|
|
15
|
+
registerDevice,
|
|
16
|
+
verifyDeviceToken,
|
|
17
|
+
getDeviceById,
|
|
18
|
+
updateDevice,
|
|
19
|
+
deleteDevice,
|
|
20
|
+
regenerateToken,
|
|
21
|
+
listUserDevices,
|
|
22
|
+
listOrgDevices,
|
|
23
|
+
deactivateDevice,
|
|
24
|
+
activateDevice,
|
|
25
|
+
cleanupExpiredTokens,
|
|
26
|
+
} from './devices-plugin.js';
|
|
27
|
+
|
|
28
|
+
// Types
|
|
29
|
+
export type {
|
|
30
|
+
Device,
|
|
31
|
+
DeviceWithToken,
|
|
32
|
+
CreateDeviceInput,
|
|
33
|
+
UpdateDeviceInput,
|
|
34
|
+
DeviceSearchParams,
|
|
35
|
+
DeviceListResponse,
|
|
36
|
+
TokenVerificationResult,
|
|
37
|
+
DeviceAdapter,
|
|
38
|
+
ValidationResult,
|
|
39
|
+
DeviceStore,
|
|
40
|
+
DevicesPluginConfig,
|
|
41
|
+
DevicesApiConfig,
|
|
42
|
+
PostgresDeviceStoreConfig,
|
|
43
|
+
ComputeDeviceMetadata,
|
|
44
|
+
MobileDeviceMetadata,
|
|
45
|
+
IoTDeviceMetadata,
|
|
46
|
+
} from './types.js';
|
|
47
|
+
|
|
48
|
+
// Stores
|
|
49
|
+
export { postgresDeviceStore } from './stores/index.js';
|
|
50
|
+
|
|
51
|
+
// Adapters
|
|
52
|
+
export { computeDeviceAdapter } from './adapters/index.js';
|
|
53
|
+
export type { ComputeAdapterConfig } from './adapters/index.js';
|
|
54
|
+
|
|
55
|
+
export { mobileDeviceAdapter } from './adapters/index.js';
|
|
56
|
+
export type { MobileAdapterConfig } from './adapters/index.js';
|
|
57
|
+
|
|
58
|
+
// Token utilities
|
|
59
|
+
export {
|
|
60
|
+
generateDeviceToken,
|
|
61
|
+
generatePairingCode,
|
|
62
|
+
hashToken,
|
|
63
|
+
verifyToken,
|
|
64
|
+
isValidTokenFormat,
|
|
65
|
+
isTokenExpired,
|
|
66
|
+
getTokenExpiration,
|
|
67
|
+
DeviceTokens,
|
|
68
|
+
} from './token-utils.js';
|
|
69
|
+
export type { DeviceTokenPair } from './token-utils.js';
|