@qwickapps/server 1.3.1 → 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/README.md +157 -0
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +114 -0
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/types.d.ts +19 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
- package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.js +166 -0
- package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
- package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
- package/dist/plugins/auth/config-store.d.ts +11 -0
- package/dist/plugins/auth/config-store.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.js +232 -0
- package/dist/plugins/auth/config-store.js.map +1 -0
- package/dist/plugins/auth/config-store.test.d.ts +7 -0
- package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.test.js +299 -0
- package/dist/plugins/auth/config-store.test.js.map +1 -0
- package/dist/plugins/auth/env-config.d.ts +51 -1
- package/dist/plugins/auth/env-config.d.ts.map +1 -1
- package/dist/plugins/auth/env-config.js +640 -7
- package/dist/plugins/auth/env-config.js.map +1 -1
- package/dist/plugins/auth/index.d.ts +6 -2
- package/dist/plugins/auth/index.d.ts.map +1 -1
- package/dist/plugins/auth/index.js +5 -1
- package/dist/plugins/auth/index.js.map +1 -1
- package/dist/plugins/auth/types.d.ts +106 -0
- package/dist/plugins/auth/types.d.ts.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 +18 -4
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +16 -2
- 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/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
- package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
- package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
- package/dist/plugins/rate-limit/cleanup.js +72 -0
- package/dist/plugins/rate-limit/cleanup.js.map +1 -0
- package/dist/plugins/rate-limit/env-config.d.ts +91 -0
- package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
- package/dist/plugins/rate-limit/env-config.js +318 -0
- package/dist/plugins/rate-limit/env-config.js.map +1 -0
- package/dist/plugins/rate-limit/index.d.ts +76 -0
- package/dist/plugins/rate-limit/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/index.js +79 -0
- package/dist/plugins/rate-limit/index.js.map +1 -0
- package/dist/plugins/rate-limit/middleware.d.ts +40 -0
- package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
- package/dist/plugins/rate-limit/middleware.js +169 -0
- package/dist/plugins/rate-limit/middleware.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
- package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
- package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
- package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
- package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/index.js +8 -0
- package/dist/plugins/rate-limit/stores/index.js.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.js +27 -0
- package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
- package/dist/plugins/rate-limit/types.d.ts +265 -0
- package/dist/plugins/rate-limit/types.d.ts.map +1 -0
- package/dist/plugins/rate-limit/types.js +9 -0
- package/dist/plugins/rate-limit/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-BY8OxNgO.js.map → index-CynOqPkb.js.map} +1 -1
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +187 -0
- package/dist-ui-lib/components/StatCard.d.ts +16 -0
- package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +3 -0
- package/dist-ui-lib/index.js +3579 -2379
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
- package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
- package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
- package/dist-ui-lib/utils/formatters.d.ts +19 -0
- package/package.json +1 -1
- package/src/core/control-panel.ts +128 -0
- package/src/core/types.ts +17 -0
- package/src/index.ts +216 -0
- package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
- package/src/plugins/auth/adapter-wrapper.ts +205 -0
- package/src/plugins/auth/config-store.test.ts +417 -0
- package/src/plugins/auth/config-store.ts +305 -0
- package/src/plugins/auth/env-config.ts +714 -7
- package/src/plugins/auth/index.ts +22 -1
- package/src/plugins/auth/types.ts +138 -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 +267 -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/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
- package/src/plugins/rate-limit/cleanup.ts +117 -0
- package/src/plugins/rate-limit/env-config.ts +400 -0
- package/src/plugins/rate-limit/index.ts +128 -0
- package/src/plugins/rate-limit/middleware.ts +212 -0
- package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
- package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
- package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
- package/src/plugins/rate-limit/stores/index.ts +8 -0
- package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
- package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
- package/src/plugins/rate-limit/strategies/index.ts +30 -0
- package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
- package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
- package/src/plugins/rate-limit/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 +36 -14
- package/ui/src/api/controlPanelApi.ts +329 -6
- package/ui/src/components/StatCard.tsx +58 -0
- package/ui/src/dashboard/builtInWidgets.tsx +7 -1
- package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
- package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
- package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
- package/ui/src/dashboard/widgets/index.ts +3 -0
- package/ui/src/pages/AuthPage.tsx +986 -142
- package/ui/src/pages/IntegrationsPage.tsx +288 -0
- package/ui/src/pages/NotificationsPage.tsx +417 -0
- package/ui/src/pages/RateLimitPage.tsx +292 -0
- package/ui/src/utils/formatters.ts +33 -0
- package/dist-ui/assets/index-BY8OxNgO.js +0 -465
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parental Plugin
|
|
3
|
+
*
|
|
4
|
+
* Generic parental/guardian controls for @qwickapps/server.
|
|
5
|
+
* Supports PIN protection, profile restrictions, schedules, and activity logging.
|
|
6
|
+
* Uses adapters for domain-specific behavior (kids, gaming, education).
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
import type { Request, Response } from 'express';
|
|
13
|
+
import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
|
|
14
|
+
import type {
|
|
15
|
+
ParentalPluginConfig,
|
|
16
|
+
ParentalStore,
|
|
17
|
+
ParentalAdapter,
|
|
18
|
+
GuardianSettings,
|
|
19
|
+
ProfileRestriction,
|
|
20
|
+
ActivityLog,
|
|
21
|
+
AccessCheckResult,
|
|
22
|
+
CreateGuardianSettingsInput,
|
|
23
|
+
UpdateGuardianSettingsInput,
|
|
24
|
+
CreateRestrictionInput,
|
|
25
|
+
LogActivityInput,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
|
|
28
|
+
// Store instances for helper access
|
|
29
|
+
let currentStore: ParentalStore | null = null;
|
|
30
|
+
let currentAdapter: ParentalAdapter | null = null;
|
|
31
|
+
let currentConfig: ParentalPluginConfig | null = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hash a PIN using SHA-256
|
|
35
|
+
*/
|
|
36
|
+
function hashPin(pin: string): string {
|
|
37
|
+
return createHash('sha256').update(pin).digest('hex');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if current time is within allowed schedule
|
|
42
|
+
*/
|
|
43
|
+
function isWithinSchedule(schedule: Record<string, { start: string; end: string }>): boolean {
|
|
44
|
+
const now = new Date();
|
|
45
|
+
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
46
|
+
const today = dayNames[now.getDay()];
|
|
47
|
+
|
|
48
|
+
const todaySchedule = schedule[today];
|
|
49
|
+
if (!todaySchedule) return true; // No schedule for today = allowed
|
|
50
|
+
|
|
51
|
+
const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
|
|
52
|
+
|
|
53
|
+
return currentTime >= todaySchedule.start && currentTime <= todaySchedule.end;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create the Parental plugin
|
|
58
|
+
*/
|
|
59
|
+
export function createParentalPlugin(config: ParentalPluginConfig): Plugin {
|
|
60
|
+
const debug = config.debug || false;
|
|
61
|
+
const apiPrefix = config.api?.prefix || '/parental';
|
|
62
|
+
const maxPinAttempts = config.maxPinAttempts || 5;
|
|
63
|
+
const pinLockoutMinutes = config.pinLockoutMinutes || 30;
|
|
64
|
+
|
|
65
|
+
function log(message: string, data?: Record<string, unknown>) {
|
|
66
|
+
if (debug) {
|
|
67
|
+
console.log(`[ParentalPlugin] ${message}`, data || '');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
id: 'parental',
|
|
73
|
+
name: 'Parental',
|
|
74
|
+
version: '1.0.0',
|
|
75
|
+
|
|
76
|
+
async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
|
|
77
|
+
log('Starting parental plugin');
|
|
78
|
+
|
|
79
|
+
// Initialize the store (creates tables if needed)
|
|
80
|
+
await config.store.initialize();
|
|
81
|
+
log('Parental store initialized');
|
|
82
|
+
|
|
83
|
+
// Store references for helper access
|
|
84
|
+
currentStore = config.store;
|
|
85
|
+
currentAdapter = config.adapter;
|
|
86
|
+
currentConfig = config;
|
|
87
|
+
|
|
88
|
+
// Register health check
|
|
89
|
+
registry.registerHealthCheck({
|
|
90
|
+
name: 'parental-store',
|
|
91
|
+
type: 'custom',
|
|
92
|
+
check: async () => {
|
|
93
|
+
try {
|
|
94
|
+
return { healthy: true };
|
|
95
|
+
} catch {
|
|
96
|
+
return { healthy: false };
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Add API routes if enabled
|
|
102
|
+
if (config.api?.enabled !== false) {
|
|
103
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
104
|
+
// Guardian Settings Routes
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
106
|
+
|
|
107
|
+
// Get guardian settings
|
|
108
|
+
registry.addRoute({
|
|
109
|
+
method: 'get',
|
|
110
|
+
path: `${apiPrefix}/settings/:userId`,
|
|
111
|
+
pluginId: 'parental',
|
|
112
|
+
handler: async (req: Request, res: Response) => {
|
|
113
|
+
try {
|
|
114
|
+
const { userId } = req.params;
|
|
115
|
+
const settings = await getGuardianSettings(userId);
|
|
116
|
+
|
|
117
|
+
if (!settings) {
|
|
118
|
+
return res.status(404).json({ error: 'Settings not found' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Don't expose PIN hash
|
|
122
|
+
const { pin_hash: _pin, ...safeSettings } = settings;
|
|
123
|
+
res.json({ ...safeSettings, has_pin: !!_pin });
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[ParentalPlugin] Get settings error:', error);
|
|
126
|
+
res.status(500).json({ error: 'Failed to get settings' });
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Create guardian settings
|
|
132
|
+
registry.addRoute({
|
|
133
|
+
method: 'post',
|
|
134
|
+
path: `${apiPrefix}/settings`,
|
|
135
|
+
pluginId: 'parental',
|
|
136
|
+
handler: async (req: Request, res: Response) => {
|
|
137
|
+
try {
|
|
138
|
+
const input = req.body as CreateGuardianSettingsInput;
|
|
139
|
+
|
|
140
|
+
// Hash PIN if provided
|
|
141
|
+
const processedInput = {
|
|
142
|
+
...input,
|
|
143
|
+
adapter_type: config.adapter.name,
|
|
144
|
+
pin: input.pin ? hashPin(input.pin) : undefined,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const settings = await createGuardianSettings(processedInput);
|
|
148
|
+
const { pin_hash: _pin, ...safeSettings } = settings;
|
|
149
|
+
res.status(201).json({ ...safeSettings, has_pin: !!_pin });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('[ParentalPlugin] Create settings error:', error);
|
|
152
|
+
res.status(500).json({ error: 'Failed to create settings' });
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Update guardian settings
|
|
158
|
+
registry.addRoute({
|
|
159
|
+
method: 'patch',
|
|
160
|
+
path: `${apiPrefix}/settings/:userId`,
|
|
161
|
+
pluginId: 'parental',
|
|
162
|
+
handler: async (req: Request, res: Response) => {
|
|
163
|
+
try {
|
|
164
|
+
const { userId } = req.params;
|
|
165
|
+
const input = req.body as UpdateGuardianSettingsInput;
|
|
166
|
+
|
|
167
|
+
const settings = await updateGuardianSettings(userId, input);
|
|
168
|
+
if (!settings) {
|
|
169
|
+
return res.status(404).json({ error: 'Settings not found' });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const { pin_hash: _pin, ...safeSettings } = settings;
|
|
173
|
+
res.json({ ...safeSettings, has_pin: !!_pin });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('[ParentalPlugin] Update settings error:', error);
|
|
176
|
+
res.status(500).json({ error: 'Failed to update settings' });
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Set PIN
|
|
182
|
+
registry.addRoute({
|
|
183
|
+
method: 'post',
|
|
184
|
+
path: `${apiPrefix}/settings/:userId/pin`,
|
|
185
|
+
pluginId: 'parental',
|
|
186
|
+
handler: async (req: Request, res: Response) => {
|
|
187
|
+
try {
|
|
188
|
+
const { userId } = req.params;
|
|
189
|
+
const { pin, current_pin } = req.body;
|
|
190
|
+
|
|
191
|
+
if (!pin || pin.length < 4) {
|
|
192
|
+
return res.status(400).json({ error: 'PIN must be at least 4 characters' });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// If user already has PIN, verify current PIN
|
|
196
|
+
const settings = await getGuardianSettings(userId);
|
|
197
|
+
if (settings?.pin_hash) {
|
|
198
|
+
if (!current_pin) {
|
|
199
|
+
return res.status(400).json({ error: 'Current PIN required to change PIN' });
|
|
200
|
+
}
|
|
201
|
+
const isValid = await verifyPin(userId, current_pin);
|
|
202
|
+
if (!isValid) {
|
|
203
|
+
return res.status(401).json({ error: 'Invalid current PIN' });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await setPin(userId, pin);
|
|
208
|
+
res.json({ success: true });
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('[ParentalPlugin] Set PIN error:', error);
|
|
211
|
+
res.status(500).json({ error: 'Failed to set PIN' });
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Verify PIN
|
|
217
|
+
registry.addRoute({
|
|
218
|
+
method: 'post',
|
|
219
|
+
path: `${apiPrefix}/settings/:userId/verify-pin`,
|
|
220
|
+
pluginId: 'parental',
|
|
221
|
+
handler: async (req: Request, res: Response) => {
|
|
222
|
+
try {
|
|
223
|
+
const { userId } = req.params;
|
|
224
|
+
const { pin } = req.body;
|
|
225
|
+
|
|
226
|
+
if (!pin) {
|
|
227
|
+
return res.status(400).json({ error: 'PIN required' });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const isValid = await verifyPin(userId, pin);
|
|
231
|
+
|
|
232
|
+
if (!isValid) {
|
|
233
|
+
// Increment failed attempts
|
|
234
|
+
const attempts = await incrementFailedPinAttempts(userId);
|
|
235
|
+
|
|
236
|
+
if (attempts >= maxPinAttempts) {
|
|
237
|
+
// Lock the account
|
|
238
|
+
const lockUntil = new Date(Date.now() + pinLockoutMinutes * 60 * 1000);
|
|
239
|
+
return res.status(423).json({
|
|
240
|
+
error: 'Account locked due to too many failed attempts',
|
|
241
|
+
locked_until: lockUntil,
|
|
242
|
+
attempts,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return res.status(401).json({
|
|
247
|
+
error: 'Invalid PIN',
|
|
248
|
+
attempts_remaining: maxPinAttempts - attempts,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Reset failed attempts on success
|
|
253
|
+
await resetFailedPinAttempts(userId);
|
|
254
|
+
res.json({ success: true });
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('[ParentalPlugin] Verify PIN error:', error);
|
|
257
|
+
res.status(500).json({ error: 'Failed to verify PIN' });
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
263
|
+
// Profile Restrictions Routes
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
265
|
+
|
|
266
|
+
// Get restrictions for a profile
|
|
267
|
+
registry.addRoute({
|
|
268
|
+
method: 'get',
|
|
269
|
+
path: `${apiPrefix}/restrictions/:profileId`,
|
|
270
|
+
pluginId: 'parental',
|
|
271
|
+
handler: async (req: Request, res: Response) => {
|
|
272
|
+
try {
|
|
273
|
+
const { profileId } = req.params;
|
|
274
|
+
const restrictions = await getRestrictions(profileId);
|
|
275
|
+
res.json(restrictions);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error('[ParentalPlugin] Get restrictions error:', error);
|
|
278
|
+
res.status(500).json({ error: 'Failed to get restrictions' });
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Create restriction
|
|
284
|
+
registry.addRoute({
|
|
285
|
+
method: 'post',
|
|
286
|
+
path: `${apiPrefix}/restrictions`,
|
|
287
|
+
pluginId: 'parental',
|
|
288
|
+
handler: async (req: Request, res: Response) => {
|
|
289
|
+
try {
|
|
290
|
+
const input = req.body as CreateRestrictionInput;
|
|
291
|
+
|
|
292
|
+
// Validate with adapter
|
|
293
|
+
if (config.adapter.validateRestriction) {
|
|
294
|
+
const validation = config.adapter.validateRestriction(input);
|
|
295
|
+
if (!validation.valid) {
|
|
296
|
+
return res.status(400).json({ error: 'Invalid restriction', errors: validation.errors });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const restriction = await createRestriction(input);
|
|
301
|
+
res.status(201).json(restriction);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('[ParentalPlugin] Create restriction error:', error);
|
|
304
|
+
res.status(500).json({ error: 'Failed to create restriction' });
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Update restriction
|
|
310
|
+
registry.addRoute({
|
|
311
|
+
method: 'patch',
|
|
312
|
+
path: `${apiPrefix}/restrictions/:id`,
|
|
313
|
+
pluginId: 'parental',
|
|
314
|
+
handler: async (req: Request, res: Response) => {
|
|
315
|
+
try {
|
|
316
|
+
const { id } = req.params;
|
|
317
|
+
const updates = req.body as Partial<ProfileRestriction>;
|
|
318
|
+
|
|
319
|
+
const restriction = await updateRestriction(id, updates);
|
|
320
|
+
if (!restriction) {
|
|
321
|
+
return res.status(404).json({ error: 'Restriction not found' });
|
|
322
|
+
}
|
|
323
|
+
res.json(restriction);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('[ParentalPlugin] Update restriction error:', error);
|
|
326
|
+
res.status(500).json({ error: 'Failed to update restriction' });
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Delete restriction
|
|
332
|
+
registry.addRoute({
|
|
333
|
+
method: 'delete',
|
|
334
|
+
path: `${apiPrefix}/restrictions/:id`,
|
|
335
|
+
pluginId: 'parental',
|
|
336
|
+
handler: async (req: Request, res: Response) => {
|
|
337
|
+
try {
|
|
338
|
+
const { id } = req.params;
|
|
339
|
+
const deleted = await deleteRestriction(id);
|
|
340
|
+
|
|
341
|
+
if (!deleted) {
|
|
342
|
+
return res.status(404).json({ error: 'Restriction not found' });
|
|
343
|
+
}
|
|
344
|
+
res.status(204).send();
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error('[ParentalPlugin] Delete restriction error:', error);
|
|
347
|
+
res.status(500).json({ error: 'Failed to delete restriction' });
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Pause profile
|
|
353
|
+
registry.addRoute({
|
|
354
|
+
method: 'post',
|
|
355
|
+
path: `${apiPrefix}/restrictions/:profileId/pause`,
|
|
356
|
+
pluginId: 'parental',
|
|
357
|
+
handler: async (req: Request, res: Response) => {
|
|
358
|
+
try {
|
|
359
|
+
const { profileId } = req.params;
|
|
360
|
+
const { until, reason } = req.body;
|
|
361
|
+
|
|
362
|
+
await pauseProfile(profileId, until ? new Date(until) : undefined, reason);
|
|
363
|
+
res.json({ success: true, paused: true });
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error('[ParentalPlugin] Pause profile error:', error);
|
|
366
|
+
res.status(500).json({ error: 'Failed to pause profile' });
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Resume profile
|
|
372
|
+
registry.addRoute({
|
|
373
|
+
method: 'post',
|
|
374
|
+
path: `${apiPrefix}/restrictions/:profileId/resume`,
|
|
375
|
+
pluginId: 'parental',
|
|
376
|
+
handler: async (req: Request, res: Response) => {
|
|
377
|
+
try {
|
|
378
|
+
const { profileId } = req.params;
|
|
379
|
+
await resumeProfile(profileId);
|
|
380
|
+
res.json({ success: true, paused: false });
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error('[ParentalPlugin] Resume profile error:', error);
|
|
383
|
+
res.status(500).json({ error: 'Failed to resume profile' });
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Check profile access
|
|
389
|
+
registry.addRoute({
|
|
390
|
+
method: 'get',
|
|
391
|
+
path: `${apiPrefix}/restrictions/:profileId/check`,
|
|
392
|
+
pluginId: 'parental',
|
|
393
|
+
handler: async (req: Request, res: Response) => {
|
|
394
|
+
try {
|
|
395
|
+
const { profileId } = req.params;
|
|
396
|
+
const result = await checkProfileAccess(profileId);
|
|
397
|
+
res.json(result);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error('[ParentalPlugin] Check access error:', error);
|
|
400
|
+
res.status(500).json({ error: 'Failed to check access' });
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
406
|
+
// Activity Log Routes
|
|
407
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
408
|
+
|
|
409
|
+
// Log activity
|
|
410
|
+
registry.addRoute({
|
|
411
|
+
method: 'post',
|
|
412
|
+
path: `${apiPrefix}/activity`,
|
|
413
|
+
pluginId: 'parental',
|
|
414
|
+
handler: async (req: Request, res: Response) => {
|
|
415
|
+
try {
|
|
416
|
+
const input = req.body as LogActivityInput;
|
|
417
|
+
const activity = await logActivity({
|
|
418
|
+
...input,
|
|
419
|
+
adapter_type: config.adapter.name,
|
|
420
|
+
});
|
|
421
|
+
res.status(201).json(activity);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error('[ParentalPlugin] Log activity error:', error);
|
|
424
|
+
res.status(500).json({ error: 'Failed to log activity' });
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Get activity log
|
|
430
|
+
registry.addRoute({
|
|
431
|
+
method: 'get',
|
|
432
|
+
path: `${apiPrefix}/activity/:userId`,
|
|
433
|
+
pluginId: 'parental',
|
|
434
|
+
handler: async (req: Request, res: Response) => {
|
|
435
|
+
try {
|
|
436
|
+
const { userId } = req.params;
|
|
437
|
+
const limit = parseInt(req.query.limit as string) || 100;
|
|
438
|
+
const profileId = req.query.profileId as string | undefined;
|
|
439
|
+
|
|
440
|
+
const activities = await getActivityLog(userId, limit, profileId);
|
|
441
|
+
|
|
442
|
+
// Format details with adapter if available
|
|
443
|
+
const formattedActivities = activities.map((activity) => {
|
|
444
|
+
if (config.adapter.formatActivityDetails) {
|
|
445
|
+
return {
|
|
446
|
+
...activity,
|
|
447
|
+
formatted_details: config.adapter.formatActivityDetails(activity),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
return activity;
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
res.json(formattedActivities);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error('[ParentalPlugin] Get activity error:', error);
|
|
456
|
+
res.status(500).json({ error: 'Failed to get activity log' });
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Get adapter info (activity types, defaults)
|
|
462
|
+
registry.addRoute({
|
|
463
|
+
method: 'get',
|
|
464
|
+
path: `${apiPrefix}/adapter-info`,
|
|
465
|
+
pluginId: 'parental',
|
|
466
|
+
handler: async (_req: Request, res: Response) => {
|
|
467
|
+
try {
|
|
468
|
+
res.json({
|
|
469
|
+
name: config.adapter.name,
|
|
470
|
+
activity_types: config.adapter.getActivityTypes(),
|
|
471
|
+
default_daily_limit: config.adapter.getDefaultDailyLimit(),
|
|
472
|
+
});
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error('[ParentalPlugin] Get adapter info error:', error);
|
|
475
|
+
res.status(500).json({ error: 'Failed to get adapter info' });
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
log('Parental plugin started');
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
async onStop(): Promise<void> {
|
|
485
|
+
log('Stopping parental plugin');
|
|
486
|
+
await config.store.shutdown();
|
|
487
|
+
currentStore = null;
|
|
488
|
+
currentAdapter = null;
|
|
489
|
+
currentConfig = null;
|
|
490
|
+
log('Parental plugin stopped');
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
496
|
+
// Helper Functions
|
|
497
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Get the current parental store instance
|
|
501
|
+
*/
|
|
502
|
+
export function getParentalStore(): ParentalStore | null {
|
|
503
|
+
return currentStore;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get the current parental adapter instance
|
|
508
|
+
*/
|
|
509
|
+
export function getParentalAdapter(): ParentalAdapter | null {
|
|
510
|
+
return currentAdapter;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
514
|
+
// Guardian Settings Helpers
|
|
515
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Get guardian settings for a user
|
|
519
|
+
*/
|
|
520
|
+
export async function getGuardianSettings(userId: string): Promise<GuardianSettings | null> {
|
|
521
|
+
if (!currentStore) {
|
|
522
|
+
throw new Error('Parental plugin not initialized');
|
|
523
|
+
}
|
|
524
|
+
return currentStore.getSettings(userId);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Create guardian settings
|
|
529
|
+
*/
|
|
530
|
+
export async function createGuardianSettings(input: CreateGuardianSettingsInput): Promise<GuardianSettings> {
|
|
531
|
+
if (!currentStore) {
|
|
532
|
+
throw new Error('Parental plugin not initialized');
|
|
533
|
+
}
|
|
534
|
+
return currentStore.createSettings(input);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Update guardian settings
|
|
539
|
+
*/
|
|
540
|
+
export async function updateGuardianSettings(
|
|
541
|
+
userId: string,
|
|
542
|
+
input: UpdateGuardianSettingsInput
|
|
543
|
+
): Promise<GuardianSettings | null> {
|
|
544
|
+
if (!currentStore) {
|
|
545
|
+
throw new Error('Parental plugin not initialized');
|
|
546
|
+
}
|
|
547
|
+
return currentStore.updateSettings(userId, input);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Set PIN for guardian
|
|
552
|
+
*/
|
|
553
|
+
export async function setPin(userId: string, pin: string): Promise<void> {
|
|
554
|
+
if (!currentStore) {
|
|
555
|
+
throw new Error('Parental plugin not initialized');
|
|
556
|
+
}
|
|
557
|
+
const pinHash = hashPin(pin);
|
|
558
|
+
return currentStore.setPin(userId, pinHash);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Verify PIN
|
|
563
|
+
*/
|
|
564
|
+
export async function verifyPin(userId: string, pin: string): Promise<boolean> {
|
|
565
|
+
if (!currentStore) {
|
|
566
|
+
throw new Error('Parental plugin not initialized');
|
|
567
|
+
}
|
|
568
|
+
const pinHash = hashPin(pin);
|
|
569
|
+
return currentStore.verifyPin(userId, pinHash);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Increment failed PIN attempts
|
|
574
|
+
*/
|
|
575
|
+
export async function incrementFailedPinAttempts(userId: string): Promise<number> {
|
|
576
|
+
if (!currentStore) {
|
|
577
|
+
throw new Error('Parental plugin not initialized');
|
|
578
|
+
}
|
|
579
|
+
return currentStore.incrementFailedPinAttempts(userId);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Reset failed PIN attempts
|
|
584
|
+
*/
|
|
585
|
+
export async function resetFailedPinAttempts(userId: string): Promise<void> {
|
|
586
|
+
if (!currentStore) {
|
|
587
|
+
throw new Error('Parental plugin not initialized');
|
|
588
|
+
}
|
|
589
|
+
return currentStore.resetFailedPinAttempts(userId);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
593
|
+
// Profile Restrictions Helpers
|
|
594
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Get restrictions for a profile
|
|
598
|
+
*/
|
|
599
|
+
export async function getRestrictions(profileId: string): Promise<ProfileRestriction[]> {
|
|
600
|
+
if (!currentStore) {
|
|
601
|
+
throw new Error('Parental plugin not initialized');
|
|
602
|
+
}
|
|
603
|
+
return currentStore.getRestrictions(profileId);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Create a restriction
|
|
608
|
+
*/
|
|
609
|
+
export async function createRestriction(input: CreateRestrictionInput): Promise<ProfileRestriction> {
|
|
610
|
+
if (!currentStore) {
|
|
611
|
+
throw new Error('Parental plugin not initialized');
|
|
612
|
+
}
|
|
613
|
+
return currentStore.createRestriction(input);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Update a restriction
|
|
618
|
+
*/
|
|
619
|
+
export async function updateRestriction(
|
|
620
|
+
id: string,
|
|
621
|
+
updates: Partial<ProfileRestriction>
|
|
622
|
+
): Promise<ProfileRestriction | null> {
|
|
623
|
+
if (!currentStore) {
|
|
624
|
+
throw new Error('Parental plugin not initialized');
|
|
625
|
+
}
|
|
626
|
+
return currentStore.updateRestriction(id, updates);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Delete a restriction (soft delete)
|
|
631
|
+
*/
|
|
632
|
+
export async function deleteRestriction(id: string): Promise<boolean> {
|
|
633
|
+
if (!currentStore) {
|
|
634
|
+
throw new Error('Parental plugin not initialized');
|
|
635
|
+
}
|
|
636
|
+
return currentStore.deleteRestriction(id);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Pause a profile's access
|
|
641
|
+
*/
|
|
642
|
+
export async function pauseProfile(profileId: string, until?: Date, reason?: string): Promise<void> {
|
|
643
|
+
if (!currentStore) {
|
|
644
|
+
throw new Error('Parental plugin not initialized');
|
|
645
|
+
}
|
|
646
|
+
return currentStore.pauseProfile(profileId, until, reason);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Resume a profile's access
|
|
651
|
+
*/
|
|
652
|
+
export async function resumeProfile(profileId: string): Promise<void> {
|
|
653
|
+
if (!currentStore) {
|
|
654
|
+
throw new Error('Parental plugin not initialized');
|
|
655
|
+
}
|
|
656
|
+
return currentStore.resumeProfile(profileId);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Check if a profile has access based on restrictions
|
|
661
|
+
*/
|
|
662
|
+
export async function checkProfileAccess(profileId: string): Promise<AccessCheckResult> {
|
|
663
|
+
if (!currentStore) {
|
|
664
|
+
throw new Error('Parental plugin not initialized');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const restrictions = await currentStore.getRestrictions(profileId);
|
|
668
|
+
|
|
669
|
+
// Check if any restrictions are paused
|
|
670
|
+
for (const restriction of restrictions) {
|
|
671
|
+
if (restriction.is_paused) {
|
|
672
|
+
// Check if pause has expired
|
|
673
|
+
if (restriction.pause_until && new Date() > restriction.pause_until) {
|
|
674
|
+
// Auto-resume (should be handled by cron, but check here too)
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
allowed: false,
|
|
679
|
+
reason: restriction.pause_reason || 'Profile is paused',
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Check schedule restrictions
|
|
684
|
+
if (restriction.schedule) {
|
|
685
|
+
if (!isWithinSchedule(restriction.schedule)) {
|
|
686
|
+
// Find when access becomes available
|
|
687
|
+
const now = new Date();
|
|
688
|
+
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
689
|
+
const today = dayNames[now.getDay()];
|
|
690
|
+
const todaySchedule = restriction.schedule[today];
|
|
691
|
+
|
|
692
|
+
let availableAt: Date | undefined;
|
|
693
|
+
if (todaySchedule) {
|
|
694
|
+
const [hours, minutes] = todaySchedule.start.split(':').map(Number);
|
|
695
|
+
availableAt = new Date(now);
|
|
696
|
+
availableAt.setHours(hours, minutes, 0, 0);
|
|
697
|
+
|
|
698
|
+
// If we're past today's start, set to tomorrow
|
|
699
|
+
if (now > availableAt) {
|
|
700
|
+
availableAt.setDate(availableAt.getDate() + 1);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
allowed: false,
|
|
706
|
+
reason: 'Outside allowed hours',
|
|
707
|
+
available_at: availableAt,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Get time limit restriction and check remaining time
|
|
714
|
+
const timeLimitRestriction = restrictions.find((r) => r.restriction_type === 'time_limit');
|
|
715
|
+
if (timeLimitRestriction?.daily_limit_minutes) {
|
|
716
|
+
// Note: Actual time tracking would be done via usage-plugin
|
|
717
|
+
// This is a placeholder for the check result
|
|
718
|
+
return {
|
|
719
|
+
allowed: true,
|
|
720
|
+
minutes_remaining: timeLimitRestriction.daily_limit_minutes,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return { allowed: true };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
728
|
+
// Activity Log Helpers
|
|
729
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Log an activity
|
|
733
|
+
*/
|
|
734
|
+
export async function logActivity(input: LogActivityInput): Promise<ActivityLog> {
|
|
735
|
+
if (!currentStore) {
|
|
736
|
+
throw new Error('Parental plugin not initialized');
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Call adapter hook if available
|
|
740
|
+
if (currentAdapter?.onRestrictionViolation && input.activity_type === 'restriction_violation') {
|
|
741
|
+
await currentAdapter.onRestrictionViolation(input.profile_id || '', input.details?.reason as string || 'Unknown');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (currentAdapter?.onDailyLimitReached && input.activity_type === 'time_limit_reached') {
|
|
745
|
+
await currentAdapter.onDailyLimitReached(input.profile_id || '');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return currentStore.logActivity(input);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Get activity log
|
|
753
|
+
*/
|
|
754
|
+
export async function getActivityLog(userId: string, limit = 100, profileId?: string): Promise<ActivityLog[]> {
|
|
755
|
+
if (!currentStore) {
|
|
756
|
+
throw new Error('Parental plugin not initialized');
|
|
757
|
+
}
|
|
758
|
+
return currentStore.getActivityLog(userId, limit, profileId);
|
|
759
|
+
}
|