@qwickapps/server 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +12 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
  6. package/dist/plugins/bans/bans-plugin.js +12 -3
  7. package/dist/plugins/bans/bans-plugin.js.map +1 -1
  8. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
  9. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
  10. package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
  11. package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
  12. package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
  13. package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
  14. package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
  15. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
  16. package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
  17. package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
  18. package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
  19. package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
  20. package/dist/plugins/devices/adapters/index.d.ts +12 -0
  21. package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
  22. package/dist/plugins/devices/adapters/index.js +10 -0
  23. package/dist/plugins/devices/adapters/index.js.map +1 -0
  24. package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
  25. package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
  26. package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
  27. package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
  28. package/dist/plugins/devices/devices-plugin.d.ts +70 -0
  29. package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
  30. package/dist/plugins/devices/devices-plugin.js +453 -0
  31. package/dist/plugins/devices/devices-plugin.js.map +1 -0
  32. package/dist/plugins/devices/index.d.ts +18 -0
  33. package/dist/plugins/devices/index.d.ts.map +1 -0
  34. package/dist/plugins/devices/index.js +18 -0
  35. package/dist/plugins/devices/index.js.map +1 -0
  36. package/dist/plugins/devices/stores/index.d.ts +9 -0
  37. package/dist/plugins/devices/stores/index.d.ts.map +1 -0
  38. package/dist/plugins/devices/stores/index.js +9 -0
  39. package/dist/plugins/devices/stores/index.js.map +1 -0
  40. package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
  41. package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
  42. package/dist/plugins/devices/stores/postgres-store.js +199 -0
  43. package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
  44. package/dist/plugins/devices/token-utils.d.ts +100 -0
  45. package/dist/plugins/devices/token-utils.d.ts.map +1 -0
  46. package/dist/plugins/devices/token-utils.js +162 -0
  47. package/dist/plugins/devices/token-utils.js.map +1 -0
  48. package/dist/plugins/devices/types.d.ts +307 -0
  49. package/dist/plugins/devices/types.d.ts.map +1 -0
  50. package/dist/plugins/devices/types.js +10 -0
  51. package/dist/plugins/devices/types.js.map +1 -0
  52. package/dist/plugins/index.d.ts +14 -2
  53. package/dist/plugins/index.d.ts.map +1 -1
  54. package/dist/plugins/index.js +13 -1
  55. package/dist/plugins/index.js.map +1 -1
  56. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
  57. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
  58. package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
  59. package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
  60. package/dist/plugins/notifications/index.d.ts +71 -0
  61. package/dist/plugins/notifications/index.d.ts.map +1 -0
  62. package/dist/plugins/notifications/index.js +72 -0
  63. package/dist/plugins/notifications/index.js.map +1 -0
  64. package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
  65. package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
  66. package/dist/plugins/notifications/notifications-manager.js +610 -0
  67. package/dist/plugins/notifications/notifications-manager.js.map +1 -0
  68. package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
  69. package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
  70. package/dist/plugins/notifications/notifications-plugin.js +337 -0
  71. package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
  72. package/dist/plugins/notifications/types.d.ts +164 -0
  73. package/dist/plugins/notifications/types.d.ts.map +1 -0
  74. package/dist/plugins/notifications/types.js +9 -0
  75. package/dist/plugins/notifications/types.js.map +1 -0
  76. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
  77. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
  78. package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
  79. package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
  80. package/dist/plugins/parental/adapters/index.d.ts +8 -0
  81. package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
  82. package/dist/plugins/parental/adapters/index.js +7 -0
  83. package/dist/plugins/parental/adapters/index.js.map +1 -0
  84. package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
  85. package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
  86. package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
  87. package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
  88. package/dist/plugins/parental/index.d.ts +14 -0
  89. package/dist/plugins/parental/index.d.ts.map +1 -0
  90. package/dist/plugins/parental/index.js +15 -0
  91. package/dist/plugins/parental/index.js.map +1 -0
  92. package/dist/plugins/parental/parental-plugin.d.ts +88 -0
  93. package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
  94. package/dist/plugins/parental/parental-plugin.js +666 -0
  95. package/dist/plugins/parental/parental-plugin.js.map +1 -0
  96. package/dist/plugins/parental/stores/index.d.ts +7 -0
  97. package/dist/plugins/parental/stores/index.d.ts.map +1 -0
  98. package/dist/plugins/parental/stores/index.js +7 -0
  99. package/dist/plugins/parental/stores/index.js.map +1 -0
  100. package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
  101. package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
  102. package/dist/plugins/parental/stores/postgres-store.js +209 -0
  103. package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
  104. package/dist/plugins/parental/types.d.ts +154 -0
  105. package/dist/plugins/parental/types.d.ts.map +1 -0
  106. package/dist/plugins/parental/types.js +10 -0
  107. package/dist/plugins/parental/types.js.map +1 -0
  108. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
  109. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
  110. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
  111. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
  112. package/dist/plugins/profiles/index.d.ts +12 -0
  113. package/dist/plugins/profiles/index.d.ts.map +1 -0
  114. package/dist/plugins/profiles/index.js +13 -0
  115. package/dist/plugins/profiles/index.js.map +1 -0
  116. package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
  117. package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
  118. package/dist/plugins/profiles/profiles-plugin.js +481 -0
  119. package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
  120. package/dist/plugins/profiles/stores/index.d.ts +9 -0
  121. package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
  122. package/dist/plugins/profiles/stores/index.js +9 -0
  123. package/dist/plugins/profiles/stores/index.js.map +1 -0
  124. package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
  125. package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
  126. package/dist/plugins/profiles/stores/postgres-store.js +310 -0
  127. package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
  128. package/dist/plugins/profiles/types.d.ts +289 -0
  129. package/dist/plugins/profiles/types.d.ts.map +1 -0
  130. package/dist/plugins/profiles/types.js +10 -0
  131. package/dist/plugins/profiles/types.js.map +1 -0
  132. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
  133. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
  134. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
  135. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
  136. package/dist/plugins/subscriptions/index.d.ts +12 -0
  137. package/dist/plugins/subscriptions/index.d.ts.map +1 -0
  138. package/dist/plugins/subscriptions/index.js +13 -0
  139. package/dist/plugins/subscriptions/index.js.map +1 -0
  140. package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
  141. package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
  142. package/dist/plugins/subscriptions/stores/index.js +9 -0
  143. package/dist/plugins/subscriptions/stores/index.js.map +1 -0
  144. package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
  145. package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
  146. package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
  147. package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
  148. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
  149. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
  150. package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
  151. package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
  152. package/dist/plugins/subscriptions/types.d.ts +308 -0
  153. package/dist/plugins/subscriptions/types.d.ts.map +1 -0
  154. package/dist/plugins/subscriptions/types.js +10 -0
  155. package/dist/plugins/subscriptions/types.js.map +1 -0
  156. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
  157. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
  158. package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
  159. package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
  160. package/dist/plugins/usage/index.d.ts +12 -0
  161. package/dist/plugins/usage/index.d.ts.map +1 -0
  162. package/dist/plugins/usage/index.js +13 -0
  163. package/dist/plugins/usage/index.js.map +1 -0
  164. package/dist/plugins/usage/stores/index.d.ts +9 -0
  165. package/dist/plugins/usage/stores/index.d.ts.map +1 -0
  166. package/dist/plugins/usage/stores/index.js +9 -0
  167. package/dist/plugins/usage/stores/index.js.map +1 -0
  168. package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
  169. package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
  170. package/dist/plugins/usage/stores/postgres-store.js +146 -0
  171. package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
  172. package/dist/plugins/usage/types.d.ts +195 -0
  173. package/dist/plugins/usage/types.d.ts.map +1 -0
  174. package/dist/plugins/usage/types.js +10 -0
  175. package/dist/plugins/usage/types.js.map +1 -0
  176. package/dist/plugins/usage/usage-plugin.d.ts +51 -0
  177. package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
  178. package/dist/plugins/usage/usage-plugin.js +412 -0
  179. package/dist/plugins/usage/usage-plugin.js.map +1 -0
  180. package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
  181. package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
  182. package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
  183. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
  184. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  185. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  186. package/dist/plugins/users/index.d.ts +2 -2
  187. package/dist/plugins/users/index.d.ts.map +1 -1
  188. package/dist/plugins/users/index.js +1 -1
  189. package/dist/plugins/users/index.js.map +1 -1
  190. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  191. package/dist/plugins/users/stores/postgres-store.js +76 -0
  192. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  193. package/dist/plugins/users/types.d.ts +74 -6
  194. package/dist/plugins/users/types.d.ts.map +1 -1
  195. package/dist/plugins/users/users-plugin.d.ts +15 -1
  196. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  197. package/dist/plugins/users/users-plugin.js +29 -0
  198. package/dist/plugins/users/users-plugin.js.map +1 -1
  199. package/dist-ui/assets/index-CynOqPkb.js +469 -0
  200. package/dist-ui/assets/index-CynOqPkb.js.map +1 -0
  201. package/dist-ui/index.html +1 -1
  202. package/dist-ui-lib/api/controlPanelApi.d.ts +46 -0
  203. package/dist-ui-lib/components/StatCard.d.ts +16 -0
  204. package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
  205. package/dist-ui-lib/dashboard/widgets/index.d.ts +1 -0
  206. package/dist-ui-lib/index.js +1822 -1611
  207. package/dist-ui-lib/index.js.map +1 -1
  208. package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
  209. package/dist-ui-lib/utils/formatters.d.ts +19 -0
  210. package/package.json +1 -1
  211. package/src/index.ts +178 -0
  212. package/src/plugins/bans/bans-plugin.ts +15 -3
  213. package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
  214. package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
  215. package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
  216. package/src/plugins/devices/adapters/index.ts +13 -0
  217. package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
  218. package/src/plugins/devices/devices-plugin.ts +538 -0
  219. package/src/plugins/devices/index.ts +69 -0
  220. package/src/plugins/devices/stores/index.ts +9 -0
  221. package/src/plugins/devices/stores/postgres-store.ts +304 -0
  222. package/src/plugins/devices/token-utils.ts +213 -0
  223. package/src/plugins/devices/types.ts +351 -0
  224. package/src/plugins/index.ts +218 -0
  225. package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
  226. package/src/plugins/notifications/index.ts +91 -0
  227. package/src/plugins/notifications/notifications-manager.ts +773 -0
  228. package/src/plugins/notifications/notifications-plugin.ts +398 -0
  229. package/src/plugins/notifications/types.ts +207 -0
  230. package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
  231. package/src/plugins/parental/adapters/index.ts +8 -0
  232. package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
  233. package/src/plugins/parental/index.ts +55 -0
  234. package/src/plugins/parental/parental-plugin.ts +759 -0
  235. package/src/plugins/parental/stores/index.ts +7 -0
  236. package/src/plugins/parental/stores/postgres-store.ts +304 -0
  237. package/src/plugins/parental/types.ts +180 -0
  238. package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
  239. package/src/plugins/profiles/index.ts +49 -0
  240. package/src/plugins/profiles/profiles-plugin.ts +546 -0
  241. package/src/plugins/profiles/stores/index.ts +9 -0
  242. package/src/plugins/profiles/stores/postgres-store.ts +439 -0
  243. package/src/plugins/profiles/types.ts +338 -0
  244. package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
  245. package/src/plugins/subscriptions/index.ts +51 -0
  246. package/src/plugins/subscriptions/stores/index.ts +9 -0
  247. package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
  248. package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
  249. package/src/plugins/subscriptions/types.ts +355 -0
  250. package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
  251. package/src/plugins/usage/index.ts +39 -0
  252. package/src/plugins/usage/stores/index.ts +9 -0
  253. package/src/plugins/usage/stores/postgres-store.ts +213 -0
  254. package/src/plugins/usage/types.ts +222 -0
  255. package/src/plugins/usage/usage-plugin.ts +484 -0
  256. package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
  257. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  258. package/src/plugins/users/index.ts +6 -0
  259. package/src/plugins/users/stores/postgres-store.ts +104 -0
  260. package/src/plugins/users/types.ts +82 -6
  261. package/src/plugins/users/users-plugin.ts +37 -0
  262. package/ui/src/App.tsx +5 -1
  263. package/ui/src/api/controlPanelApi.ts +103 -6
  264. package/ui/src/components/StatCard.tsx +58 -0
  265. package/ui/src/dashboard/builtInWidgets.tsx +3 -1
  266. package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
  267. package/ui/src/dashboard/widgets/index.ts +1 -0
  268. package/ui/src/pages/NotificationsPage.tsx +417 -0
  269. package/ui/src/utils/formatters.ts +33 -0
  270. package/dist-ui/assets/index-D7DoZ9rL.js +0 -478
  271. 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
+ }