@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,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiles Plugin
|
|
3
|
+
*
|
|
4
|
+
* Generic multi-profile management plugin for @qwickapps/server.
|
|
5
|
+
* Supports age-based categorization and content filtering.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Request, Response } from 'express';
|
|
11
|
+
import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
|
|
12
|
+
import type {
|
|
13
|
+
ProfilesPluginConfig,
|
|
14
|
+
ProfileStore,
|
|
15
|
+
Profile,
|
|
16
|
+
CreateProfileInput,
|
|
17
|
+
UpdateProfileInput,
|
|
18
|
+
ProfileSearchParams,
|
|
19
|
+
AgeGroup,
|
|
20
|
+
ContentFilterLevel,
|
|
21
|
+
TimeRestrictionResult,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
// Store instance for helper access
|
|
25
|
+
let currentStore: ProfileStore | null = null;
|
|
26
|
+
let currentConfig: ProfilesPluginConfig | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculate age from birth date
|
|
30
|
+
*/
|
|
31
|
+
function calculateAge(birthDate: Date): number {
|
|
32
|
+
const today = new Date();
|
|
33
|
+
const birth = new Date(birthDate);
|
|
34
|
+
let age = today.getFullYear() - birth.getFullYear();
|
|
35
|
+
const monthDiff = today.getMonth() - birth.getMonth();
|
|
36
|
+
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
|
37
|
+
age--;
|
|
38
|
+
}
|
|
39
|
+
return age;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if current time is within allowed hours
|
|
44
|
+
*/
|
|
45
|
+
function isWithinAllowedHours(start?: string, end?: string): boolean {
|
|
46
|
+
if (!start || !end) {
|
|
47
|
+
return true; // No restriction
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const now = new Date();
|
|
51
|
+
const currentTime = now.getHours() * 60 + now.getMinutes();
|
|
52
|
+
|
|
53
|
+
const [startHour, startMin] = start.split(':').map(Number);
|
|
54
|
+
const [endHour, endMin] = end.split(':').map(Number);
|
|
55
|
+
|
|
56
|
+
const startTime = startHour * 60 + startMin;
|
|
57
|
+
const endTime = endHour * 60 + endMin;
|
|
58
|
+
|
|
59
|
+
if (startTime <= endTime) {
|
|
60
|
+
// Normal range (e.g., 08:00 - 20:00)
|
|
61
|
+
return currentTime >= startTime && currentTime <= endTime;
|
|
62
|
+
} else {
|
|
63
|
+
// Overnight range (e.g., 20:00 - 08:00)
|
|
64
|
+
return currentTime >= startTime || currentTime <= endTime;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create the Profiles plugin
|
|
70
|
+
*/
|
|
71
|
+
export function createProfilesPlugin(config: ProfilesPluginConfig): Plugin {
|
|
72
|
+
const debug = config.debug || false;
|
|
73
|
+
const maxProfilesPerUser = config.maxProfilesPerUser || 10;
|
|
74
|
+
const defaultFilterLevel = config.defaultFilterLevel || 'moderate';
|
|
75
|
+
const apiPrefix = config.api?.prefix || '/profiles';
|
|
76
|
+
|
|
77
|
+
function log(message: string, data?: Record<string, unknown>) {
|
|
78
|
+
if (debug) {
|
|
79
|
+
console.log(`[ProfilesPlugin] ${message}`, data || '');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
id: 'profiles',
|
|
85
|
+
name: 'Profiles',
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
|
|
88
|
+
async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
|
|
89
|
+
log('Starting profiles plugin');
|
|
90
|
+
|
|
91
|
+
// Initialize the store (creates tables if needed)
|
|
92
|
+
await config.store.initialize();
|
|
93
|
+
log('Profiles plugin migrations complete');
|
|
94
|
+
|
|
95
|
+
// Store references for helper access
|
|
96
|
+
currentStore = config.store;
|
|
97
|
+
currentConfig = config;
|
|
98
|
+
|
|
99
|
+
// Register health check
|
|
100
|
+
registry.registerHealthCheck({
|
|
101
|
+
name: 'profiles-store',
|
|
102
|
+
type: 'custom',
|
|
103
|
+
check: async () => {
|
|
104
|
+
try {
|
|
105
|
+
await config.store.search({ limit: 1 });
|
|
106
|
+
return {
|
|
107
|
+
healthy: true,
|
|
108
|
+
details: {
|
|
109
|
+
maxProfilesPerUser,
|
|
110
|
+
defaultFilterLevel,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
} catch {
|
|
114
|
+
return { healthy: false };
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Add API routes if enabled
|
|
120
|
+
if (config.api?.crud !== false) {
|
|
121
|
+
// List/Search profiles
|
|
122
|
+
registry.addRoute({
|
|
123
|
+
method: 'get',
|
|
124
|
+
path: apiPrefix,
|
|
125
|
+
pluginId: 'profiles',
|
|
126
|
+
handler: async (req: Request, res: Response) => {
|
|
127
|
+
try {
|
|
128
|
+
const params: ProfileSearchParams = {
|
|
129
|
+
org_id: req.query.org_id as string,
|
|
130
|
+
user_id: req.query.user_id as string,
|
|
131
|
+
age_group: req.query.age_group as AgeGroup,
|
|
132
|
+
is_active: req.query.is_active === 'true' ? true : req.query.is_active === 'false' ? false : undefined,
|
|
133
|
+
query: req.query.q as string,
|
|
134
|
+
page: parseInt(req.query.page as string) || 1,
|
|
135
|
+
limit: Math.min(parseInt(req.query.limit as string) || 20, 100),
|
|
136
|
+
sortBy: (req.query.sortBy as ProfileSearchParams['sortBy']) || 'created_at',
|
|
137
|
+
sortOrder: (req.query.sortOrder as ProfileSearchParams['sortOrder']) || 'desc',
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const result = await config.store.search(params);
|
|
141
|
+
res.json(result);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('[ProfilesPlugin] Search error:', error);
|
|
144
|
+
res.status(500).json({ error: 'Failed to search profiles' });
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Get profile by ID
|
|
150
|
+
registry.addRoute({
|
|
151
|
+
method: 'get',
|
|
152
|
+
path: `${apiPrefix}/:id`,
|
|
153
|
+
pluginId: 'profiles',
|
|
154
|
+
handler: async (req: Request, res: Response) => {
|
|
155
|
+
try {
|
|
156
|
+
const profile = await config.store.getById(req.params.id);
|
|
157
|
+
if (!profile) {
|
|
158
|
+
return res.status(404).json({ error: 'Profile not found' });
|
|
159
|
+
}
|
|
160
|
+
res.json(profile);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('[ProfilesPlugin] Get profile error:', error);
|
|
163
|
+
res.status(500).json({ error: 'Failed to get profile' });
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Create profile
|
|
169
|
+
registry.addRoute({
|
|
170
|
+
method: 'post',
|
|
171
|
+
path: apiPrefix,
|
|
172
|
+
pluginId: 'profiles',
|
|
173
|
+
handler: async (req: Request, res: Response) => {
|
|
174
|
+
try {
|
|
175
|
+
const input: CreateProfileInput = {
|
|
176
|
+
org_id: req.body.org_id,
|
|
177
|
+
user_id: req.body.user_id,
|
|
178
|
+
name: req.body.name,
|
|
179
|
+
avatar: req.body.avatar,
|
|
180
|
+
birth_date: req.body.birth_date ? new Date(req.body.birth_date) : undefined,
|
|
181
|
+
age: req.body.age,
|
|
182
|
+
content_filter_level: req.body.content_filter_level || defaultFilterLevel,
|
|
183
|
+
daily_time_limit_minutes: req.body.daily_time_limit_minutes,
|
|
184
|
+
allowed_hours_start: req.body.allowed_hours_start,
|
|
185
|
+
allowed_hours_end: req.body.allowed_hours_end,
|
|
186
|
+
is_default: req.body.is_default,
|
|
187
|
+
metadata: req.body.metadata,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Validate required fields
|
|
191
|
+
if (!input.user_id) {
|
|
192
|
+
return res.status(400).json({ error: 'user_id is required' });
|
|
193
|
+
}
|
|
194
|
+
if (!input.name || input.name.trim().length === 0) {
|
|
195
|
+
return res.status(400).json({ error: 'name is required' });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check profile limit
|
|
199
|
+
const currentCount = await config.store.getProfileCount(input.user_id);
|
|
200
|
+
if (currentCount >= maxProfilesPerUser) {
|
|
201
|
+
return res.status(400).json({
|
|
202
|
+
error: `Maximum profiles (${maxProfilesPerUser}) reached for this user`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const profile = await config.store.create(input);
|
|
207
|
+
res.status(201).json(profile);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('[ProfilesPlugin] Create profile error:', error);
|
|
210
|
+
res.status(500).json({ error: 'Failed to create profile' });
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Update profile
|
|
216
|
+
registry.addRoute({
|
|
217
|
+
method: 'put',
|
|
218
|
+
path: `${apiPrefix}/:id`,
|
|
219
|
+
pluginId: 'profiles',
|
|
220
|
+
handler: async (req: Request, res: Response) => {
|
|
221
|
+
try {
|
|
222
|
+
const input: UpdateProfileInput = {
|
|
223
|
+
name: req.body.name,
|
|
224
|
+
avatar: req.body.avatar,
|
|
225
|
+
birth_date: req.body.birth_date !== undefined
|
|
226
|
+
? (req.body.birth_date ? new Date(req.body.birth_date) : null)
|
|
227
|
+
: undefined,
|
|
228
|
+
age: req.body.age,
|
|
229
|
+
content_filter_level: req.body.content_filter_level,
|
|
230
|
+
daily_time_limit_minutes: req.body.daily_time_limit_minutes,
|
|
231
|
+
allowed_hours_start: req.body.allowed_hours_start,
|
|
232
|
+
allowed_hours_end: req.body.allowed_hours_end,
|
|
233
|
+
is_active: req.body.is_active,
|
|
234
|
+
is_default: req.body.is_default,
|
|
235
|
+
metadata: req.body.metadata,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const profile = await config.store.update(req.params.id, input);
|
|
239
|
+
if (!profile) {
|
|
240
|
+
return res.status(404).json({ error: 'Profile not found' });
|
|
241
|
+
}
|
|
242
|
+
res.json(profile);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('[ProfilesPlugin] Update profile error:', error);
|
|
245
|
+
res.status(500).json({ error: 'Failed to update profile' });
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Delete profile
|
|
251
|
+
registry.addRoute({
|
|
252
|
+
method: 'delete',
|
|
253
|
+
path: `${apiPrefix}/:id`,
|
|
254
|
+
pluginId: 'profiles',
|
|
255
|
+
handler: async (req: Request, res: Response) => {
|
|
256
|
+
try {
|
|
257
|
+
const deleted = await config.store.delete(req.params.id);
|
|
258
|
+
if (!deleted) {
|
|
259
|
+
return res.status(404).json({ error: 'Profile not found' });
|
|
260
|
+
}
|
|
261
|
+
res.status(204).send();
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('[ProfilesPlugin] Delete profile error:', error);
|
|
264
|
+
res.status(500).json({ error: 'Failed to delete profile' });
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// List profiles for a user
|
|
270
|
+
registry.addRoute({
|
|
271
|
+
method: 'get',
|
|
272
|
+
path: `${apiPrefix}/user/:userId`,
|
|
273
|
+
pluginId: 'profiles',
|
|
274
|
+
handler: async (req: Request, res: Response) => {
|
|
275
|
+
try {
|
|
276
|
+
const profiles = await config.store.listByUser(req.params.userId);
|
|
277
|
+
res.json({ profiles });
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('[ProfilesPlugin] List user profiles error:', error);
|
|
280
|
+
res.status(500).json({ error: 'Failed to list profiles' });
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Get default profile for a user
|
|
286
|
+
registry.addRoute({
|
|
287
|
+
method: 'get',
|
|
288
|
+
path: `${apiPrefix}/user/:userId/default`,
|
|
289
|
+
pluginId: 'profiles',
|
|
290
|
+
handler: async (req: Request, res: Response) => {
|
|
291
|
+
try {
|
|
292
|
+
const profile = await config.store.getDefaultProfile(req.params.userId);
|
|
293
|
+
if (!profile) {
|
|
294
|
+
return res.status(404).json({ error: 'No default profile found' });
|
|
295
|
+
}
|
|
296
|
+
res.json(profile);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error('[ProfilesPlugin] Get default profile error:', error);
|
|
299
|
+
res.status(500).json({ error: 'Failed to get default profile' });
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Set default profile
|
|
305
|
+
registry.addRoute({
|
|
306
|
+
method: 'post',
|
|
307
|
+
path: `${apiPrefix}/:id/set-default`,
|
|
308
|
+
pluginId: 'profiles',
|
|
309
|
+
handler: async (req: Request, res: Response) => {
|
|
310
|
+
try {
|
|
311
|
+
const profile = await config.store.getById(req.params.id);
|
|
312
|
+
if (!profile) {
|
|
313
|
+
return res.status(404).json({ error: 'Profile not found' });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const success = await config.store.setDefaultProfile(req.params.id, profile.user_id);
|
|
317
|
+
if (!success) {
|
|
318
|
+
return res.status(500).json({ error: 'Failed to set default profile' });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const updated = await config.store.getById(req.params.id);
|
|
322
|
+
res.json(updated);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('[ProfilesPlugin] Set default profile error:', error);
|
|
325
|
+
res.status(500).json({ error: 'Failed to set default profile' });
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Check time restrictions
|
|
331
|
+
registry.addRoute({
|
|
332
|
+
method: 'get',
|
|
333
|
+
path: `${apiPrefix}/:id/time-check`,
|
|
334
|
+
pluginId: 'profiles',
|
|
335
|
+
handler: async (req: Request, res: Response) => {
|
|
336
|
+
try {
|
|
337
|
+
const profile = await config.store.getById(req.params.id);
|
|
338
|
+
if (!profile) {
|
|
339
|
+
return res.status(404).json({ error: 'Profile not found' });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result = checkTimeRestrictions(profile);
|
|
343
|
+
res.json(result);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error('[ProfilesPlugin] Time check error:', error);
|
|
346
|
+
res.status(500).json({ error: 'Failed to check time restrictions' });
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
log('Profiles plugin started');
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
async onStop(): Promise<void> {
|
|
356
|
+
log('Stopping profiles plugin');
|
|
357
|
+
await config.store.shutdown();
|
|
358
|
+
currentStore = null;
|
|
359
|
+
currentConfig = null;
|
|
360
|
+
log('Profiles plugin stopped');
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
366
|
+
// Helper Functions
|
|
367
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Get the current profile store instance
|
|
371
|
+
*/
|
|
372
|
+
export function getProfileStore(): ProfileStore | null {
|
|
373
|
+
return currentStore;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Create a new profile
|
|
378
|
+
*/
|
|
379
|
+
export async function createProfile(input: CreateProfileInput): Promise<Profile> {
|
|
380
|
+
if (!currentStore || !currentConfig) {
|
|
381
|
+
throw new Error('Profiles plugin not initialized');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Check profile limit
|
|
385
|
+
const currentCount = await currentStore.getProfileCount(input.user_id);
|
|
386
|
+
const maxProfiles = currentConfig.maxProfilesPerUser || 10;
|
|
387
|
+
if (currentCount >= maxProfiles) {
|
|
388
|
+
throw new Error(`Maximum profiles (${maxProfiles}) reached for this user`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Apply default filter level
|
|
392
|
+
if (!input.content_filter_level) {
|
|
393
|
+
input.content_filter_level = currentConfig.defaultFilterLevel || 'moderate';
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return currentStore.create(input);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get a profile by ID
|
|
401
|
+
*/
|
|
402
|
+
export async function getProfileById(id: string): Promise<Profile | null> {
|
|
403
|
+
if (!currentStore) {
|
|
404
|
+
throw new Error('Profiles plugin not initialized');
|
|
405
|
+
}
|
|
406
|
+
return currentStore.getById(id);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Update a profile
|
|
411
|
+
*/
|
|
412
|
+
export async function updateProfile(id: string, input: UpdateProfileInput): Promise<Profile | null> {
|
|
413
|
+
if (!currentStore) {
|
|
414
|
+
throw new Error('Profiles plugin not initialized');
|
|
415
|
+
}
|
|
416
|
+
return currentStore.update(id, input);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Delete a profile
|
|
421
|
+
*/
|
|
422
|
+
export async function deleteProfile(id: string): Promise<boolean> {
|
|
423
|
+
if (!currentStore) {
|
|
424
|
+
throw new Error('Profiles plugin not initialized');
|
|
425
|
+
}
|
|
426
|
+
return currentStore.delete(id);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* List profiles for a user
|
|
431
|
+
*/
|
|
432
|
+
export async function listUserProfiles(userId: string): Promise<Profile[]> {
|
|
433
|
+
if (!currentStore) {
|
|
434
|
+
throw new Error('Profiles plugin not initialized');
|
|
435
|
+
}
|
|
436
|
+
return currentStore.listByUser(userId);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Get the default profile for a user
|
|
441
|
+
*/
|
|
442
|
+
export async function getDefaultProfile(userId: string): Promise<Profile | null> {
|
|
443
|
+
if (!currentStore) {
|
|
444
|
+
throw new Error('Profiles plugin not initialized');
|
|
445
|
+
}
|
|
446
|
+
return currentStore.getDefaultProfile(userId);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Set a profile as the default
|
|
451
|
+
*/
|
|
452
|
+
export async function setDefaultProfile(profileId: string, userId: string): Promise<boolean> {
|
|
453
|
+
if (!currentStore) {
|
|
454
|
+
throw new Error('Profiles plugin not initialized');
|
|
455
|
+
}
|
|
456
|
+
return currentStore.setDefaultProfile(profileId, userId);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get profiles by age group
|
|
461
|
+
*/
|
|
462
|
+
export async function getProfilesByAgeGroup(userId: string, ageGroup: AgeGroup): Promise<Profile[]> {
|
|
463
|
+
if (!currentStore) {
|
|
464
|
+
throw new Error('Profiles plugin not initialized');
|
|
465
|
+
}
|
|
466
|
+
return currentStore.getByAgeGroup(userId, ageGroup);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Get child profiles (age_group = 'child')
|
|
471
|
+
*/
|
|
472
|
+
export async function getChildProfiles(userId: string): Promise<Profile[]> {
|
|
473
|
+
return getProfilesByAgeGroup(userId, 'child');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get the current age for a profile
|
|
478
|
+
*/
|
|
479
|
+
export function getProfileAge(profile: Profile): number | null {
|
|
480
|
+
if (profile.birth_date) {
|
|
481
|
+
return calculateAge(profile.birth_date);
|
|
482
|
+
}
|
|
483
|
+
return profile.age || null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Check time restrictions for a profile
|
|
488
|
+
*/
|
|
489
|
+
export function checkTimeRestrictions(profile: Profile): TimeRestrictionResult {
|
|
490
|
+
// Check allowed hours
|
|
491
|
+
const withinHours = isWithinAllowedHours(
|
|
492
|
+
profile.allowed_hours_start,
|
|
493
|
+
profile.allowed_hours_end
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
if (!withinHours) {
|
|
497
|
+
// Calculate when access will be available
|
|
498
|
+
const now = new Date();
|
|
499
|
+
let availableAt: Date | undefined;
|
|
500
|
+
|
|
501
|
+
if (profile.allowed_hours_start) {
|
|
502
|
+
const [hour, min] = profile.allowed_hours_start.split(':').map(Number);
|
|
503
|
+
availableAt = new Date(now);
|
|
504
|
+
availableAt.setHours(hour, min, 0, 0);
|
|
505
|
+
if (availableAt <= now) {
|
|
506
|
+
availableAt.setDate(availableAt.getDate() + 1);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
allowed: false,
|
|
512
|
+
reason: 'Outside allowed hours',
|
|
513
|
+
available_at: availableAt,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Note: Daily time limit tracking requires usage plugin integration
|
|
518
|
+
// For now, just return allowed if within hours
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
allowed: true,
|
|
522
|
+
minutes_remaining: profile.daily_time_limit_minutes || undefined,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Get content filter level for a profile
|
|
528
|
+
*/
|
|
529
|
+
export function getContentFilterLevel(profile: Profile): ContentFilterLevel {
|
|
530
|
+
return profile.content_filter_level;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Check if profile can access content based on filter level
|
|
535
|
+
*/
|
|
536
|
+
export function canAccessContent(
|
|
537
|
+
profile: Profile,
|
|
538
|
+
requiredLevel: ContentFilterLevel
|
|
539
|
+
): boolean {
|
|
540
|
+
const levels: ContentFilterLevel[] = ['strict', 'moderate', 'minimal', 'none'];
|
|
541
|
+
const profileLevelIndex = levels.indexOf(profile.content_filter_level);
|
|
542
|
+
const requiredLevelIndex = levels.indexOf(requiredLevel);
|
|
543
|
+
|
|
544
|
+
// Profile level must be >= required level (stricter or equal)
|
|
545
|
+
return profileLevelIndex <= requiredLevelIndex;
|
|
546
|
+
}
|