@tumbaland/backend-core 1.24.0 → 1.26.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 (65) hide show
  1. package/dist/app/createBaseApp.d.ts +24 -0
  2. package/dist/app/createBaseApp.d.ts.map +1 -1
  3. package/dist/app/createBaseApp.js +20 -1
  4. package/dist/app/createBaseApp.js.map +1 -1
  5. package/dist/entitlements/client.d.ts.map +1 -1
  6. package/dist/entitlements/client.js +17 -1
  7. package/dist/entitlements/client.js.map +1 -1
  8. package/dist/entitlements/index.d.ts +3 -2
  9. package/dist/entitlements/index.d.ts.map +1 -1
  10. package/dist/entitlements/index.js +5 -4
  11. package/dist/entitlements/index.js.map +1 -1
  12. package/dist/entitlements/middleware.d.ts.map +1 -1
  13. package/dist/entitlements/middleware.js +8 -19
  14. package/dist/entitlements/middleware.js.map +1 -1
  15. package/dist/entitlements/reconcile.d.ts +74 -0
  16. package/dist/entitlements/reconcile.d.ts.map +1 -0
  17. package/dist/entitlements/reconcile.js +297 -0
  18. package/dist/entitlements/reconcile.js.map +1 -0
  19. package/dist/entitlements/types.d.ts +0 -8
  20. package/dist/entitlements/types.d.ts.map +1 -1
  21. package/dist/entitlements/usage.d.ts +1 -1
  22. package/dist/entitlements/usage.d.ts.map +1 -1
  23. package/dist/entitlements/usage.js +11 -20
  24. package/dist/entitlements/usage.js.map +1 -1
  25. package/dist/groups/client.d.ts +27 -0
  26. package/dist/groups/client.d.ts.map +1 -0
  27. package/dist/groups/client.js +153 -0
  28. package/dist/groups/client.js.map +1 -0
  29. package/dist/groups/index.d.ts +5 -0
  30. package/dist/groups/index.d.ts.map +1 -0
  31. package/dist/groups/index.js +10 -0
  32. package/dist/groups/index.js.map +1 -0
  33. package/dist/groups/subject.d.ts +30 -0
  34. package/dist/groups/subject.d.ts.map +1 -0
  35. package/dist/groups/subject.js +52 -0
  36. package/dist/groups/subject.js.map +1 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -0
  40. package/dist/index.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/app/createBaseApp.test.ts +31 -0
  43. package/src/app/createBaseApp.ts +28 -1
  44. package/src/entitlements/client.test.ts +30 -0
  45. package/src/entitlements/client.ts +18 -1
  46. package/src/entitlements/index.ts +4 -2
  47. package/src/entitlements/middleware.test.ts +9 -30
  48. package/src/entitlements/middleware.ts +8 -19
  49. package/src/entitlements/reconcile.test.ts +333 -0
  50. package/src/entitlements/reconcile.ts +384 -0
  51. package/src/entitlements/types.ts +0 -9
  52. package/src/entitlements/usage.test.ts +27 -31
  53. package/src/entitlements/usage.ts +12 -22
  54. package/src/groups/client.test.ts +215 -0
  55. package/src/groups/client.ts +182 -0
  56. package/src/groups/index.ts +5 -0
  57. package/src/groups/subject.test.ts +85 -0
  58. package/src/groups/subject.ts +50 -0
  59. package/src/index.ts +3 -0
  60. package/dist/entitlements/mode.d.ts +0 -10
  61. package/dist/entitlements/mode.d.ts.map +0 -1
  62. package/dist/entitlements/mode.js +0 -32
  63. package/dist/entitlements/mode.js.map +0 -1
  64. package/src/entitlements/mode.test.ts +0 -50
  65. package/src/entitlements/mode.ts +0 -28
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RECONCILED_METERS = void 0;
7
+ exports.reconcileUsageMeters = reconcileUsageMeters;
8
+ exports.runUsageReconciliation = runUsageReconciliation;
9
+ const mongoose_1 = __importDefault(require("mongoose"));
10
+ const logger_1 = __importDefault(require("../logging/logger"));
11
+ const UsageMeter_1 = require("./UsageMeter");
12
+ /**
13
+ * Rebuild the cumulative usage meters from the data they describe.
14
+ *
15
+ * **Why this can exist at all:** every meter reconciled here is *derived*.
16
+ * `storageBytes` is the sum of photo sizes, `trackedTickers` the count of
17
+ * active symbols, `seats` the members of the groups a subject owns — in each
18
+ * case the underlying collection is the record and the counter is only a cache
19
+ * of it, kept hot because a quota check cannot afford the aggregation. So the
20
+ * counter can always be recomputed, and reconciliation is cache invalidation.
21
+ *
22
+ * `aiJobs` is deliberately absent, and its absence is the same rule read the
23
+ * other way: nothing records that a GPU job happened except the counter itself,
24
+ * so there is no truth to recompute it from. That is precisely why
25
+ * `consumeQuota` rolls its increment back on rejection rather than leaving it —
26
+ * a leaked increment on that meter is permanent, where here it is temporary.
27
+ *
28
+ * **On racing with live writes:** a `consumeQuota` landing between the
29
+ * aggregation and the write is lost. That is tolerable for exactly the reason
30
+ * above — the next run re-derives the same number from source and converges —
31
+ * so this is scheduled at a quiet hour rather than defended with a lock.
32
+ *
33
+ * Reads collections owned by other services through the raw driver rather than
34
+ * mongoose models. They are not this library's documents to model, every
35
+ * service shares one database, and going through the driver avoids registering
36
+ * a second model for a schema that already exists elsewhere.
37
+ */
38
+ /** Meters whose value can be recomputed from source data. See the note above. */
39
+ exports.RECONCILED_METERS = ['storageBytes', 'trackedTickers', 'seats'];
40
+ const normalizeEmail = (value) => typeof value === 'string' && value.trim() ? value.trim().toLowerCase() : undefined;
41
+ /**
42
+ * Whoever currently holds the `owner` role — that is what group-service
43
+ * authorizes against, and `updateMemberRole` can move it while `createdBy`
44
+ * stays as first written. Several owners resolve to the lowest email so that a
45
+ * re-run bills the same person twice rather than two people once.
46
+ */
47
+ function groupOwner(group, warnings) {
48
+ const owners = (group.members ?? [])
49
+ .filter(m => m.role === 'owner')
50
+ .map(m => normalizeEmail(m.email))
51
+ .filter((e) => Boolean(e))
52
+ .sort();
53
+ if (owners.length === 1)
54
+ return owners[0];
55
+ if (owners.length === 0) {
56
+ const fallback = normalizeEmail(group.createdBy);
57
+ warnings.push(`group ${group._id} ("${group.name}") has no member with role 'owner'; billing to createdBy=${fallback ?? 'MISSING'}`);
58
+ return fallback;
59
+ }
60
+ warnings.push(`group ${group._id} ("${group.name}") has ${owners.length} owners (${owners.join(', ')}); billing to ${owners[0]}`);
61
+ return owners[0];
62
+ }
63
+ async function computeExpected() {
64
+ const db = mongoose_1.default.connection.db;
65
+ if (!db)
66
+ throw new Error('Cannot reconcile usage meters: no active database connection');
67
+ const warnings = [];
68
+ // Album.userId / TrackedTicker.userId hold `req.user.id`, which is the
69
+ // auth-service User._id as a string, while a meter's subjectId is an email.
70
+ // Nothing personal can be attributed without coming back through this map.
71
+ const users = await db.collection('users').find({}, { projection: { email: 1 } }).toArray();
72
+ const userIdToEmail = new Map();
73
+ for (const user of users) {
74
+ const email = normalizeEmail(user.email);
75
+ if (email)
76
+ userIdToEmail.set(String(user._id), email);
77
+ }
78
+ const groups = (await db.collection('groups').find({}).toArray());
79
+ const groupsById = new Map();
80
+ for (const group of groups) {
81
+ groupsById.set(String(group._id), {
82
+ doc: group,
83
+ owner: groupOwner(group, warnings),
84
+ deleted: group.deletedAt !== null && group.deletedAt !== undefined
85
+ });
86
+ }
87
+ const expected = new Map();
88
+ const bucket = (email) => {
89
+ let existing = expected.get(email);
90
+ if (!existing) {
91
+ existing = { storageBytes: 0, trackedTickers: new Set(), seats: new Set() };
92
+ expected.set(email, existing);
93
+ }
94
+ return existing;
95
+ };
96
+ // Every known user gets a bucket, so a user whose real usage is zero can
97
+ // still correct a stale non-zero counter back down.
98
+ for (const email of userIdToEmail.values())
99
+ bucket(email);
100
+ /**
101
+ * Group rows bill to the group owner: Family sells a pooled allowance, and
102
+ * charging the uploader would spend their personal free tier on it instead.
103
+ */
104
+ const subjectFor = (row, label) => {
105
+ const groupId = row.groupId ? String(row.groupId) : undefined;
106
+ if (groupId) {
107
+ const group = groupsById.get(groupId);
108
+ if (group?.owner)
109
+ return group.owner;
110
+ // The group is gone but its rows are not. The uploader is the only
111
+ // identity left, and dropping the bytes would understate real storage.
112
+ const uploader = userIdToEmail.get(String(row.userId));
113
+ warnings.push(`${label} ${row._id} references missing group ${groupId}; billing to uploader=${uploader ?? 'UNATTRIBUTED'}`);
114
+ return uploader;
115
+ }
116
+ const owner = userIdToEmail.get(String(row.userId));
117
+ if (!owner)
118
+ warnings.push(`${label} ${row._id} has userId=${row.userId}, which matches no user; UNATTRIBUTED`);
119
+ return owner;
120
+ };
121
+ // --- storageBytes ---------------------------------------------------------
122
+ // Summed from the photos themselves rather than from the denormalized
123
+ // Album.totalSize: that counter is what a drifting $inc would have corrupted,
124
+ // so reconciling against it would re-import the drift instead of fixing it.
125
+ const photoTotals = await db
126
+ .collection('photos')
127
+ .aggregate([{ $group: { _id: '$albumId', totalSize: { $sum: '$size' }, photoCount: { $sum: 1 } } }], { allowDiskUse: true })
128
+ .toArray();
129
+ const sizeByAlbum = new Map(photoTotals.map(row => [String(row._id), row.totalSize || 0]));
130
+ const albums = (await db
131
+ .collection('albums')
132
+ .find({}, { projection: { userId: 1, groupId: 1, totalSize: 1, name: 1 } })
133
+ .toArray());
134
+ const albumIds = new Set(albums.map(a => String(a._id)));
135
+ for (const album of albums) {
136
+ const realSize = sizeByAlbum.get(String(album._id)) ?? 0;
137
+ if ((album.totalSize ?? 0) !== realSize) {
138
+ warnings.push(`album ${album._id} ("${album.name}") totalSize=${album.totalSize} but photos sum to ${realSize}; ` +
139
+ `metering the photo sum. The album counter is left alone — it is album-service's to maintain, ` +
140
+ `and silently rewriting another service's denormalized field would hide the bug rather than surface it.`);
141
+ }
142
+ const subject = subjectFor(album, 'album');
143
+ if (subject)
144
+ bucket(subject).storageBytes += realSize;
145
+ }
146
+ for (const row of photoTotals) {
147
+ if (!albumIds.has(String(row._id))) {
148
+ warnings.push(`${row.photoCount} photo(s) totalling ${row.totalSize} B reference missing album ${row._id}; UNATTRIBUTED`);
149
+ }
150
+ }
151
+ // --- trackedTickers -------------------------------------------------------
152
+ // Distinct symbols, not rows: the daily Alpha Vantage spend this limit
153
+ // recovers is one call per distinct active symbol, however many rows hold it.
154
+ const tickers = (await db
155
+ .collection('trackedtickers')
156
+ .find({ isActive: true }, { projection: { userId: 1, groupId: 1, ticker: 1 } })
157
+ .toArray());
158
+ for (const ticker of tickers) {
159
+ const subject = subjectFor(ticker, 'trackedTicker');
160
+ if (subject && ticker.ticker)
161
+ bucket(subject).trackedTickers.add(String(ticker.ticker).toUpperCase());
162
+ }
163
+ // --- seats ----------------------------------------------------------------
164
+ // Distinct people across every live group the subject owns. Soft-deleted
165
+ // groups are skipped: unlike storage and tickers they cost nothing and grant
166
+ // nobody access, so charging for them would charge for nothing.
167
+ for (const { doc, owner, deleted } of groupsById.values()) {
168
+ if (deleted || !owner)
169
+ continue;
170
+ const seats = bucket(owner).seats;
171
+ for (const member of doc.members ?? []) {
172
+ const email = normalizeEmail(member.email);
173
+ if (email)
174
+ seats.add(email);
175
+ }
176
+ }
177
+ return { expected, warnings, scannedUsers: users.length };
178
+ }
179
+ /**
180
+ * Recompute every derived meter and, with `apply`, write back the ones that
181
+ * disagree.
182
+ *
183
+ * Idempotent: it computes absolute values and writes only differences, so a
184
+ * second run in a row is a no-op.
185
+ */
186
+ async function reconcileUsageMeters(options = {}) {
187
+ const { apply = false, subject: only } = options;
188
+ const onlySubject = normalizeEmail(only);
189
+ const { expected, warnings, scannedUsers } = await computeExpected();
190
+ const current = await UsageMeter_1.UsageMeter.find({ meter: { $in: exports.RECONCILED_METERS }, period: 'ALL' }).lean();
191
+ const currentByKey = new Map(current.map(doc => [`${normalizeEmail(doc.subjectId)}::${doc.meter}`, doc]));
192
+ const subjects = Array.from(expected.keys())
193
+ .filter(email => !onlySubject || email === onlySubject)
194
+ .sort();
195
+ const changes = [];
196
+ const ops = [];
197
+ for (const subjectId of subjects) {
198
+ const counts = expected.get(subjectId);
199
+ const resolved = {
200
+ storageBytes: counts.storageBytes,
201
+ trackedTickers: counts.trackedTickers.size,
202
+ seats: counts.seats.size
203
+ };
204
+ for (const meter of exports.RECONCILED_METERS) {
205
+ const to = resolved[meter];
206
+ const existing = currentByKey.get(`${subjectId}::${meter}`);
207
+ const from = existing ? existing.count : null;
208
+ if (from === to)
209
+ continue;
210
+ // Never create a row just to say zero — absence already reads as zero
211
+ // everywhere. An *existing* row must still be corrected down to zero, or
212
+ // a stale count outlives the data it was counting.
213
+ if (from === null && to === 0)
214
+ continue;
215
+ changes.push({ subjectId, meter, from, to });
216
+ ops.push({
217
+ updateOne: {
218
+ filter: { subjectId, meter, period: (0, UsageMeter_1.currentPeriod)(meter) },
219
+ update: {
220
+ $set: { count: to, updatedAt: new Date() },
221
+ $setOnInsert: { subjectId, meter, period: (0, UsageMeter_1.currentPeriod)(meter), createdAt: new Date() }
222
+ },
223
+ upsert: true
224
+ }
225
+ });
226
+ }
227
+ }
228
+ // Meters for subjects the scan no longer recognizes — a removed user, or a
229
+ // subjectId written before an ownership change. Reported, never deleted: a
230
+ // reconciliation that silently drops billing state is worse than the drift.
231
+ const orphanedMeters = current
232
+ .filter(doc => !expected.has(normalizeEmail(doc.subjectId) ?? ''))
233
+ .map(doc => ({ subjectId: doc.subjectId, meter: doc.meter, count: doc.count }));
234
+ let written = 0;
235
+ if (apply && ops.length > 0) {
236
+ const result = await UsageMeter_1.UsageMeter.bulkWrite(ops);
237
+ written = result.modifiedCount + result.upsertedCount;
238
+ }
239
+ const expectedRecord = {};
240
+ for (const subjectId of subjects) {
241
+ const counts = expected.get(subjectId);
242
+ expectedRecord[subjectId] = {
243
+ storageBytes: counts.storageBytes,
244
+ trackedTickers: counts.trackedTickers.size,
245
+ seats: counts.seats.size
246
+ };
247
+ }
248
+ return {
249
+ applied: apply,
250
+ scannedUsers,
251
+ subjects: expected.size,
252
+ expected: expectedRecord,
253
+ changes,
254
+ written,
255
+ orphanedMeters,
256
+ warnings
257
+ };
258
+ }
259
+ /**
260
+ * Run a reconciliation and log the outcome. The scheduled entry point.
261
+ *
262
+ * Never throws: this runs from a cron callback with no caller to catch it, and
263
+ * an unhandled rejection there takes the process down over a housekeeping job.
264
+ */
265
+ async function runUsageReconciliation() {
266
+ const startedAt = Date.now();
267
+ try {
268
+ const report = await reconcileUsageMeters({ apply: true });
269
+ // A correction is drift that already happened — the counter had gone wrong
270
+ // and users were being metered against a wrong number until this ran.
271
+ // Logged at `warn` so a persistent leak in consumeQuota/releaseQuota is
272
+ // visible as a recurring signal rather than buried in an info line.
273
+ const level = report.changes.length > 0 ? 'warn' : 'info';
274
+ logger_1.default[level]('entitlement.reconciled', {
275
+ event: 'entitlement.reconciled',
276
+ subjects: report.subjects,
277
+ corrected: report.changes.length,
278
+ written: report.written,
279
+ orphaned: report.orphanedMeters.length,
280
+ warnings: report.warnings.length,
281
+ durationMs: Date.now() - startedAt,
282
+ changes: report.changes.slice(0, 20)
283
+ });
284
+ for (const warning of report.warnings.slice(0, 20)) {
285
+ logger_1.default.warn('entitlement.reconcile_warning', { event: 'entitlement.reconcile_warning', detail: warning });
286
+ }
287
+ return report;
288
+ }
289
+ catch (err) {
290
+ logger_1.default.error('Usage meter reconciliation failed', {
291
+ error: err?.message,
292
+ durationMs: Date.now() - startedAt
293
+ });
294
+ return null;
295
+ }
296
+ }
297
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/entitlements/reconcile.ts"],"names":[],"mappings":"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"}
@@ -18,12 +18,4 @@ export interface Entitlements {
18
18
  */
19
19
  stale?: boolean;
20
20
  }
21
- /**
22
- * `off` — no metering, no blocking. `observe` — record usage and log what
23
- * would have been blocked, block nothing. `enforce` — block.
24
- *
25
- * Observe is the default because the configured limits start as guesses; two
26
- * weeks of `entitlement.would_block` volume is what turns them into numbers.
27
- */
28
- export type EntitlementsMode = 'off' | 'observe' | 'enforce';
29
21
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entitlements/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entitlements/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
@@ -7,7 +7,7 @@ export interface QuotaRequest {
7
7
  /** How much this request wants. Bytes for `storageBytes`, otherwise a count. */
8
8
  amount?: number;
9
9
  entitlements: Entitlements;
10
- /** Attached to the observe-mode log line so a would-block can be traced to a route. */
10
+ /** Attached to the rejection log so a 402 can be traced back to a route. */
11
11
  route?: string;
12
12
  }
13
13
  /** Current count for one meter in its live period. */
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAqC,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC;IAC3B,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9F;AAED,sFAAsF;AACtF,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAYvG;AA2BD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAejE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BnE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,MAAM,SAAI,EACV,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAqC,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9F;AAED,sFAAsF;AACtF,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAYvG;AAiCD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CASjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,MAAM,SAAI,EACV,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -13,7 +13,6 @@ const logger_1 = __importDefault(require("../logging/logger"));
13
13
  const HttpError_1 = require("../errors/HttpError");
14
14
  const UsageMeter_1 = require("./UsageMeter");
15
15
  const definitions_1 = require("./definitions");
16
- const mode_1 = require("./mode");
17
16
  /** Current count for one meter in its live period. */
18
17
  async function getUsage(subjectId, meter, now) {
19
18
  const doc = await UsageMeter_1.UsageMeter.findOne({
@@ -47,9 +46,15 @@ function quotaError(req, used, limit) {
47
46
  upgradeTo: req.entitlements.upgradeTo
48
47
  });
49
48
  }
50
- function logWouldBlock(req, used, limit) {
51
- logger_1.default.info('entitlement.would_block', {
52
- event: 'entitlement.would_block',
49
+ /**
50
+ * Every rejection is logged, not just thrown. The 402 reaches the user, but
51
+ * only this line reaches the operator — and the two questions worth asking of a
52
+ * paywall (which meter fires most, and how often it fires on a *stale* fallback
53
+ * rather than on real limits) are answerable from nothing else.
54
+ */
55
+ function logBlocked(req, used, limit) {
56
+ logger_1.default.info('entitlement.blocked', {
57
+ event: 'entitlement.blocked',
53
58
  meter: req.meter,
54
59
  subjectId: req.subjectId,
55
60
  amount: req.amount ?? 1,
@@ -69,19 +74,13 @@ function logWouldBlock(req, used, limit) {
69
74
  * the underlying data. Use `consumeQuota` where the count is the only record.
70
75
  */
71
76
  async function checkQuota(req) {
72
- const mode = (0, mode_1.getEntitlementsMode)();
73
- if (mode === 'off')
74
- return;
75
77
  const limit = req.entitlements.limits.meters[req.meter] ?? definitions_1.UNLIMITED;
76
78
  if (limit === definitions_1.UNLIMITED)
77
79
  return;
78
80
  const used = await getUsage(req.subjectId, req.meter);
79
81
  if ((0, definitions_1.fitsWithin)(limit, used, req.amount ?? 1))
80
82
  return;
81
- if (mode === 'observe') {
82
- logWouldBlock(req, used, limit);
83
- return;
84
- }
83
+ logBlocked(req, used, limit);
85
84
  throw quotaError(req, used, limit);
86
85
  }
87
86
  /**
@@ -99,9 +98,6 @@ async function checkQuota(req) {
99
98
  * who cannot upload despite being well under their limit.
100
99
  */
101
100
  async function consumeQuota(req) {
102
- const mode = (0, mode_1.getEntitlementsMode)();
103
- if (mode === 'off')
104
- return;
105
101
  const amount = req.amount ?? 1;
106
102
  const limit = req.entitlements.limits.meters[req.meter] ?? definitions_1.UNLIMITED;
107
103
  const period = (0, UsageMeter_1.currentPeriod)(req.meter);
@@ -110,12 +106,7 @@ async function consumeQuota(req) {
110
106
  if (limit === definitions_1.UNLIMITED || usage.count <= limit)
111
107
  return;
112
108
  const usedBefore = usage.count - amount;
113
- if (mode === 'observe') {
114
- // Keep the increment: observe mode exists to measure real demand, and a
115
- // rolled-back count would under-report exactly the users who matter.
116
- logWouldBlock(req, usedBefore, limit);
117
- return;
118
- }
109
+ logBlocked(req, usedBefore, limit);
119
110
  await releaseQuota(subjectId, req.meter, amount);
120
111
  throw quotaError(req, usedBefore, limit);
121
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/entitlements/usage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export interface GroupOwner {
2
+ groupId: string;
3
+ ownerEmail: string;
4
+ memberCount: number;
5
+ /** Soft-deleted groups still resolve: their photos still occupy storage. */
6
+ deleted: boolean;
7
+ /** True when group-service could not be reached and nothing was resolved. */
8
+ stale?: boolean;
9
+ }
10
+ /**
11
+ * The group's owner, cached for {@link CACHE_TTL_MS}, or `null` when the group
12
+ * does not exist or group-service could not answer.
13
+ *
14
+ * Nothing negative is cached — neither a missing group nor a failure. A group
15
+ * that 404s is nearly always a caller passing a bad id, so caching it would
16
+ * spend memory on garbage, and caching a failure would keep billing the wrong
17
+ * subject for a minute after the outage ended.
18
+ */
19
+ export declare function getGroupOwner(groupId: string): Promise<GroupOwner | null>;
20
+ /**
21
+ * Drop a group's cached owner. Call after anything that moves the `owner` role,
22
+ * to collapse the window in which usage bills to the previous owner.
23
+ */
24
+ export declare function invalidateGroupOwner(groupId: string): void;
25
+ /** Empties the whole cache. Test seam, and a lever after a bulk ownership edit. */
26
+ export declare function clearGroupOwnerCache(): void;
27
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/groups/client.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAyGD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqB/E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getGroupOwner = getGroupOwner;
7
+ exports.invalidateGroupOwner = invalidateGroupOwner;
8
+ exports.clearGroupOwnerCache = clearGroupOwnerCache;
9
+ const logger_1 = __importDefault(require("../logging/logger"));
10
+ /**
11
+ * Who a group's pooled usage bills to, read over the internal-service channel.
12
+ *
13
+ * The JWT carries `groups` — the ids a user belongs to — but never says who
14
+ * owns them, and no service outside group-service has the collection. So a
15
+ * chokepoint that needs the *quota subject* for a group album or a group ticker
16
+ * has to ask. See MONETIZATION.md's note on why that subject is the owner and
17
+ * not the uploader.
18
+ *
19
+ * Same 60s TTL as the entitlement client, for a weaker reason: ownership moves
20
+ * far more rarely than a plan does, so the cache could be much longer, but a
21
+ * stale owner bills the wrong person and one shared number is one fewer thing
22
+ * to reason about. The enforcement points are writes, so the traffic is low
23
+ * either way.
24
+ */
25
+ const CACHE_TTL_MS = 60_000;
26
+ /** Bounded so a service under enumeration can't grow the cache without limit. */
27
+ const MAX_CACHE_ENTRIES = 5_000;
28
+ const REQUEST_TIMEOUT_MS = 3_000;
29
+ const cache = new Map();
30
+ /**
31
+ * Concurrent callers for one group share a request — a burst of uploads into
32
+ * the same shared album is the expected shape of this traffic.
33
+ */
34
+ const inFlight = new Map();
35
+ function putInCache(groupId, value) {
36
+ if (cache.size >= MAX_CACHE_ENTRIES) {
37
+ // Map iterates in insertion order, so the first key is the oldest write.
38
+ const oldest = cache.keys().next();
39
+ if (!oldest.done)
40
+ cache.delete(oldest.value);
41
+ }
42
+ cache.set(groupId, { value, expiresAt: Date.now() + CACHE_TTL_MS });
43
+ }
44
+ /**
45
+ * Prefers the container-network address over the public hostname.
46
+ *
47
+ * `GROUP_API_URL` cannot simply be pointed inward: the frontend containers
48
+ * template that same variable into the `config.json` the *browser* downloads,
49
+ * so a Docker-internal value there breaks every group call in the UI. One
50
+ * variable genuinely has two consumers with incompatible needs, so this is the
51
+ * server-side half of it.
52
+ *
53
+ * Falling back to the public URL keeps local dev (where only `GROUP_API_URL` is
54
+ * set) and any un-migrated deployment working — it just takes the long way
55
+ * round, out through the proxy and back, carrying the internal token across the
56
+ * public edge.
57
+ */
58
+ function internalGroupUrl() {
59
+ return process.env.GROUP_INTERNAL_URL || process.env.GROUP_API_URL;
60
+ }
61
+ async function fetchGroupOwner(groupId) {
62
+ const baseUrl = internalGroupUrl();
63
+ const token = process.env.INTERNAL_SERVICE_TOKEN;
64
+ const serviceId = process.env.SERVICE_NAME || 'unknown-service';
65
+ if (!baseUrl || !token) {
66
+ logger_1.default.error('Group owner lookup not configured', {
67
+ hasGroupApiUrl: Boolean(baseUrl),
68
+ hasInternalToken: Boolean(token)
69
+ });
70
+ return null;
71
+ }
72
+ try {
73
+ const response = await fetch(`${baseUrl}/internal/groups/${encodeURIComponent(groupId)}/owner`, {
74
+ method: 'GET',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ 'x-internal-token': token,
78
+ 'x-service-id': serviceId
79
+ },
80
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
81
+ });
82
+ // A 404 is an answer, not an outage: the group is gone. Distinguished from
83
+ // an unreachable service because the two want opposite handling — one is
84
+ // permanent and cacheable, the other must be retried on the next request.
85
+ if (response.status === 404) {
86
+ logger_1.default.warn('Group owner lookup found no such group', { groupId });
87
+ return null;
88
+ }
89
+ if (!response.ok) {
90
+ logger_1.default.error('Group owner lookup failed', { groupId, status: response.status });
91
+ return null;
92
+ }
93
+ const body = (await response.json());
94
+ if (!body.ownerEmail) {
95
+ logger_1.default.error('Group owner lookup returned no owner', { groupId });
96
+ return null;
97
+ }
98
+ return {
99
+ groupId: body.groupId ?? groupId,
100
+ ownerEmail: body.ownerEmail.trim().toLowerCase(),
101
+ memberCount: body.memberCount ?? 0,
102
+ deleted: body.deleted === true
103
+ };
104
+ }
105
+ catch (err) {
106
+ logger_1.default.error('Group owner lookup errored', { groupId, error: err?.message });
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * The group's owner, cached for {@link CACHE_TTL_MS}, or `null` when the group
112
+ * does not exist or group-service could not answer.
113
+ *
114
+ * Nothing negative is cached — neither a missing group nor a failure. A group
115
+ * that 404s is nearly always a caller passing a bad id, so caching it would
116
+ * spend memory on garbage, and caching a failure would keep billing the wrong
117
+ * subject for a minute after the outage ended.
118
+ */
119
+ async function getGroupOwner(groupId) {
120
+ const key = String(groupId).trim();
121
+ if (!key)
122
+ return null;
123
+ const cached = cache.get(key);
124
+ if (cached && cached.expiresAt > Date.now())
125
+ return cached.value;
126
+ const pending = inFlight.get(key);
127
+ if (pending)
128
+ return pending;
129
+ const request = fetchGroupOwner(key)
130
+ .then(value => {
131
+ if (value)
132
+ putInCache(key, value);
133
+ return value;
134
+ })
135
+ .finally(() => {
136
+ inFlight.delete(key);
137
+ });
138
+ inFlight.set(key, request);
139
+ return request;
140
+ }
141
+ /**
142
+ * Drop a group's cached owner. Call after anything that moves the `owner` role,
143
+ * to collapse the window in which usage bills to the previous owner.
144
+ */
145
+ function invalidateGroupOwner(groupId) {
146
+ cache.delete(String(groupId).trim());
147
+ }
148
+ /** Empties the whole cache. Test seam, and a lever after a bulk ownership edit. */
149
+ function clearGroupOwnerCache() {
150
+ cache.clear();
151
+ inFlight.clear();
152
+ }
153
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/groups/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { getGroupOwner, invalidateGroupOwner, clearGroupOwnerCache } from './client';
2
+ export type { GroupOwner } from './client';
3
+ export { groupOwnerSubject } from './subject';
4
+ export type { GroupIdResolver } from './subject';
5
+ //# sourceMappingURL=index.d.ts.map