@qwickapps/server 1.4.0 → 1.5.1
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/CHANGELOG.md +507 -0
- package/README.md +9 -0
- 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 +3 -2
- 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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Parental Store
|
|
3
|
+
*
|
|
4
|
+
* Parental controls storage implementation using PostgreSQL.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ParentalStore,
|
|
11
|
+
GuardianSettings,
|
|
12
|
+
ProfileRestriction,
|
|
13
|
+
ActivityLog,
|
|
14
|
+
CreateGuardianSettingsInput,
|
|
15
|
+
UpdateGuardianSettingsInput,
|
|
16
|
+
CreateRestrictionInput,
|
|
17
|
+
LogActivityInput,
|
|
18
|
+
PostgresParentalStoreConfig,
|
|
19
|
+
} from '../types.js';
|
|
20
|
+
|
|
21
|
+
interface PgPool {
|
|
22
|
+
query(text: string, values?: unknown[]): Promise<{ rows: unknown[]; rowCount: number | null }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function postgresParentalStore(config: PostgresParentalStoreConfig): ParentalStore {
|
|
26
|
+
const {
|
|
27
|
+
pool: poolOrFn,
|
|
28
|
+
settingsTable = 'guardian_settings',
|
|
29
|
+
restrictionsTable = 'profile_restrictions',
|
|
30
|
+
activityTable = 'activity_log',
|
|
31
|
+
schema = 'public',
|
|
32
|
+
autoCreateTables = true,
|
|
33
|
+
} = config;
|
|
34
|
+
|
|
35
|
+
const getPool = (): PgPool => {
|
|
36
|
+
const pool = typeof poolOrFn === 'function' ? poolOrFn() : poolOrFn;
|
|
37
|
+
return pool as PgPool;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const settingsFull = `"${schema}"."${settingsTable}"`;
|
|
41
|
+
const restrictionsFull = `"${schema}"."${restrictionsTable}"`;
|
|
42
|
+
const activityFull = `"${schema}"."${activityTable}"`;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
name: 'postgres',
|
|
46
|
+
|
|
47
|
+
async initialize(): Promise<void> {
|
|
48
|
+
if (!autoCreateTables) return;
|
|
49
|
+
|
|
50
|
+
await getPool().query(`
|
|
51
|
+
CREATE TABLE IF NOT EXISTS ${settingsFull} (
|
|
52
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
53
|
+
user_id UUID UNIQUE NOT NULL,
|
|
54
|
+
adapter_type VARCHAR(50) NOT NULL,
|
|
55
|
+
pin_hash VARCHAR(64),
|
|
56
|
+
pin_failed_attempts INTEGER DEFAULT 0,
|
|
57
|
+
pin_locked_until TIMESTAMPTZ,
|
|
58
|
+
biometric_enabled BOOLEAN DEFAULT false,
|
|
59
|
+
notifications_enabled BOOLEAN DEFAULT true,
|
|
60
|
+
weekly_report_enabled BOOLEAN DEFAULT true,
|
|
61
|
+
metadata JSONB DEFAULT '{}',
|
|
62
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
63
|
+
);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_${settingsTable}_user ON ${settingsFull}(user_id);
|
|
65
|
+
`);
|
|
66
|
+
|
|
67
|
+
await getPool().query(`
|
|
68
|
+
CREATE TABLE IF NOT EXISTS ${restrictionsFull} (
|
|
69
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
70
|
+
profile_id UUID NOT NULL,
|
|
71
|
+
restriction_type VARCHAR(50) NOT NULL,
|
|
72
|
+
daily_limit_minutes INTEGER,
|
|
73
|
+
schedule JSONB,
|
|
74
|
+
is_paused BOOLEAN DEFAULT false,
|
|
75
|
+
pause_until TIMESTAMPTZ,
|
|
76
|
+
pause_reason TEXT,
|
|
77
|
+
is_active BOOLEAN DEFAULT true,
|
|
78
|
+
metadata JSONB DEFAULT '{}',
|
|
79
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
80
|
+
);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_${restrictionsTable}_profile ON ${restrictionsFull}(profile_id);
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
await getPool().query(`
|
|
85
|
+
CREATE TABLE IF NOT EXISTS ${activityFull} (
|
|
86
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
87
|
+
user_id UUID NOT NULL,
|
|
88
|
+
profile_id UUID,
|
|
89
|
+
device_id UUID,
|
|
90
|
+
adapter_type VARCHAR(50) NOT NULL,
|
|
91
|
+
activity_type VARCHAR(50) NOT NULL,
|
|
92
|
+
details JSONB DEFAULT '{}',
|
|
93
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
94
|
+
);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_${activityTable}_user_profile ON ${activityFull}(user_id, profile_id);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_${activityTable}_created ON ${activityFull}(created_at DESC);
|
|
97
|
+
`);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async getSettings(userId: string): Promise<GuardianSettings | null> {
|
|
101
|
+
const result = await getPool().query(
|
|
102
|
+
`SELECT * FROM ${settingsFull} WHERE user_id = $1`,
|
|
103
|
+
[userId]
|
|
104
|
+
);
|
|
105
|
+
return (result.rows[0] as GuardianSettings) || null;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
async createSettings(input: CreateGuardianSettingsInput): Promise<GuardianSettings> {
|
|
109
|
+
const result = await getPool().query(
|
|
110
|
+
`INSERT INTO ${settingsFull} (user_id, adapter_type, pin_hash, biometric_enabled, notifications_enabled, weekly_report_enabled, metadata)
|
|
111
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
112
|
+
RETURNING *`,
|
|
113
|
+
[
|
|
114
|
+
input.user_id,
|
|
115
|
+
input.adapter_type,
|
|
116
|
+
input.pin || null,
|
|
117
|
+
input.biometric_enabled || false,
|
|
118
|
+
input.notifications_enabled !== false,
|
|
119
|
+
input.weekly_report_enabled !== false,
|
|
120
|
+
JSON.stringify(input.metadata || {}),
|
|
121
|
+
]
|
|
122
|
+
);
|
|
123
|
+
return result.rows[0] as GuardianSettings;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
async updateSettings(userId: string, input: UpdateGuardianSettingsInput): Promise<GuardianSettings | null> {
|
|
127
|
+
const updates: string[] = [];
|
|
128
|
+
const values: unknown[] = [];
|
|
129
|
+
let idx = 1;
|
|
130
|
+
|
|
131
|
+
if (input.biometric_enabled !== undefined) {
|
|
132
|
+
updates.push(`biometric_enabled = $${idx++}`);
|
|
133
|
+
values.push(input.biometric_enabled);
|
|
134
|
+
}
|
|
135
|
+
if (input.notifications_enabled !== undefined) {
|
|
136
|
+
updates.push(`notifications_enabled = $${idx++}`);
|
|
137
|
+
values.push(input.notifications_enabled);
|
|
138
|
+
}
|
|
139
|
+
if (input.weekly_report_enabled !== undefined) {
|
|
140
|
+
updates.push(`weekly_report_enabled = $${idx++}`);
|
|
141
|
+
values.push(input.weekly_report_enabled);
|
|
142
|
+
}
|
|
143
|
+
if (input.metadata !== undefined) {
|
|
144
|
+
updates.push(`metadata = $${idx++}`);
|
|
145
|
+
values.push(JSON.stringify(input.metadata));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (updates.length === 0) return this.getSettings(userId);
|
|
149
|
+
|
|
150
|
+
updates.push('updated_at = NOW()');
|
|
151
|
+
values.push(userId);
|
|
152
|
+
|
|
153
|
+
const result = await getPool().query(
|
|
154
|
+
`UPDATE ${settingsFull} SET ${updates.join(', ')} WHERE user_id = $${idx} RETURNING *`,
|
|
155
|
+
values
|
|
156
|
+
);
|
|
157
|
+
return (result.rows[0] as GuardianSettings) || null;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
async setPin(userId: string, pinHash: string): Promise<void> {
|
|
161
|
+
await getPool().query(
|
|
162
|
+
`UPDATE ${settingsFull} SET pin_hash = $1, pin_failed_attempts = 0, pin_locked_until = NULL, updated_at = NOW() WHERE user_id = $2`,
|
|
163
|
+
[pinHash, userId]
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async verifyPin(userId: string, pinHash: string): Promise<boolean> {
|
|
168
|
+
const result = await getPool().query(
|
|
169
|
+
`SELECT pin_hash, pin_locked_until FROM ${settingsFull} WHERE user_id = $1`,
|
|
170
|
+
[userId]
|
|
171
|
+
);
|
|
172
|
+
if (!result.rows[0]) return false;
|
|
173
|
+
|
|
174
|
+
const settings = result.rows[0] as { pin_hash: string | null; pin_locked_until: Date | null };
|
|
175
|
+
if (settings.pin_locked_until && new Date() < settings.pin_locked_until) return false;
|
|
176
|
+
|
|
177
|
+
return settings.pin_hash === pinHash;
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async incrementFailedPinAttempts(userId: string): Promise<number> {
|
|
181
|
+
const result = await getPool().query(
|
|
182
|
+
`UPDATE ${settingsFull} SET pin_failed_attempts = pin_failed_attempts + 1, updated_at = NOW()
|
|
183
|
+
WHERE user_id = $1 RETURNING pin_failed_attempts`,
|
|
184
|
+
[userId]
|
|
185
|
+
);
|
|
186
|
+
return (result.rows[0] as { pin_failed_attempts: number })?.pin_failed_attempts || 0;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async resetFailedPinAttempts(userId: string): Promise<void> {
|
|
190
|
+
await getPool().query(
|
|
191
|
+
`UPDATE ${settingsFull} SET pin_failed_attempts = 0, pin_locked_until = NULL, updated_at = NOW() WHERE user_id = $1`,
|
|
192
|
+
[userId]
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async getRestrictions(profileId: string): Promise<ProfileRestriction[]> {
|
|
197
|
+
const result = await getPool().query(
|
|
198
|
+
`SELECT * FROM ${restrictionsFull} WHERE profile_id = $1 AND is_active = true`,
|
|
199
|
+
[profileId]
|
|
200
|
+
);
|
|
201
|
+
return result.rows as ProfileRestriction[];
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async createRestriction(input: CreateRestrictionInput): Promise<ProfileRestriction> {
|
|
205
|
+
const result = await getPool().query(
|
|
206
|
+
`INSERT INTO ${restrictionsFull} (profile_id, restriction_type, daily_limit_minutes, schedule, metadata)
|
|
207
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
208
|
+
RETURNING *`,
|
|
209
|
+
[
|
|
210
|
+
input.profile_id,
|
|
211
|
+
input.restriction_type,
|
|
212
|
+
input.daily_limit_minutes || null,
|
|
213
|
+
input.schedule ? JSON.stringify(input.schedule) : null,
|
|
214
|
+
JSON.stringify(input.metadata || {}),
|
|
215
|
+
]
|
|
216
|
+
);
|
|
217
|
+
return result.rows[0] as ProfileRestriction;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
async updateRestriction(id: string, updates: Partial<ProfileRestriction>): Promise<ProfileRestriction | null> {
|
|
221
|
+
const setClause: string[] = [];
|
|
222
|
+
const values: unknown[] = [];
|
|
223
|
+
let idx = 1;
|
|
224
|
+
|
|
225
|
+
const allowedFields = ['daily_limit_minutes', 'schedule', 'is_paused', 'pause_until', 'pause_reason', 'is_active', 'metadata'];
|
|
226
|
+
for (const field of allowedFields) {
|
|
227
|
+
if (updates[field as keyof ProfileRestriction] !== undefined) {
|
|
228
|
+
setClause.push(`${field} = $${idx++}`);
|
|
229
|
+
const val = updates[field as keyof ProfileRestriction];
|
|
230
|
+
values.push(field === 'schedule' || field === 'metadata' ? JSON.stringify(val) : val);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (setClause.length === 0) return null;
|
|
235
|
+
setClause.push('updated_at = NOW()');
|
|
236
|
+
values.push(id);
|
|
237
|
+
|
|
238
|
+
const result = await getPool().query(
|
|
239
|
+
`UPDATE ${restrictionsFull} SET ${setClause.join(', ')} WHERE id = $${idx} RETURNING *`,
|
|
240
|
+
values
|
|
241
|
+
);
|
|
242
|
+
return (result.rows[0] as ProfileRestriction) || null;
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
async deleteRestriction(id: string): Promise<boolean> {
|
|
246
|
+
const result = await getPool().query(
|
|
247
|
+
`UPDATE ${restrictionsFull} SET is_active = false, updated_at = NOW() WHERE id = $1`,
|
|
248
|
+
[id]
|
|
249
|
+
);
|
|
250
|
+
return (result.rowCount ?? 0) > 0;
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
async pauseProfile(profileId: string, until?: Date, reason?: string): Promise<void> {
|
|
254
|
+
await getPool().query(
|
|
255
|
+
`UPDATE ${restrictionsFull} SET is_paused = true, pause_until = $2, pause_reason = $3, updated_at = NOW()
|
|
256
|
+
WHERE profile_id = $1 AND is_active = true`,
|
|
257
|
+
[profileId, until || null, reason || null]
|
|
258
|
+
);
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
async resumeProfile(profileId: string): Promise<void> {
|
|
262
|
+
await getPool().query(
|
|
263
|
+
`UPDATE ${restrictionsFull} SET is_paused = false, pause_until = NULL, pause_reason = NULL, updated_at = NOW()
|
|
264
|
+
WHERE profile_id = $1`,
|
|
265
|
+
[profileId]
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
async logActivity(input: LogActivityInput): Promise<ActivityLog> {
|
|
270
|
+
const result = await getPool().query(
|
|
271
|
+
`INSERT INTO ${activityFull} (user_id, profile_id, device_id, adapter_type, activity_type, details)
|
|
272
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
273
|
+
RETURNING *`,
|
|
274
|
+
[
|
|
275
|
+
input.user_id,
|
|
276
|
+
input.profile_id || null,
|
|
277
|
+
input.device_id || null,
|
|
278
|
+
input.adapter_type,
|
|
279
|
+
input.activity_type,
|
|
280
|
+
JSON.stringify(input.details || {}),
|
|
281
|
+
]
|
|
282
|
+
);
|
|
283
|
+
return result.rows[0] as ActivityLog;
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
async getActivityLog(userId: string, limit = 100, profileId?: string): Promise<ActivityLog[]> {
|
|
287
|
+
let query = `SELECT * FROM ${activityFull} WHERE user_id = $1`;
|
|
288
|
+
const values: unknown[] = [userId];
|
|
289
|
+
|
|
290
|
+
if (profileId) {
|
|
291
|
+
query += ` AND profile_id = $2`;
|
|
292
|
+
values.push(profileId);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
query += ` ORDER BY created_at DESC LIMIT $${values.length + 1}`;
|
|
296
|
+
values.push(limit);
|
|
297
|
+
|
|
298
|
+
const result = await getPool().query(query, values);
|
|
299
|
+
return result.rows as ActivityLog[];
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
async shutdown(): Promise<void> {},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parental Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for parental/guardian controls with adapter support.
|
|
5
|
+
* Supports different use cases through adapters (kids, gaming, education).
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Guardian settings for a user
|
|
12
|
+
*/
|
|
13
|
+
export interface GuardianSettings {
|
|
14
|
+
id: string;
|
|
15
|
+
user_id: string;
|
|
16
|
+
adapter_type: string;
|
|
17
|
+
pin_hash?: string;
|
|
18
|
+
pin_failed_attempts: number;
|
|
19
|
+
pin_locked_until?: Date;
|
|
20
|
+
biometric_enabled: boolean;
|
|
21
|
+
notifications_enabled: boolean;
|
|
22
|
+
weekly_report_enabled: boolean;
|
|
23
|
+
metadata: Record<string, unknown>;
|
|
24
|
+
updated_at: Date;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Profile restriction
|
|
29
|
+
*/
|
|
30
|
+
export interface ProfileRestriction {
|
|
31
|
+
id: string;
|
|
32
|
+
profile_id: string;
|
|
33
|
+
restriction_type: string;
|
|
34
|
+
daily_limit_minutes?: number;
|
|
35
|
+
schedule?: Record<string, { start: string; end: string }>;
|
|
36
|
+
is_paused: boolean;
|
|
37
|
+
pause_until?: Date;
|
|
38
|
+
pause_reason?: string;
|
|
39
|
+
is_active: boolean;
|
|
40
|
+
metadata: Record<string, unknown>;
|
|
41
|
+
updated_at: Date;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Activity log entry
|
|
46
|
+
*/
|
|
47
|
+
export interface ActivityLog {
|
|
48
|
+
id: string;
|
|
49
|
+
user_id: string;
|
|
50
|
+
profile_id?: string;
|
|
51
|
+
device_id?: string;
|
|
52
|
+
adapter_type: string;
|
|
53
|
+
activity_type: string;
|
|
54
|
+
details: Record<string, unknown>;
|
|
55
|
+
created_at: Date;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Access check result
|
|
60
|
+
*/
|
|
61
|
+
export interface AccessCheckResult {
|
|
62
|
+
allowed: boolean;
|
|
63
|
+
reason?: string;
|
|
64
|
+
minutes_remaining?: number;
|
|
65
|
+
available_at?: Date;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
+
// Input Types
|
|
70
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
71
|
+
|
|
72
|
+
export interface CreateGuardianSettingsInput {
|
|
73
|
+
user_id: string;
|
|
74
|
+
adapter_type: string;
|
|
75
|
+
pin?: string;
|
|
76
|
+
biometric_enabled?: boolean;
|
|
77
|
+
notifications_enabled?: boolean;
|
|
78
|
+
weekly_report_enabled?: boolean;
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface UpdateGuardianSettingsInput {
|
|
83
|
+
biometric_enabled?: boolean;
|
|
84
|
+
notifications_enabled?: boolean;
|
|
85
|
+
weekly_report_enabled?: boolean;
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CreateRestrictionInput {
|
|
90
|
+
profile_id: string;
|
|
91
|
+
restriction_type: string;
|
|
92
|
+
daily_limit_minutes?: number;
|
|
93
|
+
schedule?: Record<string, { start: string; end: string }>;
|
|
94
|
+
metadata?: Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LogActivityInput {
|
|
98
|
+
user_id: string;
|
|
99
|
+
profile_id?: string;
|
|
100
|
+
device_id?: string;
|
|
101
|
+
adapter_type: string;
|
|
102
|
+
activity_type: string;
|
|
103
|
+
details?: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
// Adapter Interface
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parental adapter interface
|
|
112
|
+
*/
|
|
113
|
+
export interface ParentalAdapter {
|
|
114
|
+
name: string;
|
|
115
|
+
getActivityTypes(): string[];
|
|
116
|
+
getDefaultDailyLimit(): number;
|
|
117
|
+
validateRestriction?(restriction: CreateRestrictionInput): { valid: boolean; errors?: string[] };
|
|
118
|
+
formatActivityDetails?(activity: ActivityLog): Record<string, unknown>;
|
|
119
|
+
onRestrictionViolation?(profileId: string, reason: string): Promise<void>;
|
|
120
|
+
onDailyLimitReached?(profileId: string): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
// Store Interface
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
|
|
127
|
+
export interface ParentalStore {
|
|
128
|
+
name: string;
|
|
129
|
+
initialize(): Promise<void>;
|
|
130
|
+
|
|
131
|
+
// Guardian settings
|
|
132
|
+
getSettings(userId: string): Promise<GuardianSettings | null>;
|
|
133
|
+
createSettings(input: CreateGuardianSettingsInput): Promise<GuardianSettings>;
|
|
134
|
+
updateSettings(userId: string, input: UpdateGuardianSettingsInput): Promise<GuardianSettings | null>;
|
|
135
|
+
setPin(userId: string, pinHash: string): Promise<void>;
|
|
136
|
+
verifyPin(userId: string, pinHash: string): Promise<boolean>;
|
|
137
|
+
incrementFailedPinAttempts(userId: string): Promise<number>;
|
|
138
|
+
resetFailedPinAttempts(userId: string): Promise<void>;
|
|
139
|
+
|
|
140
|
+
// Restrictions
|
|
141
|
+
getRestrictions(profileId: string): Promise<ProfileRestriction[]>;
|
|
142
|
+
createRestriction(input: CreateRestrictionInput): Promise<ProfileRestriction>;
|
|
143
|
+
updateRestriction(id: string, updates: Partial<ProfileRestriction>): Promise<ProfileRestriction | null>;
|
|
144
|
+
deleteRestriction(id: string): Promise<boolean>;
|
|
145
|
+
pauseProfile(profileId: string, until?: Date, reason?: string): Promise<void>;
|
|
146
|
+
resumeProfile(profileId: string): Promise<void>;
|
|
147
|
+
|
|
148
|
+
// Activity log
|
|
149
|
+
logActivity(input: LogActivityInput): Promise<ActivityLog>;
|
|
150
|
+
getActivityLog(userId: string, limit?: number, profileId?: string): Promise<ActivityLog[]>;
|
|
151
|
+
|
|
152
|
+
shutdown(): Promise<void>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
156
|
+
// Configuration Types
|
|
157
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
158
|
+
|
|
159
|
+
export interface PostgresParentalStoreConfig {
|
|
160
|
+
pool: unknown | (() => unknown);
|
|
161
|
+
settingsTable?: string;
|
|
162
|
+
restrictionsTable?: string;
|
|
163
|
+
activityTable?: string;
|
|
164
|
+
schema?: string;
|
|
165
|
+
autoCreateTables?: boolean;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ParentalApiConfig {
|
|
169
|
+
prefix?: string;
|
|
170
|
+
enabled?: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ParentalPluginConfig {
|
|
174
|
+
store: ParentalStore;
|
|
175
|
+
adapter: ParentalAdapter;
|
|
176
|
+
maxPinAttempts?: number;
|
|
177
|
+
pinLockoutMinutes?: number;
|
|
178
|
+
api?: ParentalApiConfig;
|
|
179
|
+
debug?: boolean;
|
|
180
|
+
}
|