@stamprally/server 0.13.0 → 0.15.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.
- package/README.md +10 -3
- package/dist/index.cjs +515 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -4
- package/dist/index.d.ts +92 -4
- package/dist/index.js +511 -118
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -13,6 +13,10 @@ async function executeClaimRewardTransaction(database, store, params, mutation)
|
|
|
13
13
|
return { success: false, error: next.error };
|
|
14
14
|
}
|
|
15
15
|
if (next.nextStock !== null) await store.writeStock(transaction, params, next.nextStock);
|
|
16
|
+
if (next.nextSecondaryStock !== void 0 && store.writeSecondaryStock !== void 0) {
|
|
17
|
+
if (next.nextSecondaryStock !== null)
|
|
18
|
+
await store.writeSecondaryStock(transaction, params, next.nextSecondaryStock);
|
|
19
|
+
}
|
|
16
20
|
await store.writeUserState(transaction, params, next.nextUserState);
|
|
17
21
|
await store.writeClaimRecord(transaction, params, next.nextUserState);
|
|
18
22
|
await store.writeAudit(transaction, next.auditLog);
|
|
@@ -24,6 +28,39 @@ async function executeClaimRewardTransaction(database, store, params, mutation)
|
|
|
24
28
|
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
25
29
|
}
|
|
26
30
|
}
|
|
31
|
+
async function executeCheckInTransaction(database, store, params, mutation, current) {
|
|
32
|
+
try {
|
|
33
|
+
return await database.transaction(async (transaction) => {
|
|
34
|
+
const userState = current ?? (store.readUserState === void 0 ? (() => {
|
|
35
|
+
throw new Error("A current user state or readUserState implementation is required.");
|
|
36
|
+
})() : await store.readUserState(transaction, params));
|
|
37
|
+
const next = mutation({ userState });
|
|
38
|
+
if (next.error !== void 0) {
|
|
39
|
+
await store.writeAudit(transaction, next.auditLog);
|
|
40
|
+
if (params.idempotencyKey !== void 0 && next.result !== void 0)
|
|
41
|
+
await store.writeIdempotency(transaction, params, next.result);
|
|
42
|
+
return { success: false, error: next.error };
|
|
43
|
+
}
|
|
44
|
+
await store.writeUserState(transaction, params, next.nextUserState);
|
|
45
|
+
await store.writeAudit(transaction, next.auditLog);
|
|
46
|
+
if (params.idempotencyKey !== void 0 && next.result !== void 0)
|
|
47
|
+
await store.writeIdempotency(transaction, params, next.result);
|
|
48
|
+
return { success: true };
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function executeRedisTransaction(redis, queue) {
|
|
55
|
+
try {
|
|
56
|
+
const multi = redis.multi();
|
|
57
|
+
queue(multi);
|
|
58
|
+
const result = await redis.exec(multi);
|
|
59
|
+
return result === null ? { success: false, error: "Redis transaction was aborted." } : { success: true };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
27
64
|
|
|
28
65
|
// src/persistence.ts
|
|
29
66
|
var InMemoryServerPersistenceAdapter = class {
|
|
@@ -89,8 +126,14 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
89
126
|
const userState = await this.getUserState(params.rallyId, params.userId) ?? params.initialUserState;
|
|
90
127
|
if (userState === void 0)
|
|
91
128
|
return { success: false, error: "A user state is required for this transaction." };
|
|
129
|
+
const storedStock = await this.getRewardStock(
|
|
130
|
+
params.rallyId,
|
|
131
|
+
params.stockKey ?? params.rewardId
|
|
132
|
+
);
|
|
133
|
+
const storedSecondaryStock = params.secondaryStockKey === void 0 ? null : await this.getRewardStock(params.rallyId, params.secondaryStockKey);
|
|
92
134
|
const mutationResult = mutation({
|
|
93
|
-
stock:
|
|
135
|
+
stock: storedStock ?? params.initialStock ?? null,
|
|
136
|
+
secondaryStock: storedSecondaryStock ?? params.initialSecondaryStock ?? null,
|
|
94
137
|
claimCount: await this.getUserClaimCount(params.rallyId, params.userId, params.rewardId),
|
|
95
138
|
userState
|
|
96
139
|
});
|
|
@@ -106,16 +149,45 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
106
149
|
);
|
|
107
150
|
return { success: false, error: mutationResult.error };
|
|
108
151
|
}
|
|
109
|
-
const currentStock = await this.getRewardStock(
|
|
110
|
-
|
|
152
|
+
const currentStock = await this.getRewardStock(
|
|
153
|
+
params.rallyId,
|
|
154
|
+
params.stockKey ?? params.rewardId
|
|
155
|
+
);
|
|
156
|
+
const effectiveStock = currentStock ?? params.initialStock ?? null;
|
|
157
|
+
if (currentStock === null && params.initialStock !== void 0 && params.initialStock !== null)
|
|
158
|
+
this.#stocks.set(
|
|
159
|
+
this.#stockKey(params.rallyId, params.stockKey ?? params.rewardId),
|
|
160
|
+
params.initialStock
|
|
161
|
+
);
|
|
162
|
+
const currentSecondaryStock = params.secondaryStockKey === void 0 ? null : await this.getRewardStock(params.rallyId, params.secondaryStockKey);
|
|
163
|
+
const effectiveSecondaryStock = currentSecondaryStock ?? params.initialSecondaryStock ?? null;
|
|
164
|
+
if (currentSecondaryStock === null && params.secondaryStockKey !== void 0 && params.initialSecondaryStock !== void 0 && params.initialSecondaryStock !== null)
|
|
165
|
+
this.#stocks.set(
|
|
166
|
+
this.#stockKey(params.rallyId, params.secondaryStockKey),
|
|
167
|
+
params.initialSecondaryStock
|
|
168
|
+
);
|
|
169
|
+
if (effectiveStock !== null && (mutationResult.nextStock === null || mutationResult.nextStock < 0))
|
|
111
170
|
throw new Error("The transaction produced an invalid stock value.");
|
|
112
|
-
if (
|
|
171
|
+
if (effectiveStock === null && mutationResult.nextStock !== null)
|
|
113
172
|
throw new Error("The transaction changed an unlimited stock to a limited stock.");
|
|
114
173
|
if (mutationResult.nextStock !== null)
|
|
115
174
|
this.#stocks.set(
|
|
116
|
-
this.#stockKey(params.rallyId, params.rewardId),
|
|
175
|
+
this.#stockKey(params.rallyId, params.stockKey ?? params.rewardId),
|
|
117
176
|
mutationResult.nextStock
|
|
118
177
|
);
|
|
178
|
+
if (params.secondaryStockKey !== void 0 && mutationResult.nextSecondaryStock !== void 0) {
|
|
179
|
+
if (effectiveSecondaryStock !== null && (mutationResult.nextSecondaryStock === null || mutationResult.nextSecondaryStock < 0))
|
|
180
|
+
throw new Error("The transaction produced an invalid secondary stock value.");
|
|
181
|
+
if (effectiveSecondaryStock === null && mutationResult.nextSecondaryStock !== null)
|
|
182
|
+
throw new Error(
|
|
183
|
+
"The transaction changed an unlimited secondary stock to a limited stock."
|
|
184
|
+
);
|
|
185
|
+
if (mutationResult.nextSecondaryStock !== null)
|
|
186
|
+
this.#stocks.set(
|
|
187
|
+
this.#stockKey(params.rallyId, params.secondaryStockKey),
|
|
188
|
+
mutationResult.nextSecondaryStock
|
|
189
|
+
);
|
|
190
|
+
}
|
|
119
191
|
await this.saveUserState(params.rallyId, params.userId, mutationResult.nextUserState);
|
|
120
192
|
const reward = mutationResult.nextUserState.rewards.find(
|
|
121
193
|
(item) => item.rewardId === params.rewardId
|
|
@@ -142,6 +214,39 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
142
214
|
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
143
215
|
}
|
|
144
216
|
}
|
|
217
|
+
async executeCheckInTransaction(params, mutation) {
|
|
218
|
+
try {
|
|
219
|
+
return await this.runTransaction(params.rallyId, async () => {
|
|
220
|
+
const userState = await this.getUserState(params.rallyId, params.userId) ?? params.initialUserState;
|
|
221
|
+
if (userState === void 0)
|
|
222
|
+
return { success: false, error: "A user state is required for this transaction." };
|
|
223
|
+
const mutationResult = mutation({ userState });
|
|
224
|
+
if (mutationResult.error !== void 0) {
|
|
225
|
+
await this.recordAuditLog(mutationResult.auditLog);
|
|
226
|
+
if (params.idempotencyKey !== void 0 && mutationResult.result !== void 0)
|
|
227
|
+
await this.saveIdempotentResult(
|
|
228
|
+
params.rallyId,
|
|
229
|
+
params.idempotencyKey,
|
|
230
|
+
mutationResult.result,
|
|
231
|
+
params.idempotencyTtlMs ?? 864e5
|
|
232
|
+
);
|
|
233
|
+
return { success: false, error: mutationResult.error };
|
|
234
|
+
}
|
|
235
|
+
await this.saveUserState(params.rallyId, params.userId, mutationResult.nextUserState);
|
|
236
|
+
await this.recordAuditLog(mutationResult.auditLog);
|
|
237
|
+
if (params.idempotencyKey !== void 0 && mutationResult.result !== void 0)
|
|
238
|
+
await this.saveIdempotentResult(
|
|
239
|
+
params.rallyId,
|
|
240
|
+
params.idempotencyKey,
|
|
241
|
+
mutationResult.result,
|
|
242
|
+
params.idempotencyTtlMs ?? 864e5
|
|
243
|
+
);
|
|
244
|
+
return { success: true };
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
145
250
|
async rollbackUserState(rallyId, userId, previousState) {
|
|
146
251
|
const key = `${rallyId}:${userId}`;
|
|
147
252
|
if (previousState === null) this.#states.delete(key);
|
|
@@ -193,7 +298,7 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
193
298
|
if (count <= 1) this.#claims.delete(key);
|
|
194
299
|
else this.#claims.set(key, count - 1);
|
|
195
300
|
const index = this.#claimRecords.findIndex(
|
|
196
|
-
(
|
|
301
|
+
(record2) => record2.rallyId === rallyId && record2.userId === userId && record2.rewardId === rewardId && record2.ticketNumber === ticketNumber
|
|
197
302
|
);
|
|
198
303
|
if (index >= 0) this.#claimRecords.splice(index, 1);
|
|
199
304
|
}
|
|
@@ -241,6 +346,138 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
241
346
|
}
|
|
242
347
|
}
|
|
243
348
|
};
|
|
349
|
+
|
|
350
|
+
// src/security.ts
|
|
351
|
+
function errors(...items) {
|
|
352
|
+
return { success: false, errors: items };
|
|
353
|
+
}
|
|
354
|
+
function record(value) {
|
|
355
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
356
|
+
}
|
|
357
|
+
function nonEmpty(value) {
|
|
358
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
359
|
+
}
|
|
360
|
+
function contextErrors(value) {
|
|
361
|
+
if (!record(value) || typeof value.type !== "string")
|
|
362
|
+
return [
|
|
363
|
+
{
|
|
364
|
+
path: "proof",
|
|
365
|
+
message: "proof is not a valid verification context.",
|
|
366
|
+
code: "INVALID_TYPE"
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
if (value.type === "qr" && !nonEmpty(value.token))
|
|
370
|
+
return [
|
|
371
|
+
{ path: "proof.token", message: "token must be a non-empty string.", code: "INVALID_TYPE" }
|
|
372
|
+
];
|
|
373
|
+
if (value.type === "passcode" && !nonEmpty(value.code))
|
|
374
|
+
return [
|
|
375
|
+
{ path: "proof.code", message: "code must be a non-empty string.", code: "INVALID_TYPE" }
|
|
376
|
+
];
|
|
377
|
+
if (value.type === "nfc" && !nonEmpty(value.tagId))
|
|
378
|
+
return [
|
|
379
|
+
{ path: "proof.tagId", message: "tagId must be a non-empty string.", code: "INVALID_TYPE" }
|
|
380
|
+
];
|
|
381
|
+
if (value.type === "custom")
|
|
382
|
+
return "value" in value ? [] : [{ path: "proof.value", message: "value is required.", code: "REQUIRED" }];
|
|
383
|
+
if (value.type === "qr" || value.type === "passcode" || value.type === "nfc") return [];
|
|
384
|
+
if (value.type !== "gps")
|
|
385
|
+
return [{ path: "proof.type", message: "Unknown verification type.", code: "INVALID_ENUM" }];
|
|
386
|
+
const result = [];
|
|
387
|
+
if (typeof value.latitude !== "number" || !Number.isFinite(value.latitude))
|
|
388
|
+
result.push({
|
|
389
|
+
path: "proof.latitude",
|
|
390
|
+
message: "Latitude must be a finite number.",
|
|
391
|
+
code: "INVALID_TYPE"
|
|
392
|
+
});
|
|
393
|
+
else if (value.latitude < -90 || value.latitude > 90)
|
|
394
|
+
result.push({
|
|
395
|
+
path: "proof.latitude",
|
|
396
|
+
message: "Latitude must be between -90 and 90.",
|
|
397
|
+
code: "INVALID_RANGE"
|
|
398
|
+
});
|
|
399
|
+
if (typeof value.longitude !== "number" || !Number.isFinite(value.longitude))
|
|
400
|
+
result.push({
|
|
401
|
+
path: "proof.longitude",
|
|
402
|
+
message: "Longitude must be a finite number.",
|
|
403
|
+
code: "INVALID_TYPE"
|
|
404
|
+
});
|
|
405
|
+
else if (value.longitude < -180 || value.longitude > 180)
|
|
406
|
+
result.push({
|
|
407
|
+
path: "proof.longitude",
|
|
408
|
+
message: "Longitude must be between -180 and 180.",
|
|
409
|
+
code: "INVALID_RANGE"
|
|
410
|
+
});
|
|
411
|
+
if ("radiusMeters" in value && (typeof value.radiusMeters !== "number" || !Number.isFinite(value.radiusMeters) || value.radiusMeters <= 0))
|
|
412
|
+
result.push({
|
|
413
|
+
path: "proof.radiusMeters",
|
|
414
|
+
message: "Radius must be greater than zero.",
|
|
415
|
+
code: "INVALID_RANGE"
|
|
416
|
+
});
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
function dateInput(value) {
|
|
420
|
+
if (typeof value === "number") return Number.isInteger(value) && value > 0;
|
|
421
|
+
if (typeof value !== "string" || value.trim() === "") return false;
|
|
422
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:?\d{2})$/.test(value) && !Number.isNaN(Date.parse(value));
|
|
423
|
+
}
|
|
424
|
+
function requiredErrors(value, fields) {
|
|
425
|
+
if (record(value) && fields.every((field) => nonEmpty(value[field]))) return [];
|
|
426
|
+
return fields.filter((field) => !record(value) || !nonEmpty(value[field])).map((field) => ({
|
|
427
|
+
path: field,
|
|
428
|
+
message: `${field} must be a non-empty string.`,
|
|
429
|
+
code: "REQUIRED"
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
function validateCheckInRequest(value) {
|
|
433
|
+
const required = requiredErrors(value, ["rallyId", "spotId", "idempotencyKey"]);
|
|
434
|
+
if (required.length > 0) return errors(...required);
|
|
435
|
+
if (!record(value))
|
|
436
|
+
return errors({ path: "$", message: "Expected an object.", code: "INVALID_TYPE" });
|
|
437
|
+
const proof = contextErrors(value.context);
|
|
438
|
+
if (proof.length > 0) return errors(...proof);
|
|
439
|
+
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
440
|
+
return errors({ path: "userId", message: "userId must be non-empty.", code: "INVALID_TYPE" });
|
|
441
|
+
if (value.now !== void 0 && !dateInput(value.now))
|
|
442
|
+
return errors({
|
|
443
|
+
path: "now",
|
|
444
|
+
message: "now must be an ISO 8601 date or positive timestamp.",
|
|
445
|
+
code: "INVALID_DATE"
|
|
446
|
+
});
|
|
447
|
+
return { success: true, data: value };
|
|
448
|
+
}
|
|
449
|
+
function validateClaimRewardRequest(value) {
|
|
450
|
+
const required = requiredErrors(value, ["rallyId", "rewardId", "idempotencyKey"]);
|
|
451
|
+
if (required.length > 0) return errors(...required);
|
|
452
|
+
if (!record(value))
|
|
453
|
+
return errors({ path: "$", message: "Expected an object.", code: "INVALID_TYPE" });
|
|
454
|
+
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
455
|
+
return errors({ path: "userId", message: "userId must be non-empty.", code: "INVALID_TYPE" });
|
|
456
|
+
if (value.staffPasscode !== void 0 && !nonEmpty(value.staffPasscode))
|
|
457
|
+
return errors({
|
|
458
|
+
path: "staffPasscode",
|
|
459
|
+
message: "staffPasscode must be non-empty.",
|
|
460
|
+
code: "INVALID_TYPE"
|
|
461
|
+
});
|
|
462
|
+
if (value.staffId !== void 0 && !nonEmpty(value.staffId))
|
|
463
|
+
return errors({ path: "staffId", message: "staffId must be non-empty.", code: "INVALID_TYPE" });
|
|
464
|
+
if (value.now !== void 0 && !nonEmpty(value.now))
|
|
465
|
+
return errors({
|
|
466
|
+
path: "now",
|
|
467
|
+
message: "now must be an ISO 8601 date or positive timestamp.",
|
|
468
|
+
code: "INVALID_DATE"
|
|
469
|
+
});
|
|
470
|
+
return { success: true, data: value };
|
|
471
|
+
}
|
|
472
|
+
function validateSyncRequest(value) {
|
|
473
|
+
const required = requiredErrors(value, ["rallyId"]);
|
|
474
|
+
if (required.length > 0) return errors(...required);
|
|
475
|
+
if (!record(value))
|
|
476
|
+
return errors({ path: "$", message: "Expected an object.", code: "INVALID_TYPE" });
|
|
477
|
+
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
478
|
+
return errors({ path: "userId", message: "userId must be non-empty.", code: "INVALID_TYPE" });
|
|
479
|
+
return { success: true, data: value };
|
|
480
|
+
}
|
|
244
481
|
function json(body, status = 200) {
|
|
245
482
|
return new Response(JSON.stringify(body), {
|
|
246
483
|
status,
|
|
@@ -250,11 +487,27 @@ function json(body, status = 200) {
|
|
|
250
487
|
function isObject(value) {
|
|
251
488
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
252
489
|
}
|
|
490
|
+
function isUuidV4(value) {
|
|
491
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
492
|
+
}
|
|
253
493
|
function requestId(prefix) {
|
|
254
494
|
return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
|
|
255
495
|
}
|
|
256
|
-
function now(options
|
|
257
|
-
return
|
|
496
|
+
function now(options) {
|
|
497
|
+
return options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
498
|
+
}
|
|
499
|
+
function timestampMillis(timestamp) {
|
|
500
|
+
const value = Date.parse(timestamp);
|
|
501
|
+
return Number.isFinite(value) ? value : Date.now();
|
|
502
|
+
}
|
|
503
|
+
function validationResponse(errors2) {
|
|
504
|
+
return json(
|
|
505
|
+
{
|
|
506
|
+
error: "VALIDATION_FAILED",
|
|
507
|
+
details: errors2.map(({ path, message, code }) => ({ path, message, code }))
|
|
508
|
+
},
|
|
509
|
+
400
|
|
510
|
+
);
|
|
258
511
|
}
|
|
259
512
|
function initialState(config, userId, timestamp) {
|
|
260
513
|
return {
|
|
@@ -265,6 +518,27 @@ function initialState(config, userId, timestamp) {
|
|
|
265
518
|
updatedAt: timestamp
|
|
266
519
|
};
|
|
267
520
|
}
|
|
521
|
+
function rewardStock(config, rewardId, stockLimit) {
|
|
522
|
+
const configured = config.inventory?.[rewardId];
|
|
523
|
+
if (stockLimit === void 0) return configured ?? null;
|
|
524
|
+
if (configured === void 0) return stockLimit;
|
|
525
|
+
return Math.min(stockLimit, configured);
|
|
526
|
+
}
|
|
527
|
+
function sharedStock(config) {
|
|
528
|
+
return config.inventory?.sharedStock ?? config.inventory?.global ?? null;
|
|
529
|
+
}
|
|
530
|
+
function inventoryPlan(config, rewardId, stockLimit) {
|
|
531
|
+
const individual = rewardStock(config, rewardId, stockLimit);
|
|
532
|
+
const shared = sharedStock(config);
|
|
533
|
+
if (config.inventoryMode === "shared" && shared !== null) {
|
|
534
|
+
return {
|
|
535
|
+
primaryKey: "__shared__",
|
|
536
|
+
primaryInitial: shared,
|
|
537
|
+
...individual === null ? {} : { secondaryKey: rewardId, secondaryInitial: individual }
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
return { primaryKey: rewardId, primaryInitial: individual };
|
|
541
|
+
}
|
|
268
542
|
function getProof(context) {
|
|
269
543
|
return context.type === "qr" ? context.token : context.type === "passcode" ? context.code : context.type === "gps" ? { latitude: context.latitude, longitude: context.longitude } : context.type === "nfc" ? context.tagId : context.value;
|
|
270
544
|
}
|
|
@@ -317,47 +591,69 @@ var StampRallyServer = class {
|
|
|
317
591
|
return json({ ok: false, code: "NOT_FOUND", message: "Route not found." }, 404);
|
|
318
592
|
}
|
|
319
593
|
async handleCheckIn(request) {
|
|
320
|
-
const body = await this.#body(request);
|
|
321
|
-
|
|
322
|
-
if (body
|
|
323
|
-
return
|
|
594
|
+
const body = validateCheckInRequest(await this.#body(request));
|
|
595
|
+
if (!body.success) return validationResponse(body.errors);
|
|
596
|
+
if (body.data.rallyId !== this.#config.id)
|
|
597
|
+
return validationResponse([
|
|
324
598
|
{
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
599
|
+
path: "rallyId",
|
|
600
|
+
message: "The rally does not match this server.",
|
|
601
|
+
code: "INVALID_VALUE"
|
|
602
|
+
}
|
|
603
|
+
]);
|
|
604
|
+
const userId = await this.#user(request);
|
|
605
|
+
if (userId === null)
|
|
606
|
+
return json(
|
|
607
|
+
{ ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
|
|
608
|
+
401
|
|
330
609
|
);
|
|
331
|
-
const result = await this.checkIn({ ...body, userId });
|
|
610
|
+
const result = await this.checkIn({ ...body.data, userId });
|
|
332
611
|
return json(
|
|
333
612
|
{ ...result, status: operationStatus(result) },
|
|
334
613
|
result.ok ? 200 : result.code === "SPOT_NOT_FOUND" ? 404 : 422
|
|
335
614
|
);
|
|
336
615
|
}
|
|
337
616
|
async handleClaimReward(request) {
|
|
338
|
-
const body = await this.#body(request);
|
|
339
|
-
|
|
340
|
-
if (body
|
|
341
|
-
return
|
|
617
|
+
const body = validateClaimRewardRequest(await this.#body(request));
|
|
618
|
+
if (!body.success) return validationResponse(body.errors);
|
|
619
|
+
if (body.data.rallyId !== this.#config.id)
|
|
620
|
+
return validationResponse([
|
|
342
621
|
{
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
622
|
+
path: "rallyId",
|
|
623
|
+
message: "The rally does not match this server.",
|
|
624
|
+
code: "INVALID_VALUE"
|
|
625
|
+
}
|
|
626
|
+
]);
|
|
627
|
+
const userId = await this.#user(request);
|
|
628
|
+
if (userId === null)
|
|
629
|
+
return json(
|
|
630
|
+
{ ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
|
|
631
|
+
401
|
|
348
632
|
);
|
|
349
|
-
const result = await this.claimReward({ ...body, userId });
|
|
633
|
+
const result = await this.claimReward({ ...body.data, userId });
|
|
350
634
|
return json(
|
|
351
635
|
{ ...result, status: operationStatus(result) },
|
|
352
636
|
result.ok ? 200 : result.code === "REWARD_NOT_FOUND" ? 404 : 422
|
|
353
637
|
);
|
|
354
638
|
}
|
|
355
639
|
async handleSync(request) {
|
|
356
|
-
const body = await this.#body(request);
|
|
357
|
-
|
|
358
|
-
if (body
|
|
359
|
-
return
|
|
360
|
-
|
|
640
|
+
const body = validateSyncRequest(await this.#body(request));
|
|
641
|
+
if (!body.success) return validationResponse(body.errors);
|
|
642
|
+
if (body.data.rallyId !== this.#config.id)
|
|
643
|
+
return validationResponse([
|
|
644
|
+
{
|
|
645
|
+
path: "rallyId",
|
|
646
|
+
message: "The rally does not match this server.",
|
|
647
|
+
code: "INVALID_VALUE"
|
|
648
|
+
}
|
|
649
|
+
]);
|
|
650
|
+
const userId = await this.#user(request);
|
|
651
|
+
if (userId === null)
|
|
652
|
+
return json(
|
|
653
|
+
{ ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
|
|
654
|
+
401
|
|
655
|
+
);
|
|
656
|
+
return json({ ok: true, state: await this.sync(body.data.rallyId, userId) });
|
|
361
657
|
}
|
|
362
658
|
async checkIn(request) {
|
|
363
659
|
const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
|
|
@@ -373,47 +669,68 @@ var StampRallyServer = class {
|
|
|
373
669
|
this.#options.lockTtlMs ?? 5e3
|
|
374
670
|
))
|
|
375
671
|
return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
|
|
376
|
-
const timestamp = now(this.#options
|
|
672
|
+
const timestamp = now(this.#options);
|
|
377
673
|
try {
|
|
674
|
+
const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
|
|
675
|
+
const responseHolder = { value: null };
|
|
676
|
+
const makeAudit = (status, code) => audit(
|
|
677
|
+
request.rallyId,
|
|
678
|
+
request.userId,
|
|
679
|
+
"CHECK_IN",
|
|
680
|
+
request.spotId,
|
|
681
|
+
request.idempotencyKey,
|
|
682
|
+
status,
|
|
683
|
+
timestamp,
|
|
684
|
+
code
|
|
685
|
+
);
|
|
378
686
|
const spot = this.#config.spots.find((item) => item.id === request.spotId);
|
|
379
|
-
if (spot === void 0)
|
|
380
|
-
|
|
687
|
+
if (spot === void 0) {
|
|
688
|
+
responseHolder.value = {
|
|
689
|
+
ok: false,
|
|
690
|
+
code: "SPOT_NOT_FOUND",
|
|
691
|
+
message: "Spot was not found."
|
|
692
|
+
};
|
|
693
|
+
return await this.#rememberCheckInTransaction(
|
|
694
|
+
request,
|
|
695
|
+
timestamp,
|
|
381
696
|
key,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
697
|
+
current,
|
|
698
|
+
{
|
|
699
|
+
nextUserState: current,
|
|
700
|
+
auditLog: makeAudit("REJECTED", "SPOT_NOT_FOUND"),
|
|
701
|
+
result: responseHolder.value,
|
|
702
|
+
error: "SPOT_NOT_FOUND"
|
|
703
|
+
},
|
|
704
|
+
responseHolder
|
|
389
705
|
);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
706
|
+
}
|
|
707
|
+
const rejected = (code, message) => {
|
|
708
|
+
responseHolder.value = { ok: false, code, message };
|
|
709
|
+
return {
|
|
710
|
+
nextUserState: current,
|
|
711
|
+
auditLog: makeAudit("REJECTED", code),
|
|
712
|
+
result: responseHolder.value,
|
|
713
|
+
error: code
|
|
714
|
+
};
|
|
715
|
+
};
|
|
716
|
+
if (current.records.some((record2) => record2.stampId === request.spotId))
|
|
717
|
+
return await this.#rememberCheckInTransaction(
|
|
718
|
+
request,
|
|
719
|
+
timestamp,
|
|
393
720
|
key,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
"CHECK_IN",
|
|
398
|
-
request.spotId,
|
|
399
|
-
request.idempotencyKey,
|
|
400
|
-
timestamp
|
|
721
|
+
current,
|
|
722
|
+
rejected("STAMP_ALREADY_ACQUIRED", "Spot was already claimed."),
|
|
723
|
+
responseHolder
|
|
401
724
|
);
|
|
402
|
-
const acquired = new Set(current.records.map((
|
|
725
|
+
const acquired = new Set(current.records.map((record2) => record2.stampId));
|
|
403
726
|
if (spot.prerequisites?.some((id) => !acquired.has(id)))
|
|
404
|
-
return this.#
|
|
727
|
+
return await this.#rememberCheckInTransaction(
|
|
728
|
+
request,
|
|
729
|
+
timestamp,
|
|
405
730
|
key,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
message: "Prerequisite spots are not complete."
|
|
410
|
-
},
|
|
411
|
-
request.rallyId,
|
|
412
|
-
request.userId,
|
|
413
|
-
"CHECK_IN",
|
|
414
|
-
request.spotId,
|
|
415
|
-
request.idempotencyKey,
|
|
416
|
-
timestamp
|
|
731
|
+
current,
|
|
732
|
+
rejected("PREREQUISITES_NOT_MET", "Prerequisite spots are not complete."),
|
|
733
|
+
responseHolder
|
|
417
734
|
);
|
|
418
735
|
for (const condition of spot.conditions)
|
|
419
736
|
if (!await evaluate(
|
|
@@ -422,15 +739,13 @@ var StampRallyServer = class {
|
|
|
422
739
|
condition.type === "custom" ? this.#options.customValidators?.[condition.validatorName] : void 0,
|
|
423
740
|
{ rallyId: request.rallyId, spotId: request.spotId, state: current }
|
|
424
741
|
))
|
|
425
|
-
return this.#
|
|
742
|
+
return await this.#rememberCheckInTransaction(
|
|
743
|
+
request,
|
|
744
|
+
timestamp,
|
|
426
745
|
key,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
"CHECK_IN",
|
|
431
|
-
request.spotId,
|
|
432
|
-
request.idempotencyKey,
|
|
433
|
-
timestamp
|
|
746
|
+
current,
|
|
747
|
+
rejected("INVALID_PROOF", "Verification failed."),
|
|
748
|
+
responseHolder
|
|
434
749
|
);
|
|
435
750
|
const next = {
|
|
436
751
|
...current,
|
|
@@ -443,17 +758,30 @@ var StampRallyServer = class {
|
|
|
443
758
|
),
|
|
444
759
|
updatedAt: timestamp
|
|
445
760
|
};
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
761
|
+
responseHolder.value = { ok: true, state: next };
|
|
762
|
+
const transaction = await this.#persistence.executeCheckInTransaction(
|
|
763
|
+
{
|
|
764
|
+
rallyId: request.rallyId,
|
|
765
|
+
userId: request.userId,
|
|
766
|
+
spotId: request.spotId,
|
|
767
|
+
timestamp: timestampMillis(timestamp),
|
|
768
|
+
idempotencyKey: key,
|
|
769
|
+
proofData: request.context,
|
|
770
|
+
...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
|
|
771
|
+
initialUserState: current
|
|
772
|
+
},
|
|
773
|
+
() => ({
|
|
774
|
+
nextUserState: next,
|
|
775
|
+
auditLog: makeAudit("SUCCESS"),
|
|
776
|
+
result: responseHolder.value
|
|
777
|
+
})
|
|
456
778
|
);
|
|
779
|
+
if (transaction.success && responseHolder.value !== null) return responseHolder.value;
|
|
780
|
+
return {
|
|
781
|
+
ok: false,
|
|
782
|
+
code: "PERSISTENCE_FAILED",
|
|
783
|
+
message: transaction.error ?? "Check-in failed."
|
|
784
|
+
};
|
|
457
785
|
} finally {
|
|
458
786
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
459
787
|
}
|
|
@@ -471,16 +799,17 @@ var StampRallyServer = class {
|
|
|
471
799
|
key,
|
|
472
800
|
{ ok: false, code: "REWARD_NOT_FOUND", message: "Reward was not found." },
|
|
473
801
|
request,
|
|
474
|
-
now(this.#options
|
|
802
|
+
now(this.#options)
|
|
475
803
|
);
|
|
476
|
-
const lockKey = `reward:${request.rallyId}:${reward.id}`;
|
|
804
|
+
const lockKey = this.#config.inventoryMode === "shared" ? "inventory:shared" : `reward:${request.rallyId}:${reward.id}`;
|
|
477
805
|
if (!await this.#persistence.acquireLock(
|
|
478
806
|
request.rallyId,
|
|
479
807
|
lockKey,
|
|
480
808
|
this.#options.lockTtlMs ?? 5e3
|
|
481
809
|
))
|
|
482
810
|
return { ok: false, code: "CONFLICT", message: "The reward is being claimed." };
|
|
483
|
-
const timestamp = now(this.#options
|
|
811
|
+
const timestamp = now(this.#options);
|
|
812
|
+
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
484
813
|
try {
|
|
485
814
|
const checked = await this.#persistence.getIdempotentResult(
|
|
486
815
|
request.rallyId,
|
|
@@ -493,6 +822,10 @@ var StampRallyServer = class {
|
|
|
493
822
|
rallyId: request.rallyId,
|
|
494
823
|
userId: request.userId,
|
|
495
824
|
rewardId: reward.id,
|
|
825
|
+
stockKey: plan.primaryKey,
|
|
826
|
+
...plan.secondaryKey === void 0 ? {} : { secondaryStockKey: plan.secondaryKey },
|
|
827
|
+
initialStock: plan.primaryInitial,
|
|
828
|
+
...plan.secondaryInitial === void 0 ? {} : { initialSecondaryStock: plan.secondaryInitial },
|
|
496
829
|
ticketNumber: request.idempotencyKey,
|
|
497
830
|
timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp),
|
|
498
831
|
idempotencyKey: key,
|
|
@@ -500,7 +833,7 @@ var StampRallyServer = class {
|
|
|
500
833
|
...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
|
|
501
834
|
initialUserState: initialState(this.#config, request.userId, timestamp)
|
|
502
835
|
},
|
|
503
|
-
({ stock, claimCount, userState }) => {
|
|
836
|
+
({ stock, secondaryStock, claimCount, userState }) => {
|
|
504
837
|
const storedReward = userState.rewards.find((item) => item.rewardId === reward.id) ?? {
|
|
505
838
|
rewardId: reward.id,
|
|
506
839
|
status: "LOCKED"
|
|
@@ -516,7 +849,7 @@ var StampRallyServer = class {
|
|
|
516
849
|
timestamp,
|
|
517
850
|
code
|
|
518
851
|
);
|
|
519
|
-
if (stock !== null && stock <= 0) {
|
|
852
|
+
if (stock !== null && stock <= 0 || secondaryStock !== null && secondaryStock <= 0) {
|
|
520
853
|
responseHolder.value = {
|
|
521
854
|
ok: false,
|
|
522
855
|
code: "OUT_OF_STOCK",
|
|
@@ -524,14 +857,16 @@ var StampRallyServer = class {
|
|
|
524
857
|
};
|
|
525
858
|
return {
|
|
526
859
|
nextStock: stock,
|
|
860
|
+
...plan.secondaryKey === void 0 ? {} : { nextSecondaryStock: secondaryStock },
|
|
527
861
|
nextUserState: userState,
|
|
528
862
|
auditLog: makeAudit("REJECTED", "OUT_OF_STOCK"),
|
|
529
863
|
result: responseHolder.value,
|
|
530
864
|
error: "OUT_OF_STOCK"
|
|
531
865
|
};
|
|
532
866
|
}
|
|
867
|
+
const effectiveReward = reward.staffPasscode === void 0 && this.#config.staffPasscode !== void 0 ? { ...reward, staffPasscode: this.#config.staffPasscode } : reward;
|
|
533
868
|
const local = consumeReward({
|
|
534
|
-
reward,
|
|
869
|
+
reward: effectiveReward,
|
|
535
870
|
currentState: currentReward,
|
|
536
871
|
now: timestamp,
|
|
537
872
|
userRedemptionCount: claimCount,
|
|
@@ -553,14 +888,35 @@ var StampRallyServer = class {
|
|
|
553
888
|
};
|
|
554
889
|
}
|
|
555
890
|
const nextRewards = userState.rewards.some((item) => item.rewardId === reward.id) ? userState.rewards.map((item) => item.rewardId === reward.id ? local.value : item) : [...userState.rewards, local.value];
|
|
891
|
+
const consumed = local.value.claimTicketNumber !== void 0;
|
|
892
|
+
const nextStock = consumed && stock !== null ? Math.max(0, stock - 1) : stock;
|
|
893
|
+
const nextSecondaryStock = consumed && secondaryStock !== null ? Math.max(0, secondaryStock - 1) : secondaryStock;
|
|
556
894
|
const next = {
|
|
557
895
|
...userState,
|
|
558
896
|
rewards: nextRewards,
|
|
559
|
-
updatedAt: timestamp
|
|
897
|
+
updatedAt: timestamp,
|
|
898
|
+
inventory: {
|
|
899
|
+
...plan.primaryKey === "__shared__" && nextStock !== null ? { sharedRemaining: nextStock } : {},
|
|
900
|
+
...plan.secondaryKey !== void 0 && nextSecondaryStock !== null ? { rewardRemaining: { [reward.id]: nextSecondaryStock } } : plan.primaryKey === reward.id && nextStock !== null ? { rewardRemaining: { [reward.id]: nextStock } } : {}
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
const inventory = {
|
|
904
|
+
...plan.primaryKey === "__shared__" && nextStock !== null ? { sharedRemaining: nextStock } : {},
|
|
905
|
+
...plan.secondaryKey !== void 0 && nextSecondaryStock !== null ? { rewardRemaining: nextSecondaryStock } : plan.primaryKey === reward.id && nextStock !== null ? { rewardRemaining: nextStock } : {}
|
|
906
|
+
};
|
|
907
|
+
responseHolder.value = local.value.claimTicketNumber === void 0 ? {
|
|
908
|
+
ok: true,
|
|
909
|
+
state: next,
|
|
910
|
+
...Object.keys(inventory).length === 0 ? {} : { inventory }
|
|
911
|
+
} : {
|
|
912
|
+
ok: true,
|
|
913
|
+
state: next,
|
|
914
|
+
claimTicketNumber: local.value.claimTicketNumber,
|
|
915
|
+
...Object.keys(inventory).length === 0 ? {} : { inventory }
|
|
560
916
|
};
|
|
561
|
-
responseHolder.value = local.value.claimTicketNumber === void 0 ? { ok: true, state: next } : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };
|
|
562
917
|
return {
|
|
563
|
-
nextStock
|
|
918
|
+
nextStock,
|
|
919
|
+
...plan.secondaryKey === void 0 ? {} : { nextSecondaryStock },
|
|
564
920
|
nextUserState: next,
|
|
565
921
|
auditLog: makeAudit("SUCCESS"),
|
|
566
922
|
result: responseHolder.value
|
|
@@ -593,7 +949,35 @@ var StampRallyServer = class {
|
|
|
593
949
|
}
|
|
594
950
|
}
|
|
595
951
|
async sync(rallyId, userId) {
|
|
596
|
-
|
|
952
|
+
const state = await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, userId, now(this.#options));
|
|
953
|
+
return this.#attachInventory(state);
|
|
954
|
+
}
|
|
955
|
+
async #attachInventory(state) {
|
|
956
|
+
const rewardRemaining = {};
|
|
957
|
+
for (const reward of this.#config.rewards) {
|
|
958
|
+
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
959
|
+
if (plan.secondaryKey !== void 0) {
|
|
960
|
+
const stock = await this.#persistence.getRewardStock(state.rallyId, plan.secondaryKey);
|
|
961
|
+
const remaining = stock ?? plan.secondaryInitial ?? null;
|
|
962
|
+
if (remaining !== null) rewardRemaining[reward.id] = Math.max(0, remaining);
|
|
963
|
+
} else if (plan.primaryKey !== "__shared__") {
|
|
964
|
+
const stock = await this.#persistence.getRewardStock(state.rallyId, plan.primaryKey);
|
|
965
|
+
const remaining = stock ?? plan.primaryInitial;
|
|
966
|
+
if (remaining !== null) rewardRemaining[reward.id] = Math.max(0, remaining);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const shared = sharedStock(this.#config);
|
|
970
|
+
const storedShared = await this.#persistence.getRewardStock(state.rallyId, "__shared__");
|
|
971
|
+
const sharedRemaining = this.#config.inventoryMode === "shared" && shared !== null ? Math.max(0, storedShared ?? shared) : void 0;
|
|
972
|
+
return {
|
|
973
|
+
...state,
|
|
974
|
+
...Object.keys(rewardRemaining).length === 0 && sharedRemaining === void 0 ? {} : {
|
|
975
|
+
inventory: {
|
|
976
|
+
...sharedRemaining === void 0 ? {} : { sharedRemaining },
|
|
977
|
+
...Object.keys(rewardRemaining).length === 0 ? {} : { rewardRemaining }
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
};
|
|
597
981
|
}
|
|
598
982
|
async #body(request) {
|
|
599
983
|
try {
|
|
@@ -603,31 +987,40 @@ var StampRallyServer = class {
|
|
|
603
987
|
return null;
|
|
604
988
|
}
|
|
605
989
|
}
|
|
606
|
-
async #user(request
|
|
607
|
-
if (this.#options.authenticate !== void 0)
|
|
608
|
-
|
|
609
|
-
|
|
990
|
+
async #user(request) {
|
|
991
|
+
if (this.#options.authenticate !== void 0) {
|
|
992
|
+
const identity = await this.#options.authenticate(request);
|
|
993
|
+
if (typeof identity === "string") return identity.length > 0 ? identity : null;
|
|
994
|
+
if (identity === null) return null;
|
|
995
|
+
const authenticatedUserId = identity.authenticatedUserId;
|
|
996
|
+
return authenticatedUserId.length > 0 ? authenticatedUserId : null;
|
|
997
|
+
}
|
|
998
|
+
if (this.#options.anonymousPolicy === "reject") return null;
|
|
999
|
+
const sessionId = request.headers.get("X-Anonymous-Session-Id");
|
|
1000
|
+
if (sessionId !== null && isUuidV4(sessionId)) return sessionId;
|
|
1001
|
+
if (this.#options.anonymousPolicy === "session_scoped") return null;
|
|
1002
|
+
return "anonymous";
|
|
610
1003
|
}
|
|
611
|
-
async #
|
|
612
|
-
await this.#persistence.
|
|
613
|
-
|
|
614
|
-
rallyId,
|
|
615
|
-
userId,
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
idempotencyKey,
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
)
|
|
623
|
-
);
|
|
624
|
-
await this.#persistence.saveIdempotentResult(
|
|
625
|
-
rallyId,
|
|
626
|
-
key,
|
|
627
|
-
result,
|
|
628
|
-
this.#options.idempotencyTtlMs ?? 864e5
|
|
1004
|
+
async #rememberCheckInTransaction(request, timestamp, key, current, mutation, responseHolder) {
|
|
1005
|
+
const transaction = await this.#persistence.executeCheckInTransaction(
|
|
1006
|
+
{
|
|
1007
|
+
rallyId: request.rallyId,
|
|
1008
|
+
userId: request.userId,
|
|
1009
|
+
spotId: request.spotId,
|
|
1010
|
+
timestamp: timestampMillis(timestamp),
|
|
1011
|
+
idempotencyKey: key,
|
|
1012
|
+
...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
|
|
1013
|
+
initialUserState: current
|
|
1014
|
+
},
|
|
1015
|
+
() => mutation
|
|
629
1016
|
);
|
|
630
|
-
|
|
1017
|
+
if (responseHolder.value !== null && (transaction.success || transaction.error === mutation.error))
|
|
1018
|
+
return responseHolder.value;
|
|
1019
|
+
return {
|
|
1020
|
+
ok: false,
|
|
1021
|
+
code: "PERSISTENCE_FAILED",
|
|
1022
|
+
message: transaction.error ?? "Check-in failed."
|
|
1023
|
+
};
|
|
631
1024
|
}
|
|
632
1025
|
async #rememberClaim(key, result, request, timestamp) {
|
|
633
1026
|
await this.#persistence.recordAuditLog(
|
|
@@ -652,6 +1045,6 @@ var StampRallyServer = class {
|
|
|
652
1045
|
}
|
|
653
1046
|
};
|
|
654
1047
|
|
|
655
|
-
export { InMemoryServerPersistenceAdapter, StampRallyServer, executeClaimRewardTransaction };
|
|
1048
|
+
export { InMemoryServerPersistenceAdapter, StampRallyServer, executeCheckInTransaction, executeClaimRewardTransaction, executeRedisTransaction, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
|
|
656
1049
|
//# sourceMappingURL=index.js.map
|
|
657
1050
|
//# sourceMappingURL=index.js.map
|