@wopr-network/platform-core 0.1.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 (694) hide show
  1. package/biome.json +61 -0
  2. package/dist/admin/admin-audit-log-repository.d.ts +33 -0
  3. package/dist/admin/admin-audit-log-repository.js +102 -0
  4. package/dist/admin/audit-log.d.ts +49 -0
  5. package/dist/admin/audit-log.js +63 -0
  6. package/dist/admin/index.d.ts +6 -0
  7. package/dist/admin/index.js +3 -0
  8. package/dist/admin/role-store.d.ts +37 -0
  9. package/dist/admin/role-store.js +106 -0
  10. package/dist/auth/api-key-repository.d.ts +11 -0
  11. package/dist/auth/api-key-repository.js +33 -0
  12. package/dist/auth/api-key-repository.test.d.ts +1 -0
  13. package/dist/auth/api-key-repository.test.js +46 -0
  14. package/dist/auth/auth.test.d.ts +1 -0
  15. package/dist/auth/auth.test.js +140 -0
  16. package/dist/auth/better-auth.d.ts +42 -0
  17. package/dist/auth/better-auth.js +196 -0
  18. package/dist/auth/index.d.ts +186 -0
  19. package/dist/auth/index.js +422 -0
  20. package/dist/auth/login-history-repository.d.ts +14 -0
  21. package/dist/auth/login-history-repository.js +15 -0
  22. package/dist/auth/login-history-repository.test.d.ts +1 -0
  23. package/dist/auth/login-history-repository.test.js +47 -0
  24. package/dist/auth/middleware.d.ts +55 -0
  25. package/dist/auth/middleware.js +101 -0
  26. package/dist/auth/middleware.test.d.ts +1 -0
  27. package/dist/auth/middleware.test.js +213 -0
  28. package/dist/auth/scoped-tokens.test.d.ts +1 -0
  29. package/dist/auth/scoped-tokens.test.js +306 -0
  30. package/dist/auth/tenant-access.test.d.ts +1 -0
  31. package/dist/auth/tenant-access.test.js +62 -0
  32. package/dist/auth/user-creator.d.ts +9 -0
  33. package/dist/auth/user-creator.js +47 -0
  34. package/dist/auth/user-creator.test.d.ts +1 -0
  35. package/dist/auth/user-creator.test.js +78 -0
  36. package/dist/auth/user-role-repository.d.ts +31 -0
  37. package/dist/auth/user-role-repository.js +53 -0
  38. package/dist/auth/user-role-repository.test.d.ts +1 -0
  39. package/dist/auth/user-role-repository.test.js +122 -0
  40. package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
  41. package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
  42. package/dist/billing/index.d.ts +7 -0
  43. package/dist/billing/index.js +7 -0
  44. package/dist/billing/payment-processor.d.ts +127 -0
  45. package/dist/billing/payment-processor.js +8 -0
  46. package/dist/billing/payment-processor.test.d.ts +1 -0
  47. package/dist/billing/payment-processor.test.js +71 -0
  48. package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
  49. package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
  50. package/dist/billing/payram/charge-store.d.ts +41 -0
  51. package/dist/billing/payram/charge-store.js +72 -0
  52. package/dist/billing/payram/charge-store.test.d.ts +1 -0
  53. package/dist/billing/payram/charge-store.test.js +64 -0
  54. package/dist/billing/payram/checkout.d.ts +15 -0
  55. package/dist/billing/payram/checkout.js +24 -0
  56. package/dist/billing/payram/checkout.test.d.ts +1 -0
  57. package/dist/billing/payram/checkout.test.js +74 -0
  58. package/dist/billing/payram/client.d.ts +7 -0
  59. package/dist/billing/payram/client.js +15 -0
  60. package/dist/billing/payram/client.test.d.ts +1 -0
  61. package/dist/billing/payram/client.test.js +52 -0
  62. package/dist/billing/payram/index.d.ts +8 -0
  63. package/dist/billing/payram/index.js +4 -0
  64. package/dist/billing/payram/types.d.ts +40 -0
  65. package/dist/billing/payram/types.js +1 -0
  66. package/dist/billing/payram/webhook.d.ts +19 -0
  67. package/dist/billing/payram/webhook.js +67 -0
  68. package/dist/billing/payram/webhook.test.d.ts +7 -0
  69. package/dist/billing/payram/webhook.test.js +248 -0
  70. package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
  71. package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
  72. package/dist/billing/stripe/checkout.d.ts +20 -0
  73. package/dist/billing/stripe/checkout.js +63 -0
  74. package/dist/billing/stripe/checkout.test.d.ts +1 -0
  75. package/dist/billing/stripe/checkout.test.js +148 -0
  76. package/dist/billing/stripe/client.d.ts +14 -0
  77. package/dist/billing/stripe/client.js +33 -0
  78. package/dist/billing/stripe/client.test.d.ts +1 -0
  79. package/dist/billing/stripe/client.test.js +58 -0
  80. package/dist/billing/stripe/credit-prices.d.ts +63 -0
  81. package/dist/billing/stripe/credit-prices.js +81 -0
  82. package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
  83. package/dist/billing/stripe/credit-prices.test.js +87 -0
  84. package/dist/billing/stripe/index.d.ts +14 -0
  85. package/dist/billing/stripe/index.js +8 -0
  86. package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
  87. package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
  88. package/dist/billing/stripe/payment-methods.d.ts +25 -0
  89. package/dist/billing/stripe/payment-methods.js +53 -0
  90. package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
  91. package/dist/billing/stripe/payment-methods.test.js +122 -0
  92. package/dist/billing/stripe/portal.d.ts +10 -0
  93. package/dist/billing/stripe/portal.js +16 -0
  94. package/dist/billing/stripe/portal.test.d.ts +1 -0
  95. package/dist/billing/stripe/portal.test.js +48 -0
  96. package/dist/billing/stripe/setup-intent.d.ts +16 -0
  97. package/dist/billing/stripe/setup-intent.js +22 -0
  98. package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
  99. package/dist/billing/stripe/setup-intent.test.js +58 -0
  100. package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
  101. package/dist/billing/stripe/stripe-payment-processor.js +166 -0
  102. package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
  103. package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
  104. package/dist/billing/stripe/tenant-store.d.ts +56 -0
  105. package/dist/billing/stripe/tenant-store.js +119 -0
  106. package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
  107. package/dist/billing/stripe/tenant-store.test.js +97 -0
  108. package/dist/billing/stripe/types.d.ts +49 -0
  109. package/dist/billing/stripe/types.js +1 -0
  110. package/dist/billing/webhook-seen-repository.d.ts +14 -0
  111. package/dist/billing/webhook-seen-repository.js +13 -0
  112. package/dist/config/billing-env.test.d.ts +1 -0
  113. package/dist/config/billing-env.test.js +48 -0
  114. package/dist/config/index.d.ts +46 -0
  115. package/dist/config/index.js +38 -0
  116. package/dist/config/logger.d.ts +2 -0
  117. package/dist/config/logger.js +11 -0
  118. package/dist/config/provider-endpoints.d.ts +6 -0
  119. package/dist/config/provider-endpoints.js +12 -0
  120. package/dist/credits/auto-topup-charge.d.ts +27 -0
  121. package/dist/credits/auto-topup-charge.js +139 -0
  122. package/dist/credits/auto-topup-charge.test.d.ts +1 -0
  123. package/dist/credits/auto-topup-charge.test.js +242 -0
  124. package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
  125. package/dist/credits/auto-topup-event-log-repository.js +18 -0
  126. package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
  127. package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
  128. package/dist/credits/auto-topup-schedule.d.ts +27 -0
  129. package/dist/credits/auto-topup-schedule.js +66 -0
  130. package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
  131. package/dist/credits/auto-topup-schedule.test.js +145 -0
  132. package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
  133. package/dist/credits/auto-topup-settings-repository.js +184 -0
  134. package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
  135. package/dist/credits/auto-topup-settings-repository.test.js +104 -0
  136. package/dist/credits/auto-topup-usage.d.ts +22 -0
  137. package/dist/credits/auto-topup-usage.js +56 -0
  138. package/dist/credits/auto-topup-usage.test.d.ts +1 -0
  139. package/dist/credits/auto-topup-usage.test.js +181 -0
  140. package/dist/credits/credit-expiry-cron.d.ts +19 -0
  141. package/dist/credits/credit-expiry-cron.js +50 -0
  142. package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
  143. package/dist/credits/credit-expiry-cron.test.js +67 -0
  144. package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
  145. package/dist/credits/credit-ledger-extra.test.js +40 -0
  146. package/dist/credits/credit-ledger.bench.d.ts +1 -0
  147. package/dist/credits/credit-ledger.bench.js +33 -0
  148. package/dist/credits/credit-ledger.d.ts +130 -0
  149. package/dist/credits/credit-ledger.js +293 -0
  150. package/dist/credits/credit-ledger.test.d.ts +4 -0
  151. package/dist/credits/credit-ledger.test.js +203 -0
  152. package/dist/credits/credit-transaction-repository.d.ts +17 -0
  153. package/dist/credits/credit-transaction-repository.js +35 -0
  154. package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
  155. package/dist/credits/credit-transaction-repository.test.js +232 -0
  156. package/dist/credits/credit.d.ts +75 -0
  157. package/dist/credits/credit.js +139 -0
  158. package/dist/credits/credit.test.d.ts +1 -0
  159. package/dist/credits/credit.test.js +196 -0
  160. package/dist/credits/dividend-cron.d.ts +29 -0
  161. package/dist/credits/dividend-cron.js +88 -0
  162. package/dist/credits/dividend-cron.test.d.ts +1 -0
  163. package/dist/credits/dividend-cron.test.js +128 -0
  164. package/dist/credits/dividend-repository.d.ts +29 -0
  165. package/dist/credits/dividend-repository.js +126 -0
  166. package/dist/credits/dividend-repository.test.d.ts +1 -0
  167. package/dist/credits/dividend-repository.test.js +176 -0
  168. package/dist/credits/index.d.ts +9 -0
  169. package/dist/credits/index.js +5 -0
  170. package/dist/credits/repository-types.d.ts +29 -0
  171. package/dist/credits/repository-types.js +1 -0
  172. package/dist/credits/signup-grant.d.ts +12 -0
  173. package/dist/credits/signup-grant.js +35 -0
  174. package/dist/credits/signup-grant.test.d.ts +1 -0
  175. package/dist/credits/signup-grant.test.js +51 -0
  176. package/dist/credits/tenant-customer-repository.d.ts +30 -0
  177. package/dist/credits/tenant-customer-repository.js +5 -0
  178. package/dist/db/auth-user-repository.d.ts +46 -0
  179. package/dist/db/auth-user-repository.js +90 -0
  180. package/dist/db/credit-column.d.ts +27 -0
  181. package/dist/db/credit-column.js +13 -0
  182. package/dist/db/index.d.ts +14 -0
  183. package/dist/db/index.js +8 -0
  184. package/dist/db/schema/account-deletion-requests.d.ts +203 -0
  185. package/dist/db/schema/account-deletion-requests.js +36 -0
  186. package/dist/db/schema/account-export-requests.d.ts +148 -0
  187. package/dist/db/schema/account-export-requests.js +19 -0
  188. package/dist/db/schema/admin-audit.d.ts +194 -0
  189. package/dist/db/schema/admin-audit.js +21 -0
  190. package/dist/db/schema/admin-users.d.ts +177 -0
  191. package/dist/db/schema/admin-users.js +23 -0
  192. package/dist/db/schema/affiliate-fraud.d.ts +160 -0
  193. package/dist/db/schema/affiliate-fraud.js +18 -0
  194. package/dist/db/schema/affiliate.d.ts +277 -0
  195. package/dist/db/schema/affiliate.js +32 -0
  196. package/dist/db/schema/coupon-codes.d.ts +143 -0
  197. package/dist/db/schema/coupon-codes.js +17 -0
  198. package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
  199. package/dist/db/schema/credit-auto-topup-settings.js +27 -0
  200. package/dist/db/schema/credit-auto-topup.d.ts +130 -0
  201. package/dist/db/schema/credit-auto-topup.js +21 -0
  202. package/dist/db/schema/credits.d.ts +283 -0
  203. package/dist/db/schema/credits.js +38 -0
  204. package/dist/db/schema/dividend-distributions.d.ts +130 -0
  205. package/dist/db/schema/dividend-distributions.js +19 -0
  206. package/dist/db/schema/email-notifications.d.ts +99 -0
  207. package/dist/db/schema/email-notifications.js +21 -0
  208. package/dist/db/schema/index.d.ts +33 -0
  209. package/dist/db/schema/index.js +33 -0
  210. package/dist/db/schema/meter-events.d.ts +599 -0
  211. package/dist/db/schema/meter-events.js +55 -0
  212. package/dist/db/schema/notification-preferences.d.ts +165 -0
  213. package/dist/db/schema/notification-preferences.js +18 -0
  214. package/dist/db/schema/notification-queue.d.ts +236 -0
  215. package/dist/db/schema/notification-queue.js +40 -0
  216. package/dist/db/schema/org-memberships.d.ts +63 -0
  217. package/dist/db/schema/org-memberships.js +15 -0
  218. package/dist/db/schema/organization-members.d.ts +235 -0
  219. package/dist/db/schema/organization-members.js +27 -0
  220. package/dist/db/schema/payram.d.ts +164 -0
  221. package/dist/db/schema/payram.js +21 -0
  222. package/dist/db/schema/platform-api-keys.d.ts +143 -0
  223. package/dist/db/schema/platform-api-keys.js +20 -0
  224. package/dist/db/schema/promotion-redemptions.d.ts +143 -0
  225. package/dist/db/schema/promotion-redemptions.js +18 -0
  226. package/dist/db/schema/promotions.d.ts +445 -0
  227. package/dist/db/schema/promotions.js +48 -0
  228. package/dist/db/schema/provider-credentials.d.ts +201 -0
  229. package/dist/db/schema/provider-credentials.js +36 -0
  230. package/dist/db/schema/rate-limit-entries.d.ts +75 -0
  231. package/dist/db/schema/rate-limit-entries.js +7 -0
  232. package/dist/db/schema/secret-audit-log.d.ts +109 -0
  233. package/dist/db/schema/secret-audit-log.js +15 -0
  234. package/dist/db/schema/session-usage.d.ts +194 -0
  235. package/dist/db/schema/session-usage.js +19 -0
  236. package/dist/db/schema/spending-limits.d.ts +92 -0
  237. package/dist/db/schema/spending-limits.js +8 -0
  238. package/dist/db/schema/tenant-addons.d.ts +58 -0
  239. package/dist/db/schema/tenant-addons.js +9 -0
  240. package/dist/db/schema/tenant-api-keys.d.ts +131 -0
  241. package/dist/db/schema/tenant-api-keys.js +21 -0
  242. package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
  243. package/dist/db/schema/tenant-capability-settings.js +12 -0
  244. package/dist/db/schema/tenant-customers.d.ts +303 -0
  245. package/dist/db/schema/tenant-customers.js +25 -0
  246. package/dist/db/schema/tenants.d.ts +126 -0
  247. package/dist/db/schema/tenants.js +18 -0
  248. package/dist/db/schema/user-roles.d.ts +98 -0
  249. package/dist/db/schema/user-roles.js +18 -0
  250. package/dist/db/schema/webhook-seen-events.d.ts +58 -0
  251. package/dist/db/schema/webhook-seen-events.js +9 -0
  252. package/dist/email/billing-emails.d.ts +51 -0
  253. package/dist/email/billing-emails.js +163 -0
  254. package/dist/email/billing-emails.test.d.ts +1 -0
  255. package/dist/email/billing-emails.test.js +162 -0
  256. package/dist/email/client.d.ts +51 -0
  257. package/dist/email/client.js +102 -0
  258. package/dist/email/client.test.d.ts +1 -0
  259. package/dist/email/client.test.js +120 -0
  260. package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
  261. package/dist/email/drizzle-billing-email-repository.js +36 -0
  262. package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
  263. package/dist/email/drizzle-billing-email-repository.test.js +42 -0
  264. package/dist/email/index.d.ts +33 -0
  265. package/dist/email/index.js +22 -0
  266. package/dist/email/notification-preferences-store.d.ts +12 -0
  267. package/dist/email/notification-preferences-store.js +82 -0
  268. package/dist/email/notification-preferences-store.test.d.ts +1 -0
  269. package/dist/email/notification-preferences-store.test.js +86 -0
  270. package/dist/email/notification-queue-store.d.ts +25 -0
  271. package/dist/email/notification-queue-store.js +97 -0
  272. package/dist/email/notification-queue-store.test.d.ts +1 -0
  273. package/dist/email/notification-queue-store.test.js +177 -0
  274. package/dist/email/notification-repository-types.d.ts +70 -0
  275. package/dist/email/notification-repository-types.js +6 -0
  276. package/dist/email/notification-service.d.ts +41 -0
  277. package/dist/email/notification-service.js +196 -0
  278. package/dist/email/notification-service.test.d.ts +1 -0
  279. package/dist/email/notification-service.test.js +160 -0
  280. package/dist/email/notification-templates.d.ts +18 -0
  281. package/dist/email/notification-templates.js +574 -0
  282. package/dist/email/notification-templates.test.d.ts +1 -0
  283. package/dist/email/notification-templates.test.js +238 -0
  284. package/dist/email/notification-worker.d.ts +24 -0
  285. package/dist/email/notification-worker.js +109 -0
  286. package/dist/email/notification-worker.test.d.ts +1 -0
  287. package/dist/email/notification-worker.test.js +153 -0
  288. package/dist/email/require-verified.d.ts +25 -0
  289. package/dist/email/require-verified.js +52 -0
  290. package/dist/email/require-verified.test.d.ts +1 -0
  291. package/dist/email/require-verified.test.js +62 -0
  292. package/dist/email/resend-adapter.d.ts +47 -0
  293. package/dist/email/resend-adapter.js +137 -0
  294. package/dist/email/resend-adapter.test.d.ts +1 -0
  295. package/dist/email/resend-adapter.test.js +190 -0
  296. package/dist/email/templates.d.ts +22 -0
  297. package/dist/email/templates.js +359 -0
  298. package/dist/email/templates.test.d.ts +1 -0
  299. package/dist/email/templates.test.js +170 -0
  300. package/dist/email/verification.d.ts +42 -0
  301. package/dist/email/verification.js +83 -0
  302. package/dist/email/verification.test.d.ts +1 -0
  303. package/dist/email/verification.test.js +141 -0
  304. package/dist/index.d.ts +13 -0
  305. package/dist/index.js +23 -0
  306. package/dist/metering/aggregator.d.ts +54 -0
  307. package/dist/metering/aggregator.js +123 -0
  308. package/dist/metering/aggregator.test.d.ts +1 -0
  309. package/dist/metering/aggregator.test.js +179 -0
  310. package/dist/metering/dlq.d.ts +31 -0
  311. package/dist/metering/dlq.js +82 -0
  312. package/dist/metering/dlq.test.d.ts +1 -0
  313. package/dist/metering/dlq.test.js +117 -0
  314. package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
  315. package/dist/metering/drizzle-usage-summary-repository.js +98 -0
  316. package/dist/metering/emitter.d.ts +66 -0
  317. package/dist/metering/emitter.js +185 -0
  318. package/dist/metering/emitter.test.d.ts +1 -0
  319. package/dist/metering/emitter.test.js +171 -0
  320. package/dist/metering/index.d.ts +11 -0
  321. package/dist/metering/index.js +5 -0
  322. package/dist/metering/load-test.bench.d.ts +1 -0
  323. package/dist/metering/load-test.bench.js +103 -0
  324. package/dist/metering/meter-event-repository.d.ts +33 -0
  325. package/dist/metering/meter-event-repository.js +58 -0
  326. package/dist/metering/meter-repositories.test.d.ts +1 -0
  327. package/dist/metering/meter-repositories.test.js +419 -0
  328. package/dist/metering/metering.test.d.ts +1 -0
  329. package/dist/metering/metering.test.js +1046 -0
  330. package/dist/metering/reconciliation-cron.d.ts +37 -0
  331. package/dist/metering/reconciliation-cron.js +85 -0
  332. package/dist/metering/reconciliation-cron.test.d.ts +1 -0
  333. package/dist/metering/reconciliation-cron.test.js +162 -0
  334. package/dist/metering/reconciliation-repository.d.ts +27 -0
  335. package/dist/metering/reconciliation-repository.js +43 -0
  336. package/dist/metering/reconciliation-repository.test.d.ts +1 -0
  337. package/dist/metering/reconciliation-repository.test.js +160 -0
  338. package/dist/metering/types.d.ts +88 -0
  339. package/dist/metering/types.js +1 -0
  340. package/dist/metering/wal.d.ts +49 -0
  341. package/dist/metering/wal.js +124 -0
  342. package/dist/metering/wal.test.d.ts +1 -0
  343. package/dist/metering/wal.test.js +175 -0
  344. package/dist/middleware/csrf.d.ts +24 -0
  345. package/dist/middleware/csrf.js +80 -0
  346. package/dist/middleware/csrf.test.d.ts +1 -0
  347. package/dist/middleware/csrf.test.js +152 -0
  348. package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
  349. package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
  350. package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
  351. package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
  352. package/dist/middleware/get-client-ip.d.ts +22 -0
  353. package/dist/middleware/get-client-ip.js +51 -0
  354. package/dist/middleware/get-client-ip.test.d.ts +1 -0
  355. package/dist/middleware/get-client-ip.test.js +40 -0
  356. package/dist/middleware/index.d.ts +5 -0
  357. package/dist/middleware/index.js +4 -0
  358. package/dist/middleware/rate-limit-repository.d.ts +19 -0
  359. package/dist/middleware/rate-limit-repository.js +1 -0
  360. package/dist/middleware/rate-limit.d.ts +57 -0
  361. package/dist/middleware/rate-limit.js +109 -0
  362. package/dist/middleware/rate-limit.test.d.ts +1 -0
  363. package/dist/middleware/rate-limit.test.js +247 -0
  364. package/dist/security/credential-vault/audit-repository.d.ts +27 -0
  365. package/dist/security/credential-vault/audit-repository.js +42 -0
  366. package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
  367. package/dist/security/credential-vault/audit-repository.test.js +78 -0
  368. package/dist/security/credential-vault/credential-repository.d.ts +94 -0
  369. package/dist/security/credential-vault/credential-repository.js +145 -0
  370. package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
  371. package/dist/security/credential-vault/credential-repository.test.js +206 -0
  372. package/dist/security/credential-vault/index.d.ts +12 -0
  373. package/dist/security/credential-vault/index.js +6 -0
  374. package/dist/security/credential-vault/key-rotation.d.ts +18 -0
  375. package/dist/security/credential-vault/key-rotation.js +52 -0
  376. package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
  377. package/dist/security/credential-vault/key-rotation.test.js +95 -0
  378. package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
  379. package/dist/security/credential-vault/migrate-plaintext.js +80 -0
  380. package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
  381. package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
  382. package/dist/security/credential-vault/migration-check.d.ts +15 -0
  383. package/dist/security/credential-vault/migration-check.js +71 -0
  384. package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
  385. package/dist/security/credential-vault/migration-check.test.js +457 -0
  386. package/dist/security/credential-vault/store.d.ts +106 -0
  387. package/dist/security/credential-vault/store.js +181 -0
  388. package/dist/security/credential-vault/store.test.d.ts +1 -0
  389. package/dist/security/credential-vault/store.test.js +482 -0
  390. package/dist/security/encryption.d.ts +22 -0
  391. package/dist/security/encryption.js +53 -0
  392. package/dist/security/encryption.test.d.ts +1 -0
  393. package/dist/security/encryption.test.js +95 -0
  394. package/dist/security/host-validation.d.ts +11 -0
  395. package/dist/security/host-validation.js +108 -0
  396. package/dist/security/host-validation.test.d.ts +1 -0
  397. package/dist/security/host-validation.test.js +106 -0
  398. package/dist/security/index.d.ts +11 -0
  399. package/dist/security/index.js +11 -0
  400. package/dist/security/key-audit.d.ts +16 -0
  401. package/dist/security/key-audit.js +35 -0
  402. package/dist/security/key-audit.test.d.ts +1 -0
  403. package/dist/security/key-audit.test.js +50 -0
  404. package/dist/security/key-injection.d.ts +28 -0
  405. package/dist/security/key-injection.js +57 -0
  406. package/dist/security/key-injection.test.d.ts +1 -0
  407. package/dist/security/key-injection.test.js +97 -0
  408. package/dist/security/key-validation.d.ts +16 -0
  409. package/dist/security/key-validation.js +78 -0
  410. package/dist/security/key-validation.test.d.ts +1 -0
  411. package/dist/security/key-validation.test.js +87 -0
  412. package/dist/security/redirect-allowlist.d.ts +6 -0
  413. package/dist/security/redirect-allowlist.js +36 -0
  414. package/dist/security/redirect-allowlist.test.d.ts +1 -0
  415. package/dist/security/redirect-allowlist.test.js +55 -0
  416. package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
  417. package/dist/security/tenant-keys/capability-settings-store.js +33 -0
  418. package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
  419. package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
  420. package/dist/security/tenant-keys/index.d.ts +10 -0
  421. package/dist/security/tenant-keys/index.js +5 -0
  422. package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
  423. package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
  424. package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
  425. package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
  426. package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
  427. package/dist/security/tenant-keys/key-resolution.js +59 -0
  428. package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
  429. package/dist/security/tenant-keys/key-resolution.test.js +97 -0
  430. package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
  431. package/dist/security/tenant-keys/org-key-resolution.js +50 -0
  432. package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
  433. package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
  434. package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
  435. package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
  436. package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
  437. package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
  438. package/dist/security/types.d.ts +35 -0
  439. package/dist/security/types.js +15 -0
  440. package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
  441. package/dist/tenancy/drizzle-org-repository.js +126 -0
  442. package/dist/tenancy/index.d.ts +6 -0
  443. package/dist/tenancy/index.js +3 -0
  444. package/dist/tenancy/org-member-repository.d.ts +57 -0
  445. package/dist/tenancy/org-member-repository.js +99 -0
  446. package/dist/tenancy/org-repository.test.d.ts +1 -0
  447. package/dist/tenancy/org-repository.test.js +143 -0
  448. package/dist/tenancy/org-service.d.ts +70 -0
  449. package/dist/tenancy/org-service.js +223 -0
  450. package/dist/tenancy/org-service.test.d.ts +1 -0
  451. package/dist/tenancy/org-service.test.js +550 -0
  452. package/dist/test/db.d.ts +33 -0
  453. package/dist/test/db.js +65 -0
  454. package/dist/trpc/index.d.ts +1 -0
  455. package/dist/trpc/index.js +1 -0
  456. package/dist/trpc/init.d.ts +49 -0
  457. package/dist/trpc/init.js +108 -0
  458. package/dist/trpc/init.test.d.ts +1 -0
  459. package/dist/trpc/init.test.js +154 -0
  460. package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
  461. package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
  462. package/drizzle/migrations/meta/_journal.json +13 -0
  463. package/drizzle.config.ts +41 -0
  464. package/package.json +64 -0
  465. package/src/admin/admin-audit-log-repository.ts +135 -0
  466. package/src/admin/audit-log.ts +111 -0
  467. package/src/admin/index.ts +6 -0
  468. package/src/admin/role-store.ts +134 -0
  469. package/src/auth/api-key-repository.test.ts +63 -0
  470. package/src/auth/api-key-repository.ts +46 -0
  471. package/src/auth/auth.test.ts +166 -0
  472. package/src/auth/better-auth.ts +216 -0
  473. package/src/auth/index.ts +520 -0
  474. package/src/auth/login-history-repository.test.ts +54 -0
  475. package/src/auth/login-history-repository.ts +28 -0
  476. package/src/auth/middleware.test.ts +264 -0
  477. package/src/auth/middleware.ts +117 -0
  478. package/src/auth/scoped-tokens.test.ts +362 -0
  479. package/src/auth/tenant-access.test.ts +69 -0
  480. package/src/auth/user-creator.test.ts +98 -0
  481. package/src/auth/user-creator.ts +54 -0
  482. package/src/auth/user-role-repository.test.ts +149 -0
  483. package/src/auth/user-role-repository.ts +67 -0
  484. package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
  485. package/src/billing/index.ts +22 -0
  486. package/src/billing/payment-processor.test.ts +93 -0
  487. package/src/billing/payment-processor.ts +150 -0
  488. package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
  489. package/src/billing/payram/charge-store.test.ts +84 -0
  490. package/src/billing/payram/charge-store.ts +109 -0
  491. package/src/billing/payram/checkout.test.ts +99 -0
  492. package/src/billing/payram/checkout.ts +40 -0
  493. package/src/billing/payram/client.test.ts +62 -0
  494. package/src/billing/payram/client.ts +21 -0
  495. package/src/billing/payram/index.ts +14 -0
  496. package/src/billing/payram/types.ts +44 -0
  497. package/src/billing/payram/webhook.test.ts +318 -0
  498. package/src/billing/payram/webhook.ts +97 -0
  499. package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
  500. package/src/billing/stripe/checkout.test.ts +186 -0
  501. package/src/billing/stripe/checkout.ts +82 -0
  502. package/src/billing/stripe/client.test.ts +64 -0
  503. package/src/billing/stripe/client.ts +39 -0
  504. package/src/billing/stripe/credit-prices.test.ts +114 -0
  505. package/src/billing/stripe/credit-prices.ts +113 -0
  506. package/src/billing/stripe/index.ts +14 -0
  507. package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
  508. package/src/billing/stripe/payment-methods.test.ts +157 -0
  509. package/src/billing/stripe/payment-methods.ts +76 -0
  510. package/src/billing/stripe/portal.test.ts +63 -0
  511. package/src/billing/stripe/portal.ts +25 -0
  512. package/src/billing/stripe/setup-intent.test.ts +78 -0
  513. package/src/billing/stripe/setup-intent.ts +34 -0
  514. package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
  515. package/src/billing/stripe/stripe-payment-processor.ts +255 -0
  516. package/src/billing/stripe/tenant-store.test.ts +124 -0
  517. package/src/billing/stripe/tenant-store.ts +151 -0
  518. package/src/billing/stripe/types.ts +53 -0
  519. package/src/billing/webhook-seen-repository.ts +24 -0
  520. package/src/config/billing-env.test.ts +54 -0
  521. package/src/config/index.ts +44 -0
  522. package/src/config/logger.ts +12 -0
  523. package/src/config/provider-endpoints.ts +14 -0
  524. package/src/credits/auto-topup-charge.test.ts +292 -0
  525. package/src/credits/auto-topup-charge.ts +171 -0
  526. package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
  527. package/src/credits/auto-topup-event-log-repository.ts +30 -0
  528. package/src/credits/auto-topup-schedule.test.ts +179 -0
  529. package/src/credits/auto-topup-schedule.ts +93 -0
  530. package/src/credits/auto-topup-settings-repository.test.ts +123 -0
  531. package/src/credits/auto-topup-settings-repository.ts +245 -0
  532. package/src/credits/auto-topup-usage.test.ts +220 -0
  533. package/src/credits/auto-topup-usage.ts +68 -0
  534. package/src/credits/credit-expiry-cron.test.ts +125 -0
  535. package/src/credits/credit-expiry-cron.ts +76 -0
  536. package/src/credits/credit-ledger-extra.test.ts +57 -0
  537. package/src/credits/credit-ledger.bench.ts +56 -0
  538. package/src/credits/credit-ledger.test.ts +276 -0
  539. package/src/credits/credit-ledger.ts +450 -0
  540. package/src/credits/credit-transaction-repository.test.ts +274 -0
  541. package/src/credits/credit-transaction-repository.ts +62 -0
  542. package/src/credits/credit.test.ts +234 -0
  543. package/src/credits/credit.ts +160 -0
  544. package/src/credits/dividend-cron.test.ts +158 -0
  545. package/src/credits/dividend-cron.ts +127 -0
  546. package/src/credits/dividend-repository.test.ts +223 -0
  547. package/src/credits/dividend-repository.ts +182 -0
  548. package/src/credits/index.ts +25 -0
  549. package/src/credits/repository-types.ts +33 -0
  550. package/src/credits/signup-grant.test.ts +63 -0
  551. package/src/credits/signup-grant.ts +44 -0
  552. package/src/credits/tenant-customer-repository.ts +28 -0
  553. package/src/db/auth-user-repository.ts +124 -0
  554. package/src/db/credit-column.ts +17 -0
  555. package/src/db/index.ts +21 -0
  556. package/src/db/schema/account-deletion-requests.ts +41 -0
  557. package/src/db/schema/account-export-requests.ts +24 -0
  558. package/src/db/schema/admin-audit.ts +26 -0
  559. package/src/db/schema/admin-users.ts +31 -0
  560. package/src/db/schema/affiliate-fraud.ts +23 -0
  561. package/src/db/schema/affiliate.ts +38 -0
  562. package/src/db/schema/coupon-codes.ts +22 -0
  563. package/src/db/schema/credit-auto-topup-settings.ts +32 -0
  564. package/src/db/schema/credit-auto-topup.ts +26 -0
  565. package/src/db/schema/credits.ts +44 -0
  566. package/src/db/schema/dividend-distributions.ts +24 -0
  567. package/src/db/schema/email-notifications.ts +26 -0
  568. package/src/db/schema/index.ts +33 -0
  569. package/src/db/schema/meter-events.ts +70 -0
  570. package/src/db/schema/notification-preferences.ts +19 -0
  571. package/src/db/schema/notification-queue.ts +45 -0
  572. package/src/db/schema/org-memberships.ts +20 -0
  573. package/src/db/schema/organization-members.ts +37 -0
  574. package/src/db/schema/payram.ts +26 -0
  575. package/src/db/schema/platform-api-keys.ts +25 -0
  576. package/src/db/schema/promotion-redemptions.ts +23 -0
  577. package/src/db/schema/promotions.ts +57 -0
  578. package/src/db/schema/provider-credentials.ts +41 -0
  579. package/src/db/schema/rate-limit-entries.ts +12 -0
  580. package/src/db/schema/secret-audit-log.ts +20 -0
  581. package/src/db/schema/session-usage.ts +24 -0
  582. package/src/db/schema/spending-limits.ts +9 -0
  583. package/src/db/schema/tenant-addons.ts +14 -0
  584. package/src/db/schema/tenant-api-keys.ts +26 -0
  585. package/src/db/schema/tenant-capability-settings.ts +17 -0
  586. package/src/db/schema/tenant-customers.ts +35 -0
  587. package/src/db/schema/tenants.ts +23 -0
  588. package/src/db/schema/user-roles.ts +23 -0
  589. package/src/db/schema/webhook-seen-events.ts +14 -0
  590. package/src/email/billing-emails.test.ts +198 -0
  591. package/src/email/billing-emails.ts +211 -0
  592. package/src/email/client.test.ts +149 -0
  593. package/src/email/client.ts +137 -0
  594. package/src/email/drizzle-billing-email-repository.test.ts +52 -0
  595. package/src/email/drizzle-billing-email-repository.ts +59 -0
  596. package/src/email/index.ts +57 -0
  597. package/src/email/notification-preferences-store.test.ts +102 -0
  598. package/src/email/notification-preferences-store.ts +90 -0
  599. package/src/email/notification-queue-store.test.ts +215 -0
  600. package/src/email/notification-queue-store.ts +127 -0
  601. package/src/email/notification-repository-types.ts +101 -0
  602. package/src/email/notification-service.test.ts +178 -0
  603. package/src/email/notification-service.ts +265 -0
  604. package/src/email/notification-templates.test.ts +261 -0
  605. package/src/email/notification-templates.ts +727 -0
  606. package/src/email/notification-worker.test.ts +189 -0
  607. package/src/email/notification-worker.ts +133 -0
  608. package/src/email/require-verified.ts +65 -0
  609. package/src/email/resend-adapter.test.ts +253 -0
  610. package/src/email/resend-adapter.ts +157 -0
  611. package/src/email/templates.test.ts +217 -0
  612. package/src/email/templates.ts +469 -0
  613. package/src/email/verification.test.ts +185 -0
  614. package/src/email/verification.ts +110 -0
  615. package/src/index.ts +51 -0
  616. package/src/metering/aggregator.test.ts +239 -0
  617. package/src/metering/aggregator.ts +160 -0
  618. package/src/metering/dlq.test.ts +134 -0
  619. package/src/metering/dlq.ts +102 -0
  620. package/src/metering/drizzle-usage-summary-repository.ts +167 -0
  621. package/src/metering/emitter.test.ts +202 -0
  622. package/src/metering/emitter.ts +227 -0
  623. package/src/metering/index.ts +21 -0
  624. package/src/metering/load-test.bench.ts +130 -0
  625. package/src/metering/meter-event-repository.ts +87 -0
  626. package/src/metering/meter-repositories.test.ts +491 -0
  627. package/src/metering/metering.test.ts +1317 -0
  628. package/src/metering/reconciliation-cron.test.ts +202 -0
  629. package/src/metering/reconciliation-cron.ts +134 -0
  630. package/src/metering/reconciliation-repository.test.ts +196 -0
  631. package/src/metering/reconciliation-repository.ts +83 -0
  632. package/src/metering/types.ts +93 -0
  633. package/src/metering/wal.test.ts +222 -0
  634. package/src/metering/wal.ts +139 -0
  635. package/src/middleware/csrf.test.ts +178 -0
  636. package/src/middleware/csrf.ts +101 -0
  637. package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
  638. package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
  639. package/src/middleware/get-client-ip.test.ts +49 -0
  640. package/src/middleware/get-client-ip.ts +62 -0
  641. package/src/middleware/index.ts +12 -0
  642. package/src/middleware/rate-limit-repository.ts +22 -0
  643. package/src/middleware/rate-limit.test.ts +338 -0
  644. package/src/middleware/rate-limit.ts +169 -0
  645. package/src/security/credential-vault/audit-repository.test.ts +91 -0
  646. package/src/security/credential-vault/audit-repository.ts +64 -0
  647. package/src/security/credential-vault/credential-repository.test.ts +264 -0
  648. package/src/security/credential-vault/credential-repository.ts +233 -0
  649. package/src/security/credential-vault/index.ts +26 -0
  650. package/src/security/credential-vault/key-rotation.test.ts +139 -0
  651. package/src/security/credential-vault/key-rotation.ts +70 -0
  652. package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
  653. package/src/security/credential-vault/migrate-plaintext.ts +101 -0
  654. package/src/security/credential-vault/migration-check.test.ts +533 -0
  655. package/src/security/credential-vault/migration-check.ts +88 -0
  656. package/src/security/credential-vault/store.test.ts +569 -0
  657. package/src/security/credential-vault/store.ts +284 -0
  658. package/src/security/encryption.test.ts +114 -0
  659. package/src/security/encryption.ts +65 -0
  660. package/src/security/host-validation.test.ts +136 -0
  661. package/src/security/host-validation.ts +116 -0
  662. package/src/security/index.ts +59 -0
  663. package/src/security/key-audit.test.ts +57 -0
  664. package/src/security/key-audit.ts +45 -0
  665. package/src/security/key-injection.test.ts +131 -0
  666. package/src/security/key-injection.ts +71 -0
  667. package/src/security/key-validation.test.ts +111 -0
  668. package/src/security/key-validation.ts +84 -0
  669. package/src/security/redirect-allowlist.test.ts +70 -0
  670. package/src/security/redirect-allowlist.ts +35 -0
  671. package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
  672. package/src/security/tenant-keys/capability-settings-store.ts +53 -0
  673. package/src/security/tenant-keys/index.ts +10 -0
  674. package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
  675. package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
  676. package/src/security/tenant-keys/key-resolution.test.ts +173 -0
  677. package/src/security/tenant-keys/key-resolution.ts +87 -0
  678. package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
  679. package/src/security/tenant-keys/org-key-resolution.ts +76 -0
  680. package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
  681. package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
  682. package/src/security/types.ts +43 -0
  683. package/src/tenancy/drizzle-org-repository.ts +169 -0
  684. package/src/tenancy/index.ts +6 -0
  685. package/src/tenancy/org-member-repository.ts +159 -0
  686. package/src/tenancy/org-repository.test.ts +172 -0
  687. package/src/tenancy/org-service.test.ts +634 -0
  688. package/src/tenancy/org-service.ts +290 -0
  689. package/src/test/db.ts +97 -0
  690. package/src/trpc/index.ts +11 -0
  691. package/src/trpc/init.test.ts +196 -0
  692. package/src/trpc/init.ts +138 -0
  693. package/tsconfig.json +20 -0
  694. package/vitest.config.ts +8 -0
@@ -0,0 +1,189 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { EmailClient } from "./client.js";
3
+ import type {
4
+ INotificationPreferencesRepository,
5
+ INotificationQueueRepository,
6
+ QueuedNotification,
7
+ } from "./notification-repository-types.js";
8
+ import { NotificationWorker } from "./notification-worker.js";
9
+
10
+ vi.mock("../config/logger.js", () => ({
11
+ logger: {
12
+ info: vi.fn(),
13
+ error: vi.fn(),
14
+ warn: vi.fn(),
15
+ debug: vi.fn(),
16
+ },
17
+ }));
18
+
19
+ function makeNotif(overrides: Partial<QueuedNotification> = {}): QueuedNotification {
20
+ return {
21
+ id: "notif-1",
22
+ tenantId: "tenant-1",
23
+ template: "low-balance",
24
+ data: JSON.stringify({ email: "user@example.com", balanceDollars: "$1.00" }),
25
+ status: "pending",
26
+ attempts: 0,
27
+ retryAfter: null,
28
+ sentAt: null,
29
+ createdAt: Date.now(),
30
+ ...overrides,
31
+ };
32
+ }
33
+
34
+ function makeQueue(pending: QueuedNotification[] = []): INotificationQueueRepository {
35
+ return {
36
+ enqueue: vi.fn().mockReturnValue("notif-id"),
37
+ fetchPending: vi.fn().mockReturnValue(pending),
38
+ markSent: vi.fn(),
39
+ markFailed: vi.fn(),
40
+ listForTenant: vi.fn().mockReturnValue({ entries: [], total: 0 }),
41
+ } as unknown as INotificationQueueRepository;
42
+ }
43
+
44
+ function makePrefs(prefs: Record<string, boolean> = {}): INotificationPreferencesRepository {
45
+ const defaultPrefs = {
46
+ billing_low_balance: true,
47
+ billing_receipts: true,
48
+ billing_auto_topup: true,
49
+ agent_channel_disconnect: true,
50
+ agent_status_changes: false,
51
+ account_role_changes: true,
52
+ account_team_invites: true,
53
+ };
54
+ return {
55
+ get: vi.fn().mockReturnValue({ ...defaultPrefs, ...prefs }),
56
+ update: vi.fn(),
57
+ } as unknown as INotificationPreferencesRepository;
58
+ }
59
+
60
+ function makeEmailClient(): EmailClient {
61
+ return {
62
+ send: vi.fn().mockResolvedValue({ id: "email-123", success: true }),
63
+ onEmailSent: vi.fn(),
64
+ } as unknown as EmailClient;
65
+ }
66
+
67
+ describe("NotificationWorker", () => {
68
+ let emailClient: EmailClient;
69
+
70
+ beforeEach(() => {
71
+ emailClient = makeEmailClient();
72
+ });
73
+
74
+ describe("processBatch", () => {
75
+ it("sends emails for pending notifications and marks them sent", async () => {
76
+ const notif = makeNotif();
77
+ const queue = makeQueue([notif]);
78
+ const prefs = makePrefs();
79
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
80
+
81
+ const count = await worker.processBatch();
82
+
83
+ expect(emailClient.send).toHaveBeenCalledOnce();
84
+ expect(queue.markSent).toHaveBeenCalledWith("notif-1");
85
+ expect(count).toBe(1);
86
+ });
87
+
88
+ it("returns 0 when no pending notifications", async () => {
89
+ const queue = makeQueue([]);
90
+ const prefs = makePrefs();
91
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
92
+
93
+ const count = await worker.processBatch();
94
+ expect(count).toBe(0);
95
+ expect(emailClient.send).not.toHaveBeenCalled();
96
+ });
97
+
98
+ it("marks notification as failed (not sent) when email is missing", async () => {
99
+ const notif = makeNotif({ data: JSON.stringify({}) }); // no email
100
+ const queue = makeQueue([notif]);
101
+ const prefs = makePrefs();
102
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
103
+
104
+ await worker.processBatch();
105
+
106
+ expect(emailClient.send).not.toHaveBeenCalled();
107
+ expect(queue.markFailed).toHaveBeenCalledWith("notif-1", 1);
108
+ });
109
+
110
+ it("marks as sent (skipped) when user preference disables that template", async () => {
111
+ const notif = makeNotif({ template: "agent-created" }); // pref: agent_status_changes
112
+ const queue = makeQueue([notif]);
113
+ const prefs = makePrefs({ agent_status_changes: false }); // disabled
114
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
115
+
116
+ await worker.processBatch();
117
+
118
+ expect(emailClient.send).not.toHaveBeenCalled();
119
+ expect(queue.markSent).toHaveBeenCalledWith("notif-1"); // cleared from queue
120
+ });
121
+
122
+ it("sends critical templates even when preferences would disable them", async () => {
123
+ // grace-period-start is critical — should bypass preference check
124
+ const notif = makeNotif({
125
+ template: "grace-period-start",
126
+ data: JSON.stringify({
127
+ email: "user@example.com",
128
+ balanceDollars: "$0.00",
129
+ graceDays: 7,
130
+ creditsUrl: "https://app.wopr.bot/billing/credits",
131
+ }),
132
+ });
133
+ const queue = makeQueue([notif]);
134
+ // Even if all prefs disabled, critical templates must send
135
+ const prefs = makePrefs({
136
+ billing_low_balance: false,
137
+ billing_receipts: false,
138
+ billing_auto_topup: false,
139
+ });
140
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
141
+
142
+ await worker.processBatch();
143
+
144
+ expect(emailClient.send).toHaveBeenCalledOnce();
145
+ expect(queue.markSent).toHaveBeenCalledWith(notif.id);
146
+ });
147
+
148
+ it("marks as failed and increments attempts when send throws", async () => {
149
+ const notif = makeNotif({ attempts: 2 });
150
+ const queue = makeQueue([notif]);
151
+ const prefs = makePrefs();
152
+ vi.spyOn(emailClient, "send").mockRejectedValueOnce(new Error("Network error"));
153
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
154
+
155
+ await worker.processBatch();
156
+
157
+ expect(queue.markFailed).toHaveBeenCalledWith("notif-1", 3); // attempts + 1
158
+ expect(queue.markSent).not.toHaveBeenCalled();
159
+ });
160
+
161
+ it("processes multiple notifications in one batch", async () => {
162
+ const notif1 = makeNotif({ id: "n1" });
163
+ const notif2 = makeNotif({
164
+ id: "n2",
165
+ template: "welcome",
166
+ data: JSON.stringify({ email: "b@b.com" }),
167
+ });
168
+ const queue = makeQueue([notif1, notif2]);
169
+ const prefs = makePrefs();
170
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
171
+
172
+ const count = await worker.processBatch();
173
+
174
+ expect(count).toBe(2);
175
+ expect(emailClient.send).toHaveBeenCalledTimes(2);
176
+ });
177
+
178
+ it("respects custom batchSize", async () => {
179
+ const notifs = Array.from({ length: 5 }, (_, i) => makeNotif({ id: `n${i}` }));
180
+ const queue = makeQueue(notifs.slice(0, 3)); // fetchPending called with batchSize
181
+ const prefs = makePrefs();
182
+ const worker = new NotificationWorker({ queue, emailClient, preferences: prefs, batchSize: 3 });
183
+
184
+ await worker.processBatch();
185
+
186
+ expect(queue.fetchPending).toHaveBeenCalledWith(3);
187
+ });
188
+ });
189
+ });
@@ -0,0 +1,133 @@
1
+ /**
2
+ * NotificationWorker — processes pending notification queue entries.
3
+ *
4
+ * Called on a timer (e.g. every 30s) from the server startup code.
5
+ * Do NOT put the interval inside this class.
6
+ */
7
+
8
+ import { logger } from "../config/logger.js";
9
+ import type { EmailClient } from "./client.js";
10
+ import type {
11
+ INotificationPreferencesRepository,
12
+ INotificationQueueRepository,
13
+ } from "./notification-repository-types.js";
14
+ import { renderNotificationTemplate, type TemplateName } from "./notification-templates.js";
15
+
16
+ export interface NotificationWorkerConfig {
17
+ queue: INotificationQueueRepository;
18
+ emailClient: EmailClient;
19
+ preferences: INotificationPreferencesRepository;
20
+ batchSize?: number;
21
+ }
22
+
23
+ /** Templates that bypass user preference checks — always sent. */
24
+ const CRITICAL_TEMPLATES: Set<string> = new Set([
25
+ "grace-period-start",
26
+ "grace-period-warning",
27
+ "auto-suspended",
28
+ "admin-suspended",
29
+ "admin-reactivated",
30
+ "password-reset",
31
+ "welcome",
32
+ "account-deletion-requested",
33
+ "account-deletion-cancelled",
34
+ "account-deletion-completed",
35
+ ]);
36
+
37
+ /** Map from template name to preference key. */
38
+ const PREF_MAP: Record<string, string> = {
39
+ "low-balance": "billing_low_balance",
40
+ "credits-depleted": "billing_low_balance",
41
+ "auto-topup-success": "billing_auto_topup",
42
+ "auto-topup-failed": "billing_auto_topup",
43
+ "credit-purchase-receipt": "billing_receipts",
44
+ "crypto-payment-confirmed": "billing_receipts",
45
+ "channel-disconnected": "agent_channel_disconnect",
46
+ "agent-created": "agent_status_changes",
47
+ "channel-connected": "agent_status_changes",
48
+ "agent-suspended": "agent_status_changes",
49
+ "credits-granted": "billing_receipts",
50
+ "dividend-weekly-digest": "billing_receipts",
51
+ "role-changed": "account_role_changes",
52
+ "team-invite": "account_team_invites",
53
+ };
54
+
55
+ export class NotificationWorker {
56
+ private readonly queue: INotificationQueueRepository;
57
+ private readonly emailClient: EmailClient;
58
+ private readonly preferences: INotificationPreferencesRepository;
59
+ private readonly batchSize: number;
60
+
61
+ constructor(config: NotificationWorkerConfig) {
62
+ this.queue = config.queue;
63
+ this.emailClient = config.emailClient;
64
+ this.preferences = config.preferences;
65
+ this.batchSize = config.batchSize ?? 10;
66
+ }
67
+
68
+ /** Process one batch of pending notifications. Returns count of processed items. */
69
+ async processBatch(): Promise<number> {
70
+ const pending = await this.queue.fetchPending(this.batchSize);
71
+ let processed = 0;
72
+
73
+ for (const notif of pending) {
74
+ try {
75
+ const data = JSON.parse(notif.data) as Record<string, unknown>;
76
+ const email = data.email as string | undefined;
77
+
78
+ if (!email) {
79
+ logger.error("Notification missing email field", {
80
+ notificationId: notif.id,
81
+ template: notif.template,
82
+ });
83
+ this.queue.markFailed(notif.id, notif.attempts + 1);
84
+ processed++;
85
+ continue;
86
+ }
87
+
88
+ // Check preferences (skip for critical notifications)
89
+ if (!CRITICAL_TEMPLATES.has(notif.template)) {
90
+ const prefs = this.preferences.get(notif.tenantId);
91
+ if (!this.isEnabledByPreferences(notif.template, prefs as unknown as Record<string, boolean>)) {
92
+ // User has disabled this notification type — mark sent to clear queue
93
+ this.queue.markSent(notif.id);
94
+ processed++;
95
+ continue;
96
+ }
97
+ }
98
+
99
+ // Render the template
100
+ const rendered = renderNotificationTemplate(notif.template as TemplateName, data);
101
+
102
+ // Send via email client
103
+ await this.emailClient.send({
104
+ to: email,
105
+ subject: rendered.subject,
106
+ html: rendered.html,
107
+ text: rendered.text,
108
+ userId: notif.tenantId,
109
+ templateName: notif.template,
110
+ });
111
+
112
+ this.queue.markSent(notif.id);
113
+ processed++;
114
+ } catch (err) {
115
+ logger.error("Notification send failed", {
116
+ notificationId: notif.id,
117
+ template: notif.template,
118
+ error: err instanceof Error ? err.message : String(err),
119
+ });
120
+ this.queue.markFailed(notif.id, notif.attempts + 1);
121
+ processed++;
122
+ }
123
+ }
124
+
125
+ return processed;
126
+ }
127
+
128
+ private isEnabledByPreferences(template: string, prefs: Record<string, boolean>): boolean {
129
+ const prefKey = PREF_MAP[template];
130
+ if (!prefKey) return true; // unknown template -> send by default
131
+ return prefs[prefKey] !== false; // default to enabled if key missing
132
+ }
133
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Email Verification Middleware — Blocks actions until email is verified.
3
+ *
4
+ * When used after session auth middleware, checks that the authenticated user
5
+ * has verified their email. API token auth (machine-to-machine) is not subject
6
+ * to email verification and is always allowed through.
7
+ */
8
+
9
+ import type { Context, Next } from "hono";
10
+ import { logger } from "../config/logger.js";
11
+
12
+ /** Minimal interface for checking email verification status. */
13
+ export interface IEmailVerifier {
14
+ isVerified(userId: string): Promise<boolean>;
15
+ }
16
+
17
+ /**
18
+ * Create middleware that blocks session-authenticated users who haven't verified their email.
19
+ *
20
+ * API token auth (authMethod === "api_key") bypasses this check since machine
21
+ * clients don't have email addresses to verify.
22
+ *
23
+ * @param verifier - Email verification store
24
+ */
25
+ export function requireEmailVerified(verifier: IEmailVerifier) {
26
+ return async (c: Context, next: Next) => {
27
+ let authMethod: string | undefined;
28
+ let userId: string | undefined;
29
+
30
+ try {
31
+ authMethod = c.get("authMethod");
32
+ const user = c.get("user") as { id: string } | undefined;
33
+ userId = user?.id;
34
+ } catch {
35
+ // No auth context set — let downstream auth middleware handle 401
36
+ return next();
37
+ }
38
+
39
+ // API token auth bypasses email verification
40
+ if (authMethod === "api_key") {
41
+ return next();
42
+ }
43
+
44
+ // Session auth requires verified email
45
+ if (authMethod === "session" && userId) {
46
+ try {
47
+ if (!(await verifier.isVerified(userId))) {
48
+ return c.json(
49
+ {
50
+ error: "Email verification required",
51
+ message: "Please verify your email address before creating bots",
52
+ code: "EMAIL_NOT_VERIFIED",
53
+ },
54
+ 403,
55
+ );
56
+ }
57
+ } catch (error) {
58
+ // If we can't check verification (DB issue), don't block the user
59
+ logger.warn("Email verification check failed", { error });
60
+ }
61
+ }
62
+
63
+ return next();
64
+ };
65
+ }
@@ -0,0 +1,253 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import {
3
+ type EmailOptions,
4
+ escapeHtml,
5
+ passwordResetTemplate,
6
+ passwordResetText,
7
+ sendEmail,
8
+ } from "./resend-adapter.js";
9
+
10
+ // Create a mock send function that can be controlled in tests
11
+ const mockSend = vi.fn();
12
+
13
+ // Mock the Resend module
14
+ vi.mock("resend", () => {
15
+ return {
16
+ Resend: class MockResend {
17
+ emails = {
18
+ send: mockSend,
19
+ };
20
+ },
21
+ };
22
+ });
23
+
24
+ describe("escapeHtml", () => {
25
+ it("should escape all dangerous HTML characters", () => {
26
+ expect(escapeHtml("<script>alert('XSS')</script>")).toBe("&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;");
27
+ expect(escapeHtml('Test "quotes" & <tags>')).toBe("Test &quot;quotes&quot; &amp; &lt;tags&gt;");
28
+ expect(escapeHtml("user@example.com")).toBe("user@example.com");
29
+ });
30
+
31
+ it("should handle empty strings", () => {
32
+ expect(escapeHtml("")).toBe("");
33
+ });
34
+
35
+ it("should handle strings with no special characters", () => {
36
+ expect(escapeHtml("normal text 123")).toBe("normal text 123");
37
+ });
38
+ });
39
+
40
+ describe("sendEmail", () => {
41
+ beforeEach(() => {
42
+ vi.clearAllMocks();
43
+ delete process.env.RESEND_API_KEY;
44
+ delete process.env.RESEND_FROM_EMAIL;
45
+ // Reset mock to default success response
46
+ mockSend.mockResolvedValue({
47
+ data: { id: "test-email-id" },
48
+ error: null,
49
+ });
50
+ });
51
+
52
+ it("should send email with required options", async () => {
53
+ process.env.RESEND_API_KEY = "test-api-key";
54
+
55
+ const options: EmailOptions = {
56
+ to: "user@example.com",
57
+ subject: "Test Subject",
58
+ html: "<p>Test HTML</p>",
59
+ };
60
+
61
+ const result = await sendEmail(options);
62
+
63
+ expect(result).toEqual({
64
+ id: "test-email-id",
65
+ success: true,
66
+ });
67
+ });
68
+
69
+ it("should use custom API key when provided", async () => {
70
+ const options: EmailOptions = {
71
+ to: "user@example.com",
72
+ subject: "Test Subject",
73
+ html: "<p>Test HTML</p>",
74
+ };
75
+
76
+ const result = await sendEmail(options, "custom-api-key");
77
+
78
+ expect(result.success).toBe(true);
79
+ });
80
+
81
+ it("should use custom from address when provided", async () => {
82
+ process.env.RESEND_API_KEY = "test-api-key";
83
+
84
+ const options: EmailOptions = {
85
+ to: "user@example.com",
86
+ subject: "Test Subject",
87
+ html: "<p>Test HTML</p>",
88
+ };
89
+
90
+ const result = await sendEmail(options, undefined, "custom@example.com");
91
+
92
+ expect(result.success).toBe(true);
93
+ });
94
+
95
+ it("should throw error when RESEND_API_KEY is missing", async () => {
96
+ const options: EmailOptions = {
97
+ to: "user@example.com",
98
+ subject: "Test Subject",
99
+ html: "<p>Test HTML</p>",
100
+ };
101
+
102
+ await expect(sendEmail(options)).rejects.toThrow("RESEND_API_KEY environment variable is required");
103
+ });
104
+
105
+ it("should handle Resend API errors", async () => {
106
+ process.env.RESEND_API_KEY = "test-api-key";
107
+
108
+ // Mock Resend to return an error for this specific test
109
+ mockSend.mockResolvedValueOnce({
110
+ data: null,
111
+ error: { message: "API error" },
112
+ });
113
+
114
+ const options: EmailOptions = {
115
+ to: "user@example.com",
116
+ subject: "Test Subject",
117
+ html: "<p>Test HTML</p>",
118
+ };
119
+
120
+ await expect(sendEmail(options)).rejects.toThrow("Failed to send email: API error");
121
+ });
122
+
123
+ it("should use default from email when env var is set", async () => {
124
+ process.env.RESEND_API_KEY = "test-api-key";
125
+ process.env.RESEND_FROM_EMAIL = "default@example.com";
126
+
127
+ const options: EmailOptions = {
128
+ to: "user@example.com",
129
+ subject: "Test Subject",
130
+ html: "<p>Test HTML</p>",
131
+ };
132
+
133
+ const result = await sendEmail(options);
134
+
135
+ expect(result.success).toBe(true);
136
+ });
137
+
138
+ it("should include text version when provided", async () => {
139
+ process.env.RESEND_API_KEY = "test-api-key";
140
+
141
+ const options: EmailOptions = {
142
+ to: "user@example.com",
143
+ subject: "Test Subject",
144
+ html: "<p>Test HTML</p>",
145
+ text: "Test plain text",
146
+ };
147
+
148
+ const result = await sendEmail(options);
149
+
150
+ expect(result.success).toBe(true);
151
+ });
152
+ });
153
+
154
+ describe("passwordResetTemplate", () => {
155
+ it("should generate HTML template with reset URL", () => {
156
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
157
+ const email = "user@example.com";
158
+
159
+ const html = passwordResetTemplate(resetUrl, email);
160
+
161
+ expect(html).toContain(resetUrl);
162
+ expect(html).toContain(email);
163
+ expect(html).toContain("Reset Your Password");
164
+ expect(html).toContain("Reset Password");
165
+ expect(html).toContain("<!DOCTYPE html>");
166
+ });
167
+
168
+ it("should escape HTML characters in email and URL to prevent XSS", () => {
169
+ const resetUrl = "https://wopr.bot/reset?token=<script>alert(1)</script>";
170
+ const email = "<script>alert('xss')</script>@example.com";
171
+
172
+ const html = passwordResetTemplate(resetUrl, email);
173
+
174
+ // Verify email is escaped in the text where it's displayed
175
+ expect(html).toContain("&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;@example.com");
176
+
177
+ // Verify resetUrl is escaped in the displayed text (not href)
178
+ expect(html).toContain("&lt;script&gt;alert(1)&lt;/script&gt;");
179
+
180
+ // Verify the email script tag doesn't appear unescaped in body text
181
+ // (it may appear in href attribute, which is safe from XSS)
182
+ const bodyMatch = html.match(/<p>.*<\/p>/gs);
183
+ const bodyText = bodyMatch ? bodyMatch.join("") : "";
184
+ expect(bodyText).not.toContain("<script>alert('xss')</script>");
185
+ });
186
+
187
+ it("should include current year in footer", () => {
188
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
189
+ const email = "user@example.com";
190
+
191
+ const html = passwordResetTemplate(resetUrl, email);
192
+
193
+ const currentYear = new Date().getFullYear();
194
+ expect(html).toContain(`© ${currentYear} WOPR Network`);
195
+ });
196
+
197
+ it("should be valid HTML structure", () => {
198
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
199
+ const email = "user@example.com";
200
+
201
+ const html = passwordResetTemplate(resetUrl, email);
202
+
203
+ expect(html).toContain("<html>");
204
+ expect(html).toContain("</html>");
205
+ expect(html).toContain("<head>");
206
+ expect(html).toContain("</head>");
207
+ expect(html).toContain("<body");
208
+ expect(html).toContain("</body>");
209
+ });
210
+ });
211
+
212
+ describe("passwordResetText", () => {
213
+ it("should generate plain text with reset URL", () => {
214
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
215
+ const email = "user@example.com";
216
+
217
+ const text = passwordResetText(resetUrl, email);
218
+
219
+ expect(text).toContain(resetUrl);
220
+ expect(text).toContain(email);
221
+ expect(text).toContain("Reset Your Password");
222
+ });
223
+
224
+ it("should not contain HTML tags", () => {
225
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
226
+ const email = "user@example.com";
227
+
228
+ const text = passwordResetText(resetUrl, email);
229
+
230
+ expect(text).not.toContain("<");
231
+ expect(text).not.toContain(">");
232
+ });
233
+
234
+ it("should include current year in footer", () => {
235
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
236
+ const email = "user@example.com";
237
+
238
+ const text = passwordResetText(resetUrl, email);
239
+
240
+ const currentYear = new Date().getFullYear();
241
+ expect(text).toContain(`© ${currentYear} WOPR Network`);
242
+ });
243
+
244
+ it("should be readable plain text format", () => {
245
+ const resetUrl = "https://wopr.bot/reset?token=abc123";
246
+ const email = "user@example.com";
247
+
248
+ const text = passwordResetText(resetUrl, email);
249
+
250
+ // Should have line breaks and be formatted
251
+ expect(text.split("\n").length).toBeGreaterThan(5);
252
+ });
253
+ });