@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.
Files changed (271) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +12 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
  6. package/dist/plugins/bans/bans-plugin.js +12 -3
  7. package/dist/plugins/bans/bans-plugin.js.map +1 -1
  8. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
  9. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
  10. package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
  11. package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
  12. package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
  13. package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
  14. package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
  15. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
  16. package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
  17. package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
  18. package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
  19. package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
  20. package/dist/plugins/devices/adapters/index.d.ts +12 -0
  21. package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
  22. package/dist/plugins/devices/adapters/index.js +10 -0
  23. package/dist/plugins/devices/adapters/index.js.map +1 -0
  24. package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
  25. package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
  26. package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
  27. package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
  28. package/dist/plugins/devices/devices-plugin.d.ts +70 -0
  29. package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
  30. package/dist/plugins/devices/devices-plugin.js +453 -0
  31. package/dist/plugins/devices/devices-plugin.js.map +1 -0
  32. package/dist/plugins/devices/index.d.ts +18 -0
  33. package/dist/plugins/devices/index.d.ts.map +1 -0
  34. package/dist/plugins/devices/index.js +18 -0
  35. package/dist/plugins/devices/index.js.map +1 -0
  36. package/dist/plugins/devices/stores/index.d.ts +9 -0
  37. package/dist/plugins/devices/stores/index.d.ts.map +1 -0
  38. package/dist/plugins/devices/stores/index.js +9 -0
  39. package/dist/plugins/devices/stores/index.js.map +1 -0
  40. package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
  41. package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
  42. package/dist/plugins/devices/stores/postgres-store.js +199 -0
  43. package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
  44. package/dist/plugins/devices/token-utils.d.ts +100 -0
  45. package/dist/plugins/devices/token-utils.d.ts.map +1 -0
  46. package/dist/plugins/devices/token-utils.js +162 -0
  47. package/dist/plugins/devices/token-utils.js.map +1 -0
  48. package/dist/plugins/devices/types.d.ts +307 -0
  49. package/dist/plugins/devices/types.d.ts.map +1 -0
  50. package/dist/plugins/devices/types.js +10 -0
  51. package/dist/plugins/devices/types.js.map +1 -0
  52. package/dist/plugins/index.d.ts +14 -2
  53. package/dist/plugins/index.d.ts.map +1 -1
  54. package/dist/plugins/index.js +13 -1
  55. package/dist/plugins/index.js.map +1 -1
  56. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
  57. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
  58. package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
  59. package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
  60. package/dist/plugins/notifications/index.d.ts +71 -0
  61. package/dist/plugins/notifications/index.d.ts.map +1 -0
  62. package/dist/plugins/notifications/index.js +72 -0
  63. package/dist/plugins/notifications/index.js.map +1 -0
  64. package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
  65. package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
  66. package/dist/plugins/notifications/notifications-manager.js +610 -0
  67. package/dist/plugins/notifications/notifications-manager.js.map +1 -0
  68. package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
  69. package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
  70. package/dist/plugins/notifications/notifications-plugin.js +337 -0
  71. package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
  72. package/dist/plugins/notifications/types.d.ts +164 -0
  73. package/dist/plugins/notifications/types.d.ts.map +1 -0
  74. package/dist/plugins/notifications/types.js +9 -0
  75. package/dist/plugins/notifications/types.js.map +1 -0
  76. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
  77. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
  78. package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
  79. package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
  80. package/dist/plugins/parental/adapters/index.d.ts +8 -0
  81. package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
  82. package/dist/plugins/parental/adapters/index.js +7 -0
  83. package/dist/plugins/parental/adapters/index.js.map +1 -0
  84. package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
  85. package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
  86. package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
  87. package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
  88. package/dist/plugins/parental/index.d.ts +14 -0
  89. package/dist/plugins/parental/index.d.ts.map +1 -0
  90. package/dist/plugins/parental/index.js +15 -0
  91. package/dist/plugins/parental/index.js.map +1 -0
  92. package/dist/plugins/parental/parental-plugin.d.ts +88 -0
  93. package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
  94. package/dist/plugins/parental/parental-plugin.js +666 -0
  95. package/dist/plugins/parental/parental-plugin.js.map +1 -0
  96. package/dist/plugins/parental/stores/index.d.ts +7 -0
  97. package/dist/plugins/parental/stores/index.d.ts.map +1 -0
  98. package/dist/plugins/parental/stores/index.js +7 -0
  99. package/dist/plugins/parental/stores/index.js.map +1 -0
  100. package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
  101. package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
  102. package/dist/plugins/parental/stores/postgres-store.js +209 -0
  103. package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
  104. package/dist/plugins/parental/types.d.ts +154 -0
  105. package/dist/plugins/parental/types.d.ts.map +1 -0
  106. package/dist/plugins/parental/types.js +10 -0
  107. package/dist/plugins/parental/types.js.map +1 -0
  108. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
  109. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
  110. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
  111. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
  112. package/dist/plugins/profiles/index.d.ts +12 -0
  113. package/dist/plugins/profiles/index.d.ts.map +1 -0
  114. package/dist/plugins/profiles/index.js +13 -0
  115. package/dist/plugins/profiles/index.js.map +1 -0
  116. package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
  117. package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
  118. package/dist/plugins/profiles/profiles-plugin.js +481 -0
  119. package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
  120. package/dist/plugins/profiles/stores/index.d.ts +9 -0
  121. package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
  122. package/dist/plugins/profiles/stores/index.js +9 -0
  123. package/dist/plugins/profiles/stores/index.js.map +1 -0
  124. package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
  125. package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
  126. package/dist/plugins/profiles/stores/postgres-store.js +310 -0
  127. package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
  128. package/dist/plugins/profiles/types.d.ts +289 -0
  129. package/dist/plugins/profiles/types.d.ts.map +1 -0
  130. package/dist/plugins/profiles/types.js +10 -0
  131. package/dist/plugins/profiles/types.js.map +1 -0
  132. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
  133. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
  134. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
  135. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
  136. package/dist/plugins/subscriptions/index.d.ts +12 -0
  137. package/dist/plugins/subscriptions/index.d.ts.map +1 -0
  138. package/dist/plugins/subscriptions/index.js +13 -0
  139. package/dist/plugins/subscriptions/index.js.map +1 -0
  140. package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
  141. package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
  142. package/dist/plugins/subscriptions/stores/index.js +9 -0
  143. package/dist/plugins/subscriptions/stores/index.js.map +1 -0
  144. package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
  145. package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
  146. package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
  147. package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
  148. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
  149. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
  150. package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
  151. package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
  152. package/dist/plugins/subscriptions/types.d.ts +308 -0
  153. package/dist/plugins/subscriptions/types.d.ts.map +1 -0
  154. package/dist/plugins/subscriptions/types.js +10 -0
  155. package/dist/plugins/subscriptions/types.js.map +1 -0
  156. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
  157. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
  158. package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
  159. package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
  160. package/dist/plugins/usage/index.d.ts +12 -0
  161. package/dist/plugins/usage/index.d.ts.map +1 -0
  162. package/dist/plugins/usage/index.js +13 -0
  163. package/dist/plugins/usage/index.js.map +1 -0
  164. package/dist/plugins/usage/stores/index.d.ts +9 -0
  165. package/dist/plugins/usage/stores/index.d.ts.map +1 -0
  166. package/dist/plugins/usage/stores/index.js +9 -0
  167. package/dist/plugins/usage/stores/index.js.map +1 -0
  168. package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
  169. package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
  170. package/dist/plugins/usage/stores/postgres-store.js +146 -0
  171. package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
  172. package/dist/plugins/usage/types.d.ts +195 -0
  173. package/dist/plugins/usage/types.d.ts.map +1 -0
  174. package/dist/plugins/usage/types.js +10 -0
  175. package/dist/plugins/usage/types.js.map +1 -0
  176. package/dist/plugins/usage/usage-plugin.d.ts +51 -0
  177. package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
  178. package/dist/plugins/usage/usage-plugin.js +412 -0
  179. package/dist/plugins/usage/usage-plugin.js.map +1 -0
  180. package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
  181. package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
  182. package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
  183. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
  184. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  185. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  186. package/dist/plugins/users/index.d.ts +2 -2
  187. package/dist/plugins/users/index.d.ts.map +1 -1
  188. package/dist/plugins/users/index.js +1 -1
  189. package/dist/plugins/users/index.js.map +1 -1
  190. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  191. package/dist/plugins/users/stores/postgres-store.js +76 -0
  192. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  193. package/dist/plugins/users/types.d.ts +74 -6
  194. package/dist/plugins/users/types.d.ts.map +1 -1
  195. package/dist/plugins/users/users-plugin.d.ts +15 -1
  196. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  197. package/dist/plugins/users/users-plugin.js +29 -0
  198. package/dist/plugins/users/users-plugin.js.map +1 -1
  199. package/dist-ui/assets/index-CynOqPkb.js +469 -0
  200. package/dist-ui/assets/index-CynOqPkb.js.map +1 -0
  201. package/dist-ui/index.html +1 -1
  202. package/dist-ui-lib/api/controlPanelApi.d.ts +46 -0
  203. package/dist-ui-lib/components/StatCard.d.ts +16 -0
  204. package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
  205. package/dist-ui-lib/dashboard/widgets/index.d.ts +1 -0
  206. package/dist-ui-lib/index.js +1822 -1611
  207. package/dist-ui-lib/index.js.map +1 -1
  208. package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
  209. package/dist-ui-lib/utils/formatters.d.ts +19 -0
  210. package/package.json +1 -1
  211. package/src/index.ts +178 -0
  212. package/src/plugins/bans/bans-plugin.ts +15 -3
  213. package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
  214. package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
  215. package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
  216. package/src/plugins/devices/adapters/index.ts +13 -0
  217. package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
  218. package/src/plugins/devices/devices-plugin.ts +538 -0
  219. package/src/plugins/devices/index.ts +69 -0
  220. package/src/plugins/devices/stores/index.ts +9 -0
  221. package/src/plugins/devices/stores/postgres-store.ts +304 -0
  222. package/src/plugins/devices/token-utils.ts +213 -0
  223. package/src/plugins/devices/types.ts +351 -0
  224. package/src/plugins/index.ts +218 -0
  225. package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
  226. package/src/plugins/notifications/index.ts +91 -0
  227. package/src/plugins/notifications/notifications-manager.ts +773 -0
  228. package/src/plugins/notifications/notifications-plugin.ts +398 -0
  229. package/src/plugins/notifications/types.ts +207 -0
  230. package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
  231. package/src/plugins/parental/adapters/index.ts +8 -0
  232. package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
  233. package/src/plugins/parental/index.ts +55 -0
  234. package/src/plugins/parental/parental-plugin.ts +759 -0
  235. package/src/plugins/parental/stores/index.ts +7 -0
  236. package/src/plugins/parental/stores/postgres-store.ts +304 -0
  237. package/src/plugins/parental/types.ts +180 -0
  238. package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
  239. package/src/plugins/profiles/index.ts +49 -0
  240. package/src/plugins/profiles/profiles-plugin.ts +546 -0
  241. package/src/plugins/profiles/stores/index.ts +9 -0
  242. package/src/plugins/profiles/stores/postgres-store.ts +439 -0
  243. package/src/plugins/profiles/types.ts +338 -0
  244. package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
  245. package/src/plugins/subscriptions/index.ts +51 -0
  246. package/src/plugins/subscriptions/stores/index.ts +9 -0
  247. package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
  248. package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
  249. package/src/plugins/subscriptions/types.ts +355 -0
  250. package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
  251. package/src/plugins/usage/index.ts +39 -0
  252. package/src/plugins/usage/stores/index.ts +9 -0
  253. package/src/plugins/usage/stores/postgres-store.ts +213 -0
  254. package/src/plugins/usage/types.ts +222 -0
  255. package/src/plugins/usage/usage-plugin.ts +484 -0
  256. package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
  257. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  258. package/src/plugins/users/index.ts +6 -0
  259. package/src/plugins/users/stores/postgres-store.ts +104 -0
  260. package/src/plugins/users/types.ts +82 -6
  261. package/src/plugins/users/users-plugin.ts +37 -0
  262. package/ui/src/App.tsx +5 -1
  263. package/ui/src/api/controlPanelApi.ts +103 -6
  264. package/ui/src/components/StatCard.tsx +58 -0
  265. package/ui/src/dashboard/builtInWidgets.tsx +3 -1
  266. package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
  267. package/ui/src/dashboard/widgets/index.ts +1 -0
  268. package/ui/src/pages/NotificationsPage.tsx +417 -0
  269. package/ui/src/utils/formatters.ts +33 -0
  270. package/dist-ui/assets/index-D7DoZ9rL.js +0 -478
  271. 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';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Device Stores
3
+ *
4
+ * Export all device storage implementations.
5
+ *
6
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
+ */
8
+
9
+ export { postgresDeviceStore } from './postgres-store.js';