@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,199 @@
1
+ /**
2
+ * PostgreSQL Device Store
3
+ *
4
+ * Device storage implementation using PostgreSQL.
5
+ * Supports multi-tenant isolation via org_id.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+ /**
10
+ * Create a PostgreSQL device store
11
+ *
12
+ * @param config Configuration including a pg Pool instance
13
+ * @returns DeviceStore implementation
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { Pool } from 'pg';
18
+ * import { postgresDeviceStore } from '@qwickapps/server';
19
+ *
20
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
21
+ * const store = postgresDeviceStore({ pool });
22
+ * ```
23
+ */
24
+ export function postgresDeviceStore(config) {
25
+ const { pool: poolOrFn, tableName = 'devices', schema = 'public', autoCreateTables = true, } = config;
26
+ // Helper to get pool (supports lazy initialization via function)
27
+ const getPool = () => {
28
+ const pool = typeof poolOrFn === 'function' ? poolOrFn() : poolOrFn;
29
+ return pool;
30
+ };
31
+ const devicesTableFull = `"${schema}"."${tableName}"`;
32
+ return {
33
+ name: 'postgres',
34
+ async initialize() {
35
+ if (!autoCreateTables)
36
+ return;
37
+ // Create devices table
38
+ await getPool().query(`
39
+ CREATE TABLE IF NOT EXISTS ${devicesTableFull} (
40
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
41
+ org_id UUID,
42
+ user_id UUID,
43
+ adapter_type VARCHAR(50) NOT NULL,
44
+ name VARCHAR(255) NOT NULL,
45
+ token_hash VARCHAR(64) NOT NULL,
46
+ token_prefix VARCHAR(12),
47
+ token_expires_at TIMESTAMPTZ NOT NULL,
48
+ last_seen_at TIMESTAMPTZ,
49
+ last_ip INET,
50
+ is_active BOOLEAN DEFAULT true,
51
+ metadata JSONB DEFAULT '{}',
52
+ created_at TIMESTAMPTZ DEFAULT NOW(),
53
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
54
+ deleted_at TIMESTAMPTZ
55
+ );
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_org ON ${devicesTableFull}(org_id) WHERE deleted_at IS NULL;
58
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_user ON ${devicesTableFull}(user_id) WHERE deleted_at IS NULL;
59
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_token ON ${devicesTableFull}(token_hash) WHERE is_active = true AND deleted_at IS NULL;
60
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_adapter ON ${devicesTableFull}(adapter_type);
61
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_expires ON ${devicesTableFull}(token_expires_at) WHERE deleted_at IS NULL;
62
+ `);
63
+ },
64
+ async getById(id) {
65
+ const result = await getPool().query(`SELECT * FROM ${devicesTableFull} WHERE id = $1 AND deleted_at IS NULL`, [id]);
66
+ return result.rows[0] || null;
67
+ },
68
+ async getByTokenHash(tokenHash) {
69
+ const result = await getPool().query(`SELECT * FROM ${devicesTableFull}
70
+ WHERE token_hash = $1
71
+ AND is_active = true
72
+ AND deleted_at IS NULL
73
+ AND token_expires_at > NOW()`, [tokenHash]);
74
+ return result.rows[0] || null;
75
+ },
76
+ async create(input) {
77
+ const result = await getPool().query(`INSERT INTO ${devicesTableFull}
78
+ (org_id, user_id, adapter_type, name, token_hash, token_prefix, token_expires_at, metadata)
79
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
80
+ RETURNING *`, [
81
+ input.org_id || null,
82
+ input.user_id || null,
83
+ input.adapterType,
84
+ input.name,
85
+ input.tokenHash,
86
+ input.tokenPrefix,
87
+ input.tokenExpiresAt,
88
+ JSON.stringify(input.metadata || {}),
89
+ ]);
90
+ return result.rows[0];
91
+ },
92
+ async update(id, input) {
93
+ const updates = [];
94
+ const values = [];
95
+ let paramIndex = 1;
96
+ if (input.name !== undefined) {
97
+ updates.push(`name = $${paramIndex++}`);
98
+ values.push(input.name);
99
+ }
100
+ if (input.is_active !== undefined) {
101
+ updates.push(`is_active = $${paramIndex++}`);
102
+ values.push(input.is_active);
103
+ }
104
+ if (input.metadata !== undefined) {
105
+ updates.push(`metadata = $${paramIndex++}`);
106
+ values.push(JSON.stringify(input.metadata));
107
+ }
108
+ if (updates.length === 0) {
109
+ return this.getById(id);
110
+ }
111
+ updates.push(`updated_at = NOW()`);
112
+ values.push(id);
113
+ const result = await getPool().query(`UPDATE ${devicesTableFull}
114
+ SET ${updates.join(', ')}
115
+ WHERE id = $${paramIndex} AND deleted_at IS NULL
116
+ RETURNING *`, values);
117
+ return result.rows[0] || null;
118
+ },
119
+ async delete(id) {
120
+ // Soft delete
121
+ const result = await getPool().query(`UPDATE ${devicesTableFull}
122
+ SET deleted_at = NOW(), is_active = false, updated_at = NOW()
123
+ WHERE id = $1 AND deleted_at IS NULL`, [id]);
124
+ return (result.rowCount ?? 0) > 0;
125
+ },
126
+ async search(params) {
127
+ const { org_id, user_id, adapter_type, is_active, query, page = 1, limit = 20, sortBy = 'created_at', sortOrder = 'desc', } = params;
128
+ const conditions = ['deleted_at IS NULL'];
129
+ const values = [];
130
+ let paramIndex = 1;
131
+ if (org_id) {
132
+ conditions.push(`org_id = $${paramIndex++}`);
133
+ values.push(org_id);
134
+ }
135
+ if (user_id) {
136
+ conditions.push(`user_id = $${paramIndex++}`);
137
+ values.push(user_id);
138
+ }
139
+ if (adapter_type) {
140
+ conditions.push(`adapter_type = $${paramIndex++}`);
141
+ values.push(adapter_type);
142
+ }
143
+ if (is_active !== undefined) {
144
+ conditions.push(`is_active = $${paramIndex++}`);
145
+ values.push(is_active);
146
+ }
147
+ if (query) {
148
+ conditions.push(`LOWER(name) LIKE $${paramIndex++}`);
149
+ values.push(`%${query.toLowerCase()}%`);
150
+ }
151
+ const whereClause = `WHERE ${conditions.join(' AND ')}`;
152
+ // Validate sort column to prevent SQL injection
153
+ const validSortColumns = ['name', 'created_at', 'last_seen_at'];
154
+ const sortColumn = validSortColumns.includes(sortBy) ? sortBy : 'created_at';
155
+ const sortDir = sortOrder === 'asc' ? 'ASC' : 'DESC';
156
+ const offset = (page - 1) * limit;
157
+ // Get total count
158
+ const countResult = await getPool().query(`SELECT COUNT(*) FROM ${devicesTableFull} ${whereClause}`, values);
159
+ const countRow = countResult.rows[0];
160
+ const total = countRow ? parseInt(countRow.count, 10) : 0;
161
+ // Get devices
162
+ const result = await getPool().query(`SELECT * FROM ${devicesTableFull} ${whereClause}
163
+ ORDER BY ${sortColumn} ${sortDir}
164
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [...values, limit, offset]);
165
+ return {
166
+ devices: result.rows,
167
+ total,
168
+ page,
169
+ limit,
170
+ totalPages: Math.ceil(total / limit),
171
+ };
172
+ },
173
+ async updateLastSeen(id, ip) {
174
+ if (ip) {
175
+ await getPool().query(`UPDATE ${devicesTableFull} SET last_seen_at = NOW(), last_ip = $1 WHERE id = $2`, [ip, id]);
176
+ }
177
+ else {
178
+ await getPool().query(`UPDATE ${devicesTableFull} SET last_seen_at = NOW() WHERE id = $1`, [id]);
179
+ }
180
+ },
181
+ async updateToken(id, tokenHash, tokenPrefix, expiresAt) {
182
+ const result = await getPool().query(`UPDATE ${devicesTableFull}
183
+ SET token_hash = $1, token_prefix = $2, token_expires_at = $3, updated_at = NOW()
184
+ WHERE id = $4 AND deleted_at IS NULL`, [tokenHash, tokenPrefix, expiresAt, id]);
185
+ return (result.rowCount ?? 0) > 0;
186
+ },
187
+ async cleanupExpired() {
188
+ // Deactivate expired tokens
189
+ const result = await getPool().query(`UPDATE ${devicesTableFull}
190
+ SET is_active = false, updated_at = NOW()
191
+ WHERE token_expires_at < NOW() AND is_active = true AND deleted_at IS NULL`);
192
+ return result.rowCount ?? 0;
193
+ },
194
+ async shutdown() {
195
+ // Pool is managed externally, nothing to do here
196
+ },
197
+ };
198
+ }
199
+ //# sourceMappingURL=postgres-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-store.js","sourceRoot":"","sources":["../../../../src/plugins/devices/stores/postgres-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Device Token Utilities
3
+ *
4
+ * Utilities for generating, hashing, and verifying device tokens.
5
+ * Adapted from QwickForge's device-tokens implementation.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+ export interface DeviceTokenPair {
10
+ /** Raw token to return to client (store securely!) */
11
+ token: string;
12
+ /** Hashed token to store in database */
13
+ hash: string;
14
+ /** First 8 characters for identification */
15
+ prefix: string;
16
+ }
17
+ export interface TokenVerificationResult {
18
+ /** Whether the token is valid */
19
+ valid: boolean;
20
+ /** Error message if invalid */
21
+ error?: string;
22
+ }
23
+ /**
24
+ * Generate a cryptographically secure device token
25
+ *
26
+ * Returns both the raw token (to send to client) and the hash (to store in DB).
27
+ * The raw token should be shown to the user ONCE and never stored server-side.
28
+ *
29
+ * Token format: `<prefix>_<32 bytes base64url>`
30
+ *
31
+ * @param prefix - Token prefix for identification (e.g., 'qwf_dev', 'qwb_mob')
32
+ * @returns Token pair with raw token, hash, and display prefix
33
+ */
34
+ export declare function generateDeviceToken(prefix: string): Promise<DeviceTokenPair>;
35
+ /**
36
+ * Generate a short-lived pairing code for device registration
37
+ *
38
+ * Used for device pairing flow. User enters this code in web/mobile UI.
39
+ * Format: 6 uppercase alphanumeric characters (e.g., 'A1B2C3')
40
+ *
41
+ * @returns 6-character pairing code
42
+ */
43
+ export declare function generatePairingCode(): string;
44
+ /**
45
+ * Hash a token using SHA-256
46
+ *
47
+ * We use SHA-256 instead of bcrypt for tokens because:
48
+ * 1. Tokens are high-entropy (32 random bytes)
49
+ * 2. No need for slow hashing (not user passwords)
50
+ * 3. Faster verification for high-throughput API calls
51
+ *
52
+ * @param token - Raw token to hash
53
+ * @returns Hex-encoded SHA-256 hash
54
+ */
55
+ export declare function hashToken(token: string): Promise<string>;
56
+ /**
57
+ * Verify a token against its stored hash
58
+ *
59
+ * Constant-time comparison to prevent timing attacks.
60
+ *
61
+ * @param token - Raw token from client
62
+ * @param storedHash - Hash from database
63
+ * @returns Verification result
64
+ */
65
+ export declare function verifyToken(token: string, storedHash: string): Promise<TokenVerificationResult>;
66
+ /**
67
+ * Validate token format (prefix and length)
68
+ *
69
+ * Checks if token matches expected format before attempting verification.
70
+ * Useful for fast rejection of malformed tokens.
71
+ *
72
+ * @param token - Token to validate
73
+ * @param expectedPrefix - Expected token prefix
74
+ * @returns True if format is valid
75
+ */
76
+ export declare function isValidTokenFormat(token: string, expectedPrefix: string): boolean;
77
+ /**
78
+ * Check if token is expired
79
+ *
80
+ * @param expiresAt - Expiration timestamp
81
+ * @returns True if token is expired
82
+ */
83
+ export declare function isTokenExpired(expiresAt: Date): boolean;
84
+ /**
85
+ * Calculate token expiration date
86
+ *
87
+ * @param daysValid - Number of days token should be valid (default: 90)
88
+ * @returns Expiration timestamp
89
+ */
90
+ export declare function getTokenExpiration(daysValid?: number): Date;
91
+ export declare const DeviceTokens: {
92
+ generate: typeof generateDeviceToken;
93
+ generatePairingCode: typeof generatePairingCode;
94
+ hash: typeof hashToken;
95
+ verify: typeof verifyToken;
96
+ isValidFormat: typeof isValidTokenFormat;
97
+ isExpired: typeof isTokenExpired;
98
+ getExpiration: typeof getTokenExpiration;
99
+ };
100
+ //# sourceMappingURL=token-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-utils.d.ts","sourceRoot":"","sources":["../../../src/plugins/devices/token-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBlF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAU5C;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG9D;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,uBAAuB,CAAC,CAsBlC;AAMD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAqBjF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,IAAI,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,MAAW,GAAG,IAAI,CAI/D;AAMD,eAAO,MAAM,YAAY;;;;;;;;CAQxB,CAAC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Device Token Utilities
3
+ *
4
+ * Utilities for generating, hashing, and verifying device tokens.
5
+ * Adapted from QwickForge's device-tokens implementation.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+ import crypto from 'crypto';
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ // Token Generation
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ /**
14
+ * Generate a cryptographically secure device token
15
+ *
16
+ * Returns both the raw token (to send to client) and the hash (to store in DB).
17
+ * The raw token should be shown to the user ONCE and never stored server-side.
18
+ *
19
+ * Token format: `<prefix>_<32 bytes base64url>`
20
+ *
21
+ * @param prefix - Token prefix for identification (e.g., 'qwf_dev', 'qwb_mob')
22
+ * @returns Token pair with raw token, hash, and display prefix
23
+ */
24
+ export async function generateDeviceToken(prefix) {
25
+ // Generate 32 bytes of random data
26
+ const randomBytes = crypto.randomBytes(32);
27
+ // Convert to base64url (URL-safe, no padding)
28
+ const tokenSecret = randomBytes.toString('base64url');
29
+ // Combine prefix with secret
30
+ const token = `${prefix}_${tokenSecret}`;
31
+ // Hash the token for storage
32
+ const hash = await hashToken(token);
33
+ // Get first 8 chars of full token for display
34
+ const displayPrefix = token.substring(0, 8);
35
+ return { token, hash, prefix: displayPrefix };
36
+ }
37
+ /**
38
+ * Generate a short-lived pairing code for device registration
39
+ *
40
+ * Used for device pairing flow. User enters this code in web/mobile UI.
41
+ * Format: 6 uppercase alphanumeric characters (e.g., 'A1B2C3')
42
+ *
43
+ * @returns 6-character pairing code
44
+ */
45
+ export function generatePairingCode() {
46
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Avoid confusing chars (0, O, I, 1)
47
+ let code = '';
48
+ for (let i = 0; i < 6; i++) {
49
+ const randomIndex = crypto.randomInt(0, chars.length);
50
+ code += chars[randomIndex];
51
+ }
52
+ return code;
53
+ }
54
+ // ═══════════════════════════════════════════════════════════════════════════
55
+ // Token Hashing
56
+ // ═══════════════════════════════════════════════════════════════════════════
57
+ /**
58
+ * Hash a token using SHA-256
59
+ *
60
+ * We use SHA-256 instead of bcrypt for tokens because:
61
+ * 1. Tokens are high-entropy (32 random bytes)
62
+ * 2. No need for slow hashing (not user passwords)
63
+ * 3. Faster verification for high-throughput API calls
64
+ *
65
+ * @param token - Raw token to hash
66
+ * @returns Hex-encoded SHA-256 hash
67
+ */
68
+ export async function hashToken(token) {
69
+ const hash = crypto.createHash('sha256').update(token).digest('hex');
70
+ return hash;
71
+ }
72
+ /**
73
+ * Verify a token against its stored hash
74
+ *
75
+ * Constant-time comparison to prevent timing attacks.
76
+ *
77
+ * @param token - Raw token from client
78
+ * @param storedHash - Hash from database
79
+ * @returns Verification result
80
+ */
81
+ export async function verifyToken(token, storedHash) {
82
+ try {
83
+ // Hash the provided token
84
+ const tokenHash = await hashToken(token);
85
+ // Constant-time comparison
86
+ const valid = crypto.timingSafeEqual(Buffer.from(tokenHash, 'hex'), Buffer.from(storedHash, 'hex'));
87
+ if (!valid) {
88
+ return { valid: false, error: 'Invalid token' };
89
+ }
90
+ return { valid: true };
91
+ }
92
+ catch (error) {
93
+ return {
94
+ valid: false,
95
+ error: error instanceof Error ? error.message : 'Token verification failed',
96
+ };
97
+ }
98
+ }
99
+ // ═══════════════════════════════════════════════════════════════════════════
100
+ // Token Validation
101
+ // ═══════════════════════════════════════════════════════════════════════════
102
+ /**
103
+ * Validate token format (prefix and length)
104
+ *
105
+ * Checks if token matches expected format before attempting verification.
106
+ * Useful for fast rejection of malformed tokens.
107
+ *
108
+ * @param token - Token to validate
109
+ * @param expectedPrefix - Expected token prefix
110
+ * @returns True if format is valid
111
+ */
112
+ export function isValidTokenFormat(token, expectedPrefix) {
113
+ // Check prefix
114
+ if (!token.startsWith(`${expectedPrefix}_`)) {
115
+ return false;
116
+ }
117
+ // Extract secret part
118
+ const secret = token.slice(expectedPrefix.length + 1);
119
+ // Validate length (32 bytes base64url = 43 characters)
120
+ if (secret.length !== 43) {
121
+ return false;
122
+ }
123
+ // Validate characters (base64url: A-Za-z0-9_-)
124
+ const base64urlPattern = /^[A-Za-z0-9_-]+$/;
125
+ if (!base64urlPattern.test(secret)) {
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+ /**
131
+ * Check if token is expired
132
+ *
133
+ * @param expiresAt - Expiration timestamp
134
+ * @returns True if token is expired
135
+ */
136
+ export function isTokenExpired(expiresAt) {
137
+ return new Date() > expiresAt;
138
+ }
139
+ /**
140
+ * Calculate token expiration date
141
+ *
142
+ * @param daysValid - Number of days token should be valid (default: 90)
143
+ * @returns Expiration timestamp
144
+ */
145
+ export function getTokenExpiration(daysValid = 90) {
146
+ const expiresAt = new Date();
147
+ expiresAt.setDate(expiresAt.getDate() + daysValid);
148
+ return expiresAt;
149
+ }
150
+ // ═══════════════════════════════════════════════════════════════════════════
151
+ // Exports
152
+ // ═══════════════════════════════════════════════════════════════════════════
153
+ export const DeviceTokens = {
154
+ generate: generateDeviceToken,
155
+ generatePairingCode,
156
+ hash: hashToken,
157
+ verify: verifyToken,
158
+ isValidFormat: isValidTokenFormat,
159
+ isExpired: isTokenExpired,
160
+ getExpiration: getTokenExpiration,
161
+ };
162
+ //# sourceMappingURL=token-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-utils.js","sourceRoot":"","sources":["../../../src/plugins/devices/token-utils.ts"],"names":[],"mappings":"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"}