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