@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.
Files changed (395) hide show
  1. package/README.md +157 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +114 -0
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/types.d.ts +19 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +15 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
  12. package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
  13. package/dist/plugins/auth/adapter-wrapper.js +166 -0
  14. package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
  15. package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
  16. package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
  17. package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
  18. package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
  19. package/dist/plugins/auth/config-store.d.ts +11 -0
  20. package/dist/plugins/auth/config-store.d.ts.map +1 -0
  21. package/dist/plugins/auth/config-store.js +232 -0
  22. package/dist/plugins/auth/config-store.js.map +1 -0
  23. package/dist/plugins/auth/config-store.test.d.ts +7 -0
  24. package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
  25. package/dist/plugins/auth/config-store.test.js +299 -0
  26. package/dist/plugins/auth/config-store.test.js.map +1 -0
  27. package/dist/plugins/auth/env-config.d.ts +51 -1
  28. package/dist/plugins/auth/env-config.d.ts.map +1 -1
  29. package/dist/plugins/auth/env-config.js +640 -7
  30. package/dist/plugins/auth/env-config.js.map +1 -1
  31. package/dist/plugins/auth/index.d.ts +6 -2
  32. package/dist/plugins/auth/index.d.ts.map +1 -1
  33. package/dist/plugins/auth/index.js +5 -1
  34. package/dist/plugins/auth/index.js.map +1 -1
  35. package/dist/plugins/auth/types.d.ts +106 -0
  36. package/dist/plugins/auth/types.d.ts.map +1 -1
  37. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
  38. package/dist/plugins/bans/bans-plugin.js +12 -3
  39. package/dist/plugins/bans/bans-plugin.js.map +1 -1
  40. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
  41. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
  42. package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
  43. package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
  44. package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
  45. package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
  46. package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
  47. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
  48. package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
  49. package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
  50. package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
  51. package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
  52. package/dist/plugins/devices/adapters/index.d.ts +12 -0
  53. package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
  54. package/dist/plugins/devices/adapters/index.js +10 -0
  55. package/dist/plugins/devices/adapters/index.js.map +1 -0
  56. package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
  57. package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
  58. package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
  59. package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
  60. package/dist/plugins/devices/devices-plugin.d.ts +70 -0
  61. package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
  62. package/dist/plugins/devices/devices-plugin.js +453 -0
  63. package/dist/plugins/devices/devices-plugin.js.map +1 -0
  64. package/dist/plugins/devices/index.d.ts +18 -0
  65. package/dist/plugins/devices/index.d.ts.map +1 -0
  66. package/dist/plugins/devices/index.js +18 -0
  67. package/dist/plugins/devices/index.js.map +1 -0
  68. package/dist/plugins/devices/stores/index.d.ts +9 -0
  69. package/dist/plugins/devices/stores/index.d.ts.map +1 -0
  70. package/dist/plugins/devices/stores/index.js +9 -0
  71. package/dist/plugins/devices/stores/index.js.map +1 -0
  72. package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
  73. package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
  74. package/dist/plugins/devices/stores/postgres-store.js +199 -0
  75. package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
  76. package/dist/plugins/devices/token-utils.d.ts +100 -0
  77. package/dist/plugins/devices/token-utils.d.ts.map +1 -0
  78. package/dist/plugins/devices/token-utils.js +162 -0
  79. package/dist/plugins/devices/token-utils.js.map +1 -0
  80. package/dist/plugins/devices/types.d.ts +307 -0
  81. package/dist/plugins/devices/types.d.ts.map +1 -0
  82. package/dist/plugins/devices/types.js +10 -0
  83. package/dist/plugins/devices/types.js.map +1 -0
  84. package/dist/plugins/index.d.ts +18 -4
  85. package/dist/plugins/index.d.ts.map +1 -1
  86. package/dist/plugins/index.js +16 -2
  87. package/dist/plugins/index.js.map +1 -1
  88. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
  89. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
  90. package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
  91. package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
  92. package/dist/plugins/notifications/index.d.ts +71 -0
  93. package/dist/plugins/notifications/index.d.ts.map +1 -0
  94. package/dist/plugins/notifications/index.js +72 -0
  95. package/dist/plugins/notifications/index.js.map +1 -0
  96. package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
  97. package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
  98. package/dist/plugins/notifications/notifications-manager.js +610 -0
  99. package/dist/plugins/notifications/notifications-manager.js.map +1 -0
  100. package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
  101. package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
  102. package/dist/plugins/notifications/notifications-plugin.js +337 -0
  103. package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
  104. package/dist/plugins/notifications/types.d.ts +164 -0
  105. package/dist/plugins/notifications/types.d.ts.map +1 -0
  106. package/dist/plugins/notifications/types.js +9 -0
  107. package/dist/plugins/notifications/types.js.map +1 -0
  108. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
  109. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
  110. package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
  111. package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
  112. package/dist/plugins/parental/adapters/index.d.ts +8 -0
  113. package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
  114. package/dist/plugins/parental/adapters/index.js +7 -0
  115. package/dist/plugins/parental/adapters/index.js.map +1 -0
  116. package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
  117. package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
  118. package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
  119. package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
  120. package/dist/plugins/parental/index.d.ts +14 -0
  121. package/dist/plugins/parental/index.d.ts.map +1 -0
  122. package/dist/plugins/parental/index.js +15 -0
  123. package/dist/plugins/parental/index.js.map +1 -0
  124. package/dist/plugins/parental/parental-plugin.d.ts +88 -0
  125. package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
  126. package/dist/plugins/parental/parental-plugin.js +666 -0
  127. package/dist/plugins/parental/parental-plugin.js.map +1 -0
  128. package/dist/plugins/parental/stores/index.d.ts +7 -0
  129. package/dist/plugins/parental/stores/index.d.ts.map +1 -0
  130. package/dist/plugins/parental/stores/index.js +7 -0
  131. package/dist/plugins/parental/stores/index.js.map +1 -0
  132. package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
  133. package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
  134. package/dist/plugins/parental/stores/postgres-store.js +209 -0
  135. package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
  136. package/dist/plugins/parental/types.d.ts +154 -0
  137. package/dist/plugins/parental/types.d.ts.map +1 -0
  138. package/dist/plugins/parental/types.js +10 -0
  139. package/dist/plugins/parental/types.js.map +1 -0
  140. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
  141. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
  142. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
  143. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
  144. package/dist/plugins/profiles/index.d.ts +12 -0
  145. package/dist/plugins/profiles/index.d.ts.map +1 -0
  146. package/dist/plugins/profiles/index.js +13 -0
  147. package/dist/plugins/profiles/index.js.map +1 -0
  148. package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
  149. package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
  150. package/dist/plugins/profiles/profiles-plugin.js +481 -0
  151. package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
  152. package/dist/plugins/profiles/stores/index.d.ts +9 -0
  153. package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
  154. package/dist/plugins/profiles/stores/index.js +9 -0
  155. package/dist/plugins/profiles/stores/index.js.map +1 -0
  156. package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
  157. package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
  158. package/dist/plugins/profiles/stores/postgres-store.js +310 -0
  159. package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
  160. package/dist/plugins/profiles/types.d.ts +289 -0
  161. package/dist/plugins/profiles/types.d.ts.map +1 -0
  162. package/dist/plugins/profiles/types.js +10 -0
  163. package/dist/plugins/profiles/types.js.map +1 -0
  164. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
  165. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
  166. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
  167. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
  168. package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
  169. package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
  170. package/dist/plugins/rate-limit/cleanup.js +72 -0
  171. package/dist/plugins/rate-limit/cleanup.js.map +1 -0
  172. package/dist/plugins/rate-limit/env-config.d.ts +91 -0
  173. package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
  174. package/dist/plugins/rate-limit/env-config.js +318 -0
  175. package/dist/plugins/rate-limit/env-config.js.map +1 -0
  176. package/dist/plugins/rate-limit/index.d.ts +76 -0
  177. package/dist/plugins/rate-limit/index.d.ts.map +1 -0
  178. package/dist/plugins/rate-limit/index.js +79 -0
  179. package/dist/plugins/rate-limit/index.js.map +1 -0
  180. package/dist/plugins/rate-limit/middleware.d.ts +40 -0
  181. package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
  182. package/dist/plugins/rate-limit/middleware.js +169 -0
  183. package/dist/plugins/rate-limit/middleware.js.map +1 -0
  184. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
  185. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
  186. package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
  187. package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
  188. package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
  189. package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
  190. package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
  191. package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
  192. package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
  193. package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
  194. package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
  195. package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
  196. package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
  197. package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
  198. package/dist/plugins/rate-limit/stores/index.js +8 -0
  199. package/dist/plugins/rate-limit/stores/index.js.map +1 -0
  200. package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
  201. package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
  202. package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
  203. package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
  204. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
  205. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
  206. package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
  207. package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
  208. package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
  209. package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
  210. package/dist/plugins/rate-limit/strategies/index.js +27 -0
  211. package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
  212. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
  213. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
  214. package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
  215. package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
  216. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
  217. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
  218. package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
  219. package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
  220. package/dist/plugins/rate-limit/types.d.ts +265 -0
  221. package/dist/plugins/rate-limit/types.d.ts.map +1 -0
  222. package/dist/plugins/rate-limit/types.js +9 -0
  223. package/dist/plugins/rate-limit/types.js.map +1 -0
  224. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
  225. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
  226. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
  227. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
  228. package/dist/plugins/subscriptions/index.d.ts +12 -0
  229. package/dist/plugins/subscriptions/index.d.ts.map +1 -0
  230. package/dist/plugins/subscriptions/index.js +13 -0
  231. package/dist/plugins/subscriptions/index.js.map +1 -0
  232. package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
  233. package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
  234. package/dist/plugins/subscriptions/stores/index.js +9 -0
  235. package/dist/plugins/subscriptions/stores/index.js.map +1 -0
  236. package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
  237. package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
  238. package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
  239. package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
  240. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
  241. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
  242. package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
  243. package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
  244. package/dist/plugins/subscriptions/types.d.ts +308 -0
  245. package/dist/plugins/subscriptions/types.d.ts.map +1 -0
  246. package/dist/plugins/subscriptions/types.js +10 -0
  247. package/dist/plugins/subscriptions/types.js.map +1 -0
  248. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
  249. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
  250. package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
  251. package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
  252. package/dist/plugins/usage/index.d.ts +12 -0
  253. package/dist/plugins/usage/index.d.ts.map +1 -0
  254. package/dist/plugins/usage/index.js +13 -0
  255. package/dist/plugins/usage/index.js.map +1 -0
  256. package/dist/plugins/usage/stores/index.d.ts +9 -0
  257. package/dist/plugins/usage/stores/index.d.ts.map +1 -0
  258. package/dist/plugins/usage/stores/index.js +9 -0
  259. package/dist/plugins/usage/stores/index.js.map +1 -0
  260. package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
  261. package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
  262. package/dist/plugins/usage/stores/postgres-store.js +146 -0
  263. package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
  264. package/dist/plugins/usage/types.d.ts +195 -0
  265. package/dist/plugins/usage/types.d.ts.map +1 -0
  266. package/dist/plugins/usage/types.js +10 -0
  267. package/dist/plugins/usage/types.js.map +1 -0
  268. package/dist/plugins/usage/usage-plugin.d.ts +51 -0
  269. package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
  270. package/dist/plugins/usage/usage-plugin.js +412 -0
  271. package/dist/plugins/usage/usage-plugin.js.map +1 -0
  272. package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
  273. package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
  274. package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
  275. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
  276. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  277. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  278. package/dist/plugins/users/index.d.ts +2 -2
  279. package/dist/plugins/users/index.d.ts.map +1 -1
  280. package/dist/plugins/users/index.js +1 -1
  281. package/dist/plugins/users/index.js.map +1 -1
  282. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  283. package/dist/plugins/users/stores/postgres-store.js +76 -0
  284. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  285. package/dist/plugins/users/types.d.ts +74 -6
  286. package/dist/plugins/users/types.d.ts.map +1 -1
  287. package/dist/plugins/users/users-plugin.d.ts +15 -1
  288. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  289. package/dist/plugins/users/users-plugin.js +29 -0
  290. package/dist/plugins/users/users-plugin.js.map +1 -1
  291. package/dist-ui/assets/index-CynOqPkb.js +469 -0
  292. package/dist-ui/assets/{index-BY8OxNgO.js.map → index-CynOqPkb.js.map} +1 -1
  293. package/dist-ui/index.html +1 -1
  294. package/dist-ui-lib/api/controlPanelApi.d.ts +187 -0
  295. package/dist-ui-lib/components/StatCard.d.ts +16 -0
  296. package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
  297. package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
  298. package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
  299. package/dist-ui-lib/dashboard/widgets/index.d.ts +3 -0
  300. package/dist-ui-lib/index.js +3579 -2379
  301. package/dist-ui-lib/index.js.map +1 -1
  302. package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
  303. package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
  304. package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
  305. package/dist-ui-lib/utils/formatters.d.ts +19 -0
  306. package/package.json +1 -1
  307. package/src/core/control-panel.ts +128 -0
  308. package/src/core/types.ts +17 -0
  309. package/src/index.ts +216 -0
  310. package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
  311. package/src/plugins/auth/adapter-wrapper.ts +205 -0
  312. package/src/plugins/auth/config-store.test.ts +417 -0
  313. package/src/plugins/auth/config-store.ts +305 -0
  314. package/src/plugins/auth/env-config.ts +714 -7
  315. package/src/plugins/auth/index.ts +22 -1
  316. package/src/plugins/auth/types.ts +138 -0
  317. package/src/plugins/bans/bans-plugin.ts +15 -3
  318. package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
  319. package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
  320. package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
  321. package/src/plugins/devices/adapters/index.ts +13 -0
  322. package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
  323. package/src/plugins/devices/devices-plugin.ts +538 -0
  324. package/src/plugins/devices/index.ts +69 -0
  325. package/src/plugins/devices/stores/index.ts +9 -0
  326. package/src/plugins/devices/stores/postgres-store.ts +304 -0
  327. package/src/plugins/devices/token-utils.ts +213 -0
  328. package/src/plugins/devices/types.ts +351 -0
  329. package/src/plugins/index.ts +267 -0
  330. package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
  331. package/src/plugins/notifications/index.ts +91 -0
  332. package/src/plugins/notifications/notifications-manager.ts +773 -0
  333. package/src/plugins/notifications/notifications-plugin.ts +398 -0
  334. package/src/plugins/notifications/types.ts +207 -0
  335. package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
  336. package/src/plugins/parental/adapters/index.ts +8 -0
  337. package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
  338. package/src/plugins/parental/index.ts +55 -0
  339. package/src/plugins/parental/parental-plugin.ts +759 -0
  340. package/src/plugins/parental/stores/index.ts +7 -0
  341. package/src/plugins/parental/stores/postgres-store.ts +304 -0
  342. package/src/plugins/parental/types.ts +180 -0
  343. package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
  344. package/src/plugins/profiles/index.ts +49 -0
  345. package/src/plugins/profiles/profiles-plugin.ts +546 -0
  346. package/src/plugins/profiles/stores/index.ts +9 -0
  347. package/src/plugins/profiles/stores/postgres-store.ts +439 -0
  348. package/src/plugins/profiles/types.ts +338 -0
  349. package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
  350. package/src/plugins/rate-limit/cleanup.ts +117 -0
  351. package/src/plugins/rate-limit/env-config.ts +400 -0
  352. package/src/plugins/rate-limit/index.ts +128 -0
  353. package/src/plugins/rate-limit/middleware.ts +212 -0
  354. package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
  355. package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
  356. package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
  357. package/src/plugins/rate-limit/stores/index.ts +8 -0
  358. package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
  359. package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
  360. package/src/plugins/rate-limit/strategies/index.ts +30 -0
  361. package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
  362. package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
  363. package/src/plugins/rate-limit/types.ts +338 -0
  364. package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
  365. package/src/plugins/subscriptions/index.ts +51 -0
  366. package/src/plugins/subscriptions/stores/index.ts +9 -0
  367. package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
  368. package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
  369. package/src/plugins/subscriptions/types.ts +355 -0
  370. package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
  371. package/src/plugins/usage/index.ts +39 -0
  372. package/src/plugins/usage/stores/index.ts +9 -0
  373. package/src/plugins/usage/stores/postgres-store.ts +213 -0
  374. package/src/plugins/usage/types.ts +222 -0
  375. package/src/plugins/usage/usage-plugin.ts +484 -0
  376. package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
  377. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  378. package/src/plugins/users/index.ts +6 -0
  379. package/src/plugins/users/stores/postgres-store.ts +104 -0
  380. package/src/plugins/users/types.ts +82 -6
  381. package/src/plugins/users/users-plugin.ts +37 -0
  382. package/ui/src/App.tsx +36 -14
  383. package/ui/src/api/controlPanelApi.ts +329 -6
  384. package/ui/src/components/StatCard.tsx +58 -0
  385. package/ui/src/dashboard/builtInWidgets.tsx +7 -1
  386. package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
  387. package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
  388. package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
  389. package/ui/src/dashboard/widgets/index.ts +3 -0
  390. package/ui/src/pages/AuthPage.tsx +986 -142
  391. package/ui/src/pages/IntegrationsPage.tsx +288 -0
  392. package/ui/src/pages/NotificationsPage.tsx +417 -0
  393. package/ui/src/pages/RateLimitPage.tsx +292 -0
  394. package/ui/src/utils/formatters.ts +33 -0
  395. package/dist-ui/assets/index-BY8OxNgO.js +0 -465
@@ -0,0 +1,482 @@
1
+ /**
2
+ * PostgreSQL Subscriptions Store
3
+ *
4
+ * Subscriptions storage implementation using PostgreSQL.
5
+ * Manages tiers, entitlements, and user subscriptions.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import type {
11
+ SubscriptionsStore,
12
+ SubscriptionTier,
13
+ SubscriptionEntitlement,
14
+ UserSubscription,
15
+ UserSubscriptionWithTier,
16
+ CreateTierInput,
17
+ UpdateTierInput,
18
+ CreateEntitlementInput,
19
+ CreateUserSubscriptionInput,
20
+ UpdateUserSubscriptionInput,
21
+ PostgresSubscriptionsStoreConfig,
22
+ } from '../types.js';
23
+
24
+ // Pool interface (from pg package)
25
+ interface PgPool {
26
+ query(text: string, values?: unknown[]): Promise<{ rows: unknown[]; rowCount: number | null }>;
27
+ }
28
+
29
+ /**
30
+ * Create a PostgreSQL subscriptions store
31
+ */
32
+ export function postgresSubscriptionsStore(config: PostgresSubscriptionsStoreConfig): SubscriptionsStore {
33
+ const {
34
+ pool: poolOrFn,
35
+ tiersTable = 'subscription_tiers',
36
+ entitlementsTable = 'subscription_entitlements',
37
+ userSubscriptionsTable = 'user_subscriptions',
38
+ schema = 'public',
39
+ autoCreateTables = true,
40
+ } = config;
41
+
42
+ // Helper to get pool (supports lazy initialization via function)
43
+ const getPool = (): PgPool => {
44
+ const pool = typeof poolOrFn === 'function' ? poolOrFn() : poolOrFn;
45
+ return pool as PgPool;
46
+ };
47
+
48
+ const tiersTableFull = `"${schema}"."${tiersTable}"`;
49
+ const entitlementsTableFull = `"${schema}"."${entitlementsTable}"`;
50
+ const userSubsTableFull = `"${schema}"."${userSubscriptionsTable}"`;
51
+
52
+ return {
53
+ name: 'postgres',
54
+
55
+ async initialize(): Promise<void> {
56
+ if (!autoCreateTables) return;
57
+
58
+ // Create subscription_tiers table
59
+ await getPool().query(`
60
+ CREATE TABLE IF NOT EXISTS ${tiersTableFull} (
61
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
62
+ slug VARCHAR(50) UNIQUE NOT NULL,
63
+ name VARCHAR(100) NOT NULL,
64
+ description TEXT,
65
+ price_monthly_cents INTEGER,
66
+ price_yearly_cents INTEGER,
67
+ stripe_price_id_monthly VARCHAR(100),
68
+ stripe_price_id_yearly VARCHAR(100),
69
+ is_active BOOLEAN DEFAULT true,
70
+ sort_order INTEGER DEFAULT 0,
71
+ metadata JSONB DEFAULT '{}',
72
+ created_at TIMESTAMPTZ DEFAULT NOW(),
73
+ updated_at TIMESTAMPTZ DEFAULT NOW()
74
+ );
75
+
76
+ CREATE INDEX IF NOT EXISTS idx_${tiersTable}_slug ON ${tiersTableFull}(slug);
77
+ CREATE INDEX IF NOT EXISTS idx_${tiersTable}_active ON ${tiersTableFull}(is_active);
78
+ `);
79
+
80
+ // Create subscription_entitlements table
81
+ await getPool().query(`
82
+ CREATE TABLE IF NOT EXISTS ${entitlementsTableFull} (
83
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
84
+ tier_id UUID NOT NULL REFERENCES ${tiersTableFull}(id) ON DELETE CASCADE,
85
+ feature_code VARCHAR(100) NOT NULL,
86
+ limit_value INTEGER,
87
+ metadata JSONB DEFAULT '{}',
88
+ UNIQUE(tier_id, feature_code)
89
+ );
90
+
91
+ CREATE INDEX IF NOT EXISTS idx_${entitlementsTable}_tier ON ${entitlementsTableFull}(tier_id);
92
+ CREATE INDEX IF NOT EXISTS idx_${entitlementsTable}_feature ON ${entitlementsTableFull}(feature_code);
93
+ `);
94
+
95
+ // Create user_subscriptions table
96
+ await getPool().query(`
97
+ CREATE TABLE IF NOT EXISTS ${userSubsTableFull} (
98
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
99
+ user_id UUID NOT NULL,
100
+ tier_id UUID NOT NULL REFERENCES ${tiersTableFull}(id),
101
+ stripe_customer_id VARCHAR(100),
102
+ stripe_subscription_id VARCHAR(100),
103
+ status VARCHAR(20) DEFAULT 'active'
104
+ CHECK (status IN ('active', 'canceled', 'past_due', 'trialing', 'inactive')),
105
+ current_period_start TIMESTAMPTZ,
106
+ current_period_end TIMESTAMPTZ,
107
+ cancel_at_period_end BOOLEAN DEFAULT false,
108
+ metadata JSONB DEFAULT '{}',
109
+ created_at TIMESTAMPTZ DEFAULT NOW(),
110
+ updated_at TIMESTAMPTZ DEFAULT NOW()
111
+ );
112
+
113
+ CREATE INDEX IF NOT EXISTS idx_${userSubscriptionsTable}_user ON ${userSubsTableFull}(user_id);
114
+ CREATE INDEX IF NOT EXISTS idx_${userSubscriptionsTable}_stripe ON ${userSubsTableFull}(stripe_customer_id);
115
+ CREATE INDEX IF NOT EXISTS idx_${userSubscriptionsTable}_stripe_sub ON ${userSubsTableFull}(stripe_subscription_id);
116
+ CREATE INDEX IF NOT EXISTS idx_${userSubscriptionsTable}_status ON ${userSubsTableFull}(status);
117
+ `);
118
+ },
119
+
120
+ // ═══════════════════════════════════════════════════════════════════════
121
+ // Tiers
122
+ // ═══════════════════════════════════════════════════════════════════════
123
+
124
+ async createTier(input: CreateTierInput): Promise<SubscriptionTier> {
125
+ const result = await getPool().query(
126
+ `INSERT INTO ${tiersTableFull}
127
+ (slug, name, description, price_monthly_cents, price_yearly_cents,
128
+ stripe_price_id_monthly, stripe_price_id_yearly, is_active, sort_order, metadata)
129
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
130
+ RETURNING *`,
131
+ [
132
+ input.slug,
133
+ input.name,
134
+ input.description || null,
135
+ input.price_monthly_cents || null,
136
+ input.price_yearly_cents || null,
137
+ input.stripe_price_id_monthly || null,
138
+ input.stripe_price_id_yearly || null,
139
+ input.is_active !== false,
140
+ input.sort_order || 0,
141
+ JSON.stringify(input.metadata || {}),
142
+ ]
143
+ );
144
+ return result.rows[0] as SubscriptionTier;
145
+ },
146
+
147
+ async getTierById(id: string): Promise<SubscriptionTier | null> {
148
+ const result = await getPool().query(
149
+ `SELECT * FROM ${tiersTableFull} WHERE id = $1`,
150
+ [id]
151
+ );
152
+ return (result.rows[0] as SubscriptionTier) || null;
153
+ },
154
+
155
+ async getTierBySlug(slug: string): Promise<SubscriptionTier | null> {
156
+ const result = await getPool().query(
157
+ `SELECT * FROM ${tiersTableFull} WHERE slug = $1`,
158
+ [slug]
159
+ );
160
+ return (result.rows[0] as SubscriptionTier) || null;
161
+ },
162
+
163
+ async listTiers(activeOnly = true): Promise<SubscriptionTier[]> {
164
+ let query = `SELECT * FROM ${tiersTableFull}`;
165
+ if (activeOnly) {
166
+ query += ` WHERE is_active = true`;
167
+ }
168
+ query += ` ORDER BY sort_order ASC, name ASC`;
169
+
170
+ const result = await getPool().query(query);
171
+ return result.rows as SubscriptionTier[];
172
+ },
173
+
174
+ async updateTier(id: string, input: UpdateTierInput): Promise<SubscriptionTier | null> {
175
+ const updates: string[] = [];
176
+ const values: unknown[] = [];
177
+ let paramIndex = 1;
178
+
179
+ if (input.name !== undefined) {
180
+ updates.push(`name = $${paramIndex++}`);
181
+ values.push(input.name);
182
+ }
183
+ if (input.description !== undefined) {
184
+ updates.push(`description = $${paramIndex++}`);
185
+ values.push(input.description);
186
+ }
187
+ if (input.price_monthly_cents !== undefined) {
188
+ updates.push(`price_monthly_cents = $${paramIndex++}`);
189
+ values.push(input.price_monthly_cents);
190
+ }
191
+ if (input.price_yearly_cents !== undefined) {
192
+ updates.push(`price_yearly_cents = $${paramIndex++}`);
193
+ values.push(input.price_yearly_cents);
194
+ }
195
+ if (input.stripe_price_id_monthly !== undefined) {
196
+ updates.push(`stripe_price_id_monthly = $${paramIndex++}`);
197
+ values.push(input.stripe_price_id_monthly);
198
+ }
199
+ if (input.stripe_price_id_yearly !== undefined) {
200
+ updates.push(`stripe_price_id_yearly = $${paramIndex++}`);
201
+ values.push(input.stripe_price_id_yearly);
202
+ }
203
+ if (input.is_active !== undefined) {
204
+ updates.push(`is_active = $${paramIndex++}`);
205
+ values.push(input.is_active);
206
+ }
207
+ if (input.sort_order !== undefined) {
208
+ updates.push(`sort_order = $${paramIndex++}`);
209
+ values.push(input.sort_order);
210
+ }
211
+ if (input.metadata !== undefined) {
212
+ updates.push(`metadata = $${paramIndex++}`);
213
+ values.push(JSON.stringify(input.metadata));
214
+ }
215
+
216
+ if (updates.length === 0) {
217
+ return this.getTierById(id);
218
+ }
219
+
220
+ updates.push(`updated_at = NOW()`);
221
+ values.push(id);
222
+
223
+ const result = await getPool().query(
224
+ `UPDATE ${tiersTableFull} SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
225
+ values
226
+ );
227
+ return (result.rows[0] as SubscriptionTier) || null;
228
+ },
229
+
230
+ async deleteTier(id: string): Promise<boolean> {
231
+ // Soft delete by setting is_active = false
232
+ const result = await getPool().query(
233
+ `UPDATE ${tiersTableFull} SET is_active = false, updated_at = NOW() WHERE id = $1`,
234
+ [id]
235
+ );
236
+ return (result.rowCount ?? 0) > 0;
237
+ },
238
+
239
+ // ═══════════════════════════════════════════════════════════════════════
240
+ // Entitlements
241
+ // ═══════════════════════════════════════════════════════════════════════
242
+
243
+ async createEntitlement(input: CreateEntitlementInput): Promise<SubscriptionEntitlement> {
244
+ const result = await getPool().query(
245
+ `INSERT INTO ${entitlementsTableFull} (tier_id, feature_code, limit_value, metadata)
246
+ VALUES ($1, $2, $3, $4)
247
+ ON CONFLICT (tier_id, feature_code) DO UPDATE SET
248
+ limit_value = EXCLUDED.limit_value,
249
+ metadata = EXCLUDED.metadata
250
+ RETURNING *`,
251
+ [
252
+ input.tier_id,
253
+ input.feature_code,
254
+ input.limit_value ?? null,
255
+ JSON.stringify(input.metadata || {}),
256
+ ]
257
+ );
258
+ return result.rows[0] as SubscriptionEntitlement;
259
+ },
260
+
261
+ async getEntitlementsByTier(tierId: string): Promise<SubscriptionEntitlement[]> {
262
+ const result = await getPool().query(
263
+ `SELECT * FROM ${entitlementsTableFull} WHERE tier_id = $1 ORDER BY feature_code`,
264
+ [tierId]
265
+ );
266
+ return result.rows as SubscriptionEntitlement[];
267
+ },
268
+
269
+ async updateEntitlement(id: string, limitValue: number | null): Promise<SubscriptionEntitlement | null> {
270
+ const result = await getPool().query(
271
+ `UPDATE ${entitlementsTableFull} SET limit_value = $1 WHERE id = $2 RETURNING *`,
272
+ [limitValue, id]
273
+ );
274
+ return (result.rows[0] as SubscriptionEntitlement) || null;
275
+ },
276
+
277
+ async deleteEntitlement(id: string): Promise<boolean> {
278
+ const result = await getPool().query(
279
+ `DELETE FROM ${entitlementsTableFull} WHERE id = $1`,
280
+ [id]
281
+ );
282
+ return (result.rowCount ?? 0) > 0;
283
+ },
284
+
285
+ async setTierEntitlements(
286
+ tierId: string,
287
+ entitlements: Array<{ feature_code: string; limit_value?: number }>
288
+ ): Promise<void> {
289
+ // Delete existing entitlements
290
+ await getPool().query(
291
+ `DELETE FROM ${entitlementsTableFull} WHERE tier_id = $1`,
292
+ [tierId]
293
+ );
294
+
295
+ // Insert new entitlements
296
+ for (const ent of entitlements) {
297
+ await this.createEntitlement({
298
+ tier_id: tierId,
299
+ feature_code: ent.feature_code,
300
+ limit_value: ent.limit_value,
301
+ });
302
+ }
303
+ },
304
+
305
+ // ═══════════════════════════════════════════════════════════════════════
306
+ // User Subscriptions
307
+ // ═══════════════════════════════════════════════════════════════════════
308
+
309
+ async createUserSubscription(input: CreateUserSubscriptionInput): Promise<UserSubscription> {
310
+ const result = await getPool().query(
311
+ `INSERT INTO ${userSubsTableFull}
312
+ (user_id, tier_id, stripe_customer_id, stripe_subscription_id, status,
313
+ current_period_start, current_period_end, metadata)
314
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
315
+ RETURNING *`,
316
+ [
317
+ input.user_id,
318
+ input.tier_id,
319
+ input.stripe_customer_id || null,
320
+ input.stripe_subscription_id || null,
321
+ input.status || 'active',
322
+ input.current_period_start || null,
323
+ input.current_period_end || null,
324
+ JSON.stringify(input.metadata || {}),
325
+ ]
326
+ );
327
+ return result.rows[0] as UserSubscription;
328
+ },
329
+
330
+ async getUserSubscriptionById(id: string): Promise<UserSubscription | null> {
331
+ const result = await getPool().query(
332
+ `SELECT * FROM ${userSubsTableFull} WHERE id = $1`,
333
+ [id]
334
+ );
335
+ return (result.rows[0] as UserSubscription) || null;
336
+ },
337
+
338
+ async getActiveSubscription(userId: string): Promise<UserSubscriptionWithTier | null> {
339
+ const result = await getPool().query(
340
+ `SELECT us.*, t.slug as tier_slug, t.name as tier_name, t.description as tier_description,
341
+ t.price_monthly_cents, t.price_yearly_cents, t.metadata as tier_metadata
342
+ FROM ${userSubsTableFull} us
343
+ JOIN ${tiersTableFull} t ON us.tier_id = t.id
344
+ WHERE us.user_id = $1 AND us.status = 'active'
345
+ ORDER BY us.created_at DESC
346
+ LIMIT 1`,
347
+ [userId]
348
+ );
349
+
350
+ if (result.rows.length === 0) {
351
+ return null;
352
+ }
353
+
354
+ const row = result.rows[0] as Record<string, unknown>;
355
+ return {
356
+ id: row.id as string,
357
+ user_id: row.user_id as string,
358
+ tier_id: row.tier_id as string,
359
+ stripe_customer_id: row.stripe_customer_id as string | undefined,
360
+ stripe_subscription_id: row.stripe_subscription_id as string | undefined,
361
+ status: row.status as UserSubscription['status'],
362
+ current_period_start: row.current_period_start as Date | undefined,
363
+ current_period_end: row.current_period_end as Date | undefined,
364
+ cancel_at_period_end: row.cancel_at_period_end as boolean,
365
+ metadata: row.metadata as Record<string, unknown>,
366
+ created_at: row.created_at as Date,
367
+ updated_at: row.updated_at as Date,
368
+ tier: {
369
+ id: row.tier_id as string,
370
+ slug: row.tier_slug as string,
371
+ name: row.tier_name as string,
372
+ description: row.tier_description as string | undefined,
373
+ price_monthly_cents: row.price_monthly_cents as number | undefined,
374
+ price_yearly_cents: row.price_yearly_cents as number | undefined,
375
+ is_active: true,
376
+ sort_order: 0,
377
+ metadata: row.tier_metadata as Record<string, unknown>,
378
+ created_at: row.created_at as Date,
379
+ updated_at: row.updated_at as Date,
380
+ },
381
+ };
382
+ },
383
+
384
+ async getByStripeSubscriptionId(stripeSubId: string): Promise<UserSubscription | null> {
385
+ const result = await getPool().query(
386
+ `SELECT * FROM ${userSubsTableFull} WHERE stripe_subscription_id = $1`,
387
+ [stripeSubId]
388
+ );
389
+ return (result.rows[0] as UserSubscription) || null;
390
+ },
391
+
392
+ async updateUserSubscription(id: string, input: UpdateUserSubscriptionInput): Promise<UserSubscription | null> {
393
+ const updates: string[] = [];
394
+ const values: unknown[] = [];
395
+ let paramIndex = 1;
396
+
397
+ if (input.tier_id !== undefined) {
398
+ updates.push(`tier_id = $${paramIndex++}`);
399
+ values.push(input.tier_id);
400
+ }
401
+ if (input.stripe_customer_id !== undefined) {
402
+ updates.push(`stripe_customer_id = $${paramIndex++}`);
403
+ values.push(input.stripe_customer_id);
404
+ }
405
+ if (input.stripe_subscription_id !== undefined) {
406
+ updates.push(`stripe_subscription_id = $${paramIndex++}`);
407
+ values.push(input.stripe_subscription_id);
408
+ }
409
+ if (input.status !== undefined) {
410
+ updates.push(`status = $${paramIndex++}`);
411
+ values.push(input.status);
412
+ }
413
+ if (input.current_period_start !== undefined) {
414
+ updates.push(`current_period_start = $${paramIndex++}`);
415
+ values.push(input.current_period_start);
416
+ }
417
+ if (input.current_period_end !== undefined) {
418
+ updates.push(`current_period_end = $${paramIndex++}`);
419
+ values.push(input.current_period_end);
420
+ }
421
+ if (input.cancel_at_period_end !== undefined) {
422
+ updates.push(`cancel_at_period_end = $${paramIndex++}`);
423
+ values.push(input.cancel_at_period_end);
424
+ }
425
+ if (input.metadata !== undefined) {
426
+ updates.push(`metadata = $${paramIndex++}`);
427
+ values.push(JSON.stringify(input.metadata));
428
+ }
429
+
430
+ if (updates.length === 0) {
431
+ return this.getUserSubscriptionById(id);
432
+ }
433
+
434
+ updates.push(`updated_at = NOW()`);
435
+ values.push(id);
436
+
437
+ const result = await getPool().query(
438
+ `UPDATE ${userSubsTableFull} SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
439
+ values
440
+ );
441
+ return (result.rows[0] as UserSubscription) || null;
442
+ },
443
+
444
+ async cancelSubscription(id: string): Promise<boolean> {
445
+ const result = await getPool().query(
446
+ `UPDATE ${userSubsTableFull}
447
+ SET cancel_at_period_end = true, updated_at = NOW()
448
+ WHERE id = $1`,
449
+ [id]
450
+ );
451
+ return (result.rowCount ?? 0) > 0;
452
+ },
453
+
454
+ async getFeatureLimit(userId: string, featureCode: string): Promise<number | null> {
455
+ const result = await getPool().query(
456
+ `SELECT se.limit_value
457
+ FROM ${userSubsTableFull} us
458
+ JOIN ${entitlementsTableFull} se ON us.tier_id = se.tier_id
459
+ WHERE us.user_id = $1 AND us.status = 'active' AND se.feature_code = $2
460
+ ORDER BY us.created_at DESC
461
+ LIMIT 1`,
462
+ [userId, featureCode]
463
+ );
464
+
465
+ if (result.rows.length === 0) {
466
+ return null; // No subscription or feature not found
467
+ }
468
+
469
+ return (result.rows[0] as { limit_value: number | null }).limit_value;
470
+ },
471
+
472
+ async hasFeature(userId: string, featureCode: string): Promise<boolean> {
473
+ const limit = await this.getFeatureLimit(userId, featureCode);
474
+ // Has feature if limit is not null and not 0 (0 means disabled)
475
+ return limit !== null && limit !== 0;
476
+ },
477
+
478
+ async shutdown(): Promise<void> {
479
+ // Pool is managed externally, nothing to do here
480
+ },
481
+ };
482
+ }