@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,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiles Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for generic multi-profile management with age support.
|
|
5
|
+
* Supports child profiles for QwickBot, player profiles for gaming, etc.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Content filter levels for age-appropriate content
|
|
12
|
+
*/
|
|
13
|
+
export type ContentFilterLevel = 'strict' | 'moderate' | 'minimal' | 'none';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Age groups for categorization
|
|
17
|
+
*/
|
|
18
|
+
export type AgeGroup = 'child' | 'teen' | 'adult';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Profile record in the database
|
|
22
|
+
*/
|
|
23
|
+
export interface Profile {
|
|
24
|
+
/** Primary key - UUID */
|
|
25
|
+
id: string;
|
|
26
|
+
/** Organization ID for multi-tenant isolation */
|
|
27
|
+
org_id?: string;
|
|
28
|
+
/** Owner/parent user ID */
|
|
29
|
+
user_id: string;
|
|
30
|
+
/** Profile name */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Avatar identifier or URL */
|
|
33
|
+
avatar?: string;
|
|
34
|
+
|
|
35
|
+
// Age-based features
|
|
36
|
+
/** Birth date for precise age calculation */
|
|
37
|
+
birth_date?: Date;
|
|
38
|
+
/** Static age (if birth_date not provided) */
|
|
39
|
+
age?: number;
|
|
40
|
+
/** Computed age group (child, teen, adult) */
|
|
41
|
+
age_group?: AgeGroup;
|
|
42
|
+
|
|
43
|
+
// Content/access control
|
|
44
|
+
/** Content filter level */
|
|
45
|
+
content_filter_level: ContentFilterLevel;
|
|
46
|
+
|
|
47
|
+
// Time restrictions
|
|
48
|
+
/** Daily time limit in minutes (null = no limit) */
|
|
49
|
+
daily_time_limit_minutes?: number;
|
|
50
|
+
/** Allowed hours start (null = no restriction) */
|
|
51
|
+
allowed_hours_start?: string;
|
|
52
|
+
/** Allowed hours end (null = no restriction) */
|
|
53
|
+
allowed_hours_end?: string;
|
|
54
|
+
|
|
55
|
+
// Status
|
|
56
|
+
/** Whether profile is active */
|
|
57
|
+
is_active: boolean;
|
|
58
|
+
/** Whether this is the default profile for the user */
|
|
59
|
+
is_default: boolean;
|
|
60
|
+
|
|
61
|
+
// Extensibility
|
|
62
|
+
/** App-specific metadata */
|
|
63
|
+
metadata: Record<string, unknown>;
|
|
64
|
+
|
|
65
|
+
/** When the profile was created */
|
|
66
|
+
created_at: Date;
|
|
67
|
+
/** When the profile was last updated */
|
|
68
|
+
updated_at: Date;
|
|
69
|
+
/** Soft delete timestamp */
|
|
70
|
+
deleted_at?: Date;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Profile creation payload
|
|
75
|
+
*/
|
|
76
|
+
export interface CreateProfileInput {
|
|
77
|
+
/** Organization ID */
|
|
78
|
+
org_id?: string;
|
|
79
|
+
/** Owner user ID */
|
|
80
|
+
user_id: string;
|
|
81
|
+
/** Profile name */
|
|
82
|
+
name: string;
|
|
83
|
+
/** Avatar identifier or URL */
|
|
84
|
+
avatar?: string;
|
|
85
|
+
/** Birth date for age calculation */
|
|
86
|
+
birth_date?: Date;
|
|
87
|
+
/** Static age (if birth_date not provided) */
|
|
88
|
+
age?: number;
|
|
89
|
+
/** Content filter level (default: 'moderate') */
|
|
90
|
+
content_filter_level?: ContentFilterLevel;
|
|
91
|
+
/** Daily time limit in minutes */
|
|
92
|
+
daily_time_limit_minutes?: number;
|
|
93
|
+
/** Allowed hours start (HH:MM format) */
|
|
94
|
+
allowed_hours_start?: string;
|
|
95
|
+
/** Allowed hours end (HH:MM format) */
|
|
96
|
+
allowed_hours_end?: string;
|
|
97
|
+
/** Whether this is the default profile */
|
|
98
|
+
is_default?: boolean;
|
|
99
|
+
/** App-specific metadata */
|
|
100
|
+
metadata?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Profile update payload
|
|
105
|
+
*/
|
|
106
|
+
export interface UpdateProfileInput {
|
|
107
|
+
/** Profile name */
|
|
108
|
+
name?: string;
|
|
109
|
+
/** Avatar identifier or URL */
|
|
110
|
+
avatar?: string;
|
|
111
|
+
/** Birth date for age calculation */
|
|
112
|
+
birth_date?: Date | null;
|
|
113
|
+
/** Static age */
|
|
114
|
+
age?: number | null;
|
|
115
|
+
/** Content filter level */
|
|
116
|
+
content_filter_level?: ContentFilterLevel;
|
|
117
|
+
/** Daily time limit in minutes (null to remove) */
|
|
118
|
+
daily_time_limit_minutes?: number | null;
|
|
119
|
+
/** Allowed hours start (null to remove) */
|
|
120
|
+
allowed_hours_start?: string | null;
|
|
121
|
+
/** Allowed hours end (null to remove) */
|
|
122
|
+
allowed_hours_end?: string | null;
|
|
123
|
+
/** Whether profile is active */
|
|
124
|
+
is_active?: boolean;
|
|
125
|
+
/** Whether this is the default profile */
|
|
126
|
+
is_default?: boolean;
|
|
127
|
+
/** App-specific metadata */
|
|
128
|
+
metadata?: Record<string, unknown>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Profile search parameters
|
|
133
|
+
*/
|
|
134
|
+
export interface ProfileSearchParams {
|
|
135
|
+
/** Organization ID filter */
|
|
136
|
+
org_id?: string;
|
|
137
|
+
/** User ID filter */
|
|
138
|
+
user_id?: string;
|
|
139
|
+
/** Age group filter */
|
|
140
|
+
age_group?: AgeGroup;
|
|
141
|
+
/** Active status filter */
|
|
142
|
+
is_active?: boolean;
|
|
143
|
+
/** Search query (searches name) */
|
|
144
|
+
query?: string;
|
|
145
|
+
/** Page number (1-indexed) */
|
|
146
|
+
page?: number;
|
|
147
|
+
/** Items per page */
|
|
148
|
+
limit?: number;
|
|
149
|
+
/** Sort field */
|
|
150
|
+
sortBy?: 'name' | 'created_at' | 'age';
|
|
151
|
+
/** Sort direction */
|
|
152
|
+
sortOrder?: 'asc' | 'desc';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Paginated profile list response
|
|
157
|
+
*/
|
|
158
|
+
export interface ProfileListResponse {
|
|
159
|
+
profiles: Profile[];
|
|
160
|
+
total: number;
|
|
161
|
+
page: number;
|
|
162
|
+
limit: number;
|
|
163
|
+
totalPages: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Time restriction check result
|
|
168
|
+
*/
|
|
169
|
+
export interface TimeRestrictionResult {
|
|
170
|
+
/** Whether access is allowed right now */
|
|
171
|
+
allowed: boolean;
|
|
172
|
+
/** Reason if not allowed */
|
|
173
|
+
reason?: string;
|
|
174
|
+
/** Minutes remaining today (if has limit) */
|
|
175
|
+
minutes_remaining?: number;
|
|
176
|
+
/** When restriction ends (if currently restricted) */
|
|
177
|
+
available_at?: Date;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
+
// Store Interface
|
|
182
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Profile store interface - all storage backends must implement this
|
|
186
|
+
*/
|
|
187
|
+
export interface ProfileStore {
|
|
188
|
+
/** Store name (e.g., 'postgres', 'memory') */
|
|
189
|
+
name: string;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Initialize the store (create tables, etc.)
|
|
193
|
+
*/
|
|
194
|
+
initialize(): Promise<void>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get a profile by ID
|
|
198
|
+
*/
|
|
199
|
+
getById(id: string): Promise<Profile | null>;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a new profile
|
|
203
|
+
*/
|
|
204
|
+
create(input: CreateProfileInput): Promise<Profile>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Update an existing profile
|
|
208
|
+
*/
|
|
209
|
+
update(id: string, input: UpdateProfileInput): Promise<Profile | null>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Soft delete a profile
|
|
213
|
+
*/
|
|
214
|
+
delete(id: string): Promise<boolean>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Search/list profiles
|
|
218
|
+
*/
|
|
219
|
+
search(params: ProfileSearchParams): Promise<ProfileListResponse>;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* List profiles for a user
|
|
223
|
+
*/
|
|
224
|
+
listByUser(userId: string): Promise<Profile[]>;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the default profile for a user
|
|
228
|
+
*/
|
|
229
|
+
getDefaultProfile(userId: string): Promise<Profile | null>;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Count profiles for a user
|
|
233
|
+
*/
|
|
234
|
+
getProfileCount(userId: string): Promise<number>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get profiles by age group
|
|
238
|
+
*/
|
|
239
|
+
getByAgeGroup(userId: string, ageGroup: AgeGroup): Promise<Profile[]>;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Set a profile as default (unsets others)
|
|
243
|
+
*/
|
|
244
|
+
setDefaultProfile(profileId: string, userId: string): Promise<boolean>;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Shutdown the store
|
|
248
|
+
*/
|
|
249
|
+
shutdown(): Promise<void>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
253
|
+
// Configuration Types
|
|
254
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* PostgreSQL profile store configuration
|
|
258
|
+
*/
|
|
259
|
+
export interface PostgresProfileStoreConfig {
|
|
260
|
+
/** PostgreSQL pool instance or a function that returns one (for lazy initialization) */
|
|
261
|
+
pool: unknown | (() => unknown);
|
|
262
|
+
/** Profiles table name (default: 'profiles') */
|
|
263
|
+
tableName?: string;
|
|
264
|
+
/** Schema name (default: 'public') */
|
|
265
|
+
schema?: string;
|
|
266
|
+
/** Auto-create tables on init (default: true) */
|
|
267
|
+
autoCreateTables?: boolean;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Age thresholds for categorization
|
|
272
|
+
*/
|
|
273
|
+
export interface AgeThresholds {
|
|
274
|
+
/** Maximum age for 'child' (default: 12) */
|
|
275
|
+
child: number;
|
|
276
|
+
/** Maximum age for 'teen' (default: 17) */
|
|
277
|
+
teen: number;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* API configuration
|
|
282
|
+
*/
|
|
283
|
+
export interface ProfilesApiConfig {
|
|
284
|
+
/** API route prefix (default: '/profiles') */
|
|
285
|
+
prefix?: string;
|
|
286
|
+
/** Enable CRUD endpoints */
|
|
287
|
+
crud?: boolean;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Profiles plugin configuration
|
|
292
|
+
*/
|
|
293
|
+
export interface ProfilesPluginConfig {
|
|
294
|
+
/** Profile storage backend */
|
|
295
|
+
store: ProfileStore;
|
|
296
|
+
/** Maximum profiles per user (default: 10) */
|
|
297
|
+
maxProfilesPerUser?: number;
|
|
298
|
+
/** Default content filter level (default: 'moderate') */
|
|
299
|
+
defaultFilterLevel?: ContentFilterLevel;
|
|
300
|
+
/** Age thresholds for categorization */
|
|
301
|
+
ageThresholds?: AgeThresholds;
|
|
302
|
+
/** API configuration */
|
|
303
|
+
api?: ProfilesApiConfig;
|
|
304
|
+
/** Enable debug logging */
|
|
305
|
+
debug?: boolean;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
309
|
+
// App-specific Metadata Types (examples)
|
|
310
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* QwickBot profile metadata (stored in metadata field)
|
|
314
|
+
*/
|
|
315
|
+
export interface QwickBotProfileMetadata {
|
|
316
|
+
/** Voice ID for text-to-speech */
|
|
317
|
+
voice_id?: string;
|
|
318
|
+
/** Avatar style identifier */
|
|
319
|
+
avatar_style?: string;
|
|
320
|
+
/** Personality prompt for AI */
|
|
321
|
+
personality_prompt?: string;
|
|
322
|
+
/** User's interests */
|
|
323
|
+
interests?: string[];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Gaming profile metadata (stored in metadata field)
|
|
328
|
+
*/
|
|
329
|
+
export interface GamingProfileMetadata {
|
|
330
|
+
/** Gamer tag */
|
|
331
|
+
gamer_tag?: string;
|
|
332
|
+
/** Avatar URL */
|
|
333
|
+
avatar_url?: string;
|
|
334
|
+
/** Allowed game ratings (E, E10+, T, M) */
|
|
335
|
+
allowed_ratings?: string[];
|
|
336
|
+
/** Preferred genres */
|
|
337
|
+
preferred_genres?: string[];
|
|
338
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Plugin Tests
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import type { RateLimitStore, RateLimitCache, CachedLimit, StoredLimit, IncrementOptions } from '../types.js';
|
|
9
|
+
import { RateLimitService } from '../rate-limit-service.js';
|
|
10
|
+
import { createSlidingWindowStrategy } from '../strategies/sliding-window.js';
|
|
11
|
+
import { createFixedWindowStrategy } from '../strategies/fixed-window.js';
|
|
12
|
+
import { createTokenBucketStrategy } from '../strategies/token-bucket.js';
|
|
13
|
+
|
|
14
|
+
// Mock store implementation
|
|
15
|
+
function createMockStore(): RateLimitStore {
|
|
16
|
+
const records = new Map<string, StoredLimit>();
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
name: 'mock',
|
|
20
|
+
|
|
21
|
+
async initialize(): Promise<void> {
|
|
22
|
+
// No-op
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async get(key: string): Promise<StoredLimit | null> {
|
|
26
|
+
return records.get(key) || null;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async increment(key: string, options: IncrementOptions): Promise<StoredLimit> {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const windowMs = options.windowMs;
|
|
32
|
+
const windowStart = new Date(now.getTime() - (now.getTime() % windowMs));
|
|
33
|
+
const windowEnd = new Date(windowStart.getTime() + windowMs);
|
|
34
|
+
|
|
35
|
+
const existing = records.get(key);
|
|
36
|
+
if (existing && existing.windowStart.getTime() === windowStart.getTime()) {
|
|
37
|
+
existing.count += options.amount || 1;
|
|
38
|
+
existing.updatedAt = now;
|
|
39
|
+
return existing;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const newRecord: StoredLimit = {
|
|
43
|
+
id: `mock-${Date.now()}`,
|
|
44
|
+
key,
|
|
45
|
+
count: options.amount || 1,
|
|
46
|
+
maxRequests: options.maxRequests,
|
|
47
|
+
windowMs: options.windowMs,
|
|
48
|
+
windowStart,
|
|
49
|
+
windowEnd,
|
|
50
|
+
strategy: options.strategy,
|
|
51
|
+
userId: options.userId,
|
|
52
|
+
tenantId: options.tenantId,
|
|
53
|
+
ipAddress: options.ipAddress,
|
|
54
|
+
createdAt: now,
|
|
55
|
+
updatedAt: now,
|
|
56
|
+
};
|
|
57
|
+
records.set(key, newRecord);
|
|
58
|
+
return newRecord;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async clear(key: string): Promise<boolean> {
|
|
62
|
+
return records.delete(key);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async cleanup(): Promise<number> {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
let deleted = 0;
|
|
68
|
+
for (const [key, record] of records) {
|
|
69
|
+
if (record.windowEnd.getTime() < now) {
|
|
70
|
+
records.delete(key);
|
|
71
|
+
deleted++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return deleted;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async shutdown(): Promise<void> {
|
|
78
|
+
records.clear();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mock cache implementation
|
|
84
|
+
function createMockCache(): RateLimitCache {
|
|
85
|
+
const cache = new Map<string, { value: CachedLimit; expiresAt: number }>();
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
name: 'mock',
|
|
89
|
+
|
|
90
|
+
async get(key: string): Promise<CachedLimit | null> {
|
|
91
|
+
const entry = cache.get(key);
|
|
92
|
+
if (!entry) return null;
|
|
93
|
+
if (entry.expiresAt <= Date.now()) {
|
|
94
|
+
cache.delete(key);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return entry.value;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async set(key: string, value: CachedLimit, ttlMs: number): Promise<void> {
|
|
101
|
+
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async increment(key: string, amount = 1): Promise<number | null> {
|
|
105
|
+
const entry = cache.get(key);
|
|
106
|
+
if (!entry || entry.expiresAt <= Date.now()) return null;
|
|
107
|
+
entry.value.count += amount;
|
|
108
|
+
return entry.value.count;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async delete(key: string): Promise<boolean> {
|
|
112
|
+
return cache.delete(key);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
isAvailable(): boolean {
|
|
116
|
+
return true;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
async shutdown(): Promise<void> {
|
|
120
|
+
cache.clear();
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
describe('RateLimitService', () => {
|
|
126
|
+
let store: RateLimitStore;
|
|
127
|
+
let cache: RateLimitCache;
|
|
128
|
+
let service: RateLimitService;
|
|
129
|
+
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
store = createMockStore();
|
|
132
|
+
cache = createMockCache();
|
|
133
|
+
service = new RateLimitService({
|
|
134
|
+
store,
|
|
135
|
+
cache,
|
|
136
|
+
defaults: {
|
|
137
|
+
windowMs: 60000,
|
|
138
|
+
maxRequests: 100,
|
|
139
|
+
strategy: 'sliding-window',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
afterEach(async () => {
|
|
145
|
+
await store.shutdown();
|
|
146
|
+
await cache.shutdown();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('checkLimit', () => {
|
|
150
|
+
it('should return not limited for first request', async () => {
|
|
151
|
+
const status = await service.checkLimit('test:key', { increment: false });
|
|
152
|
+
|
|
153
|
+
expect(status.limited).toBe(false);
|
|
154
|
+
expect(status.current).toBe(0);
|
|
155
|
+
expect(status.limit).toBe(100);
|
|
156
|
+
expect(status.remaining).toBe(100);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should use provided options over defaults', async () => {
|
|
160
|
+
const status = await service.checkLimit('test:key', {
|
|
161
|
+
maxRequests: 50,
|
|
162
|
+
windowMs: 30000,
|
|
163
|
+
increment: false,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(status.limit).toBe(50);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('incrementLimit', () => {
|
|
171
|
+
it('should increment the counter', async () => {
|
|
172
|
+
const status1 = await service.incrementLimit('test:key');
|
|
173
|
+
expect(status1.current).toBe(1);
|
|
174
|
+
expect(status1.remaining).toBe(99);
|
|
175
|
+
|
|
176
|
+
const status2 = await service.incrementLimit('test:key');
|
|
177
|
+
expect(status2.current).toBe(2);
|
|
178
|
+
expect(status2.remaining).toBe(98);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return limited when max reached', async () => {
|
|
182
|
+
// Set low limit for testing
|
|
183
|
+
for (let i = 0; i < 5; i++) {
|
|
184
|
+
await service.incrementLimit('test:key', { maxRequests: 5 });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const status = await service.incrementLimit('test:key', { maxRequests: 5 });
|
|
188
|
+
expect(status.limited).toBe(true);
|
|
189
|
+
expect(status.remaining).toBe(0);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('isLimited', () => {
|
|
194
|
+
it('should return false when not limited', async () => {
|
|
195
|
+
const limited = await service.isLimited('test:key');
|
|
196
|
+
expect(limited).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should return true when limited', async () => {
|
|
200
|
+
// Exhaust the limit
|
|
201
|
+
for (let i = 0; i < 5; i++) {
|
|
202
|
+
await service.incrementLimit('test:key', { maxRequests: 5 });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const limited = await service.isLimited('test:key', { maxRequests: 5 });
|
|
206
|
+
expect(limited).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('clearLimit', () => {
|
|
211
|
+
it('should clear the limit', async () => {
|
|
212
|
+
// Create some limits
|
|
213
|
+
await service.incrementLimit('test:key');
|
|
214
|
+
await service.incrementLimit('test:key');
|
|
215
|
+
|
|
216
|
+
// Verify exists
|
|
217
|
+
const beforeStatus = await service.checkLimit('test:key', { increment: false });
|
|
218
|
+
expect(beforeStatus.current).toBeGreaterThan(0);
|
|
219
|
+
|
|
220
|
+
// Clear
|
|
221
|
+
await service.clearLimit('test:key');
|
|
222
|
+
|
|
223
|
+
// Verify cleared
|
|
224
|
+
const afterStatus = await service.checkLimit('test:key', { increment: false });
|
|
225
|
+
expect(afterStatus.current).toBe(0);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('Strategies', () => {
|
|
231
|
+
describe('Sliding Window', () => {
|
|
232
|
+
it('should create strategy with correct name', () => {
|
|
233
|
+
const strategy = createSlidingWindowStrategy();
|
|
234
|
+
expect(strategy.name).toBe('sliding-window');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('Fixed Window', () => {
|
|
239
|
+
it('should create strategy with correct name', () => {
|
|
240
|
+
const strategy = createFixedWindowStrategy();
|
|
241
|
+
expect(strategy.name).toBe('fixed-window');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('Token Bucket', () => {
|
|
246
|
+
it('should create strategy with correct name', () => {
|
|
247
|
+
const strategy = createTokenBucketStrategy();
|
|
248
|
+
expect(strategy.name).toBe('token-bucket');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('Types', () => {
|
|
254
|
+
it('should export all required types', async () => {
|
|
255
|
+
// This test verifies that the types module compiles correctly
|
|
256
|
+
const types = await import('../types.js');
|
|
257
|
+
expect(types).toBeDefined();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Cleanup Job
|
|
3
|
+
*
|
|
4
|
+
* Periodically removes expired rate limit records from the database.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RateLimitStore } from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Cleanup job configuration
|
|
13
|
+
*/
|
|
14
|
+
export interface CleanupJobConfig {
|
|
15
|
+
/** Store to clean up */
|
|
16
|
+
store: RateLimitStore;
|
|
17
|
+
|
|
18
|
+
/** Cleanup interval in milliseconds (default: 300000 = 5 min) */
|
|
19
|
+
intervalMs?: number;
|
|
20
|
+
|
|
21
|
+
/** Enable debug logging */
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Cleanup job state
|
|
27
|
+
*/
|
|
28
|
+
export interface CleanupJob {
|
|
29
|
+
/** Start the cleanup job */
|
|
30
|
+
start(): void;
|
|
31
|
+
|
|
32
|
+
/** Stop the cleanup job */
|
|
33
|
+
stop(): void;
|
|
34
|
+
|
|
35
|
+
/** Run cleanup immediately */
|
|
36
|
+
runNow(): Promise<number>;
|
|
37
|
+
|
|
38
|
+
/** Check if job is running */
|
|
39
|
+
isRunning(): boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a cleanup job
|
|
44
|
+
*
|
|
45
|
+
* @param config Cleanup configuration
|
|
46
|
+
* @returns CleanupJob instance
|
|
47
|
+
*/
|
|
48
|
+
export function createCleanupJob(config: CleanupJobConfig): CleanupJob {
|
|
49
|
+
const {
|
|
50
|
+
store,
|
|
51
|
+
intervalMs = 300000, // 5 minutes default
|
|
52
|
+
debug = false,
|
|
53
|
+
} = config;
|
|
54
|
+
|
|
55
|
+
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
56
|
+
let isRunningCleanup = false;
|
|
57
|
+
|
|
58
|
+
function log(message: string, data?: Record<string, unknown>) {
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.log(`[RateLimitCleanup] ${message}`, data || '');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runCleanup(): Promise<number> {
|
|
65
|
+
if (isRunningCleanup) {
|
|
66
|
+
log('Cleanup already in progress, skipping');
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isRunningCleanup = true;
|
|
71
|
+
try {
|
|
72
|
+
log('Starting cleanup');
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const deletedCount = await store.cleanup();
|
|
75
|
+
const duration = Date.now() - startTime;
|
|
76
|
+
|
|
77
|
+
log('Cleanup complete', { deletedCount, durationMs: duration });
|
|
78
|
+
return deletedCount;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('[RateLimitCleanup] Error during cleanup:', error);
|
|
81
|
+
return 0;
|
|
82
|
+
} finally {
|
|
83
|
+
isRunningCleanup = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
start() {
|
|
89
|
+
if (intervalId) {
|
|
90
|
+
log('Cleanup job already running');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
log('Starting cleanup job', { intervalMs });
|
|
95
|
+
intervalId = setInterval(runCleanup, intervalMs);
|
|
96
|
+
|
|
97
|
+
// Run initial cleanup after a short delay
|
|
98
|
+
setTimeout(runCleanup, 10000);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
stop() {
|
|
102
|
+
if (intervalId) {
|
|
103
|
+
log('Stopping cleanup job');
|
|
104
|
+
clearInterval(intervalId);
|
|
105
|
+
intervalId = null;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async runNow(): Promise<number> {
|
|
110
|
+
return runCleanup();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
isRunning(): boolean {
|
|
114
|
+
return intervalId !== null;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|