@wopr-network/platform-core 1.0.0 → 1.0.2

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 (87) hide show
  1. package/coverage/coverage-summary.json +119 -0
  2. package/dist/admin/index.d.ts +1 -1
  3. package/dist/auth/better-auth.d.ts +45 -0
  4. package/dist/auth/better-auth.js +70 -47
  5. package/dist/auth/index.d.ts +12 -0
  6. package/dist/auth/index.js +7 -0
  7. package/dist/billing/drizzle-webhook-seen-repository.d.ts +1 -1
  8. package/dist/billing/index.d.ts +4 -4
  9. package/dist/billing/index.js +4 -4
  10. package/dist/billing/payram/webhook.test.js +1 -1
  11. package/dist/billing/stripe/index.d.ts +5 -5
  12. package/dist/billing/stripe/index.js +2 -2
  13. package/dist/billing/stripe/stripe-payment-processor.js +1 -1
  14. package/dist/credits/auto-topup-charge.d.ts +2 -2
  15. package/dist/credits/auto-topup-charge.test.js +1 -1
  16. package/dist/credits/auto-topup-schedule.d.ts +1 -1
  17. package/dist/credits/auto-topup-schedule.test.js +1 -1
  18. package/dist/credits/auto-topup-settings-repository.test.js +1 -1
  19. package/dist/credits/auto-topup-usage.d.ts +1 -1
  20. package/dist/credits/auto-topup-usage.test.js +1 -1
  21. package/dist/credits/index.d.ts +1 -1
  22. package/dist/credits/index.js +1 -1
  23. package/dist/db/schema/index.d.ts +1 -1
  24. package/dist/db/schema/index.js +1 -1
  25. package/dist/email/index.d.ts +1 -1
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.js +4 -3
  28. package/dist/metering/aggregator.test.js +1 -1
  29. package/dist/metering/emitter.test.js +1 -1
  30. package/dist/metering/load-test.bench.js +1 -1
  31. package/dist/metering/metering.test.js +1 -1
  32. package/dist/metering/reconciliation-cron.test.js +2 -2
  33. package/dist/metering/reconciliation-repository.test.js +1 -1
  34. package/dist/middleware/index.d.ts +3 -3
  35. package/dist/middleware/index.js +2 -2
  36. package/dist/security/credential-vault/index.d.ts +2 -2
  37. package/dist/security/index.d.ts +7 -7
  38. package/dist/security/index.js +7 -7
  39. package/dist/security/redirect-allowlist.js +10 -8
  40. package/dist/security/tenant-keys/index.d.ts +6 -6
  41. package/dist/security/tenant-keys/index.js +3 -3
  42. package/dist/tenancy/index.d.ts +3 -3
  43. package/dist/tenancy/org-service.d.ts +1 -1
  44. package/dist/tenancy/org-service.test.js +1 -1
  45. package/dist/trpc/index.d.ts +1 -1
  46. package/dist/trpc/index.js +1 -1
  47. package/dist/trpc/init.test.js +3 -5
  48. package/package.json +2 -1
  49. package/src/admin/index.ts +1 -1
  50. package/src/auth/better-auth.ts +129 -48
  51. package/src/auth/index.ts +31 -0
  52. package/src/billing/drizzle-webhook-seen-repository.ts +1 -1
  53. package/src/billing/index.ts +11 -13
  54. package/src/billing/payram/webhook.test.ts +1 -1
  55. package/src/billing/stripe/index.ts +17 -5
  56. package/src/billing/stripe/stripe-payment-processor.test.ts +2 -3
  57. package/src/billing/stripe/stripe-payment-processor.ts +1 -1
  58. package/src/credits/auto-topup-charge.test.ts +2 -2
  59. package/src/credits/auto-topup-charge.ts +2 -2
  60. package/src/credits/auto-topup-schedule.test.ts +1 -1
  61. package/src/credits/auto-topup-schedule.ts +1 -1
  62. package/src/credits/auto-topup-settings-repository.test.ts +1 -1
  63. package/src/credits/auto-topup-usage.test.ts +1 -1
  64. package/src/credits/auto-topup-usage.ts +1 -1
  65. package/src/credits/index.ts +1 -1
  66. package/src/db/schema/index.ts +1 -1
  67. package/src/email/index.ts +3 -3
  68. package/src/index.ts +13 -17
  69. package/src/metering/aggregator.test.ts +1 -1
  70. package/src/metering/emitter.test.ts +1 -1
  71. package/src/metering/load-test.bench.ts +1 -1
  72. package/src/metering/metering.test.ts +1 -1
  73. package/src/metering/reconciliation-cron.test.ts +2 -2
  74. package/src/metering/reconciliation-repository.test.ts +2 -2
  75. package/src/middleware/index.ts +5 -5
  76. package/src/middleware/rate-limit.test.ts +1 -1
  77. package/src/middleware/rate-limit.ts +1 -1
  78. package/src/security/credential-vault/index.ts +2 -2
  79. package/src/security/index.ts +43 -38
  80. package/src/security/redirect-allowlist.ts +11 -8
  81. package/src/security/tenant-keys/index.ts +10 -6
  82. package/src/tenancy/index.ts +3 -3
  83. package/src/tenancy/org-service.test.ts +1 -1
  84. package/src/tenancy/org-service.ts +1 -1
  85. package/src/trpc/index.ts +5 -5
  86. package/src/trpc/init.test.ts +8 -10
  87. package/vitest.config.ts +4 -0
@@ -0,0 +1,119 @@
1
+ {"total": {"lines":{"total":2379,"covered":2080,"skipped":0,"pct":87.43},"statements":{"total":2523,"covered":2197,"skipped":0,"pct":87.07},"functions":{"total":580,"covered":517,"skipped":0,"pct":89.13},"branches":{"total":1460,"covered":1075,"skipped":0,"pct":73.63},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
2
+ ,"/home/tsavo/platform-core/src/admin/admin-audit-log-repository.ts": {"lines":{"total":38,"covered":4,"skipped":0,"pct":10.52},"functions":{"total":7,"covered":2,"skipped":0,"pct":28.57},"statements":{"total":40,"covered":4,"skipped":0,"pct":10},"branches":{"total":28,"covered":0,"skipped":0,"pct":0}}
3
+ ,"/home/tsavo/platform-core/src/admin/audit-log.ts": {"lines":{"total":16,"covered":5,"skipped":0,"pct":31.25},"functions":{"total":6,"covered":2,"skipped":0,"pct":33.33},"statements":{"total":17,"covered":5,"skipped":0,"pct":29.41},"branches":{"total":24,"covered":10,"skipped":0,"pct":41.66}}
4
+ ,"/home/tsavo/platform-core/src/auth/index.ts": {"lines":{"total":143,"covered":57,"skipped":0,"pct":39.86},"functions":{"total":20,"covered":10,"skipped":0,"pct":50},"statements":{"total":156,"covered":64,"skipped":0,"pct":41.02},"branches":{"total":90,"covered":40,"skipped":0,"pct":44.44}}
5
+ ,"/home/tsavo/platform-core/src/auth/login-history-repository.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":3,"covered":2,"skipped":0,"pct":66.66}}
6
+ ,"/home/tsavo/platform-core/src/auth/middleware.ts": {"lines":{"total":36,"covered":36,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":36,"covered":36,"skipped":0,"pct":100},"branches":{"total":18,"covered":16,"skipped":0,"pct":88.88}}
7
+ ,"/home/tsavo/platform-core/src/auth/user-creator.ts": {"lines":{"total":15,"covered":15,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":16,"covered":16,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
8
+ ,"/home/tsavo/platform-core/src/auth/user-role-repository.ts": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
9
+ ,"/home/tsavo/platform-core/src/billing/drizzle-webhook-seen-repository.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
10
+ ,"/home/tsavo/platform-core/src/billing/payment-processor.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
11
+ ,"/home/tsavo/platform-core/src/billing/webhook-seen-repository.ts": {"lines":{"total":4,"covered":3,"skipped":0,"pct":75},"functions":{"total":3,"covered":2,"skipped":0,"pct":66.66},"statements":{"total":4,"covered":3,"skipped":0,"pct":75},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
12
+ ,"/home/tsavo/platform-core/src/billing/payram/charge-store.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
13
+ ,"/home/tsavo/platform-core/src/billing/payram/checkout.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
14
+ ,"/home/tsavo/platform-core/src/billing/payram/client.ts": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
15
+ ,"/home/tsavo/platform-core/src/billing/payram/webhook.ts": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":16,"covered":15,"skipped":0,"pct":93.75}}
16
+ ,"/home/tsavo/platform-core/src/billing/stripe/checkout.ts": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
17
+ ,"/home/tsavo/platform-core/src/billing/stripe/client.ts": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
18
+ ,"/home/tsavo/platform-core/src/billing/stripe/credit-prices.ts": {"lines":{"total":17,"covered":17,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
19
+ ,"/home/tsavo/platform-core/src/billing/stripe/payment-methods.ts": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":23,"covered":23,"skipped":0,"pct":100},"branches":{"total":12,"covered":12,"skipped":0,"pct":100}}
20
+ ,"/home/tsavo/platform-core/src/billing/stripe/portal.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
21
+ ,"/home/tsavo/platform-core/src/billing/stripe/setup-intent.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
22
+ ,"/home/tsavo/platform-core/src/billing/stripe/stripe-payment-processor.ts": {"lines":{"total":66,"covered":63,"skipped":0,"pct":95.45},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":68,"covered":65,"skipped":0,"pct":95.58},"branches":{"total":48,"covered":41,"skipped":0,"pct":85.41}}
23
+ ,"/home/tsavo/platform-core/src/billing/stripe/tenant-store.ts": {"lines":{"total":22,"covered":19,"skipped":0,"pct":86.36},"functions":{"total":12,"covered":10,"skipped":0,"pct":83.33},"statements":{"total":22,"covered":19,"skipped":0,"pct":86.36},"branches":{"total":12,"covered":9,"skipped":0,"pct":75}}
24
+ ,"/home/tsavo/platform-core/src/config/index.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
25
+ ,"/home/tsavo/platform-core/src/config/logger.ts": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
26
+ ,"/home/tsavo/platform-core/src/config/provider-endpoints.ts": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
27
+ ,"/home/tsavo/platform-core/src/credits/auto-topup-charge.ts": {"lines":{"total":54,"covered":44,"skipped":0,"pct":81.48},"functions":{"total":2,"covered":1,"skipped":0,"pct":50},"statements":{"total":54,"covered":44,"skipped":0,"pct":81.48},"branches":{"total":32,"covered":20,"skipped":0,"pct":62.5}}
28
+ ,"/home/tsavo/platform-core/src/credits/auto-topup-event-log-repository.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
29
+ ,"/home/tsavo/platform-core/src/credits/auto-topup-schedule.ts": {"lines":{"total":31,"covered":30,"skipped":0,"pct":96.77},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":31,"covered":30,"skipped":0,"pct":96.77},"branches":{"total":12,"covered":9,"skipped":0,"pct":75}}
30
+ ,"/home/tsavo/platform-core/src/credits/auto-topup-settings-repository.ts": {"lines":{"total":69,"covered":57,"skipped":0,"pct":82.6},"functions":{"total":16,"covered":15,"skipped":0,"pct":93.75},"statements":{"total":71,"covered":57,"skipped":0,"pct":80.28},"branches":{"total":31,"covered":20,"skipped":0,"pct":64.51}}
31
+ ,"/home/tsavo/platform-core/src/credits/auto-topup-usage.ts": {"lines":{"total":22,"covered":21,"skipped":0,"pct":95.45},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":26,"covered":25,"skipped":0,"pct":96.15},"branches":{"total":20,"covered":17,"skipped":0,"pct":85}}
32
+ ,"/home/tsavo/platform-core/src/credits/credit-expiry-cron.ts": {"lines":{"total":21,"covered":14,"skipped":0,"pct":66.66},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":21,"covered":14,"skipped":0,"pct":66.66},"branches":{"total":12,"covered":6,"skipped":0,"pct":50}}
33
+ ,"/home/tsavo/platform-core/src/credits/credit-ledger.ts": {"lines":{"total":69,"covered":48,"skipped":0,"pct":69.56},"functions":{"total":17,"covered":12,"skipped":0,"pct":70.58},"statements":{"total":72,"covered":49,"skipped":0,"pct":68.05},"branches":{"total":67,"covered":55,"skipped":0,"pct":82.08}}
34
+ ,"/home/tsavo/platform-core/src/credits/credit-transaction-repository.ts": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}}
35
+ ,"/home/tsavo/platform-core/src/credits/credit.ts": {"lines":{"total":39,"covered":35,"skipped":0,"pct":89.74},"functions":{"total":23,"covered":23,"skipped":0,"pct":100},"statements":{"total":39,"covered":35,"skipped":0,"pct":89.74},"branches":{"total":10,"covered":6,"skipped":0,"pct":60}}
36
+ ,"/home/tsavo/platform-core/src/credits/dividend-cron.ts": {"lines":{"total":35,"covered":30,"skipped":0,"pct":85.71},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":35,"covered":30,"skipped":0,"pct":85.71},"branches":{"total":10,"covered":7,"skipped":0,"pct":70}}
37
+ ,"/home/tsavo/platform-core/src/credits/dividend-repository.ts": {"lines":{"total":32,"covered":32,"skipped":0,"pct":100},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":34,"covered":34,"skipped":0,"pct":100},"branches":{"total":10,"covered":7,"skipped":0,"pct":70}}
38
+ ,"/home/tsavo/platform-core/src/credits/signup-grant.ts": {"lines":{"total":12,"covered":10,"skipped":0,"pct":83.33},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":15,"covered":12,"skipped":0,"pct":80},"branches":{"total":10,"covered":5,"skipped":0,"pct":50}}
39
+ ,"/home/tsavo/platform-core/src/db/credit-column.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
40
+ ,"/home/tsavo/platform-core/src/db/schema/account-deletion-requests.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
41
+ ,"/home/tsavo/platform-core/src/db/schema/account-export-requests.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
42
+ ,"/home/tsavo/platform-core/src/db/schema/admin-audit.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
43
+ ,"/home/tsavo/platform-core/src/db/schema/admin-users.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
44
+ ,"/home/tsavo/platform-core/src/db/schema/affiliate-fraud.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
45
+ ,"/home/tsavo/platform-core/src/db/schema/affiliate.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
46
+ ,"/home/tsavo/platform-core/src/db/schema/coupon-codes.ts": {"lines":{"total":3,"covered":2,"skipped":0,"pct":66.66},"functions":{"total":2,"covered":1,"skipped":0,"pct":50},"statements":{"total":3,"covered":2,"skipped":0,"pct":66.66},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
47
+ ,"/home/tsavo/platform-core/src/db/schema/credit-auto-topup-settings.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
48
+ ,"/home/tsavo/platform-core/src/db/schema/credit-auto-topup.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
49
+ ,"/home/tsavo/platform-core/src/db/schema/credits.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
50
+ ,"/home/tsavo/platform-core/src/db/schema/dividend-distributions.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
51
+ ,"/home/tsavo/platform-core/src/db/schema/email-notifications.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
52
+ ,"/home/tsavo/platform-core/src/db/schema/index.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
53
+ ,"/home/tsavo/platform-core/src/db/schema/meter-events.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
54
+ ,"/home/tsavo/platform-core/src/db/schema/notification-preferences.ts": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
55
+ ,"/home/tsavo/platform-core/src/db/schema/notification-queue.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
56
+ ,"/home/tsavo/platform-core/src/db/schema/org-memberships.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
57
+ ,"/home/tsavo/platform-core/src/db/schema/organization-members.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
58
+ ,"/home/tsavo/platform-core/src/db/schema/payram.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
59
+ ,"/home/tsavo/platform-core/src/db/schema/platform-api-keys.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
60
+ ,"/home/tsavo/platform-core/src/db/schema/promotion-redemptions.ts": {"lines":{"total":4,"covered":2,"skipped":0,"pct":50},"functions":{"total":3,"covered":1,"skipped":0,"pct":33.33},"statements":{"total":4,"covered":2,"skipped":0,"pct":50},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
61
+ ,"/home/tsavo/platform-core/src/db/schema/promotions.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
62
+ ,"/home/tsavo/platform-core/src/db/schema/provider-credentials.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
63
+ ,"/home/tsavo/platform-core/src/db/schema/rate-limit-entries.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
64
+ ,"/home/tsavo/platform-core/src/db/schema/secret-audit-log.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
65
+ ,"/home/tsavo/platform-core/src/db/schema/session-usage.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
66
+ ,"/home/tsavo/platform-core/src/db/schema/spending-limits.ts": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
67
+ ,"/home/tsavo/platform-core/src/db/schema/tenant-addons.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
68
+ ,"/home/tsavo/platform-core/src/db/schema/tenant-api-keys.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
69
+ ,"/home/tsavo/platform-core/src/db/schema/tenant-capability-settings.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
70
+ ,"/home/tsavo/platform-core/src/db/schema/tenant-customers.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
71
+ ,"/home/tsavo/platform-core/src/db/schema/tenants.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
72
+ ,"/home/tsavo/platform-core/src/db/schema/user-roles.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
73
+ ,"/home/tsavo/platform-core/src/db/schema/webhook-seen-events.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
74
+ ,"/home/tsavo/platform-core/src/email/billing-emails.ts": {"lines":{"total":51,"covered":43,"skipped":0,"pct":84.31},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":51,"covered":43,"skipped":0,"pct":84.31},"branches":{"total":22,"covered":12,"skipped":0,"pct":54.54}}
75
+ ,"/home/tsavo/platform-core/src/email/client.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":24,"covered":24,"skipped":0,"pct":100},"branches":{"total":14,"covered":14,"skipped":0,"pct":100}}
76
+ ,"/home/tsavo/platform-core/src/email/drizzle-billing-email-repository.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
77
+ ,"/home/tsavo/platform-core/src/email/notification-preferences-store.ts": {"lines":{"total":19,"covered":19,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":27,"covered":27,"skipped":0,"pct":100},"branches":{"total":18,"covered":18,"skipped":0,"pct":100}}
78
+ ,"/home/tsavo/platform-core/src/email/notification-queue-store.ts": {"lines":{"total":22,"covered":22,"skipped":0,"pct":100},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":24,"covered":24,"skipped":0,"pct":100},"branches":{"total":26,"covered":25,"skipped":0,"pct":96.15}}
79
+ ,"/home/tsavo/platform-core/src/email/notification-service.ts": {"lines":{"total":32,"covered":17,"skipped":0,"pct":53.12},"functions":{"total":30,"covered":16,"skipped":0,"pct":53.33},"statements":{"total":32,"covered":17,"skipped":0,"pct":53.12},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
80
+ ,"/home/tsavo/platform-core/src/email/notification-templates.ts": {"lines":{"total":165,"covered":132,"skipped":0,"pct":80},"functions":{"total":28,"covered":25,"skipped":0,"pct":89.28},"statements":{"total":176,"covered":140,"skipped":0,"pct":79.54},"branches":{"total":201,"covered":87,"skipped":0,"pct":43.28}}
81
+ ,"/home/tsavo/platform-core/src/email/notification-worker.ts": {"lines":{"total":34,"covered":34,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":35,"covered":34,"skipped":0,"pct":97.14},"branches":{"total":12,"covered":10,"skipped":0,"pct":83.33}}
82
+ ,"/home/tsavo/platform-core/src/email/resend-adapter.ts": {"lines":{"total":15,"covered":15,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":16,"covered":16,"skipped":0,"pct":100},"branches":{"total":13,"covered":11,"skipped":0,"pct":84.61}}
83
+ ,"/home/tsavo/platform-core/src/email/templates.ts": {"lines":{"total":83,"covered":77,"skipped":0,"pct":92.77},"functions":{"total":17,"covered":16,"skipped":0,"pct":94.11},"statements":{"total":90,"covered":84,"skipped":0,"pct":93.33},"branches":{"total":59,"covered":57,"skipped":0,"pct":96.61}}
84
+ ,"/home/tsavo/platform-core/src/email/verification.ts": {"lines":{"total":23,"covered":21,"skipped":0,"pct":91.3},"functions":{"total":7,"covered":5,"skipped":0,"pct":71.42},"statements":{"total":27,"covered":25,"skipped":0,"pct":92.59},"branches":{"total":12,"covered":12,"skipped":0,"pct":100}}
85
+ ,"/home/tsavo/platform-core/src/metering/aggregator.ts": {"lines":{"total":36,"covered":36,"skipped":0,"pct":100},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":37,"covered":36,"skipped":0,"pct":97.29},"branches":{"total":21,"covered":19,"skipped":0,"pct":90.47}}
86
+ ,"/home/tsavo/platform-core/src/metering/dlq.ts": {"lines":{"total":30,"covered":28,"skipped":0,"pct":93.33},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":33,"covered":31,"skipped":0,"pct":93.93},"branches":{"total":16,"covered":14,"skipped":0,"pct":87.5}}
87
+ ,"/home/tsavo/platform-core/src/metering/drizzle-usage-summary-repository.ts": {"lines":{"total":22,"covered":22,"skipped":0,"pct":100},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":19,"covered":16,"skipped":0,"pct":84.21}}
88
+ ,"/home/tsavo/platform-core/src/metering/emitter.ts": {"lines":{"total":71,"covered":70,"skipped":0,"pct":98.59},"functions":{"total":15,"covered":14,"skipped":0,"pct":93.33},"statements":{"total":79,"covered":77,"skipped":0,"pct":97.46},"branches":{"total":58,"covered":53,"skipped":0,"pct":91.37}}
89
+ ,"/home/tsavo/platform-core/src/metering/meter-event-repository.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
90
+ ,"/home/tsavo/platform-core/src/metering/reconciliation-cron.ts": {"lines":{"total":37,"covered":36,"skipped":0,"pct":97.29},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":37,"covered":36,"skipped":0,"pct":97.29},"branches":{"total":22,"covered":18,"skipped":0,"pct":81.81}}
91
+ ,"/home/tsavo/platform-core/src/metering/reconciliation-repository.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
92
+ ,"/home/tsavo/platform-core/src/metering/wal.ts": {"lines":{"total":45,"covered":43,"skipped":0,"pct":95.55},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":47,"covered":45,"skipped":0,"pct":95.74},"branches":{"total":18,"covered":15,"skipped":0,"pct":83.33}}
93
+ ,"/home/tsavo/platform-core/src/middleware/csrf.ts": {"lines":{"total":32,"covered":31,"skipped":0,"pct":96.87},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":34,"covered":33,"skipped":0,"pct":97.05},"branches":{"total":22,"covered":20,"skipped":0,"pct":90.9}}
94
+ ,"/home/tsavo/platform-core/src/middleware/drizzle-rate-limit-repository.ts": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":17,"covered":17,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}}
95
+ ,"/home/tsavo/platform-core/src/middleware/get-client-ip.ts": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":18,"covered":16,"skipped":0,"pct":88.88}}
96
+ ,"/home/tsavo/platform-core/src/middleware/rate-limit.ts": {"lines":{"total":54,"covered":54,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":55,"covered":54,"skipped":0,"pct":98.18},"branches":{"total":28,"covered":25,"skipped":0,"pct":89.28}}
97
+ ,"/home/tsavo/platform-core/src/security/encryption.ts": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":21,"covered":21,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
98
+ ,"/home/tsavo/platform-core/src/security/host-validation.ts": {"lines":{"total":46,"covered":45,"skipped":0,"pct":97.82},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":60,"covered":58,"skipped":0,"pct":96.66},"branches":{"total":67,"covered":63,"skipped":0,"pct":94.02}}
99
+ ,"/home/tsavo/platform-core/src/security/key-audit.ts": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
100
+ ,"/home/tsavo/platform-core/src/security/key-injection.ts": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":3,"covered":2,"skipped":0,"pct":66.66},"statements":{"total":17,"covered":16,"skipped":0,"pct":94.11},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
101
+ ,"/home/tsavo/platform-core/src/security/key-validation.ts": {"lines":{"total":19,"covered":16,"skipped":0,"pct":84.21},"functions":{"total":7,"covered":5,"skipped":0,"pct":71.42},"statements":{"total":19,"covered":16,"skipped":0,"pct":84.21},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}}
102
+ ,"/home/tsavo/platform-core/src/security/redirect-allowlist.ts": {"lines":{"total":14,"covered":13,"skipped":0,"pct":92.85},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":14,"covered":13,"skipped":0,"pct":92.85},"branches":{"total":12,"covered":10,"skipped":0,"pct":83.33}}
103
+ ,"/home/tsavo/platform-core/src/security/credential-vault/audit-repository.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}}
104
+ ,"/home/tsavo/platform-core/src/security/credential-vault/credential-repository.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":16,"covered":16,"skipped":0,"pct":100},"statements":{"total":24,"covered":24,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}}
105
+ ,"/home/tsavo/platform-core/src/security/credential-vault/key-rotation.ts": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":25,"covered":24,"skipped":0,"pct":96},"branches":{"total":4,"covered":1,"skipped":0,"pct":25}}
106
+ ,"/home/tsavo/platform-core/src/security/credential-vault/migrate-plaintext.ts": {"lines":{"total":36,"covered":35,"skipped":0,"pct":97.22},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":37,"covered":35,"skipped":0,"pct":94.59},"branches":{"total":22,"covered":18,"skipped":0,"pct":81.81}}
107
+ ,"/home/tsavo/platform-core/src/security/credential-vault/migration-check.ts": {"lines":{"total":22,"covered":20,"skipped":0,"pct":90.9},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":23,"covered":20,"skipped":0,"pct":86.95},"branches":{"total":26,"covered":13,"skipped":0,"pct":50}}
108
+ ,"/home/tsavo/platform-core/src/security/credential-vault/store.ts": {"lines":{"total":55,"covered":54,"skipped":0,"pct":98.18},"functions":{"total":16,"covered":15,"skipped":0,"pct":93.75},"statements":{"total":63,"covered":62,"skipped":0,"pct":98.41},"branches":{"total":26,"covered":23,"skipped":0,"pct":88.46}}
109
+ ,"/home/tsavo/platform-core/src/security/tenant-keys/capability-settings-store.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
110
+ ,"/home/tsavo/platform-core/src/security/tenant-keys/key-resolution-repository.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
111
+ ,"/home/tsavo/platform-core/src/security/tenant-keys/key-resolution.ts": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":16,"covered":16,"skipped":0,"pct":100},"branches":{"total":7,"covered":7,"skipped":0,"pct":100}}
112
+ ,"/home/tsavo/platform-core/src/security/tenant-keys/org-key-resolution.ts": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":16,"covered":16,"skipped":0,"pct":100},"branches":{"total":10,"covered":10,"skipped":0,"pct":100}}
113
+ ,"/home/tsavo/platform-core/src/security/tenant-keys/tenant-key-repository.ts": {"lines":{"total":20,"covered":20,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":5,"covered":5,"skipped":0,"pct":100}}
114
+ ,"/home/tsavo/platform-core/src/tenancy/drizzle-org-repository.ts": {"lines":{"total":38,"covered":37,"skipped":0,"pct":97.36},"functions":{"total":13,"covered":13,"skipped":0,"pct":100},"statements":{"total":45,"covered":42,"skipped":0,"pct":93.33},"branches":{"total":34,"covered":25,"skipped":0,"pct":73.52}}
115
+ ,"/home/tsavo/platform-core/src/tenancy/org-member-repository.ts": {"lines":{"total":23,"covered":19,"skipped":0,"pct":82.6},"functions":{"total":16,"covered":13,"skipped":0,"pct":81.25},"statements":{"total":23,"covered":19,"skipped":0,"pct":82.6},"branches":{"total":6,"covered":4,"skipped":0,"pct":66.66}}
116
+ ,"/home/tsavo/platform-core/src/tenancy/org-service.ts": {"lines":{"total":91,"covered":87,"skipped":0,"pct":95.6},"functions":{"total":19,"covered":18,"skipped":0,"pct":94.73},"statements":{"total":92,"covered":88,"skipped":0,"pct":95.65},"branches":{"total":47,"covered":42,"skipped":0,"pct":89.36}}
117
+ ,"/home/tsavo/platform-core/src/test/db.ts": {"lines":{"total":23,"covered":22,"skipped":0,"pct":95.65},"functions":{"total":9,"covered":8,"skipped":0,"pct":88.88},"statements":{"total":25,"covered":24,"skipped":0,"pct":96},"branches":{"total":20,"covered":13,"skipped":0,"pct":65}}
118
+ ,"/home/tsavo/platform-core/src/trpc/init.ts": {"lines":{"total":45,"covered":41,"skipped":0,"pct":91.11},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":45,"covered":41,"skipped":0,"pct":91.11},"branches":{"total":24,"covered":20,"skipped":0,"pct":83.33}}
119
+ }
@@ -1,6 +1,6 @@
1
1
  export type { IAdminAuditLogRepository } from "./admin-audit-log-repository.js";
2
2
  export { DrizzleAdminAuditLogRepository } from "./admin-audit-log-repository.js";
3
- export type { AuditCategory, AuditEntry, AdminAuditLogRow, AuditFilters } from "./audit-log.js";
3
+ export type { AdminAuditLogRow, AuditCategory, AuditEntry, AuditFilters } from "./audit-log.js";
4
4
  export { AdminAuditLog } from "./audit-log.js";
5
5
  export type { Role, UserRoleRow } from "./role-store.js";
6
6
  export { isValidRole, RoleStore } from "./role-store.js";
@@ -11,10 +11,55 @@ import { betterAuth } from "better-auth";
11
11
  import type { Pool } from "pg";
12
12
  import type { PlatformDb } from "../db/index.js";
13
13
  import { PgEmailVerifier } from "../email/verification.js";
14
+ /** OAuth provider credentials. */
15
+ export interface OAuthProvider {
16
+ clientId: string;
17
+ clientSecret: string;
18
+ }
19
+ /** Rate limit rule for a specific auth endpoint. */
20
+ export interface AuthRateLimitRule {
21
+ window: number;
22
+ max: number;
23
+ }
14
24
  /** Configuration for initializing Better Auth in platform-core. */
15
25
  export interface BetterAuthConfig {
16
26
  pool: Pool;
17
27
  db: PlatformDb;
28
+ /** HMAC secret for session tokens. Falls back to BETTER_AUTH_SECRET env var. */
29
+ secret?: string;
30
+ /** Base URL for OAuth callbacks. Falls back to BETTER_AUTH_URL env var. */
31
+ baseURL?: string;
32
+ /** Route prefix. Default: "/api/auth" */
33
+ basePath?: string;
34
+ /** Email+password config. Default: enabled with 12-char min. */
35
+ emailAndPassword?: {
36
+ enabled: boolean;
37
+ minPasswordLength?: number;
38
+ };
39
+ /** OAuth providers. Default: reads GITHUB/DISCORD/GOOGLE env vars. */
40
+ socialProviders?: {
41
+ github?: OAuthProvider;
42
+ discord?: OAuthProvider;
43
+ google?: OAuthProvider;
44
+ };
45
+ /** Trusted providers for account linking. Default: ["github", "google"] */
46
+ trustedProviders?: string[];
47
+ /** Enable 2FA plugin. Default: true */
48
+ twoFactor?: boolean;
49
+ /** Cookie cache max age in seconds. Default: 300 (5 min) */
50
+ sessionCacheMaxAge?: number;
51
+ /** Cookie prefix. Default: "better-auth" */
52
+ cookiePrefix?: string;
53
+ /** Cookie domain (e.g., ".wopr.bot"). Falls back to COOKIE_DOMAIN env var. */
54
+ cookieDomain?: string;
55
+ /** Global rate limit window in seconds. Default: 60 */
56
+ rateLimitWindow?: number;
57
+ /** Global rate limit max requests. Default: 100 */
58
+ rateLimitMax?: number;
59
+ /** Per-endpoint rate limit overrides. Default: sign-in/sign-up/reset limits. */
60
+ rateLimitRules?: Record<string, AuthRateLimitRule>;
61
+ /** Trusted origins for CORS. Falls back to UI_ORIGIN env var. */
62
+ trustedOrigins?: string[];
18
63
  /** Called after a new user signs up (e.g., create personal tenant). */
19
64
  onUserCreated?: (userId: string, userName: string, email: string) => Promise<void>;
20
65
  }
@@ -16,8 +16,11 @@ import { getEmailClient } from "../email/client.js";
16
16
  import { passwordResetEmailTemplate, verifyEmailTemplate } from "../email/templates.js";
17
17
  import { generateVerificationToken, initVerificationSchema, PgEmailVerifier } from "../email/verification.js";
18
18
  import { createUserCreator } from "./user-creator.js";
19
- const BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET || "";
20
- const BETTER_AUTH_URL = process.env.BETTER_AUTH_URL || "http://localhost:3100";
19
+ const DEFAULT_RATE_LIMIT_RULES = {
20
+ "/sign-in/email": { window: 900, max: 5 },
21
+ "/sign-up/email": { window: 3600, max: 10 },
22
+ "/request-password-reset": { window: 3600, max: 3 },
23
+ };
21
24
  let _config = null;
22
25
  let _userCreator = null;
23
26
  let _userCreatorPromise = null;
@@ -34,32 +37,57 @@ async function getUserCreator() {
34
37
  }
35
38
  return _userCreatorPromise;
36
39
  }
37
- function authOptions(pool) {
40
+ /** Resolve OAuth providers from config or env vars. */
41
+ function resolveSocialProviders(cfg) {
42
+ if (cfg.socialProviders)
43
+ return cfg.socialProviders;
44
+ return {
45
+ ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
46
+ ? { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }
47
+ : {}),
48
+ ...(process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
49
+ ? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
50
+ : {}),
51
+ ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
52
+ ? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
53
+ : {}),
54
+ };
55
+ }
56
+ function authOptions(cfg) {
57
+ const pool = cfg.pool;
58
+ const secret = cfg.secret || process.env.BETTER_AUTH_SECRET;
59
+ if (!secret) {
60
+ if (process.env.NODE_ENV === "production") {
61
+ throw new Error("BETTER_AUTH_SECRET is required in production");
62
+ }
63
+ logger.warn("BetterAuth secret not configured — sessions may be insecure");
64
+ }
65
+ const baseURL = cfg.baseURL || process.env.BETTER_AUTH_URL || "http://localhost:3100";
66
+ const basePath = cfg.basePath || "/api/auth";
67
+ const cookieDomain = cfg.cookieDomain || process.env.COOKIE_DOMAIN;
68
+ const trustedOrigins = cfg.trustedOrigins ||
69
+ (process.env.UI_ORIGIN || "http://localhost:3001")
70
+ .split(",")
71
+ .map((o) => o.trim())
72
+ .filter(Boolean);
73
+ // Default minPasswordLength: 12 — caller must explicitly override, not accidentally omit
74
+ const emailAndPassword = cfg.emailAndPassword
75
+ ? { minPasswordLength: 12, ...cfg.emailAndPassword }
76
+ : { enabled: true, minPasswordLength: 12 };
38
77
  return {
39
78
  database: pool,
40
- secret: BETTER_AUTH_SECRET,
41
- baseURL: BETTER_AUTH_URL,
42
- basePath: "/api/auth",
43
- socialProviders: {
44
- ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
45
- ? { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }
46
- : {}),
47
- ...(process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
48
- ? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
49
- : {}),
50
- ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
51
- ? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
52
- : {}),
53
- },
79
+ secret: secret || "",
80
+ baseURL,
81
+ basePath,
82
+ socialProviders: resolveSocialProviders(cfg),
54
83
  account: {
55
84
  accountLinking: {
56
85
  enabled: true,
57
- trustedProviders: ["github", "google"],
86
+ trustedProviders: cfg.trustedProviders ?? ["github", "google"],
58
87
  },
59
88
  },
60
89
  emailAndPassword: {
61
- enabled: true,
62
- minPasswordLength: 12,
90
+ ...emailAndPassword,
63
91
  sendResetPassword: async ({ user, url }) => {
64
92
  try {
65
93
  const emailClient = getEmailClient();
@@ -87,12 +115,20 @@ function authOptions(pool) {
87
115
  catch (error) {
88
116
  logger.error("Failed to run user creator:", error);
89
117
  }
118
+ if (cfg.onUserCreated) {
119
+ try {
120
+ await cfg.onUserCreated(user.id, user.name || user.email, user.email);
121
+ }
122
+ catch (error) {
123
+ logger.error("Failed to run onUserCreated callback:", error);
124
+ }
125
+ }
90
126
  if (user.emailVerified)
91
127
  return;
92
128
  try {
93
129
  await initVerificationSchema(pool);
94
130
  const { token } = await generateVerificationToken(pool, user.id);
95
- const verifyUrl = `${BETTER_AUTH_URL}/auth/verify?token=${token}`;
131
+ const verifyUrl = `${baseURL}${basePath}/verify?token=${token}`;
96
132
  const emailClient = getEmailClient();
97
133
  const template = verifyEmailTemplate(verifyUrl, user.email);
98
134
  await emailClient.send({
@@ -105,45 +141,30 @@ function authOptions(pool) {
105
141
  catch (error) {
106
142
  logger.error("Failed to send verification email:", error);
107
143
  }
108
- // Delegate personal tenant creation to the consumer
109
- if (_config?.onUserCreated) {
110
- try {
111
- await _config.onUserCreated(user.id, user.name || user.email, user.email);
112
- }
113
- catch (error) {
114
- logger.error("Failed to run onUserCreated callback:", error);
115
- }
116
- }
117
144
  },
118
145
  },
119
146
  },
120
147
  },
121
148
  session: {
122
- cookieCache: { enabled: true, maxAge: 5 * 60 },
149
+ cookieCache: { enabled: true, maxAge: cfg.sessionCacheMaxAge ?? 300 },
123
150
  },
124
151
  advanced: {
125
- cookiePrefix: "better-auth",
152
+ cookiePrefix: cfg.cookiePrefix || "better-auth",
126
153
  cookies: {
127
154
  session_token: {
128
- attributes: {
129
- domain: process.env.COOKIE_DOMAIN || ".wopr.bot",
130
- },
155
+ attributes: cookieDomain ? { domain: cookieDomain } : {},
131
156
  },
132
157
  },
133
158
  },
134
- plugins: [twoFactor()],
159
+ plugins: cfg.twoFactor !== false ? [twoFactor()] : [],
135
160
  rateLimit: {
136
161
  enabled: true,
137
- window: 60,
138
- max: 100,
139
- customRules: {
140
- "/sign-in/email": { window: 900, max: 5 },
141
- "/sign-up/email": { window: 3600, max: 10 },
142
- "/request-password-reset": { window: 3600, max: 3 },
143
- },
162
+ window: cfg.rateLimitWindow ?? 60,
163
+ max: cfg.rateLimitMax ?? 100,
164
+ customRules: { ...DEFAULT_RATE_LIMIT_RULES, ...cfg.rateLimitRules },
144
165
  storage: "memory",
145
166
  },
146
- trustedOrigins: (process.env.UI_ORIGIN || "http://localhost:3001").split(","),
167
+ trustedOrigins,
147
168
  };
148
169
  }
149
170
  /** Initialize Better Auth with the given config. Must be called before getAuth(). */
@@ -158,9 +179,11 @@ export async function runAuthMigrations() {
158
179
  if (!_config)
159
180
  throw new Error("BetterAuth not initialized — call initBetterAuth() first");
160
181
  const { getMigrations } = (await import("better-auth/db"));
161
- const { runMigrations } = await getMigrations(authOptions(_config.pool));
182
+ const { runMigrations } = await getMigrations(authOptions(_config));
162
183
  await runMigrations();
163
- await initTwoFactorSchema(_config.pool);
184
+ if (_config.twoFactor !== false) {
185
+ await initTwoFactorSchema(_config.pool);
186
+ }
164
187
  }
165
188
  let _auth = null;
166
189
  /**
@@ -171,7 +194,7 @@ export function getAuth() {
171
194
  if (!_auth) {
172
195
  if (!_config)
173
196
  throw new Error("BetterAuth not initialized — call initBetterAuth() first");
174
- _auth = betterAuth(authOptions(_config.pool));
197
+ _auth = betterAuth(authOptions(_config));
175
198
  }
176
199
  return _auth;
177
200
  }
@@ -184,3 +184,15 @@ export declare function validateTenantOwnership<T>(c: Context, resource: T | nul
184
184
  * @returns true if the user has access, false otherwise.
185
185
  */
186
186
  export declare function validateTenantAccess(userId: string, requestedTenantId: string | undefined, orgMemberRepo: IOrgMemberRepository): Promise<boolean>;
187
+ export type { IApiKeyRepository } from "./api-key-repository.js";
188
+ export { DrizzleApiKeyRepository } from "./api-key-repository.js";
189
+ export type { Auth, AuthRateLimitRule, BetterAuthConfig, OAuthProvider, } from "./better-auth.js";
190
+ export { getAuth, getEmailVerifier, initBetterAuth, resetAuth, resetUserCreator, runAuthMigrations, setAuth, } from "./better-auth.js";
191
+ export type { ILoginHistoryRepository, LoginHistoryEntry } from "./login-history-repository.js";
192
+ export { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
193
+ export type { SessionAuthEnv } from "./middleware.js";
194
+ export { dualAuth, sessionAuth } from "./middleware.js";
195
+ export type { IUserCreator } from "./user-creator.js";
196
+ export { createUserCreator } from "./user-creator.js";
197
+ export type { IUserRoleRepository } from "./user-role-repository.js";
198
+ export { DrizzleUserRoleRepository } from "./user-role-repository.js";
@@ -420,3 +420,10 @@ export async function validateTenantAccess(userId, requestedTenantId, orgMemberR
420
420
  const member = await orgMemberRepo.findMember(requestedTenantId, userId);
421
421
  return member !== null;
422
422
  }
423
+ export { DrizzleApiKeyRepository } from "./api-key-repository.js";
424
+ // Test utilities — do not call in production code
425
+ export { getAuth, getEmailVerifier, initBetterAuth, resetAuth, resetUserCreator, runAuthMigrations, setAuth, } from "./better-auth.js";
426
+ export { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
427
+ export { dualAuth, sessionAuth } from "./middleware.js";
428
+ export { createUserCreator } from "./user-creator.js";
429
+ export { DrizzleUserRoleRepository } from "./user-role-repository.js";
@@ -1,5 +1,5 @@
1
- import type { PlatformDb } from "../db/index.js";
2
1
  import type { WebhookSeenEvent } from "../credits/repository-types.js";
2
+ import type { PlatformDb } from "../db/index.js";
3
3
  import type { IWebhookSeenRepository } from "./webhook-seen-repository.js";
4
4
  export declare class DrizzleWebhookSeenRepository implements IWebhookSeenRepository {
5
5
  private readonly db;
@@ -1,7 +1,7 @@
1
- export type { SavedPaymentMethod, CheckoutOpts, CheckoutSession, ChargeOpts, ChargeResult, SetupResult, PortalOpts, WebhookResult, IPaymentProcessor, Invoice, } from "./payment-processor.js";
1
+ export { DrizzleWebhookSeenRepository } from "./drizzle-webhook-seen-repository.js";
2
+ export type { ChargeOpts, ChargeResult, CheckoutOpts, CheckoutSession, Invoice, IPaymentProcessor, PortalOpts, SavedPaymentMethod, SetupResult, WebhookResult, } from "./payment-processor.js";
2
3
  export { PaymentMethodOwnershipError } from "./payment-processor.js";
4
+ export * from "./payram/index.js";
5
+ export * from "./stripe/index.js";
3
6
  export type { IWebhookSeenRepository } from "./webhook-seen-repository.js";
4
7
  export { noOpReplayGuard } from "./webhook-seen-repository.js";
5
- export { DrizzleWebhookSeenRepository } from "./drizzle-webhook-seen-repository.js";
6
- export * from "./stripe/index.js";
7
- export * from "./payram/index.js";
@@ -1,7 +1,7 @@
1
- export { PaymentMethodOwnershipError } from "./payment-processor.js";
2
- export { noOpReplayGuard } from "./webhook-seen-repository.js";
3
1
  export { DrizzleWebhookSeenRepository } from "./drizzle-webhook-seen-repository.js";
4
- // Stripe
5
- export * from "./stripe/index.js";
2
+ export { PaymentMethodOwnershipError } from "./payment-processor.js";
6
3
  // PayRam
7
4
  export * from "./payram/index.js";
5
+ // Stripe
6
+ export * from "./stripe/index.js";
7
+ export { noOpReplayGuard } from "./webhook-seen-repository.js";
@@ -5,8 +5,8 @@
5
5
  * no-op status, idempotency, replay guard, and bot reactivation.
6
6
  */
7
7
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
8
- import { createTestDb, truncateAllTables } from "../../test/db.js";
9
8
  import { CreditLedger } from "../../credits/credit-ledger.js";
9
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
10
10
  import { DrizzleWebhookSeenRepository } from "../drizzle-webhook-seen-repository.js";
11
11
  import { noOpReplayGuard } from "../webhook-seen-repository.js";
12
12
  import { PayRamChargeRepository } from "./charge-store.js";
@@ -1,14 +1,14 @@
1
1
  export { createCreditCheckoutSession, createVpsCheckoutSession } from "./checkout.js";
2
2
  export { createStripeClient, loadStripeConfig } from "./client.js";
3
- export type { CreditPricePoint, CreditPriceMap } from "./credit-prices.js";
4
- export { CREDIT_PRICE_POINTS, loadCreditPriceMap, getCreditAmountForPurchase, lookupCreditPrice, getConfiguredPriceIds } from "./credit-prices.js";
5
- export { detachPaymentMethod, detachAllPaymentMethods } from "./payment-methods.js";
3
+ export type { CreditPriceMap, CreditPricePoint } from "./credit-prices.js";
4
+ export { CREDIT_PRICE_POINTS, getConfiguredPriceIds, getCreditAmountForPurchase, loadCreditPriceMap, lookupCreditPrice, } from "./credit-prices.js";
6
5
  export type { DetachPaymentMethodOpts } from "./payment-methods.js";
6
+ export { detachAllPaymentMethods, detachPaymentMethod } from "./payment-methods.js";
7
7
  export { createPortalSession } from "./portal.js";
8
8
  export type { SetupIntentOpts } from "./setup-intent.js";
9
9
  export { createSetupIntent } from "./setup-intent.js";
10
- export type { StripeWebhookHandlerResult, StripePaymentProcessorDeps } from "./stripe-payment-processor.js";
10
+ export type { StripePaymentProcessorDeps, StripeWebhookHandlerResult } from "./stripe-payment-processor.js";
11
11
  export { StripePaymentProcessor } from "./stripe-payment-processor.js";
12
12
  export type { ITenantCustomerRepository } from "./tenant-store.js";
13
13
  export { DrizzleTenantCustomerRepository, TenantCustomerRepository } from "./tenant-store.js";
14
- export type { TenantCustomerRow, CreditCheckoutOpts, PortalSessionOpts, VpsCheckoutOpts, StripeBillingConfig } from "./types.js";
14
+ export type { CreditCheckoutOpts, PortalSessionOpts, StripeBillingConfig, TenantCustomerRow, VpsCheckoutOpts, } from "./types.js";
@@ -1,7 +1,7 @@
1
1
  export { createCreditCheckoutSession, createVpsCheckoutSession } from "./checkout.js";
2
2
  export { createStripeClient, loadStripeConfig } from "./client.js";
3
- export { CREDIT_PRICE_POINTS, loadCreditPriceMap, getCreditAmountForPurchase, lookupCreditPrice, getConfiguredPriceIds } from "./credit-prices.js";
4
- export { detachPaymentMethod, detachAllPaymentMethods } from "./payment-methods.js";
3
+ export { CREDIT_PRICE_POINTS, getConfiguredPriceIds, getCreditAmountForPurchase, loadCreditPriceMap, lookupCreditPrice, } from "./credit-prices.js";
4
+ export { detachAllPaymentMethods, detachPaymentMethod } from "./payment-methods.js";
5
5
  export { createPortalSession } from "./portal.js";
6
6
  export { createSetupIntent } from "./setup-intent.js";
7
7
  export { StripePaymentProcessor } from "./stripe-payment-processor.js";
@@ -1,5 +1,5 @@
1
- import { Credit } from "../../credits/credit.js";
2
1
  import { chargeAutoTopup } from "../../credits/auto-topup-charge.js";
2
+ import { Credit } from "../../credits/credit.js";
3
3
  import { PaymentMethodOwnershipError, } from "../payment-processor.js";
4
4
  import { createCreditCheckoutSession } from "./checkout.js";
5
5
  import { createPortalSession } from "./portal.js";
@@ -1,8 +1,8 @@
1
1
  import Stripe from "stripe";
2
- import type { Credit } from "./credit.js";
3
- import type { ITenantCustomerRepository } from "./tenant-customer-repository.js";
4
2
  import type { IAutoTopupEventLogRepository } from "./auto-topup-event-log-repository.js";
3
+ import type { Credit } from "./credit.js";
5
4
  import type { ICreditLedger } from "./credit-ledger.js";
5
+ import type { ITenantCustomerRepository } from "./tenant-customer-repository.js";
6
6
  /** After this many consecutive Stripe failures, the auto-topup mode is disabled. */
7
7
  export declare const MAX_CONSECUTIVE_FAILURES = 3;
8
8
  export interface AutoTopupChargeDeps {