@bash-app/bash-common 30.235.0 → 30.237.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/dist/__tests__/statusEnums.test.js +88 -7
- package/dist/__tests__/statusEnums.test.js.map +1 -1
- package/dist/definitions.d.ts +45 -14
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +38 -17
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +8 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/mirroredPrismaEnums.d.ts +145 -0
- package/dist/mirroredPrismaEnums.d.ts.map +1 -0
- package/dist/mirroredPrismaEnums.js +136 -0
- package/dist/mirroredPrismaEnums.js.map +1 -0
- package/dist/utils/__tests__/nonprofitUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/nonprofitUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/nonprofitUtils.test.js +84 -0
- package/dist/utils/__tests__/nonprofitUtils.test.js.map +1 -0
- package/dist/utils/__tests__/paymentUtils.test.js +87 -0
- package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
- package/dist/utils/nonprofitUtils.d.ts +21 -0
- package/dist/utils/nonprofitUtils.d.ts.map +1 -0
- package/dist/utils/nonprofitUtils.js +29 -0
- package/dist/utils/nonprofitUtils.js.map +1 -0
- package/dist/utils/paymentUtils.d.ts +23 -0
- package/dist/utils/paymentUtils.d.ts.map +1 -1
- package/dist/utils/paymentUtils.js +24 -7
- package/dist/utils/paymentUtils.js.map +1 -1
- package/dist/utils/service/__tests__/billingCapUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.js +120 -0
- package/dist/utils/service/__tests__/billingCapUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js +1 -1
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js.map +1 -1
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts +2 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.js +243 -0
- package/dist/utils/service/__tests__/eventPromoterPhase3.test.js.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.js +333 -0
- package/dist/utils/service/__tests__/eventPromoterUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.js +52 -0
- package/dist/utils/service/__tests__/offerAllowListUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.js +111 -0
- package/dist/utils/service/__tests__/offerComparisonUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js +155 -0
- package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts +2 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.js +77 -0
- package/dist/utils/service/__tests__/unifiedPricingResolver.test.js.map +1 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js +157 -0
- package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.js +175 -0
- package/dist/utils/service/__tests__/venueSettlementUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js +118 -0
- package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js.map +1 -0
- package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -1
- package/dist/utils/service/apiServiceBookingApiUtils.js +4 -0
- package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
- package/dist/utils/service/billingCapUtils.d.ts +86 -0
- package/dist/utils/service/billingCapUtils.d.ts.map +1 -0
- package/dist/utils/service/billingCapUtils.js +96 -0
- package/dist/utils/service/billingCapUtils.js.map +1 -0
- package/dist/utils/service/cancellationPolicyRefundResolver.d.ts +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.d.ts.map +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.js +1 -1
- package/dist/utils/service/cancellationPolicyRefundResolver.js.map +1 -1
- package/dist/utils/service/eventPromoterUtils.d.ts +319 -0
- package/dist/utils/service/eventPromoterUtils.d.ts.map +1 -0
- package/dist/utils/service/eventPromoterUtils.js +372 -0
- package/dist/utils/service/eventPromoterUtils.js.map +1 -0
- package/dist/utils/service/offerAllowListUtils.d.ts +42 -0
- package/dist/utils/service/offerAllowListUtils.d.ts.map +1 -0
- package/dist/utils/service/offerAllowListUtils.js +49 -0
- package/dist/utils/service/offerAllowListUtils.js.map +1 -0
- package/dist/utils/service/offerComparisonUtils.d.ts +69 -0
- package/dist/utils/service/offerComparisonUtils.d.ts.map +1 -0
- package/dist/utils/service/offerComparisonUtils.js +110 -0
- package/dist/utils/service/offerComparisonUtils.js.map +1 -0
- package/dist/utils/service/serviceBookingExposureUtils.d.ts +111 -0
- package/dist/utils/service/serviceBookingExposureUtils.d.ts.map +1 -0
- package/dist/utils/service/serviceBookingExposureUtils.js +133 -0
- package/dist/utils/service/serviceBookingExposureUtils.js.map +1 -0
- package/dist/utils/service/serviceBookingTypes.d.ts +1 -1
- package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.d.ts +2 -1
- package/dist/utils/service/serviceUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.js +17 -16
- package/dist/utils/service/serviceUtils.js.map +1 -1
- package/dist/utils/service/unifiedPricingResolver.d.ts +67 -0
- package/dist/utils/service/unifiedPricingResolver.d.ts.map +1 -0
- package/dist/utils/service/unifiedPricingResolver.js +75 -0
- package/dist/utils/service/unifiedPricingResolver.js.map +1 -0
- package/dist/utils/service/venueFBSettlementUtils.d.ts +122 -0
- package/dist/utils/service/venueFBSettlementUtils.d.ts.map +1 -0
- package/dist/utils/service/venueFBSettlementUtils.js +106 -0
- package/dist/utils/service/venueFBSettlementUtils.js.map +1 -0
- package/dist/utils/service/venueSettlementUtils.d.ts +107 -0
- package/dist/utils/service/venueSettlementUtils.d.ts.map +1 -0
- package/dist/utils/service/venueSettlementUtils.js +117 -0
- package/dist/utils/service/venueSettlementUtils.js.map +1 -0
- package/dist/utils/service/volunteerCompensationUtils.d.ts +75 -0
- package/dist/utils/service/volunteerCompensationUtils.d.ts.map +1 -0
- package/dist/utils/service/volunteerCompensationUtils.js +88 -0
- package/dist/utils/service/volunteerCompensationUtils.js.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts +2 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js +67 -0
- package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts +2 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js +71 -0
- package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js.map +1 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.d.ts +33 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.js +49 -0
- package/dist/utils/venueLoyalty/eligibilityUtils.js.map +1 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts +35 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts.map +1 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.js +74 -0
- package/dist/utils/venueLoyalty/redemptionCodeUtils.js.map +1 -0
- package/dist/venueLoyaltyRedemption.d.ts +146 -1
- package/dist/venueLoyaltyRedemption.d.ts.map +1 -1
- package/dist/venueLoyaltyRedemption.js +18 -2
- package/dist/venueLoyaltyRedemption.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +638 -214
- package/src/__tests__/statusEnums.test.ts +119 -11
- package/src/definitions.ts +65 -22
- package/src/extendedSchemas.ts +8 -0
- package/src/index.ts +16 -6
- package/src/mirroredPrismaEnums.ts +181 -0
- package/src/utils/__tests__/nonprofitUtils.test.ts +117 -0
- package/src/utils/__tests__/paymentUtils.test.ts +93 -0
- package/src/utils/nonprofitUtils.ts +44 -0
- package/src/utils/paymentUtils.ts +33 -7
- package/src/utils/service/__tests__/billingCapUtils.test.ts +175 -0
- package/src/utils/service/__tests__/cancellationPolicyRefundResolver.test.ts +1 -1
- package/src/utils/service/__tests__/eventPromoterPhase3.test.ts +296 -0
- package/src/utils/service/__tests__/eventPromoterUtils.test.ts +396 -0
- package/src/utils/service/__tests__/offerAllowListUtils.test.ts +76 -0
- package/src/utils/service/__tests__/offerComparisonUtils.test.ts +124 -0
- package/src/utils/service/__tests__/serviceBookingExposureUtils.test.ts +171 -0
- package/src/utils/service/__tests__/unifiedPricingResolver.test.ts +83 -0
- package/src/utils/service/__tests__/venueSettlementUtils.test.ts +219 -0
- package/src/utils/service/__tests__/volunteerCompensationUtils.test.ts +172 -0
- package/src/utils/service/apiServiceBookingApiUtils.ts +4 -0
- package/src/utils/service/billingCapUtils.ts +145 -0
- package/src/utils/service/cancellationPolicyRefundResolver.ts +1 -1
- package/src/utils/service/eventPromoterUtils.ts +644 -0
- package/src/utils/service/offerAllowListUtils.ts +81 -0
- package/src/utils/service/offerComparisonUtils.ts +197 -0
- package/src/utils/service/serviceBookingExposureUtils.ts +218 -0
- package/src/utils/service/serviceBookingTypes.ts +4 -0
- package/src/utils/service/serviceUtils.ts +18 -21
- package/src/utils/service/unifiedPricingResolver.ts +149 -0
- package/src/utils/service/venueSettlementUtils.ts +198 -0
- package/src/utils/service/volunteerCompensationUtils.ts +145 -0
- package/src/utils/venueLoyalty/__tests__/eligibilityUtils.test.ts +103 -0
- package/src/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.ts +92 -0
- package/src/utils/venueLoyalty/eligibilityUtils.ts +84 -0
- package/src/utils/venueLoyalty/redemptionCodeUtils.ts +94 -0
- package/src/venueLoyaltyRedemption.ts +177 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure (no Prisma, no `bash-app` imports) eligibility evaluator for venue
|
|
3
|
+
* loyalty offers. Runs identically on the server (gating claim creation) and
|
|
4
|
+
* the client (rendering locked / progress states on the public loyalty
|
|
5
|
+
* section).
|
|
6
|
+
*
|
|
7
|
+
* The actual metric collection lives in
|
|
8
|
+
* `api/src/utils/venueLoyalty/eligibilityCalculator.ts` for the server and
|
|
9
|
+
* comes pre-computed from the eligibility endpoint for the client.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
VenueLoyaltyEligibilitySnapshot,
|
|
14
|
+
VenueLoyaltyOfferDto,
|
|
15
|
+
VenueLoyaltyOfferEligibility,
|
|
16
|
+
} from "../../venueLoyaltyRedemption.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Subset of `VenueLoyaltyOfferDto` the evaluator needs. Kept narrow so callers
|
|
20
|
+
* can pass in the Prisma row directly without remapping.
|
|
21
|
+
*/
|
|
22
|
+
export interface OfferGates {
|
|
23
|
+
id: string;
|
|
24
|
+
minTicketedEventsAttended: number | null;
|
|
25
|
+
minBookingsAsHost: number | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Evaluate one offer against one guest's metric snapshot. Optional
|
|
30
|
+
* `onCooldownUntil` (already computed from the latest non-active redemption +
|
|
31
|
+
* `offer.cooldownDays`) shadows eligibility so the UI shows "available in N
|
|
32
|
+
* days" instead of unlock progress.
|
|
33
|
+
*/
|
|
34
|
+
export function evaluateEligibility(
|
|
35
|
+
metrics: VenueLoyaltyEligibilitySnapshot,
|
|
36
|
+
offer: OfferGates,
|
|
37
|
+
onCooldownUntil: Date | string | null = null,
|
|
38
|
+
): VenueLoyaltyOfferEligibility {
|
|
39
|
+
const ticketedEventsShort =
|
|
40
|
+
offer.minTicketedEventsAttended !== null &&
|
|
41
|
+
offer.minTicketedEventsAttended > metrics.ticketedEventsAttended
|
|
42
|
+
? offer.minTicketedEventsAttended - metrics.ticketedEventsAttended
|
|
43
|
+
: null;
|
|
44
|
+
|
|
45
|
+
const bookingsAsHostShort =
|
|
46
|
+
offer.minBookingsAsHost !== null &&
|
|
47
|
+
offer.minBookingsAsHost > metrics.bookingsAsHost
|
|
48
|
+
? offer.minBookingsAsHost - metrics.bookingsAsHost
|
|
49
|
+
: null;
|
|
50
|
+
|
|
51
|
+
const cooldownIso =
|
|
52
|
+
onCooldownUntil instanceof Date
|
|
53
|
+
? onCooldownUntil.toISOString()
|
|
54
|
+
: onCooldownUntil;
|
|
55
|
+
|
|
56
|
+
const cooldownActive = cooldownIso !== null && new Date(cooldownIso) > new Date();
|
|
57
|
+
|
|
58
|
+
const gatesMet = ticketedEventsShort === null && bookingsAsHostShort === null;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
offerId: offer.id,
|
|
62
|
+
eligible: gatesMet && !cooldownActive,
|
|
63
|
+
missing: {
|
|
64
|
+
ticketedEventsShort,
|
|
65
|
+
bookingsAsHostShort,
|
|
66
|
+
},
|
|
67
|
+
onCooldownUntil: cooldownActive ? cooldownIso : null,
|
|
68
|
+
metrics,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Batch helper for rendering a venue's full offer list with eligibility in
|
|
74
|
+
* one pass. `cooldownMap` is keyed by `offerId`.
|
|
75
|
+
*/
|
|
76
|
+
export function evaluateOffersEligibility(
|
|
77
|
+
metrics: VenueLoyaltyEligibilitySnapshot,
|
|
78
|
+
offers: ReadonlyArray<Pick<VenueLoyaltyOfferDto, "id" | "minTicketedEventsAttended" | "minBookingsAsHost">>,
|
|
79
|
+
cooldownMap: Record<string, Date | string | null> = {},
|
|
80
|
+
): VenueLoyaltyOfferEligibility[] {
|
|
81
|
+
return offers.map((offer) =>
|
|
82
|
+
evaluateEligibility(metrics, offer, cooldownMap[offer.id] ?? null),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redemption code generation + validation for venue loyalty claims.
|
|
3
|
+
*
|
|
4
|
+
* Codes are 8 characters from a 32-letter alphabet that excludes the visually
|
|
5
|
+
* ambiguous `I`, `O`, `0`, `1` so a host typing the code from a phone screen
|
|
6
|
+
* doesn't fat-finger it. Collisions are scoped per-venue (see the unique
|
|
7
|
+
* constraint on `VenueLoyaltyRedemption(venueId, redemptionCode)`), so the
|
|
8
|
+
* 32^8 ≈ 1.1 trillion keyspace is enormous overkill — the retry loop in
|
|
9
|
+
* `generateUniqueRedemptionCode` exists to be defensive, not because we
|
|
10
|
+
* expect contention.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const REDEMPTION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
14
|
+
export const REDEMPTION_CODE_LENGTH = 8;
|
|
15
|
+
|
|
16
|
+
const REDEMPTION_CODE_REGEX = new RegExp(
|
|
17
|
+
`^[${REDEMPTION_CODE_ALPHABET}]{${REDEMPTION_CODE_LENGTH}}$`,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/** True iff `code` matches the canonical 8-char alphabet. */
|
|
21
|
+
export function isValidRedemptionCodeShape(code: string): boolean {
|
|
22
|
+
return REDEMPTION_CODE_REGEX.test(code);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Normalize user input — uppercase, strip whitespace + dashes — before
|
|
27
|
+
* validation or DB lookup. Lets us accept "ab12-cd34" from a phone keyboard.
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeRedemptionCode(input: string): string {
|
|
30
|
+
return input.replace(/[\s-]/g, "").toUpperCase();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Single 8-char code, sampled from a CSPRNG when available. */
|
|
34
|
+
export function generateRedemptionCode(
|
|
35
|
+
randomBytes: (length: number) => Uint8Array = defaultRandomBytes,
|
|
36
|
+
): string {
|
|
37
|
+
const bytes = randomBytes(REDEMPTION_CODE_LENGTH);
|
|
38
|
+
let out = "";
|
|
39
|
+
for (let i = 0; i < REDEMPTION_CODE_LENGTH; i += 1) {
|
|
40
|
+
out += REDEMPTION_CODE_ALPHABET[bytes[i] % REDEMPTION_CODE_ALPHABET.length];
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sample fresh codes until `existsInVenue(code)` returns false. Bounded to
|
|
47
|
+
* `maxAttempts` so a misbehaving uniqueness probe can't spin forever. Throws
|
|
48
|
+
* after exhausting attempts — caller should surface a 5xx, this would mean
|
|
49
|
+
* the keyspace is exhausted (impossible in practice) or the DB is unhealthy.
|
|
50
|
+
*/
|
|
51
|
+
export async function generateUniqueRedemptionCode(
|
|
52
|
+
existsInVenue: (code: string) => Promise<boolean>,
|
|
53
|
+
options: {
|
|
54
|
+
randomBytes?: (length: number) => Uint8Array;
|
|
55
|
+
maxAttempts?: number;
|
|
56
|
+
} = {},
|
|
57
|
+
): Promise<string> {
|
|
58
|
+
const maxAttempts = options.maxAttempts ?? 10;
|
|
59
|
+
const randomBytes = options.randomBytes ?? defaultRandomBytes;
|
|
60
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
61
|
+
const candidate = generateRedemptionCode(randomBytes);
|
|
62
|
+
if (!(await existsInVenue(candidate))) {
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Could not generate a unique venue loyalty redemption code after ${maxAttempts} attempts`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function defaultRandomBytes(length: number): Uint8Array {
|
|
72
|
+
const cryptoObj: Crypto | undefined =
|
|
73
|
+
typeof globalThis !== "undefined" &&
|
|
74
|
+
(globalThis as { crypto?: Crypto }).crypto
|
|
75
|
+
? (globalThis as { crypto?: Crypto }).crypto
|
|
76
|
+
: undefined;
|
|
77
|
+
if (cryptoObj?.getRandomValues) {
|
|
78
|
+
const buf = new Uint8Array(length);
|
|
79
|
+
cryptoObj.getRandomValues(buf);
|
|
80
|
+
return buf;
|
|
81
|
+
}
|
|
82
|
+
// Last-resort fallback for environments without WebCrypto (legacy tests).
|
|
83
|
+
// Not security-critical because redemption codes are scoped per-venue
|
|
84
|
+
// behind auth, but still tries to spread entropy across the buffer.
|
|
85
|
+
const buf = new Uint8Array(length);
|
|
86
|
+
for (let i = 0; i < length; i += 1) {
|
|
87
|
+
buf[i] = Math.floor(Math.random() * 256);
|
|
88
|
+
}
|
|
89
|
+
return buf;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const __testInternals = {
|
|
93
|
+
REDEMPTION_CODE_ALPHABET,
|
|
94
|
+
};
|
|
@@ -1,22 +1,197 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API shapes for venue loyalty perk
|
|
2
|
+
* API shapes for venue loyalty perk offers + redemptions (guest claim / decline
|
|
3
|
+
* / redeem / expire). All BashPoints movements through these flows ledger via
|
|
4
|
+
* `CreditSourceType.VenueLoyaltyRedemption` — see
|
|
5
|
+
* `api/src/services/venueLoyaltyRedemptionService.ts`.
|
|
3
6
|
*/
|
|
4
7
|
|
|
8
|
+
import type { VenueLoyaltyTemplateId } from "./utils/service/serviceUtils.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Public DTO for a `VenueLoyaltyOffer` row. `bashPointsCost` of `null` means
|
|
12
|
+
* the offer is a legacy "polite request" with no BashPoints price (host
|
|
13
|
+
* fulfills on goodwill).
|
|
14
|
+
*/
|
|
15
|
+
export interface VenueLoyaltyOfferDto {
|
|
16
|
+
id: string;
|
|
17
|
+
venueId: string;
|
|
18
|
+
templateId: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description: string | null;
|
|
21
|
+
bashPointsCost: number | null;
|
|
22
|
+
minTicketedEventsAttended: number | null;
|
|
23
|
+
minBookingsAsHost: number | null;
|
|
24
|
+
cooldownDays: number;
|
|
25
|
+
claimExpiryDays: number;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Payload for `POST /venue-loyalty/offers`. `title` defaults to the template
|
|
33
|
+
* label client-side if omitted; server validates it's non-empty before write.
|
|
34
|
+
*/
|
|
35
|
+
export interface CreateVenueLoyaltyOfferRequest {
|
|
36
|
+
venueId: string;
|
|
37
|
+
templateId: VenueLoyaltyTemplateId;
|
|
38
|
+
title: string;
|
|
39
|
+
description?: string | null;
|
|
40
|
+
bashPointsCost?: number | null;
|
|
41
|
+
minTicketedEventsAttended?: number | null;
|
|
42
|
+
minBookingsAsHost?: number | null;
|
|
43
|
+
cooldownDays?: number;
|
|
44
|
+
claimExpiryDays?: number;
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Payload for `PATCH /venue-loyalty/offers/:id`. */
|
|
49
|
+
export type UpdateVenueLoyaltyOfferRequest = Partial<
|
|
50
|
+
Omit<CreateVenueLoyaltyOfferRequest, "venueId" | "templateId">
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Snapshot of the gating metrics at claim time, persisted on the
|
|
55
|
+
* redemption row for audit. Stored as a Prisma `Json` column on the
|
|
56
|
+
* server; this type defines the canonical shape.
|
|
57
|
+
*/
|
|
58
|
+
export interface VenueLoyaltyEligibilitySnapshot {
|
|
59
|
+
ticketedEventsAttended: number;
|
|
60
|
+
bookingsAsHost: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Per-offer eligibility result for a specific guest viewing a venue's loyalty
|
|
65
|
+
* section. Drives the locked-state / progress-copy UI on the public detail page.
|
|
66
|
+
*/
|
|
67
|
+
export interface VenueLoyaltyOfferEligibility {
|
|
68
|
+
offerId: string;
|
|
69
|
+
eligible: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* How short the guest is on each gate, if any. `null` if no gate of that
|
|
72
|
+
* kind exists on the offer.
|
|
73
|
+
*/
|
|
74
|
+
missing: {
|
|
75
|
+
ticketedEventsShort: number | null;
|
|
76
|
+
bookingsAsHostShort: number | null;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* `claimedAt + cooldownDays` for the guest's most recent non-active claim
|
|
80
|
+
* against this offer, when the cooldown has not yet elapsed. `null` when no
|
|
81
|
+
* cooldown applies.
|
|
82
|
+
*/
|
|
83
|
+
onCooldownUntil: string | null;
|
|
84
|
+
/** Current metric values, useful for "1 of 3" style copy. */
|
|
85
|
+
metrics: VenueLoyaltyEligibilitySnapshot;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Response for `GET /venue-loyalty/eligibility/:venueId`. */
|
|
89
|
+
export interface VenueLoyaltyEligibilityResponse {
|
|
90
|
+
venueId: string;
|
|
91
|
+
offers: VenueLoyaltyOfferEligibility[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Guest-facing redemption DTO. `redemptionCode` is only populated for active
|
|
96
|
+
* (un-redeemed, un-declined, un-expired) claims so the guest can show it to
|
|
97
|
+
* the host. Once the redemption resolves, the code is preserved for history.
|
|
98
|
+
*/
|
|
5
99
|
export interface VenueLoyaltyRedemptionGuestDto {
|
|
6
100
|
id: string;
|
|
7
101
|
venueId: string;
|
|
102
|
+
offerId: string | null;
|
|
8
103
|
userId: string;
|
|
9
104
|
templateId: string;
|
|
105
|
+
bashPointsCost: number;
|
|
106
|
+
redemptionCode: string | null;
|
|
107
|
+
expiresAt: string | null;
|
|
10
108
|
claimedAt: string;
|
|
11
109
|
redeemedAt: string | null;
|
|
110
|
+
declinedAt: string | null;
|
|
111
|
+
expiredAt: string | null;
|
|
112
|
+
cancelReason: string | null;
|
|
12
113
|
guestNotes: string | null;
|
|
13
114
|
hostNotes: string | null;
|
|
14
115
|
}
|
|
15
116
|
|
|
16
|
-
|
|
117
|
+
/** Host-side row with guest identity for the venue's claims inbox. */
|
|
118
|
+
export interface VenueLoyaltyRedemptionListItem
|
|
119
|
+
extends VenueLoyaltyRedemptionGuestDto {
|
|
17
120
|
user: {
|
|
18
121
|
email: string;
|
|
19
122
|
givenName: string | null;
|
|
20
123
|
familyName: string | null;
|
|
21
124
|
};
|
|
125
|
+
eligibilitySnapshot: VenueLoyaltyEligibilitySnapshot | null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Lifecycle states a guest sees on the "My loyalty claims" page. */
|
|
129
|
+
export type VenueLoyaltyRedemptionStatus =
|
|
130
|
+
| "Active"
|
|
131
|
+
| "Redeemed"
|
|
132
|
+
| "Declined"
|
|
133
|
+
| "Expired";
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Compose the guest-facing status from the timestamp fields. Mirrors the
|
|
137
|
+
* server-side state machine; kept here so guards in UI + tests share one
|
|
138
|
+
* source of truth.
|
|
139
|
+
*/
|
|
140
|
+
export function getVenueLoyaltyRedemptionStatus(redemption: {
|
|
141
|
+
redeemedAt: string | null;
|
|
142
|
+
declinedAt: string | null;
|
|
143
|
+
expiredAt: string | null;
|
|
144
|
+
}): VenueLoyaltyRedemptionStatus {
|
|
145
|
+
if (redemption.redeemedAt) return "Redeemed";
|
|
146
|
+
if (redemption.declinedAt) return "Declined";
|
|
147
|
+
if (redemption.expiredAt) return "Expired";
|
|
148
|
+
return "Active";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Extended view returned by `GET /venue-loyalty/my-claims` so the guest sees
|
|
153
|
+
* which venue + offer the code is for without a second round trip.
|
|
154
|
+
*/
|
|
155
|
+
export interface VenueLoyaltyRedemptionMyClaimItem
|
|
156
|
+
extends VenueLoyaltyRedemptionGuestDto {
|
|
157
|
+
status: VenueLoyaltyRedemptionStatus;
|
|
158
|
+
venue: {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
serviceId: string | null;
|
|
162
|
+
};
|
|
163
|
+
offer: Pick<
|
|
164
|
+
VenueLoyaltyOfferDto,
|
|
165
|
+
"id" | "title" | "description" | "templateId"
|
|
166
|
+
> | null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Body for `POST /venue-loyalty/claim` (extended with `offerId`). */
|
|
170
|
+
export interface ClaimVenueLoyaltyRequest {
|
|
171
|
+
venueId: string;
|
|
172
|
+
offerId: string;
|
|
173
|
+
guestNotes?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Successful response for `POST /venue-loyalty/claim`. */
|
|
177
|
+
export interface ClaimVenueLoyaltyResponse {
|
|
178
|
+
redemption: VenueLoyaltyRedemptionGuestDto;
|
|
179
|
+
/**
|
|
180
|
+
* Convenience copy of `redemption.redemptionCode` so the success UI can
|
|
181
|
+
* pull it without branching on null. Equals `null` only when the offer was
|
|
182
|
+
* free (bashPointsCost == null) — currently still issued for traceability.
|
|
183
|
+
*/
|
|
184
|
+
code: string;
|
|
185
|
+
remainingBashPoints: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Body for `PATCH /venue-loyalty/:id/redeem` (extended with `redemptionCode`). */
|
|
189
|
+
export interface RedeemVenueLoyaltyRequest {
|
|
190
|
+
redemptionCode: string;
|
|
191
|
+
hostNotes?: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Body for `PATCH /venue-loyalty/:id/decline`. */
|
|
195
|
+
export interface DeclineVenueLoyaltyRequest {
|
|
196
|
+
hostNotes?: string;
|
|
22
197
|
}
|