@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,465 @@
1
+ /**
2
+ * Parental Plugin Tests
3
+ *
4
+ * Unit tests for the parental controls plugin including:
5
+ * - Plugin lifecycle
6
+ * - Guardian settings management
7
+ * - Profile restrictions
8
+ * - Activity logging
9
+ * - PIN verification
10
+ */
11
+
12
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
13
+ import {
14
+ createParentalPlugin,
15
+ getParentalStore,
16
+ getGuardianSettings,
17
+ updateGuardianSettings,
18
+ verifyPin,
19
+ setPin,
20
+ getRestrictions,
21
+ createRestriction,
22
+ pauseProfile,
23
+ resumeProfile,
24
+ logActivity,
25
+ getActivityLog,
26
+ checkProfileAccess,
27
+ } from '../parental-plugin.js';
28
+ import type {
29
+ ParentalStore,
30
+ GuardianSettings,
31
+ ProfileRestriction,
32
+ ActivityLog,
33
+ ParentalAdapter,
34
+ } from '../types.js';
35
+ import type { PluginRegistry } from '../../../core/plugin-registry.js';
36
+
37
+ describe('Parental Plugin', () => {
38
+ // Mock guardian settings
39
+ const mockSettings: GuardianSettings = {
40
+ id: 'settings-id-123',
41
+ user_id: 'user-123',
42
+ adapter_type: 'kids',
43
+ pin_hash: 'hashed_pin_123',
44
+ pin_failed_attempts: 0,
45
+ pin_locked_until: undefined,
46
+ biometric_enabled: false,
47
+ notifications_enabled: true,
48
+ weekly_report_enabled: true,
49
+ metadata: {},
50
+ updated_at: new Date(),
51
+ };
52
+
53
+ // Mock profile restriction
54
+ const mockRestriction: ProfileRestriction = {
55
+ id: 'restriction-id-123',
56
+ profile_id: 'profile-123',
57
+ restriction_type: 'time_limit',
58
+ daily_limit_minutes: 60,
59
+ schedule: {
60
+ monday: { start: '08:00', end: '20:00' },
61
+ tuesday: { start: '08:00', end: '20:00' },
62
+ },
63
+ is_paused: false,
64
+ pause_until: undefined,
65
+ pause_reason: undefined,
66
+ is_active: true,
67
+ metadata: {},
68
+ updated_at: new Date(),
69
+ };
70
+
71
+ // Mock activity log
72
+ const mockActivityLog: ActivityLog = {
73
+ id: 'activity-id-123',
74
+ user_id: 'user-123',
75
+ profile_id: 'profile-123',
76
+ device_id: 'device-123',
77
+ adapter_type: 'kids',
78
+ activity_type: 'conversation_start',
79
+ details: { conversation_id: 'conv-123' },
80
+ created_at: new Date(),
81
+ };
82
+
83
+ // Mock store factory
84
+ const createMockStore = (): ParentalStore => ({
85
+ name: 'mock',
86
+ initialize: vi.fn().mockResolvedValue(undefined),
87
+ // Guardian settings
88
+ getSettings: vi.fn().mockResolvedValue(mockSettings),
89
+ createSettings: vi.fn().mockResolvedValue(mockSettings),
90
+ updateSettings: vi.fn().mockResolvedValue(mockSettings),
91
+ verifyPin: vi.fn().mockResolvedValue(true),
92
+ setPin: vi.fn().mockResolvedValue(undefined),
93
+ incrementFailedPinAttempts: vi.fn().mockResolvedValue(1),
94
+ resetFailedPinAttempts: vi.fn().mockResolvedValue(undefined),
95
+ // Profile restrictions
96
+ getRestrictions: vi.fn().mockResolvedValue([mockRestriction]),
97
+ createRestriction: vi.fn().mockResolvedValue(mockRestriction),
98
+ updateRestriction: vi.fn().mockResolvedValue(mockRestriction),
99
+ deleteRestriction: vi.fn().mockResolvedValue(true),
100
+ pauseProfile: vi.fn().mockResolvedValue(undefined),
101
+ resumeProfile: vi.fn().mockResolvedValue(undefined),
102
+ // Activity logging
103
+ logActivity: vi.fn().mockResolvedValue(mockActivityLog),
104
+ getActivityLog: vi.fn().mockResolvedValue([mockActivityLog]),
105
+ shutdown: vi.fn().mockResolvedValue(undefined),
106
+ });
107
+
108
+ // Mock adapter factory
109
+ const createMockAdapter = (): ParentalAdapter => ({
110
+ name: 'kids',
111
+ getActivityTypes: vi.fn().mockReturnValue(['conversation_start', 'conversation_end', 'content_filtered']),
112
+ getDefaultDailyLimit: vi.fn().mockReturnValue(60),
113
+ validateRestriction: vi.fn().mockReturnValue({ valid: true }),
114
+ formatActivityDetails: vi.fn().mockImplementation((activity) => activity.details || {}),
115
+ onRestrictionViolation: vi.fn().mockResolvedValue(undefined),
116
+ onDailyLimitReached: vi.fn().mockResolvedValue(undefined),
117
+ });
118
+
119
+ // Mock registry factory
120
+ const createMockRegistry = (): PluginRegistry =>
121
+ ({
122
+ hasPlugin: vi.fn().mockReturnValue(false),
123
+ getPlugin: vi.fn().mockReturnValue(null),
124
+ listPlugins: vi.fn().mockReturnValue([]),
125
+ addRoute: vi.fn(),
126
+ addMenuItem: vi.fn(),
127
+ addPage: vi.fn(),
128
+ addWidget: vi.fn(),
129
+ getRoutes: vi.fn().mockReturnValue([]),
130
+ getMenuItems: vi.fn().mockReturnValue([]),
131
+ getPages: vi.fn().mockReturnValue([]),
132
+ getWidgets: vi.fn().mockReturnValue([]),
133
+ getConfig: vi.fn().mockReturnValue({}),
134
+ setConfig: vi.fn().mockResolvedValue(undefined),
135
+ subscribe: vi.fn().mockReturnValue(() => {}),
136
+ emit: vi.fn(),
137
+ registerHealthCheck: vi.fn(),
138
+ getApp: vi.fn().mockReturnValue({} as any),
139
+ getRouter: vi.fn().mockReturnValue({} as any),
140
+ getLogger: vi.fn().mockReturnValue({
141
+ debug: vi.fn(),
142
+ info: vi.fn(),
143
+ warn: vi.fn(),
144
+ error: vi.fn(),
145
+ }),
146
+ }) as unknown as PluginRegistry;
147
+
148
+ let mockStore: ParentalStore;
149
+ let mockAdapter: ParentalAdapter;
150
+ let mockRegistry: PluginRegistry;
151
+
152
+ beforeEach(() => {
153
+ vi.clearAllMocks();
154
+ mockStore = createMockStore();
155
+ mockAdapter = createMockAdapter();
156
+ mockRegistry = createMockRegistry();
157
+ });
158
+
159
+ afterEach(async () => {
160
+ // Clean up
161
+ const store = getParentalStore();
162
+ if (store) {
163
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
164
+ await plugin.onStop?.();
165
+ }
166
+ });
167
+
168
+ describe('createParentalPlugin', () => {
169
+ it('should create a plugin with correct id', () => {
170
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
171
+ expect(plugin.id).toBe('parental');
172
+ });
173
+
174
+ it('should create a plugin with correct name', () => {
175
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
176
+ expect(plugin.name).toBe('Parental');
177
+ });
178
+
179
+ it('should create a plugin with version', () => {
180
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
181
+ expect(plugin.version).toBe('1.0.0');
182
+ });
183
+ });
184
+
185
+ describe('onStart', () => {
186
+ it('should initialize store on start', async () => {
187
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
188
+ await plugin.onStart({}, mockRegistry);
189
+
190
+ expect(mockStore.initialize).toHaveBeenCalled();
191
+ });
192
+
193
+ it('should register health check', async () => {
194
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
195
+ await plugin.onStart({}, mockRegistry);
196
+
197
+ expect(mockRegistry.registerHealthCheck).toHaveBeenCalledWith(
198
+ expect.objectContaining({
199
+ name: 'parental-store',
200
+ type: 'custom',
201
+ })
202
+ );
203
+ });
204
+ });
205
+
206
+ describe('onStop', () => {
207
+ it('should shutdown store', async () => {
208
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
209
+ await plugin.onStart({}, mockRegistry);
210
+ await plugin.onStop?.();
211
+
212
+ expect(mockStore.shutdown).toHaveBeenCalled();
213
+ });
214
+
215
+ it('should clear store reference', async () => {
216
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
217
+ await plugin.onStart({}, mockRegistry);
218
+ await plugin.onStop?.();
219
+
220
+ expect(getParentalStore()).toBeNull();
221
+ });
222
+ });
223
+
224
+ describe('helper functions', () => {
225
+ describe('getParentalStore', () => {
226
+ it('should return null when plugin not started', () => {
227
+ expect(getParentalStore()).toBeNull();
228
+ });
229
+
230
+ it('should return store when plugin started', async () => {
231
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
232
+ await plugin.onStart({}, mockRegistry);
233
+
234
+ expect(getParentalStore()).toBe(mockStore);
235
+ });
236
+ });
237
+
238
+ describe('getGuardianSettings', () => {
239
+ it('should throw when plugin not initialized', async () => {
240
+ await expect(getGuardianSettings('user-id')).rejects.toThrow(
241
+ 'Parental plugin not initialized'
242
+ );
243
+ });
244
+
245
+ it('should return guardian settings', async () => {
246
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
247
+ await plugin.onStart({}, mockRegistry);
248
+
249
+ const result = await getGuardianSettings('user-123');
250
+ expect(result).toEqual(mockSettings);
251
+ });
252
+ });
253
+
254
+ describe('updateGuardianSettings', () => {
255
+ it('should throw when plugin not initialized', async () => {
256
+ await expect(
257
+ updateGuardianSettings('user-id', { notifications_enabled: false })
258
+ ).rejects.toThrow('Parental plugin not initialized');
259
+ });
260
+
261
+ it('should update settings', async () => {
262
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
263
+ await plugin.onStart({}, mockRegistry);
264
+
265
+ const result = await updateGuardianSettings('user-123', {
266
+ notifications_enabled: false,
267
+ });
268
+
269
+ expect(result).toEqual(mockSettings);
270
+ expect(mockStore.updateSettings).toHaveBeenCalled();
271
+ });
272
+ });
273
+
274
+ describe('verifyPin', () => {
275
+ it('should throw when plugin not initialized', async () => {
276
+ await expect(verifyPin('user-id', '1234')).rejects.toThrow(
277
+ 'Parental plugin not initialized'
278
+ );
279
+ });
280
+
281
+ it('should return true for correct PIN', async () => {
282
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
283
+ await plugin.onStart({}, mockRegistry);
284
+
285
+ const result = await verifyPin('user-123', '1234');
286
+ expect(result).toBe(true);
287
+ });
288
+
289
+ it('should return false for incorrect PIN', async () => {
290
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
291
+ await plugin.onStart({}, mockRegistry);
292
+
293
+ (mockStore.verifyPin as any).mockResolvedValue(false);
294
+
295
+ const result = await verifyPin('user-123', 'wrong');
296
+ expect(result).toBe(false);
297
+ });
298
+ });
299
+
300
+ describe('setPin', () => {
301
+ it('should throw when plugin not initialized', async () => {
302
+ await expect(setPin('user-id', '1234')).rejects.toThrow(
303
+ 'Parental plugin not initialized'
304
+ );
305
+ });
306
+
307
+ it('should set PIN', async () => {
308
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
309
+ await plugin.onStart({}, mockRegistry);
310
+
311
+ await setPin('user-123', '1234');
312
+
313
+ // PIN is hashed before storage, so just verify setPin was called with userId
314
+ expect(mockStore.setPin).toHaveBeenCalled();
315
+ const callArgs = (mockStore.setPin as any).mock.calls[0];
316
+ expect(callArgs[0]).toBe('user-123');
317
+ // Second argument should be a hash (64 char hex string), not the raw PIN
318
+ expect(callArgs[1]).toMatch(/^[a-f0-9]{64}$/);
319
+ });
320
+ });
321
+
322
+ describe('getRestrictions', () => {
323
+ it('should throw when plugin not initialized', async () => {
324
+ await expect(getRestrictions('profile-id')).rejects.toThrow(
325
+ 'Parental plugin not initialized'
326
+ );
327
+ });
328
+
329
+ it('should return profile restrictions', async () => {
330
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
331
+ await plugin.onStart({}, mockRegistry);
332
+
333
+ const result = await getRestrictions('profile-123');
334
+
335
+ expect(result).toHaveLength(1);
336
+ expect(result[0]).toEqual(mockRestriction);
337
+ });
338
+ });
339
+
340
+ describe('createRestriction', () => {
341
+ it('should throw when plugin not initialized', async () => {
342
+ await expect(
343
+ createRestriction({
344
+ profile_id: 'profile-id',
345
+ restriction_type: 'time_limit',
346
+ daily_limit_minutes: 60,
347
+ })
348
+ ).rejects.toThrow('Parental plugin not initialized');
349
+ });
350
+
351
+ it('should create restriction', async () => {
352
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
353
+ await plugin.onStart({}, mockRegistry);
354
+
355
+ const result = await createRestriction({
356
+ profile_id: 'profile-123',
357
+ restriction_type: 'time_limit',
358
+ daily_limit_minutes: 30,
359
+ });
360
+
361
+ expect(result).toEqual(mockRestriction);
362
+ expect(mockStore.createRestriction).toHaveBeenCalled();
363
+ });
364
+ });
365
+
366
+ describe('pauseProfile / resumeProfile', () => {
367
+ it('should pause profile', async () => {
368
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
369
+ await plugin.onStart({}, mockRegistry);
370
+
371
+ const pauseUntil = new Date(Date.now() + 60 * 60 * 1000);
372
+ await pauseProfile('profile-123', pauseUntil, 'Dinner time');
373
+
374
+ expect(mockStore.pauseProfile).toHaveBeenCalledWith(
375
+ 'profile-123',
376
+ pauseUntil,
377
+ 'Dinner time'
378
+ );
379
+ });
380
+
381
+ it('should resume profile', async () => {
382
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
383
+ await plugin.onStart({}, mockRegistry);
384
+
385
+ await resumeProfile('profile-123');
386
+
387
+ expect(mockStore.resumeProfile).toHaveBeenCalledWith('profile-123');
388
+ });
389
+ });
390
+
391
+ describe('logActivity', () => {
392
+ it('should throw when plugin not initialized', async () => {
393
+ await expect(
394
+ logActivity({
395
+ user_id: 'user-123',
396
+ adapter_type: 'kids',
397
+ activity_type: 'conversation_start',
398
+ })
399
+ ).rejects.toThrow('Parental plugin not initialized');
400
+ });
401
+
402
+ it('should log activity', async () => {
403
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
404
+ await plugin.onStart({}, mockRegistry);
405
+
406
+ const result = await logActivity({
407
+ user_id: 'user-123',
408
+ profile_id: 'profile-123',
409
+ adapter_type: 'kids',
410
+ activity_type: 'conversation_start',
411
+ details: { topic: 'dinosaurs' },
412
+ });
413
+
414
+ expect(result).toEqual(mockActivityLog);
415
+ expect(mockStore.logActivity).toHaveBeenCalled();
416
+ });
417
+ });
418
+
419
+ describe('getActivityLog', () => {
420
+ it('should throw when plugin not initialized', async () => {
421
+ await expect(getActivityLog('user-id')).rejects.toThrow(
422
+ 'Parental plugin not initialized'
423
+ );
424
+ });
425
+
426
+ it('should return activity log', async () => {
427
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
428
+ await plugin.onStart({}, mockRegistry);
429
+
430
+ const result = await getActivityLog('user-123');
431
+
432
+ // getActivityLog returns an array of ActivityLog entries
433
+ expect(result).toHaveLength(1);
434
+ expect(result[0]).toEqual(mockActivityLog);
435
+ });
436
+
437
+ it('should filter by profile', async () => {
438
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
439
+ await plugin.onStart({}, mockRegistry);
440
+
441
+ await getActivityLog('user-123', 50, 'profile-123');
442
+
443
+ expect(mockStore.getActivityLog).toHaveBeenCalled();
444
+ });
445
+ });
446
+
447
+ describe('checkProfileAccess', () => {
448
+ it('should throw when plugin not initialized', async () => {
449
+ await expect(checkProfileAccess('profile-id')).rejects.toThrow(
450
+ 'Parental plugin not initialized'
451
+ );
452
+ });
453
+
454
+ it('should return access check result', async () => {
455
+ const plugin = createParentalPlugin({ store: mockStore, adapter: mockAdapter });
456
+ await plugin.onStart({}, mockRegistry);
457
+
458
+ const result = await checkProfileAccess('profile-123');
459
+
460
+ // Should return an object with access info
461
+ expect(result).toHaveProperty('allowed');
462
+ });
463
+ });
464
+ });
465
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Parental Adapters
3
+ *
4
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
5
+ */
6
+
7
+ export { kidsAdapter } from './kids-adapter.js';
8
+ export type { KidsAdapterConfig } from './kids-adapter.js';
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Kids Parental Adapter
3
+ *
4
+ * Adapter for child safety and parental controls in apps like QwickBot.
5
+ * Focuses on content filtering, time limits, and activity monitoring.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import type { ParentalAdapter, CreateRestrictionInput, ActivityLog } from '../types.js';
11
+
12
+ export interface KidsAdapterConfig {
13
+ /** Default daily time limit in minutes */
14
+ defaultDailyLimit?: number;
15
+ /** Minimum allowed age */
16
+ minAge?: number;
17
+ /** Maximum allowed age */
18
+ maxAge?: number;
19
+ /** Custom activity types */
20
+ customActivityTypes?: string[];
21
+ }
22
+
23
+ /**
24
+ * Activity types for kids adapter
25
+ */
26
+ const DEFAULT_ACTIVITY_TYPES = [
27
+ 'conversation_start',
28
+ 'conversation_end',
29
+ 'content_filtered',
30
+ 'vision_used',
31
+ 'voice_used',
32
+ 'time_limit_warning',
33
+ 'time_limit_reached',
34
+ 'schedule_blocked',
35
+ 'profile_paused',
36
+ 'profile_resumed',
37
+ 'settings_changed',
38
+ ];
39
+
40
+ /**
41
+ * Create a kids parental adapter
42
+ */
43
+ export function kidsAdapter(config: KidsAdapterConfig = {}): ParentalAdapter {
44
+ const {
45
+ defaultDailyLimit = 60,
46
+ minAge = 1,
47
+ maxAge = 17,
48
+ customActivityTypes = [],
49
+ } = config;
50
+
51
+ const activityTypes = [...DEFAULT_ACTIVITY_TYPES, ...customActivityTypes];
52
+
53
+ return {
54
+ name: 'kids',
55
+
56
+ getActivityTypes(): string[] {
57
+ return activityTypes;
58
+ },
59
+
60
+ getDefaultDailyLimit(): number {
61
+ return defaultDailyLimit;
62
+ },
63
+
64
+ validateRestriction(restriction: CreateRestrictionInput): { valid: boolean; errors?: string[] } {
65
+ const errors: string[] = [];
66
+
67
+ // Validate restriction type
68
+ const validTypes = ['time_limit', 'schedule', 'content_filter', 'feature_block'];
69
+ if (!validTypes.includes(restriction.restriction_type)) {
70
+ errors.push(`Invalid restriction type: ${restriction.restriction_type}. Valid types: ${validTypes.join(', ')}`);
71
+ }
72
+
73
+ // Validate daily limit
74
+ if (restriction.daily_limit_minutes !== undefined) {
75
+ if (restriction.daily_limit_minutes < 0) {
76
+ errors.push('Daily limit cannot be negative');
77
+ }
78
+ if (restriction.daily_limit_minutes > 1440) {
79
+ errors.push('Daily limit cannot exceed 24 hours (1440 minutes)');
80
+ }
81
+ }
82
+
83
+ // Validate schedule format
84
+ if (restriction.schedule) {
85
+ const validDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
86
+ for (const [day, times] of Object.entries(restriction.schedule)) {
87
+ if (!validDays.includes(day.toLowerCase())) {
88
+ errors.push(`Invalid day in schedule: ${day}`);
89
+ continue;
90
+ }
91
+ if (!times.start || !times.end) {
92
+ errors.push(`Schedule for ${day} must have start and end times`);
93
+ continue;
94
+ }
95
+ // Validate time format (HH:MM)
96
+ const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/;
97
+ if (!timeRegex.test(times.start)) {
98
+ errors.push(`Invalid start time format for ${day}: ${times.start}. Use HH:MM format.`);
99
+ }
100
+ if (!timeRegex.test(times.end)) {
101
+ errors.push(`Invalid end time format for ${day}: ${times.end}. Use HH:MM format.`);
102
+ }
103
+ }
104
+ }
105
+
106
+ return {
107
+ valid: errors.length === 0,
108
+ errors: errors.length > 0 ? errors : undefined,
109
+ };
110
+ },
111
+
112
+ formatActivityDetails(activity: ActivityLog): Record<string, unknown> {
113
+ const details = { ...activity.details };
114
+
115
+ // Add kid-specific formatting
116
+ switch (activity.activity_type) {
117
+ case 'conversation_start':
118
+ return {
119
+ ...details,
120
+ display_text: 'Started a conversation',
121
+ icon: 'chat',
122
+ };
123
+ case 'conversation_end':
124
+ return {
125
+ ...details,
126
+ display_text: `Ended conversation (${details.message_count || 0} messages)`,
127
+ icon: 'chat_end',
128
+ };
129
+ case 'content_filtered':
130
+ return {
131
+ ...details,
132
+ display_text: 'Content was filtered for safety',
133
+ icon: 'shield',
134
+ severity: 'warning',
135
+ };
136
+ case 'vision_used':
137
+ return {
138
+ ...details,
139
+ display_text: 'Used camera/image feature',
140
+ icon: 'camera',
141
+ };
142
+ case 'voice_used':
143
+ return {
144
+ ...details,
145
+ display_text: 'Used voice feature',
146
+ icon: 'microphone',
147
+ };
148
+ case 'time_limit_warning':
149
+ return {
150
+ ...details,
151
+ display_text: `Time limit warning (${details.minutes_remaining || 0} min remaining)`,
152
+ icon: 'clock',
153
+ severity: 'info',
154
+ };
155
+ case 'time_limit_reached':
156
+ return {
157
+ ...details,
158
+ display_text: 'Daily time limit reached',
159
+ icon: 'clock_stop',
160
+ severity: 'warning',
161
+ };
162
+ case 'schedule_blocked':
163
+ return {
164
+ ...details,
165
+ display_text: 'Access blocked outside allowed hours',
166
+ icon: 'schedule',
167
+ severity: 'info',
168
+ };
169
+ case 'profile_paused':
170
+ return {
171
+ ...details,
172
+ display_text: `Profile paused${details.reason ? `: ${details.reason}` : ''}`,
173
+ icon: 'pause',
174
+ severity: 'warning',
175
+ };
176
+ case 'profile_resumed':
177
+ return {
178
+ ...details,
179
+ display_text: 'Profile resumed',
180
+ icon: 'play',
181
+ severity: 'success',
182
+ };
183
+ case 'settings_changed':
184
+ return {
185
+ ...details,
186
+ display_text: 'Parental settings updated',
187
+ icon: 'settings',
188
+ };
189
+ default:
190
+ return details;
191
+ }
192
+ },
193
+
194
+ async onRestrictionViolation(profileId: string, reason: string): Promise<void> {
195
+ // This hook can be used to send push notifications
196
+ // or trigger other actions when a restriction is violated
197
+ console.log(`[KidsAdapter] Restriction violation for profile ${profileId}: ${reason}`);
198
+ },
199
+
200
+ async onDailyLimitReached(profileId: string): Promise<void> {
201
+ // This hook can be used to send notifications to parents
202
+ // when a child reaches their daily limit
203
+ console.log(`[KidsAdapter] Daily limit reached for profile ${profileId}`);
204
+ },
205
+ };
206
+ }