@onesub/server 0.9.1 → 0.11.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.
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Read-only metrics endpoints — admin-gated aggregations over Subscription +
3
+ * PurchaseStore data.
4
+ */
5
+ import { describe, it, expect } from 'vitest';
6
+ import express from 'express';
7
+ import { createOneSubMiddleware, InMemorySubscriptionStore, InMemoryPurchaseStore } from '../index.js';
8
+ // ── helpers ─────────────────────────────────────────────────────────────────
9
+ function sub(overrides) {
10
+ return {
11
+ userId: 'u',
12
+ productId: 'pro_monthly',
13
+ platform: 'apple',
14
+ status: 'active',
15
+ expiresAt: '2099-01-01T00:00:00.000Z',
16
+ purchasedAt: '2026-04-01T00:00:00.000Z',
17
+ originalTransactionId: `orig_${Math.random()}`,
18
+ willRenew: true,
19
+ ...overrides,
20
+ };
21
+ }
22
+ function purchase(overrides) {
23
+ return {
24
+ userId: 'u',
25
+ productId: 'lifetime_pass',
26
+ platform: 'apple',
27
+ type: 'non_consumable',
28
+ transactionId: `tx_${Math.random()}`,
29
+ purchasedAt: '2026-04-01T00:00:00.000Z',
30
+ quantity: 1,
31
+ ...overrides,
32
+ };
33
+ }
34
+ function spinUp(handler) {
35
+ return {
36
+ async get(path, headers) {
37
+ const httpServer = handler.listen(0);
38
+ const port = httpServer.address().port;
39
+ try {
40
+ const resp = await fetch(`http://127.0.0.1:${port}${path}`, { headers });
41
+ const text = await resp.text();
42
+ let parsed = text;
43
+ try {
44
+ parsed = JSON.parse(text);
45
+ }
46
+ catch { /* keep as text */ }
47
+ return { status: resp.status, body: parsed };
48
+ }
49
+ finally {
50
+ await new Promise((r) => httpServer.close(() => r()));
51
+ }
52
+ },
53
+ };
54
+ }
55
+ function buildServer(opts) {
56
+ const store = new InMemorySubscriptionStore();
57
+ const purchaseStore = new InMemoryPurchaseStore();
58
+ const config = {
59
+ apple: { bundleId: 'com.example.app', skipJwsVerification: true },
60
+ database: { url: '' },
61
+ adminSecret: opts.adminSecret,
62
+ };
63
+ const app = express();
64
+ app.use(createOneSubMiddleware({ ...config, store, purchaseStore }));
65
+ return { store, purchaseStore, server: spinUp(app) };
66
+ }
67
+ const SECRET = 's3cr3t';
68
+ const AUTH = { 'x-admin-secret': SECRET };
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Auth gate
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+ describe('Metrics auth', () => {
73
+ it('returns 404 (router not mounted) when adminSecret is unset', async () => {
74
+ const { server } = buildServer({});
75
+ const resp = await server.get('/onesub/metrics/active');
76
+ expect(resp.status).toBe(404);
77
+ });
78
+ it('returns 401 INVALID_ADMIN_SECRET when header is missing or wrong', async () => {
79
+ const { server } = buildServer({ adminSecret: SECRET });
80
+ const noHeader = await server.get('/onesub/metrics/active');
81
+ expect(noHeader.status).toBe(401);
82
+ expect(noHeader.body.errorCode).toBe('INVALID_ADMIN_SECRET');
83
+ const wrongHeader = await server.get('/onesub/metrics/active', { 'x-admin-secret': 'wrong' });
84
+ expect(wrongHeader.status).toBe(401);
85
+ });
86
+ });
87
+ // ─────────────────────────────────────────────────────────────────────────────
88
+ // /onesub/metrics/active
89
+ // ─────────────────────────────────────────────────────────────────────────────
90
+ describe('GET /onesub/metrics/active', () => {
91
+ it('returns zeros for an empty store', async () => {
92
+ const { server } = buildServer({ adminSecret: SECRET });
93
+ const resp = await server.get('/onesub/metrics/active', AUTH);
94
+ expect(resp.status).toBe(200);
95
+ expect(resp.body).toEqual({
96
+ total: 0,
97
+ activeSubscriptions: 0,
98
+ gracePeriodSubscriptions: 0,
99
+ nonConsumablePurchases: 0,
100
+ byProduct: {},
101
+ byPlatform: {},
102
+ });
103
+ });
104
+ it('counts active subs + non-consumable purchases', async () => {
105
+ const { store, purchaseStore, server } = buildServer({ adminSecret: SECRET });
106
+ await store.save(sub({ userId: 'a', productId: 'pro_monthly', platform: 'apple' }));
107
+ await store.save(sub({ userId: 'b', productId: 'pro_yearly', platform: 'google' }));
108
+ await purchaseStore.savePurchase(purchase({ userId: 'c', platform: 'apple' }));
109
+ const resp = await server.get('/onesub/metrics/active', AUTH);
110
+ expect(resp.body.total).toBe(3);
111
+ expect(resp.body.activeSubscriptions).toBe(2);
112
+ expect(resp.body.nonConsumablePurchases).toBe(1);
113
+ expect(resp.body.byProduct).toEqual({ pro_monthly: 1, pro_yearly: 1 }); // purchases not in byProduct
114
+ expect(resp.body.byPlatform).toEqual({ apple: 2, google: 1 });
115
+ });
116
+ it('grace_period subs counted in activeSubscriptions AND gracePeriodSubscriptions', async () => {
117
+ const { store, server } = buildServer({ adminSecret: SECRET });
118
+ await store.save(sub({ userId: 'a', status: 'active' }));
119
+ await store.save(sub({ userId: 'b', status: 'grace_period' }));
120
+ const resp = await server.get('/onesub/metrics/active', AUTH);
121
+ expect(resp.body.activeSubscriptions).toBe(2);
122
+ expect(resp.body.gracePeriodSubscriptions).toBe(1);
123
+ });
124
+ it('on_hold / paused / canceled / expired subs NOT counted', async () => {
125
+ const { store, server } = buildServer({ adminSecret: SECRET });
126
+ for (const status of ['on_hold', 'paused', 'canceled', 'expired']) {
127
+ await store.save(sub({ userId: status, status }));
128
+ }
129
+ await store.save(sub({ userId: 'real_active', status: 'active' }));
130
+ const resp = await server.get('/onesub/metrics/active', AUTH);
131
+ expect(resp.body.activeSubscriptions).toBe(1);
132
+ });
133
+ it('expired-by-time subs (status=active but expiresAt < now) NOT counted', async () => {
134
+ const { store, server } = buildServer({ adminSecret: SECRET });
135
+ await store.save(sub({ userId: 'past', status: 'active', expiresAt: '2024-01-01T00:00:00.000Z' }));
136
+ await store.save(sub({ userId: 'future', status: 'active', expiresAt: '2099-01-01T00:00:00.000Z' }));
137
+ const resp = await server.get('/onesub/metrics/active', AUTH);
138
+ expect(resp.body.activeSubscriptions).toBe(1);
139
+ });
140
+ it('consumable purchases NOT counted (only non_consumable contribute)', async () => {
141
+ const { purchaseStore, server } = buildServer({ adminSecret: SECRET });
142
+ await purchaseStore.savePurchase(purchase({ userId: 'a', type: 'consumable' }));
143
+ await purchaseStore.savePurchase(purchase({ userId: 'b', type: 'non_consumable' }));
144
+ const resp = await server.get('/onesub/metrics/active', AUTH);
145
+ expect(resp.body.nonConsumablePurchases).toBe(1);
146
+ expect(resp.body.total).toBe(1);
147
+ });
148
+ });
149
+ // ─────────────────────────────────────────────────────────────────────────────
150
+ // /onesub/metrics/started
151
+ // ─────────────────────────────────────────────────────────────────────────────
152
+ describe('GET /onesub/metrics/started', () => {
153
+ it('counts subs with purchasedAt within the [from, to] window', async () => {
154
+ const { store, server } = buildServer({ adminSecret: SECRET });
155
+ await store.save(sub({ userId: 'before', purchasedAt: '2026-03-15T00:00:00Z' }));
156
+ await store.save(sub({ userId: 'in1', purchasedAt: '2026-04-01T00:00:00Z' }));
157
+ await store.save(sub({ userId: 'in2', purchasedAt: '2026-04-15T00:00:00Z' }));
158
+ await store.save(sub({ userId: 'after', purchasedAt: '2026-05-15T00:00:00Z' }));
159
+ const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
160
+ expect(resp.body.total).toBe(2);
161
+ expect(resp.body.from).toBe('2026-04-01T00:00:00Z');
162
+ expect(resp.body.to).toBe('2026-04-30T23:59:59Z');
163
+ });
164
+ it('aggregates by product and platform', async () => {
165
+ const { store, server } = buildServer({ adminSecret: SECRET });
166
+ await store.save(sub({ userId: 'a', productId: 'pro_monthly', platform: 'apple', purchasedAt: '2026-04-10T00:00:00Z' }));
167
+ await store.save(sub({ userId: 'b', productId: 'pro_yearly', platform: 'google', purchasedAt: '2026-04-20T00:00:00Z' }));
168
+ await store.save(sub({ userId: 'c', productId: 'pro_monthly', platform: 'google', purchasedAt: '2026-04-25T00:00:00Z' }));
169
+ const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
170
+ expect(resp.body.byProduct).toEqual({ pro_monthly: 2, pro_yearly: 1 });
171
+ expect(resp.body.byPlatform).toEqual({ apple: 1, google: 2 });
172
+ });
173
+ it('400 INVALID_INPUT when from/to are missing or malformed', async () => {
174
+ const { server } = buildServer({ adminSecret: SECRET });
175
+ const missing = await server.get('/onesub/metrics/started', AUTH);
176
+ expect(missing.status).toBe(400);
177
+ const malformed = await server.get('/onesub/metrics/started?from=not-a-date&to=2026-04-30T23:59:59Z', AUTH);
178
+ expect(malformed.status).toBe(400);
179
+ const reversed = await server.get('/onesub/metrics/started?from=2026-05-01T00:00:00Z&to=2026-04-01T00:00:00Z', AUTH);
180
+ expect(reversed.status).toBe(400);
181
+ });
182
+ });
183
+ // ─────────────────────────────────────────────────────────────────────────────
184
+ // /onesub/metrics/expired
185
+ // ─────────────────────────────────────────────────────────────────────────────
186
+ describe('GET /onesub/metrics/expired', () => {
187
+ it('counts only subs where status ∈ {expired, canceled} AND expiresAt in window', async () => {
188
+ const { store, server } = buildServer({ adminSecret: SECRET });
189
+ // status active but expiresAt in window — NOT counted (not actually ended)
190
+ await store.save(sub({ userId: 'still_active', status: 'active', expiresAt: '2026-04-15T00:00:00Z' }));
191
+ // status expired, expiresAt in window — counted
192
+ await store.save(sub({ userId: 'expired_in', status: 'expired', expiresAt: '2026-04-10T00:00:00Z' }));
193
+ // status canceled (refund), expiresAt in window — counted
194
+ await store.save(sub({ userId: 'canceled_in', status: 'canceled', expiresAt: '2026-04-20T00:00:00Z' }));
195
+ // status expired but expiresAt before window
196
+ await store.save(sub({ userId: 'expired_before', status: 'expired', expiresAt: '2026-03-01T00:00:00Z' }));
197
+ const resp = await server.get('/onesub/metrics/expired?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
198
+ expect(resp.body.total).toBe(2); // expired_in + canceled_in
199
+ });
200
+ it('returns 0 when nothing matches', async () => {
201
+ const { store, server } = buildServer({ adminSecret: SECRET });
202
+ await store.save(sub({ status: 'active' }));
203
+ const resp = await server.get('/onesub/metrics/expired?from=2020-01-01T00:00:00Z&to=2020-12-31T23:59:59Z', AUTH);
204
+ expect(resp.body.total).toBe(0);
205
+ });
206
+ });
207
+ // ─────────────────────────────────────────────────────────────────────────────
208
+ // Store listAll regression
209
+ // ─────────────────────────────────────────────────────────────────────────────
210
+ describe('Store listAll', () => {
211
+ it('SubscriptionStore.listAll returns every record', async () => {
212
+ const store = new InMemorySubscriptionStore();
213
+ await store.save(sub({ userId: 'a', originalTransactionId: 't1' }));
214
+ await store.save(sub({ userId: 'b', originalTransactionId: 't2' }));
215
+ await store.save(sub({ userId: 'a', originalTransactionId: 't3' }));
216
+ const all = await store.listAll();
217
+ expect(all).toHaveLength(3);
218
+ });
219
+ it('PurchaseStore.listAll returns every record', async () => {
220
+ const purchaseStore = new InMemoryPurchaseStore();
221
+ await purchaseStore.savePurchase(purchase({ userId: 'a', transactionId: 'p1' }));
222
+ await purchaseStore.savePurchase(purchase({ userId: 'b', transactionId: 'p2' }));
223
+ const all = await purchaseStore.listAll();
224
+ expect(all).toHaveLength(2);
225
+ });
226
+ });
227
+ //# sourceMappingURL=metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.test.js","sourceRoot":"","sources":["../../src/__tests__/metrics.test.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -49,6 +49,7 @@ export type { SubscriptionStore, PurchaseStore } from './store.js';
49
49
  export { PostgresSubscriptionStore, PostgresPurchaseStore } from './stores/postgres.js';
50
50
  export { validateAppleReceipt, fetchAppleSubscriptionStatus } from './providers/apple.js';
51
51
  export { validateGoogleReceipt } from './providers/google.js';
52
+ export { evaluateEntitlement } from './routes/entitlements.js';
52
53
  export { setLogger, log } from './logger.js';
53
54
  export default createOneSubMiddleware;
54
55
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAQnE;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CA+B7E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAW7F;AAGD,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGxF,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAI9D,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAG7C,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUnE;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAwC7E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAW7F;AAGD,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGxF,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAI9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAG7C,eAAe,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import { createStatusRouter } from './routes/status.js';
6
6
  import { createWebhookRouter } from './routes/webhook.js';
7
7
  import { createPurchaseRouter } from './routes/purchase.js';
8
8
  import { createAdminRouter } from './routes/admin.js';
9
+ import { createEntitlementRouter } from './routes/entitlements.js';
10
+ import { createMetricsRouter } from './routes/metrics.js';
9
11
  import { setLogger } from './logger.js';
10
12
  /**
11
13
  * Create an Express Router with all OneSub routes mounted.
@@ -46,6 +48,15 @@ export function createOneSubMiddleware(config) {
46
48
  const adminRouter = createAdminRouter(config, purchaseStore);
47
49
  if (adminRouter)
48
50
  router.use(adminRouter);
51
+ // Entitlement routes — only mounted when config.entitlements is set
52
+ const entitlementRouter = createEntitlementRouter(config, store, purchaseStore);
53
+ if (entitlementRouter)
54
+ router.use(entitlementRouter);
55
+ // Metrics routes — only mounted when config.adminSecret is set (same gate
56
+ // as admin routes; metrics expose aggregate operational data)
57
+ const metricsRouter = createMetricsRouter(config, store, purchaseStore);
58
+ if (metricsRouter)
59
+ router.use(metricsRouter);
49
60
  return router;
50
61
  }
51
62
  /**
@@ -71,6 +82,9 @@ export { PostgresSubscriptionStore, PostgresPurchaseStore } from './stores/postg
71
82
  // Provider functions for direct (non-HTTP) usage
72
83
  export { validateAppleReceipt, fetchAppleSubscriptionStatus } from './providers/apple.js';
73
84
  export { validateGoogleReceipt } from './providers/google.js';
85
+ // Entitlement evaluator — exported so hosts can evaluate entitlements
86
+ // in-process (e.g. from non-HTTP background workers, custom routes).
87
+ export { evaluateEntitlement } from './routes/entitlements.js';
74
88
  // Logger plumbing — expose so non-middleware callers (direct provider use)
75
89
  // can still redirect logs.
76
90
  export { setLogger, log } from './logger.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { Router } from 'express';
2
+ import type { Entitlement, EntitlementStatus, OneSubServerConfig, PurchaseInfo, SubscriptionInfo } from '@onesub/shared';
3
+ import type { PurchaseStore, SubscriptionStore } from '../store.js';
4
+ /**
5
+ * Evaluate a single entitlement for a user.
6
+ *
7
+ * A user is entitled when EITHER condition holds for any productId in
8
+ * `entitlement.productIds`:
9
+ * 1. an active subscription (status === active|grace_period AND
10
+ * expiresAt > now) for that productId exists
11
+ * 2. a non-consumable purchase for that productId exists
12
+ *
13
+ * Consumables are intentionally excluded — they grant a one-time resource,
14
+ * not an ongoing right.
15
+ *
16
+ * Subscription is preferred over purchase when both match (subs carry an
17
+ * expiry; non-consumables are forever) so the source field skews toward the
18
+ * more "interesting" signal for ops/analytics.
19
+ */
20
+ export declare function evaluateEntitlement(userId: string, entitlement: Entitlement, store: SubscriptionStore, purchaseStore: PurchaseStore): Promise<EntitlementStatus>;
21
+ export declare function createEntitlementRouter(config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore): Router | null;
22
+ export type { Entitlement, EntitlementStatus, PurchaseInfo, SubscriptionInfo };
23
+ //# sourceMappingURL=entitlements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../src/routes/entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EAGjB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAkC5B;AAKD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,GAAG,IAAI,CA0Ef;AAID,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,130 @@
1
+ import { Router } from 'express';
2
+ import { z } from 'zod';
3
+ import { ROUTES, SUBSCRIPTION_STATUS, ONESUB_ERROR_CODE, PURCHASE_TYPE } from '@onesub/shared';
4
+ import { log } from '../logger.js';
5
+ import { sendError } from '../errors.js';
6
+ /**
7
+ * Evaluate a single entitlement for a user.
8
+ *
9
+ * A user is entitled when EITHER condition holds for any productId in
10
+ * `entitlement.productIds`:
11
+ * 1. an active subscription (status === active|grace_period AND
12
+ * expiresAt > now) for that productId exists
13
+ * 2. a non-consumable purchase for that productId exists
14
+ *
15
+ * Consumables are intentionally excluded — they grant a one-time resource,
16
+ * not an ongoing right.
17
+ *
18
+ * Subscription is preferred over purchase when both match (subs carry an
19
+ * expiry; non-consumables are forever) so the source field skews toward the
20
+ * more "interesting" signal for ops/analytics.
21
+ */
22
+ export async function evaluateEntitlement(userId, entitlement, store, purchaseStore) {
23
+ const productIdSet = new Set(entitlement.productIds);
24
+ const now = Date.now();
25
+ // 1. Check subscriptions first (richer signal — has expiry).
26
+ const subs = await store.getAllByUserId(userId);
27
+ for (const sub of subs) {
28
+ if (!productIdSet.has(sub.productId))
29
+ continue;
30
+ const statusAllows = sub.status === SUBSCRIPTION_STATUS.ACTIVE ||
31
+ sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD;
32
+ if (!statusAllows)
33
+ continue;
34
+ if (new Date(sub.expiresAt).getTime() <= now)
35
+ continue;
36
+ return {
37
+ active: true,
38
+ source: 'subscription',
39
+ productId: sub.productId,
40
+ expiresAt: sub.expiresAt,
41
+ };
42
+ }
43
+ // 2. Check non-consumable purchases.
44
+ const purchases = await purchaseStore.getPurchasesByUserId(userId);
45
+ for (const p of purchases) {
46
+ if (p.type !== PURCHASE_TYPE.NON_CONSUMABLE)
47
+ continue;
48
+ if (!productIdSet.has(p.productId))
49
+ continue;
50
+ return {
51
+ active: true,
52
+ source: 'purchase',
53
+ productId: p.productId,
54
+ };
55
+ }
56
+ return { active: false, source: null };
57
+ }
58
+ const userIdSchema = z.string().min(1).max(256);
59
+ const entitlementIdSchema = z.string().min(1).max(128);
60
+ export function createEntitlementRouter(config, store, purchaseStore) {
61
+ // Only mount when entitlements are configured. Without this guard, the
62
+ // routes would always 503 — better to make the absence loud (404 from
63
+ // express) so misconfiguration is obvious during dev.
64
+ if (!config.entitlements || Object.keys(config.entitlements).length === 0) {
65
+ return null;
66
+ }
67
+ const entitlements = config.entitlements;
68
+ const router = Router();
69
+ /**
70
+ * GET /onesub/entitlement?userId=&id=premium
71
+ * Single entitlement check.
72
+ */
73
+ router.get(ROUTES.ENTITLEMENT, async (req, res) => {
74
+ let userId;
75
+ let id;
76
+ try {
77
+ userId = userIdSchema.parse(req.query['userId']);
78
+ id = entitlementIdSchema.parse(req.query['id']);
79
+ }
80
+ catch {
81
+ sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, 'userId and id are required');
82
+ return;
83
+ }
84
+ const entitlement = entitlements[id];
85
+ if (!entitlement) {
86
+ sendError(res, 404, ONESUB_ERROR_CODE.ENTITLEMENT_NOT_FOUND, `Unknown entitlement: ${id}`);
87
+ return;
88
+ }
89
+ try {
90
+ const status = await evaluateEntitlement(userId, entitlement, store, purchaseStore);
91
+ const response = { id, ...status };
92
+ res.status(200).json(response);
93
+ }
94
+ catch (err) {
95
+ log.error('[onesub/entitlement] evaluation error:', err);
96
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error');
97
+ }
98
+ });
99
+ /**
100
+ * GET /onesub/entitlements?userId=
101
+ * Evaluate every configured entitlement in one round-trip — useful on
102
+ * app launch / login when the host wants the full entitlement map.
103
+ */
104
+ router.get(ROUTES.ENTITLEMENTS, async (req, res) => {
105
+ let userId;
106
+ try {
107
+ userId = userIdSchema.parse(req.query['userId']);
108
+ }
109
+ catch {
110
+ sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, 'userId is required');
111
+ return;
112
+ }
113
+ try {
114
+ const entries = await Promise.all(Object.entries(entitlements).map(async ([id, entitlement]) => {
115
+ const status = await evaluateEntitlement(userId, entitlement, store, purchaseStore);
116
+ return [id, status];
117
+ }));
118
+ const response = {
119
+ entitlements: Object.fromEntries(entries),
120
+ };
121
+ res.status(200).json(response);
122
+ }
123
+ catch (err) {
124
+ log.error('[onesub/entitlements] evaluation error:', err);
125
+ sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, 'Internal server error', { entitlements: {} });
126
+ }
127
+ });
128
+ return router;
129
+ }
130
+ //# sourceMappingURL=entitlements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../src/routes/entitlements.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { Router } from 'express';
2
+ import type { OneSubServerConfig, PurchaseInfo, SubscriptionInfo } from '@onesub/shared';
3
+ import type { PurchaseStore, SubscriptionStore } from '../store.js';
4
+ /**
5
+ * Read-only aggregate metrics endpoints — gated behind `config.adminSecret`.
6
+ * Returns count-based metrics only (active count, started count, expired count).
7
+ *
8
+ * Revenue metrics (MRR, ARR, LTV) require per-product price configuration the
9
+ * server doesn't currently track; deferred to a follow-up release that adds
10
+ * `config.products: { 'pro_monthly': { price: 9.99, currency: 'USD' } }`.
11
+ *
12
+ * Aggregation strategy: pulls every record via `store.listAll()` and reduces
13
+ * in memory. Fine up to ~100k records; large deployments should optimise the
14
+ * Postgres path with SQL aggregates (separate PR).
15
+ */
16
+ export declare function createMetricsRouter(config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore): Router | null;
17
+ export type { PurchaseInfo, SubscriptionInfo };
18
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/routes/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAMpE;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,GAAG,IAAI,CAiLf;AAED,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}