@qwickapps/server 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +507 -0
- package/README.md +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
- package/dist/plugins/bans/bans-plugin.js +12 -3
- package/dist/plugins/bans/bans-plugin.js.map +1 -1
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
- package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
- package/dist/plugins/devices/adapters/index.d.ts +12 -0
- package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/index.js +10 -0
- package/dist/plugins/devices/adapters/index.js.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
- package/dist/plugins/devices/devices-plugin.d.ts +70 -0
- package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
- package/dist/plugins/devices/devices-plugin.js +453 -0
- package/dist/plugins/devices/devices-plugin.js.map +1 -0
- package/dist/plugins/devices/index.d.ts +18 -0
- package/dist/plugins/devices/index.d.ts.map +1 -0
- package/dist/plugins/devices/index.js +18 -0
- package/dist/plugins/devices/index.js.map +1 -0
- package/dist/plugins/devices/stores/index.d.ts +9 -0
- package/dist/plugins/devices/stores/index.d.ts.map +1 -0
- package/dist/plugins/devices/stores/index.js +9 -0
- package/dist/plugins/devices/stores/index.js.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.js +199 -0
- package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
- package/dist/plugins/devices/token-utils.d.ts +100 -0
- package/dist/plugins/devices/token-utils.d.ts.map +1 -0
- package/dist/plugins/devices/token-utils.js +162 -0
- package/dist/plugins/devices/token-utils.js.map +1 -0
- package/dist/plugins/devices/types.d.ts +307 -0
- package/dist/plugins/devices/types.d.ts.map +1 -0
- package/dist/plugins/devices/types.js +10 -0
- package/dist/plugins/devices/types.js.map +1 -0
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +13 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
- package/dist/plugins/notifications/index.d.ts +71 -0
- package/dist/plugins/notifications/index.d.ts.map +1 -0
- package/dist/plugins/notifications/index.js +72 -0
- package/dist/plugins/notifications/index.js.map +1 -0
- package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
- package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-manager.js +610 -0
- package/dist/plugins/notifications/notifications-manager.js.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.js +337 -0
- package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
- package/dist/plugins/notifications/types.d.ts +164 -0
- package/dist/plugins/notifications/types.d.ts.map +1 -0
- package/dist/plugins/notifications/types.js +9 -0
- package/dist/plugins/notifications/types.js.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
- package/dist/plugins/parental/adapters/index.d.ts +8 -0
- package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/index.js +7 -0
- package/dist/plugins/parental/adapters/index.js.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
- package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
- package/dist/plugins/parental/index.d.ts +14 -0
- package/dist/plugins/parental/index.d.ts.map +1 -0
- package/dist/plugins/parental/index.js +15 -0
- package/dist/plugins/parental/index.js.map +1 -0
- package/dist/plugins/parental/parental-plugin.d.ts +88 -0
- package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
- package/dist/plugins/parental/parental-plugin.js +666 -0
- package/dist/plugins/parental/parental-plugin.js.map +1 -0
- package/dist/plugins/parental/stores/index.d.ts +7 -0
- package/dist/plugins/parental/stores/index.d.ts.map +1 -0
- package/dist/plugins/parental/stores/index.js +7 -0
- package/dist/plugins/parental/stores/index.js.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.js +209 -0
- package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
- package/dist/plugins/parental/types.d.ts +154 -0
- package/dist/plugins/parental/types.d.ts.map +1 -0
- package/dist/plugins/parental/types.js +10 -0
- package/dist/plugins/parental/types.js.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
- package/dist/plugins/profiles/index.d.ts +12 -0
- package/dist/plugins/profiles/index.d.ts.map +1 -0
- package/dist/plugins/profiles/index.js +13 -0
- package/dist/plugins/profiles/index.js.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.js +481 -0
- package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
- package/dist/plugins/profiles/stores/index.d.ts +9 -0
- package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/index.js +9 -0
- package/dist/plugins/profiles/stores/index.js.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.js +310 -0
- package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
- package/dist/plugins/profiles/types.d.ts +289 -0
- package/dist/plugins/profiles/types.d.ts.map +1 -0
- package/dist/plugins/profiles/types.js +10 -0
- package/dist/plugins/profiles/types.js.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
- package/dist/plugins/subscriptions/index.d.ts +12 -0
- package/dist/plugins/subscriptions/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/index.js +13 -0
- package/dist/plugins/subscriptions/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
- package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/index.js +9 -0
- package/dist/plugins/subscriptions/stores/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
- package/dist/plugins/subscriptions/types.d.ts +308 -0
- package/dist/plugins/subscriptions/types.d.ts.map +1 -0
- package/dist/plugins/subscriptions/types.js +10 -0
- package/dist/plugins/subscriptions/types.js.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
- package/dist/plugins/usage/index.d.ts +12 -0
- package/dist/plugins/usage/index.d.ts.map +1 -0
- package/dist/plugins/usage/index.js +13 -0
- package/dist/plugins/usage/index.js.map +1 -0
- package/dist/plugins/usage/stores/index.d.ts +9 -0
- package/dist/plugins/usage/stores/index.d.ts.map +1 -0
- package/dist/plugins/usage/stores/index.js +9 -0
- package/dist/plugins/usage/stores/index.js.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.js +146 -0
- package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
- package/dist/plugins/usage/types.d.ts +195 -0
- package/dist/plugins/usage/types.d.ts.map +1 -0
- package/dist/plugins/usage/types.js +10 -0
- package/dist/plugins/usage/types.js.map +1 -0
- package/dist/plugins/usage/usage-plugin.d.ts +51 -0
- package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
- package/dist/plugins/usage/usage-plugin.js +412 -0
- package/dist/plugins/usage/usage-plugin.js.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
- package/dist/plugins/users/index.d.ts +2 -2
- package/dist/plugins/users/index.d.ts.map +1 -1
- package/dist/plugins/users/index.js +1 -1
- package/dist/plugins/users/index.js.map +1 -1
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
- package/dist/plugins/users/stores/postgres-store.js +76 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -1
- package/dist/plugins/users/types.d.ts +74 -6
- package/dist/plugins/users/types.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.d.ts +15 -1
- package/dist/plugins/users/users-plugin.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.js +29 -0
- package/dist/plugins/users/users-plugin.js.map +1 -1
- package/dist-ui/assets/index-CynOqPkb.js +469 -0
- package/dist-ui/assets/index-CynOqPkb.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +46 -0
- package/dist-ui-lib/components/StatCard.d.ts +16 -0
- package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +1 -0
- package/dist-ui-lib/index.js +1822 -1611
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
- package/dist-ui-lib/utils/formatters.d.ts +19 -0
- package/package.json +3 -2
- package/src/index.ts +178 -0
- package/src/plugins/bans/bans-plugin.ts +15 -3
- package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
- package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
- package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
- package/src/plugins/devices/adapters/index.ts +13 -0
- package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
- package/src/plugins/devices/devices-plugin.ts +538 -0
- package/src/plugins/devices/index.ts +69 -0
- package/src/plugins/devices/stores/index.ts +9 -0
- package/src/plugins/devices/stores/postgres-store.ts +304 -0
- package/src/plugins/devices/token-utils.ts +213 -0
- package/src/plugins/devices/types.ts +351 -0
- package/src/plugins/index.ts +218 -0
- package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
- package/src/plugins/notifications/index.ts +91 -0
- package/src/plugins/notifications/notifications-manager.ts +773 -0
- package/src/plugins/notifications/notifications-plugin.ts +398 -0
- package/src/plugins/notifications/types.ts +207 -0
- package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
- package/src/plugins/parental/adapters/index.ts +8 -0
- package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
- package/src/plugins/parental/index.ts +55 -0
- package/src/plugins/parental/parental-plugin.ts +759 -0
- package/src/plugins/parental/stores/index.ts +7 -0
- package/src/plugins/parental/stores/postgres-store.ts +304 -0
- package/src/plugins/parental/types.ts +180 -0
- package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
- package/src/plugins/profiles/index.ts +49 -0
- package/src/plugins/profiles/profiles-plugin.ts +546 -0
- package/src/plugins/profiles/stores/index.ts +9 -0
- package/src/plugins/profiles/stores/postgres-store.ts +439 -0
- package/src/plugins/profiles/types.ts +338 -0
- package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
- package/src/plugins/subscriptions/index.ts +51 -0
- package/src/plugins/subscriptions/stores/index.ts +9 -0
- package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
- package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
- package/src/plugins/subscriptions/types.ts +355 -0
- package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
- package/src/plugins/usage/index.ts +39 -0
- package/src/plugins/usage/stores/index.ts +9 -0
- package/src/plugins/usage/stores/postgres-store.ts +213 -0
- package/src/plugins/usage/types.ts +222 -0
- package/src/plugins/usage/usage-plugin.ts +484 -0
- package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
- package/src/plugins/users/index.ts +6 -0
- package/src/plugins/users/stores/postgres-store.ts +104 -0
- package/src/plugins/users/types.ts +82 -6
- package/src/plugins/users/users-plugin.ts +37 -0
- package/ui/src/App.tsx +5 -1
- package/ui/src/api/controlPanelApi.ts +103 -6
- package/ui/src/components/StatCard.tsx +58 -0
- package/ui/src/dashboard/builtInWidgets.tsx +3 -1
- package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
- package/ui/src/dashboard/widgets/index.ts +1 -0
- package/ui/src/pages/NotificationsPage.tsx +417 -0
- package/ui/src/utils/formatters.ts +33 -0
- package/dist-ui/assets/index-D7DoZ9rL.js +0 -478
- package/dist-ui/assets/index-D7DoZ9rL.js.map +0 -1
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscriptions Plugin
|
|
3
|
+
*
|
|
4
|
+
* Subscription tier and entitlement management plugin for @qwickapps/server.
|
|
5
|
+
* Supports Stripe integration for paid subscriptions.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Request, Response } from 'express';
|
|
11
|
+
import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
|
|
12
|
+
import type {
|
|
13
|
+
SubscriptionsPluginConfig,
|
|
14
|
+
SubscriptionsStore,
|
|
15
|
+
SubscriptionTier,
|
|
16
|
+
SubscriptionEntitlement,
|
|
17
|
+
UserSubscription,
|
|
18
|
+
UserSubscriptionWithTier,
|
|
19
|
+
CreateTierInput,
|
|
20
|
+
UpdateTierInput,
|
|
21
|
+
CreateEntitlementInput,
|
|
22
|
+
CreateUserSubscriptionInput,
|
|
23
|
+
UpdateUserSubscriptionInput,
|
|
24
|
+
FeatureLimitResult,
|
|
25
|
+
} from './types.js';
|
|
26
|
+
|
|
27
|
+
// Store instance for helper access
|
|
28
|
+
let currentStore: SubscriptionsStore | null = null;
|
|
29
|
+
let currentConfig: SubscriptionsPluginConfig | null = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create the Subscriptions plugin
|
|
33
|
+
*/
|
|
34
|
+
export function createSubscriptionsPlugin(config: SubscriptionsPluginConfig): Plugin {
|
|
35
|
+
const debug = config.debug || false;
|
|
36
|
+
const defaultTierSlug = config.defaultTierSlug || 'free';
|
|
37
|
+
const apiPrefix = config.api?.prefix || '/subscriptions';
|
|
38
|
+
|
|
39
|
+
function log(message: string, data?: Record<string, unknown>) {
|
|
40
|
+
if (debug) {
|
|
41
|
+
console.log(`[SubscriptionsPlugin] ${message}`, data || '');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
id: 'subscriptions',
|
|
47
|
+
name: 'Subscriptions',
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
|
|
50
|
+
async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
|
|
51
|
+
log('Starting subscriptions plugin');
|
|
52
|
+
|
|
53
|
+
// Initialize the store (creates tables if needed)
|
|
54
|
+
await config.store.initialize();
|
|
55
|
+
log('Subscriptions plugin migrations complete');
|
|
56
|
+
|
|
57
|
+
// Store references for helper access
|
|
58
|
+
currentStore = config.store;
|
|
59
|
+
currentConfig = config;
|
|
60
|
+
|
|
61
|
+
// Register health check
|
|
62
|
+
registry.registerHealthCheck({
|
|
63
|
+
name: 'subscriptions-store',
|
|
64
|
+
type: 'custom',
|
|
65
|
+
check: async () => {
|
|
66
|
+
try {
|
|
67
|
+
const tiers = await config.store.listTiers(true);
|
|
68
|
+
return {
|
|
69
|
+
healthy: true,
|
|
70
|
+
details: {
|
|
71
|
+
tiersCount: tiers.length,
|
|
72
|
+
defaultTier: defaultTierSlug,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
} catch {
|
|
76
|
+
return { healthy: false };
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Add tier management routes
|
|
82
|
+
if (config.api?.tierManagement !== false) {
|
|
83
|
+
// List tiers
|
|
84
|
+
registry.addRoute({
|
|
85
|
+
method: 'get',
|
|
86
|
+
path: `${apiPrefix}/tiers`,
|
|
87
|
+
pluginId: 'subscriptions',
|
|
88
|
+
handler: async (req: Request, res: Response) => {
|
|
89
|
+
try {
|
|
90
|
+
const activeOnly = req.query.active !== 'false';
|
|
91
|
+
const tiers = await config.store.listTiers(activeOnly);
|
|
92
|
+
|
|
93
|
+
// Include entitlements if requested
|
|
94
|
+
if (req.query.include === 'entitlements') {
|
|
95
|
+
const tiersWithEntitlements = await Promise.all(
|
|
96
|
+
tiers.map(async (tier) => ({
|
|
97
|
+
...tier,
|
|
98
|
+
entitlements: await config.store.getEntitlementsByTier(tier.id),
|
|
99
|
+
}))
|
|
100
|
+
);
|
|
101
|
+
return res.json({ tiers: tiersWithEntitlements });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
res.json({ tiers });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('[SubscriptionsPlugin] List tiers error:', error);
|
|
107
|
+
res.status(500).json({ error: 'Failed to list tiers' });
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Get tier by ID or slug
|
|
113
|
+
registry.addRoute({
|
|
114
|
+
method: 'get',
|
|
115
|
+
path: `${apiPrefix}/tiers/:idOrSlug`,
|
|
116
|
+
pluginId: 'subscriptions',
|
|
117
|
+
handler: async (req: Request, res: Response) => {
|
|
118
|
+
try {
|
|
119
|
+
const { idOrSlug } = req.params;
|
|
120
|
+
|
|
121
|
+
// Try by ID first, then by slug
|
|
122
|
+
let tier = await config.store.getTierById(idOrSlug);
|
|
123
|
+
if (!tier) {
|
|
124
|
+
tier = await config.store.getTierBySlug(idOrSlug);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!tier) {
|
|
128
|
+
return res.status(404).json({ error: 'Tier not found' });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const entitlements = await config.store.getEntitlementsByTier(tier.id);
|
|
132
|
+
res.json({ ...tier, entitlements });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('[SubscriptionsPlugin] Get tier error:', error);
|
|
135
|
+
res.status(500).json({ error: 'Failed to get tier' });
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Create tier (admin only)
|
|
141
|
+
registry.addRoute({
|
|
142
|
+
method: 'post',
|
|
143
|
+
path: `${apiPrefix}/tiers`,
|
|
144
|
+
pluginId: 'subscriptions',
|
|
145
|
+
handler: async (req: Request, res: Response) => {
|
|
146
|
+
try {
|
|
147
|
+
const input: CreateTierInput = req.body;
|
|
148
|
+
|
|
149
|
+
if (!input.slug || !input.name) {
|
|
150
|
+
return res.status(400).json({ error: 'slug and name are required' });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check for duplicate slug
|
|
154
|
+
const existing = await config.store.getTierBySlug(input.slug);
|
|
155
|
+
if (existing) {
|
|
156
|
+
return res.status(409).json({ error: 'Tier with this slug already exists' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const tier = await config.store.createTier(input);
|
|
160
|
+
res.status(201).json(tier);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('[SubscriptionsPlugin] Create tier error:', error);
|
|
163
|
+
res.status(500).json({ error: 'Failed to create tier' });
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Update tier (admin only)
|
|
169
|
+
registry.addRoute({
|
|
170
|
+
method: 'put',
|
|
171
|
+
path: `${apiPrefix}/tiers/:id`,
|
|
172
|
+
pluginId: 'subscriptions',
|
|
173
|
+
handler: async (req: Request, res: Response) => {
|
|
174
|
+
try {
|
|
175
|
+
const input: UpdateTierInput = req.body;
|
|
176
|
+
const tier = await config.store.updateTier(req.params.id, input);
|
|
177
|
+
|
|
178
|
+
if (!tier) {
|
|
179
|
+
return res.status(404).json({ error: 'Tier not found' });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
res.json(tier);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error('[SubscriptionsPlugin] Update tier error:', error);
|
|
185
|
+
res.status(500).json({ error: 'Failed to update tier' });
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Set tier entitlements (admin only)
|
|
191
|
+
registry.addRoute({
|
|
192
|
+
method: 'put',
|
|
193
|
+
path: `${apiPrefix}/tiers/:id/entitlements`,
|
|
194
|
+
pluginId: 'subscriptions',
|
|
195
|
+
handler: async (req: Request, res: Response) => {
|
|
196
|
+
try {
|
|
197
|
+
const { entitlements } = req.body as { entitlements: Array<{ feature_code: string; limit_value?: number }> };
|
|
198
|
+
|
|
199
|
+
if (!Array.isArray(entitlements)) {
|
|
200
|
+
return res.status(400).json({ error: 'entitlements array is required' });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await config.store.setTierEntitlements(req.params.id, entitlements);
|
|
204
|
+
const updatedEntitlements = await config.store.getEntitlementsByTier(req.params.id);
|
|
205
|
+
|
|
206
|
+
res.json({ entitlements: updatedEntitlements });
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('[SubscriptionsPlugin] Set entitlements error:', error);
|
|
209
|
+
res.status(500).json({ error: 'Failed to set entitlements' });
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Add user subscription routes
|
|
216
|
+
if (config.api?.userSubscriptions !== false) {
|
|
217
|
+
// Get user's active subscription
|
|
218
|
+
registry.addRoute({
|
|
219
|
+
method: 'get',
|
|
220
|
+
path: `${apiPrefix}/user/:userId`,
|
|
221
|
+
pluginId: 'subscriptions',
|
|
222
|
+
handler: async (req: Request, res: Response) => {
|
|
223
|
+
try {
|
|
224
|
+
const subscription = await config.store.getActiveSubscription(req.params.userId);
|
|
225
|
+
|
|
226
|
+
if (!subscription) {
|
|
227
|
+
return res.status(404).json({ error: 'No active subscription found' });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Get entitlements for the tier
|
|
231
|
+
const entitlements = await config.store.getEntitlementsByTier(subscription.tier_id);
|
|
232
|
+
|
|
233
|
+
res.json({
|
|
234
|
+
subscription,
|
|
235
|
+
entitlements,
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('[SubscriptionsPlugin] Get user subscription error:', error);
|
|
239
|
+
res.status(500).json({ error: 'Failed to get subscription' });
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Create/update user subscription
|
|
245
|
+
registry.addRoute({
|
|
246
|
+
method: 'post',
|
|
247
|
+
path: `${apiPrefix}/user/:userId`,
|
|
248
|
+
pluginId: 'subscriptions',
|
|
249
|
+
handler: async (req: Request, res: Response) => {
|
|
250
|
+
try {
|
|
251
|
+
const input: CreateUserSubscriptionInput = {
|
|
252
|
+
user_id: req.params.userId,
|
|
253
|
+
...req.body,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
if (!input.tier_id) {
|
|
257
|
+
// Use default tier if not specified
|
|
258
|
+
const defaultTier = await config.store.getTierBySlug(defaultTierSlug);
|
|
259
|
+
if (!defaultTier) {
|
|
260
|
+
return res.status(400).json({ error: 'tier_id is required (no default tier found)' });
|
|
261
|
+
}
|
|
262
|
+
input.tier_id = defaultTier.id;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const subscription = await config.store.createUserSubscription(input);
|
|
266
|
+
res.status(201).json(subscription);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('[SubscriptionsPlugin] Create subscription error:', error);
|
|
269
|
+
res.status(500).json({ error: 'Failed to create subscription' });
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Check feature limit
|
|
275
|
+
registry.addRoute({
|
|
276
|
+
method: 'get',
|
|
277
|
+
path: `${apiPrefix}/user/:userId/features/:featureCode`,
|
|
278
|
+
pluginId: 'subscriptions',
|
|
279
|
+
handler: async (req: Request, res: Response) => {
|
|
280
|
+
try {
|
|
281
|
+
const { userId, featureCode } = req.params;
|
|
282
|
+
const result = await checkFeatureLimit(userId, featureCode);
|
|
283
|
+
res.json(result);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error('[SubscriptionsPlugin] Check feature error:', error);
|
|
286
|
+
res.status(500).json({ error: 'Failed to check feature' });
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Cancel subscription
|
|
292
|
+
registry.addRoute({
|
|
293
|
+
method: 'post',
|
|
294
|
+
path: `${apiPrefix}/:id/cancel`,
|
|
295
|
+
pluginId: 'subscriptions',
|
|
296
|
+
handler: async (req: Request, res: Response) => {
|
|
297
|
+
try {
|
|
298
|
+
const success = await config.store.cancelSubscription(req.params.id);
|
|
299
|
+
|
|
300
|
+
if (!success) {
|
|
301
|
+
return res.status(404).json({ error: 'Subscription not found' });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const subscription = await config.store.getUserSubscriptionById(req.params.id);
|
|
305
|
+
res.json(subscription);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error('[SubscriptionsPlugin] Cancel subscription error:', error);
|
|
308
|
+
res.status(500).json({ error: 'Failed to cancel subscription' });
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
log('Subscriptions plugin started');
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
async onStop(): Promise<void> {
|
|
318
|
+
log('Stopping subscriptions plugin');
|
|
319
|
+
await config.store.shutdown();
|
|
320
|
+
currentStore = null;
|
|
321
|
+
currentConfig = null;
|
|
322
|
+
log('Subscriptions plugin stopped');
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
328
|
+
// Helper Functions
|
|
329
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get the current subscriptions store instance
|
|
333
|
+
*/
|
|
334
|
+
export function getSubscriptionsStore(): SubscriptionsStore | null {
|
|
335
|
+
return currentStore;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Create a subscription tier
|
|
340
|
+
*/
|
|
341
|
+
export async function createTier(input: CreateTierInput): Promise<SubscriptionTier> {
|
|
342
|
+
if (!currentStore) {
|
|
343
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
344
|
+
}
|
|
345
|
+
return currentStore.createTier(input);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get tier by slug
|
|
350
|
+
*/
|
|
351
|
+
export async function getTierBySlug(slug: string): Promise<SubscriptionTier | null> {
|
|
352
|
+
if (!currentStore) {
|
|
353
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
354
|
+
}
|
|
355
|
+
return currentStore.getTierBySlug(slug);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get tier by ID
|
|
360
|
+
*/
|
|
361
|
+
export async function getTierById(id: string): Promise<SubscriptionTier | null> {
|
|
362
|
+
if (!currentStore) {
|
|
363
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
364
|
+
}
|
|
365
|
+
return currentStore.getTierById(id);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* List all tiers
|
|
370
|
+
*/
|
|
371
|
+
export async function listTiers(activeOnly = true): Promise<SubscriptionTier[]> {
|
|
372
|
+
if (!currentStore) {
|
|
373
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
374
|
+
}
|
|
375
|
+
return currentStore.listTiers(activeOnly);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get entitlements for a tier
|
|
380
|
+
*/
|
|
381
|
+
export async function getTierEntitlements(tierId: string): Promise<SubscriptionEntitlement[]> {
|
|
382
|
+
if (!currentStore) {
|
|
383
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
384
|
+
}
|
|
385
|
+
return currentStore.getEntitlementsByTier(tierId);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Set entitlements for a tier
|
|
390
|
+
*/
|
|
391
|
+
export async function setTierEntitlements(
|
|
392
|
+
tierId: string,
|
|
393
|
+
entitlements: Array<{ feature_code: string; limit_value?: number }>
|
|
394
|
+
): Promise<void> {
|
|
395
|
+
if (!currentStore) {
|
|
396
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
397
|
+
}
|
|
398
|
+
return currentStore.setTierEntitlements(tierId, entitlements);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get user's active subscription
|
|
403
|
+
*/
|
|
404
|
+
export async function getUserSubscription(userId: string): Promise<UserSubscriptionWithTier | null> {
|
|
405
|
+
if (!currentStore) {
|
|
406
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
407
|
+
}
|
|
408
|
+
return currentStore.getActiveSubscription(userId);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Create a user subscription
|
|
413
|
+
*/
|
|
414
|
+
export async function createUserSubscription(input: CreateUserSubscriptionInput): Promise<UserSubscription> {
|
|
415
|
+
if (!currentStore) {
|
|
416
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
417
|
+
}
|
|
418
|
+
return currentStore.createUserSubscription(input);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Update a user subscription
|
|
423
|
+
*/
|
|
424
|
+
export async function updateUserSubscription(
|
|
425
|
+
id: string,
|
|
426
|
+
input: UpdateUserSubscriptionInput
|
|
427
|
+
): Promise<UserSubscription | null> {
|
|
428
|
+
if (!currentStore) {
|
|
429
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
430
|
+
}
|
|
431
|
+
return currentStore.updateUserSubscription(id, input);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Cancel a subscription
|
|
436
|
+
*/
|
|
437
|
+
export async function cancelSubscription(id: string): Promise<boolean> {
|
|
438
|
+
if (!currentStore) {
|
|
439
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
440
|
+
}
|
|
441
|
+
return currentStore.cancelSubscription(id);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get user's tier slug
|
|
446
|
+
*/
|
|
447
|
+
export async function getUserTierSlug(userId: string): Promise<string | null> {
|
|
448
|
+
const subscription = await getUserSubscription(userId);
|
|
449
|
+
return subscription?.tier.slug || null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get feature limit for a user
|
|
454
|
+
*/
|
|
455
|
+
export async function getFeatureLimit(userId: string, featureCode: string): Promise<number | null> {
|
|
456
|
+
if (!currentStore) {
|
|
457
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
458
|
+
}
|
|
459
|
+
return currentStore.getFeatureLimit(userId, featureCode);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Check if user has a feature
|
|
464
|
+
*/
|
|
465
|
+
export async function hasFeature(userId: string, featureCode: string): Promise<boolean> {
|
|
466
|
+
if (!currentStore) {
|
|
467
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
468
|
+
}
|
|
469
|
+
return currentStore.hasFeature(userId, featureCode);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Check feature limit and availability
|
|
474
|
+
*/
|
|
475
|
+
export async function checkFeatureLimit(userId: string, featureCode: string): Promise<FeatureLimitResult> {
|
|
476
|
+
if (!currentStore) {
|
|
477
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const limit = await currentStore.getFeatureLimit(userId, featureCode);
|
|
481
|
+
|
|
482
|
+
if (limit === null) {
|
|
483
|
+
return { available: false };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (limit === 0) {
|
|
487
|
+
return { available: false, limit: 0 };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// -1 means unlimited
|
|
491
|
+
if (limit === -1) {
|
|
492
|
+
return { available: true, limit: -1 };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Note: Current usage would need to come from usage-plugin
|
|
496
|
+
return { available: true, limit };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Ensure user has a subscription (create default if not)
|
|
501
|
+
*/
|
|
502
|
+
export async function ensureUserSubscription(userId: string): Promise<UserSubscriptionWithTier> {
|
|
503
|
+
if (!currentStore || !currentConfig) {
|
|
504
|
+
throw new Error('Subscriptions plugin not initialized');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
let subscription = await currentStore.getActiveSubscription(userId);
|
|
508
|
+
|
|
509
|
+
if (!subscription) {
|
|
510
|
+
// Create default subscription
|
|
511
|
+
const defaultTierSlug = currentConfig.defaultTierSlug || 'free';
|
|
512
|
+
const defaultTier = await currentStore.getTierBySlug(defaultTierSlug);
|
|
513
|
+
|
|
514
|
+
if (!defaultTier) {
|
|
515
|
+
throw new Error(`Default tier '${defaultTierSlug}' not found`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
await currentStore.createUserSubscription({
|
|
519
|
+
user_id: userId,
|
|
520
|
+
tier_id: defaultTier.id,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
subscription = await currentStore.getActiveSubscription(userId);
|
|
524
|
+
if (!subscription) {
|
|
525
|
+
throw new Error('Failed to create subscription');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return subscription;
|
|
530
|
+
}
|