@qwickapps/server 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
- package/dist/plugins/bans/bans-plugin.js +12 -3
- package/dist/plugins/bans/bans-plugin.js.map +1 -1
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
- package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
- package/dist/plugins/devices/adapters/index.d.ts +12 -0
- package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/index.js +10 -0
- package/dist/plugins/devices/adapters/index.js.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
- package/dist/plugins/devices/devices-plugin.d.ts +70 -0
- package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
- package/dist/plugins/devices/devices-plugin.js +453 -0
- package/dist/plugins/devices/devices-plugin.js.map +1 -0
- package/dist/plugins/devices/index.d.ts +18 -0
- package/dist/plugins/devices/index.d.ts.map +1 -0
- package/dist/plugins/devices/index.js +18 -0
- package/dist/plugins/devices/index.js.map +1 -0
- package/dist/plugins/devices/stores/index.d.ts +9 -0
- package/dist/plugins/devices/stores/index.d.ts.map +1 -0
- package/dist/plugins/devices/stores/index.js +9 -0
- package/dist/plugins/devices/stores/index.js.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.js +199 -0
- package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
- package/dist/plugins/devices/token-utils.d.ts +100 -0
- package/dist/plugins/devices/token-utils.d.ts.map +1 -0
- package/dist/plugins/devices/token-utils.js +162 -0
- package/dist/plugins/devices/token-utils.js.map +1 -0
- package/dist/plugins/devices/types.d.ts +307 -0
- package/dist/plugins/devices/types.d.ts.map +1 -0
- package/dist/plugins/devices/types.js +10 -0
- package/dist/plugins/devices/types.js.map +1 -0
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +13 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
- package/dist/plugins/notifications/index.d.ts +71 -0
- package/dist/plugins/notifications/index.d.ts.map +1 -0
- package/dist/plugins/notifications/index.js +72 -0
- package/dist/plugins/notifications/index.js.map +1 -0
- package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
- package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-manager.js +610 -0
- package/dist/plugins/notifications/notifications-manager.js.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.js +337 -0
- package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
- package/dist/plugins/notifications/types.d.ts +164 -0
- package/dist/plugins/notifications/types.d.ts.map +1 -0
- package/dist/plugins/notifications/types.js +9 -0
- package/dist/plugins/notifications/types.js.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
- package/dist/plugins/parental/adapters/index.d.ts +8 -0
- package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/index.js +7 -0
- package/dist/plugins/parental/adapters/index.js.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
- package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
- package/dist/plugins/parental/index.d.ts +14 -0
- package/dist/plugins/parental/index.d.ts.map +1 -0
- package/dist/plugins/parental/index.js +15 -0
- package/dist/plugins/parental/index.js.map +1 -0
- package/dist/plugins/parental/parental-plugin.d.ts +88 -0
- package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
- package/dist/plugins/parental/parental-plugin.js +666 -0
- package/dist/plugins/parental/parental-plugin.js.map +1 -0
- package/dist/plugins/parental/stores/index.d.ts +7 -0
- package/dist/plugins/parental/stores/index.d.ts.map +1 -0
- package/dist/plugins/parental/stores/index.js +7 -0
- package/dist/plugins/parental/stores/index.js.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.js +209 -0
- package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
- package/dist/plugins/parental/types.d.ts +154 -0
- package/dist/plugins/parental/types.d.ts.map +1 -0
- package/dist/plugins/parental/types.js +10 -0
- package/dist/plugins/parental/types.js.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
- package/dist/plugins/profiles/index.d.ts +12 -0
- package/dist/plugins/profiles/index.d.ts.map +1 -0
- package/dist/plugins/profiles/index.js +13 -0
- package/dist/plugins/profiles/index.js.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.js +481 -0
- package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
- package/dist/plugins/profiles/stores/index.d.ts +9 -0
- package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/index.js +9 -0
- package/dist/plugins/profiles/stores/index.js.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.js +310 -0
- package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
- package/dist/plugins/profiles/types.d.ts +289 -0
- package/dist/plugins/profiles/types.d.ts.map +1 -0
- package/dist/plugins/profiles/types.js +10 -0
- package/dist/plugins/profiles/types.js.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
- package/dist/plugins/subscriptions/index.d.ts +12 -0
- package/dist/plugins/subscriptions/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/index.js +13 -0
- package/dist/plugins/subscriptions/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
- package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/index.js +9 -0
- package/dist/plugins/subscriptions/stores/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
- package/dist/plugins/subscriptions/types.d.ts +308 -0
- package/dist/plugins/subscriptions/types.d.ts.map +1 -0
- package/dist/plugins/subscriptions/types.js +10 -0
- package/dist/plugins/subscriptions/types.js.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
- package/dist/plugins/usage/index.d.ts +12 -0
- package/dist/plugins/usage/index.d.ts.map +1 -0
- package/dist/plugins/usage/index.js +13 -0
- package/dist/plugins/usage/index.js.map +1 -0
- package/dist/plugins/usage/stores/index.d.ts +9 -0
- package/dist/plugins/usage/stores/index.d.ts.map +1 -0
- package/dist/plugins/usage/stores/index.js +9 -0
- package/dist/plugins/usage/stores/index.js.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.js +146 -0
- package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
- package/dist/plugins/usage/types.d.ts +195 -0
- package/dist/plugins/usage/types.d.ts.map +1 -0
- package/dist/plugins/usage/types.js +10 -0
- package/dist/plugins/usage/types.js.map +1 -0
- package/dist/plugins/usage/usage-plugin.d.ts +51 -0
- package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
- package/dist/plugins/usage/usage-plugin.js +412 -0
- package/dist/plugins/usage/usage-plugin.js.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
- package/dist/plugins/users/index.d.ts +2 -2
- package/dist/plugins/users/index.d.ts.map +1 -1
- package/dist/plugins/users/index.js +1 -1
- package/dist/plugins/users/index.js.map +1 -1
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
- package/dist/plugins/users/stores/postgres-store.js +76 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -1
- package/dist/plugins/users/types.d.ts +74 -6
- package/dist/plugins/users/types.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.d.ts +15 -1
- package/dist/plugins/users/users-plugin.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.js +29 -0
- package/dist/plugins/users/users-plugin.js.map +1 -1
- package/dist-ui/assets/index-CynOqPkb.js +469 -0
- package/dist-ui/assets/index-CynOqPkb.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +46 -0
- package/dist-ui-lib/components/StatCard.d.ts +16 -0
- package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +1 -0
- package/dist-ui-lib/index.js +1822 -1611
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
- package/dist-ui-lib/utils/formatters.d.ts +19 -0
- package/package.json +1 -1
- package/src/index.ts +178 -0
- package/src/plugins/bans/bans-plugin.ts +15 -3
- package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
- package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
- package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
- package/src/plugins/devices/adapters/index.ts +13 -0
- package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
- package/src/plugins/devices/devices-plugin.ts +538 -0
- package/src/plugins/devices/index.ts +69 -0
- package/src/plugins/devices/stores/index.ts +9 -0
- package/src/plugins/devices/stores/postgres-store.ts +304 -0
- package/src/plugins/devices/token-utils.ts +213 -0
- package/src/plugins/devices/types.ts +351 -0
- package/src/plugins/index.ts +218 -0
- package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
- package/src/plugins/notifications/index.ts +91 -0
- package/src/plugins/notifications/notifications-manager.ts +773 -0
- package/src/plugins/notifications/notifications-plugin.ts +398 -0
- package/src/plugins/notifications/types.ts +207 -0
- package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
- package/src/plugins/parental/adapters/index.ts +8 -0
- package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
- package/src/plugins/parental/index.ts +55 -0
- package/src/plugins/parental/parental-plugin.ts +759 -0
- package/src/plugins/parental/stores/index.ts +7 -0
- package/src/plugins/parental/stores/postgres-store.ts +304 -0
- package/src/plugins/parental/types.ts +180 -0
- package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
- package/src/plugins/profiles/index.ts +49 -0
- package/src/plugins/profiles/profiles-plugin.ts +546 -0
- package/src/plugins/profiles/stores/index.ts +9 -0
- package/src/plugins/profiles/stores/postgres-store.ts +439 -0
- package/src/plugins/profiles/types.ts +338 -0
- package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
- package/src/plugins/subscriptions/index.ts +51 -0
- package/src/plugins/subscriptions/stores/index.ts +9 -0
- package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
- package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
- package/src/plugins/subscriptions/types.ts +355 -0
- package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
- package/src/plugins/usage/index.ts +39 -0
- package/src/plugins/usage/stores/index.ts +9 -0
- package/src/plugins/usage/stores/postgres-store.ts +213 -0
- package/src/plugins/usage/types.ts +222 -0
- package/src/plugins/usage/usage-plugin.ts +484 -0
- package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
- package/src/plugins/users/index.ts +6 -0
- package/src/plugins/users/stores/postgres-store.ts +104 -0
- package/src/plugins/users/types.ts +82 -6
- package/src/plugins/users/users-plugin.ts +37 -0
- package/ui/src/App.tsx +5 -1
- package/ui/src/api/controlPanelApi.ts +103 -6
- package/ui/src/components/StatCard.tsx +58 -0
- package/ui/src/dashboard/builtInWidgets.tsx +3 -1
- package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
- package/ui/src/dashboard/widgets/index.ts +1 -0
- package/ui/src/pages/NotificationsPage.tsx +417 -0
- package/ui/src/utils/formatters.ts +33 -0
- package/dist-ui/assets/index-D7DoZ9rL.js +0 -478
- package/dist-ui/assets/index-D7DoZ9rL.js.map +0 -1
|
@@ -0,0 +1,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,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
|
+
}
|