dgc-core 2.0.4 → 2.0.7

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.
@@ -1,7 +1,7 @@
1
1
  import { GiveawayCategory, GiveawaySelectionMethod } from '../enums/giveaway';
2
2
  import { z } from 'zod';
3
3
  export declare const entryIdSchema: z.ZodString;
4
- export declare const draftSchema: z.ZodObject<{
4
+ export declare const draftSchema: z.ZodPipe<z.ZodObject<{
5
5
  images: z.ZodDefault<z.ZodArray<z.ZodString>>;
6
6
  title: z.ZodString;
7
7
  description: z.ZodString;
@@ -37,8 +37,80 @@ export declare const draftSchema: z.ZodObject<{
37
37
  message: z.ZodString;
38
38
  }, z.core.$strip>;
39
39
  }, z.core.$strip>>;
40
- }, z.core.$strip>;
41
- export declare const giveawaySchema: z.ZodObject<{
40
+ }, z.core.$strip>, z.ZodTransform<{
41
+ countries: string[];
42
+ images: string[];
43
+ title: string;
44
+ description: string;
45
+ image: string;
46
+ startsAt: string;
47
+ closesAt: string;
48
+ rules: string;
49
+ category: GiveawayCategory;
50
+ creditAmount: number;
51
+ claimWindowDays: number;
52
+ deliveryInformation: string;
53
+ prizes: {
54
+ name: string;
55
+ quantity: 1;
56
+ value: number;
57
+ _id?: string | undefined;
58
+ }[];
59
+ launch: {
60
+ enabled: boolean;
61
+ title: string;
62
+ message: string;
63
+ };
64
+ reminders: {
65
+ closing: {
66
+ enabled: boolean;
67
+ title: string;
68
+ message: string;
69
+ };
70
+ results: {
71
+ enabled: boolean;
72
+ title: string;
73
+ message: string;
74
+ };
75
+ };
76
+ }, {
77
+ images: string[];
78
+ title: string;
79
+ description: string;
80
+ image: string;
81
+ startsAt: string;
82
+ closesAt: string;
83
+ countries: string[];
84
+ rules: string;
85
+ category: GiveawayCategory;
86
+ creditAmount: number;
87
+ claimWindowDays: number;
88
+ deliveryInformation: string;
89
+ prizes: {
90
+ name: string;
91
+ quantity: 1;
92
+ value: number;
93
+ _id?: string | undefined;
94
+ }[];
95
+ launch: {
96
+ enabled: boolean;
97
+ title: string;
98
+ message: string;
99
+ };
100
+ reminders: {
101
+ closing: {
102
+ enabled: boolean;
103
+ title: string;
104
+ message: string;
105
+ };
106
+ results: {
107
+ enabled: boolean;
108
+ title: string;
109
+ message: string;
110
+ };
111
+ };
112
+ }>>;
113
+ export declare const giveawaySchema: z.ZodPipe<z.ZodObject<{
42
114
  description: z.ZodString;
43
115
  image: z.ZodString;
44
116
  rules: z.ZodString;
@@ -74,14 +146,85 @@ export declare const giveawaySchema: z.ZodObject<{
74
146
  message: z.ZodString;
75
147
  }, z.core.$strip>;
76
148
  }, z.core.$strip>>;
77
- }, z.core.$strip>;
149
+ }, z.core.$strip>, z.ZodTransform<{
150
+ countries: string[];
151
+ description: string;
152
+ image: string;
153
+ rules: string;
154
+ images: string[];
155
+ title: string;
156
+ startsAt: string;
157
+ closesAt: string;
158
+ category: GiveawayCategory;
159
+ creditAmount: number;
160
+ claimWindowDays: number;
161
+ deliveryInformation: string;
162
+ prizes: {
163
+ name: string;
164
+ quantity: 1;
165
+ value: number;
166
+ _id?: string | undefined;
167
+ }[];
168
+ launch: {
169
+ enabled: boolean;
170
+ title: string;
171
+ message: string;
172
+ };
173
+ reminders: {
174
+ closing: {
175
+ enabled: boolean;
176
+ title: string;
177
+ message: string;
178
+ };
179
+ results: {
180
+ enabled: boolean;
181
+ title: string;
182
+ message: string;
183
+ };
184
+ };
185
+ }, {
186
+ description: string;
187
+ image: string;
188
+ rules: string;
189
+ images: string[];
190
+ title: string;
191
+ startsAt: string;
192
+ closesAt: string;
193
+ countries: string[];
194
+ category: GiveawayCategory;
195
+ creditAmount: number;
196
+ claimWindowDays: number;
197
+ deliveryInformation: string;
198
+ prizes: {
199
+ name: string;
200
+ quantity: 1;
201
+ value: number;
202
+ _id?: string | undefined;
203
+ }[];
204
+ launch: {
205
+ enabled: boolean;
206
+ title: string;
207
+ message: string;
208
+ };
209
+ reminders: {
210
+ closing: {
211
+ enabled: boolean;
212
+ title: string;
213
+ message: string;
214
+ };
215
+ results: {
216
+ enabled: boolean;
217
+ title: string;
218
+ message: string;
219
+ };
220
+ };
221
+ }>>;
78
222
  export declare const entrySchema: z.ZodObject<{
79
- country: z.ZodOptional<z.ZodString>;
80
223
  rulesAccepted: z.ZodLiteral<true>;
81
224
  marketingConsent: z.ZodDefault<z.ZodBoolean>;
82
225
  }, z.core.$strip>;
83
226
  export declare const physicalClaimSchema: z.ZodObject<{
84
- fullName: z.ZodString;
227
+ fullName: z.ZodOptional<z.ZodString>;
85
228
  address: z.ZodObject<{
86
229
  line1: z.ZodString;
87
230
  line2: z.ZodDefault<z.ZodString>;
@@ -109,7 +252,7 @@ export declare const selectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
109
252
  reason: z.ZodString;
110
253
  revision: z.ZodNumber;
111
254
  }, z.core.$strip>], "method">;
112
- export declare const presetSchema: z.ZodObject<{
255
+ export declare const presetSchema: z.ZodPipe<z.ZodObject<{
113
256
  name: z.ZodString;
114
257
  title: z.ZodString;
115
258
  description: z.ZodString;
@@ -120,4 +263,26 @@ export declare const presetSchema: z.ZodObject<{
120
263
  countries: z.ZodArray<z.ZodString>;
121
264
  rules: z.ZodString;
122
265
  deliveryInformation: z.ZodDefault<z.ZodString>;
123
- }, z.core.$strip>;
266
+ }, z.core.$strip>, z.ZodTransform<{
267
+ countries: string[];
268
+ name: string;
269
+ title: string;
270
+ description: string;
271
+ image: string;
272
+ images: string[];
273
+ category: GiveawayCategory;
274
+ creditAmount: number;
275
+ rules: string;
276
+ deliveryInformation: string;
277
+ }, {
278
+ name: string;
279
+ title: string;
280
+ description: string;
281
+ image: string;
282
+ images: string[];
283
+ category: GiveawayCategory;
284
+ creditAmount: number;
285
+ countries: string[];
286
+ rules: string;
287
+ deliveryInformation: string;
288
+ }>>;
@@ -67,7 +67,8 @@ exports.draftSchema = zod_1.z
67
67
  .refine(v => new Date(v.closesAt) > new Date(v.startsAt), {
68
68
  message: 'Closing time must be after start time',
69
69
  path: ['closesAt']
70
- });
70
+ })
71
+ .transform(v => ({ ...v, countries: v.category === giveaway_1.GiveawayCategory.Credits ? [] : v.countries }));
71
72
  exports.giveawaySchema = zod_1.z
72
73
  .object({
73
74
  ...fields,
@@ -86,14 +87,14 @@ exports.giveawaySchema = zod_1.z
86
87
  for (const [name, value] of Object.entries({ launch: v.launch, ...v.reminders }))
87
88
  if (value.enabled && (!value.title || !value.message))
88
89
  issue([name], 'Add a push title and message');
89
- });
90
+ })
91
+ .transform(v => ({ ...v, countries: v.category === giveaway_1.GiveawayCategory.Credits ? [] : v.countries }));
90
92
  exports.entrySchema = zod_1.z.object({
91
- country: country.optional(),
92
93
  rulesAccepted: zod_1.z.literal(true, { error: 'You must agree to the giveaway rules' }),
93
94
  marketingConsent: zod_1.z.boolean().default(false)
94
95
  });
95
96
  exports.physicalClaimSchema = zod_1.z.object({
96
- fullName: zod_1.z.string().trim().min(2).max(120),
97
+ fullName: zod_1.z.string().trim().min(2).max(120).optional(),
97
98
  address: zod_1.z.object({
98
99
  line1: zod_1.z.string().trim().min(2).max(200),
99
100
  line2: zod_1.z.string().trim().max(200).default(''),
@@ -139,4 +140,5 @@ exports.presetSchema = zod_1.z
139
140
  .refine(v => v.category !== giveaway_1.GiveawayCategory.Credits || v.creditAmount > 0, {
140
141
  message: 'Configure the instant credit reward amount',
141
142
  path: ['creditAmount']
142
- });
143
+ })
144
+ .transform(v => ({ ...v, countries: v.category === giveaway_1.GiveawayCategory.Credits ? [] : v.countries }));
package/dist/index.d.ts CHANGED
@@ -20,3 +20,5 @@ export * from './validator';
20
20
  export * from './id';
21
21
  export * from './http-error';
22
22
  export * from './giveaways';
23
+ export * from './constants/revenuecat';
24
+ export * from './revenuecat';
package/dist/index.js CHANGED
@@ -36,3 +36,5 @@ __exportStar(require("./validator"), exports);
36
36
  __exportStar(require("./id"), exports);
37
37
  __exportStar(require("./http-error"), exports);
38
38
  __exportStar(require("./giveaways"), exports);
39
+ __exportStar(require("./constants/revenuecat"), exports);
40
+ __exportStar(require("./revenuecat"), exports);
@@ -0,0 +1,16 @@
1
+ import { HttpError } from './http-error';
2
+ import type { GiveawayCreditEligibilityEvidence, RevenueCatClient, RevenueCatEntitlementIds } from './types/revenuecat';
3
+ export type RevenueCatClientOptions = {
4
+ apiKey: string;
5
+ projectId: string;
6
+ timeoutMs?: number;
7
+ totalTimeoutMs?: number;
8
+ maxPages?: number;
9
+ };
10
+ /** Deliberately excludes the original HTTP error, which can contain the API key. */
11
+ export declare class RevenueCatLookupError extends HttpError {
12
+ constructor();
13
+ }
14
+ export declare function createRevenueCatClient(options: RevenueCatClientOptions): RevenueCatClient;
15
+ /** Resolve any eligible entitlement from one customer lookup; never infer Free from API failures. */
16
+ export declare function verifyCreditGiveawayEntitlement(userId: string, client: RevenueCatClient, entitlementIds: RevenueCatEntitlementIds): Promise<GiveawayCreditEligibilityEvidence | null>;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RevenueCatLookupError = void 0;
7
+ exports.createRevenueCatClient = createRevenueCatClient;
8
+ exports.verifyCreditGiveawayEntitlement = verifyCreditGiveawayEntitlement;
9
+ const axios_1 = __importDefault(require("axios"));
10
+ const revenuecat_1 = require("./constants/revenuecat");
11
+ const user_1 = require("./enums/user");
12
+ const http_error_1 = require("./http-error");
13
+ /** Deliberately excludes the original HTTP error, which can contain the API key. */
14
+ class RevenueCatLookupError extends http_error_1.HttpError {
15
+ constructor() {
16
+ super('Unable to verify RevenueCat entitlement access. Please try again.', 503);
17
+ this.name = 'RevenueCatLookupError';
18
+ }
19
+ }
20
+ exports.RevenueCatLookupError = RevenueCatLookupError;
21
+ function positiveInteger(value, maximum) {
22
+ return Number.isInteger(value) && value > 0 && value <= maximum;
23
+ }
24
+ function validUserId(value) {
25
+ return (typeof value === 'string' &&
26
+ value.length > 0 &&
27
+ value.length <= 128 &&
28
+ value.trim() === value &&
29
+ !/[.#$[\]/]/.test(value) &&
30
+ !Array.from(value).some(character => {
31
+ const code = character.charCodeAt(0);
32
+ return code < 32 || code === 127;
33
+ }));
34
+ }
35
+ function parsePage(value) {
36
+ if (!value || typeof value !== 'object' || Array.isArray(value))
37
+ throw new RevenueCatLookupError();
38
+ const page = value;
39
+ if (!Array.isArray(page.items) ||
40
+ (page.next_page != null && (typeof page.next_page !== 'string' || !page.next_page)))
41
+ throw new RevenueCatLookupError();
42
+ const items = page.items.map((raw) => {
43
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
44
+ throw new RevenueCatLookupError();
45
+ const entitlement = raw;
46
+ if (typeof entitlement.entitlement_id !== 'string' ||
47
+ !/^entl[a-zA-Z0-9]+$/.test(entitlement.entitlement_id) ||
48
+ (entitlement.expires_at !== null &&
49
+ (typeof entitlement.expires_at !== 'number' ||
50
+ !Number.isSafeInteger(entitlement.expires_at) ||
51
+ entitlement.expires_at < 0)))
52
+ throw new RevenueCatLookupError();
53
+ return {
54
+ entitlementId: entitlement.entitlement_id,
55
+ expiresAt: entitlement.expires_at
56
+ };
57
+ });
58
+ return { items, nextPage: page.next_page ?? null };
59
+ }
60
+ function retryAfterMs(value) {
61
+ if (typeof value === 'number')
62
+ return Number.isFinite(value) && value >= 0 ? value * 1000 : null;
63
+ if (typeof value !== 'string' || !value.trim())
64
+ return null;
65
+ const text = value.trim();
66
+ if (/^\d+$/.test(text)) {
67
+ const seconds = Number(text);
68
+ return Number.isFinite(seconds) ? seconds * 1000 : null;
69
+ }
70
+ const date = Date.parse(text);
71
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
72
+ }
73
+ /** Inspect only retry metadata; never retain or log the original transport error. */
74
+ function requestRetryDelay(error, attempt) {
75
+ if (!error || typeof error !== 'object')
76
+ return null;
77
+ const failure = error;
78
+ const response = failure.response && typeof failure.response === 'object'
79
+ ? failure.response
80
+ : undefined;
81
+ if (response) {
82
+ if (typeof response.status !== 'number' || ![429, 500, 502, 503, 504].includes(response.status))
83
+ return null;
84
+ }
85
+ else if (typeof failure.code !== 'string' ||
86
+ ![
87
+ 'ECONNRESET',
88
+ 'ETIMEDOUT',
89
+ 'ECONNABORTED',
90
+ 'EAI_AGAIN',
91
+ 'ENOTFOUND',
92
+ 'ERR_NETWORK',
93
+ 'EPIPE',
94
+ 'ECONNREFUSED'
95
+ ].includes(failure.code))
96
+ return null;
97
+ let headerDelay = null;
98
+ if (response?.headers && typeof response.headers === 'object') {
99
+ const headers = response.headers;
100
+ headerDelay = retryAfterMs(typeof headers.get === 'function'
101
+ ? headers.get('retry-after')
102
+ : (headers['retry-after'] ?? headers['Retry-After']));
103
+ }
104
+ const body = response?.data && typeof response.data === 'object'
105
+ ? response.data
106
+ : undefined;
107
+ const bodyDelay = typeof body?.backoff_ms === 'number' && Number.isFinite(body.backoff_ms) && body.backoff_ms >= 0
108
+ ? body.backoff_ms
109
+ : 0;
110
+ return Math.max(100 * 2 ** attempt, headerDelay ?? 0, bodyDelay);
111
+ }
112
+ async function requestPage(url, apiKey, timeoutMs, deadline) {
113
+ for (let attempt = 0; attempt < 3; attempt++) {
114
+ const remainingMs = deadline - Date.now();
115
+ if (remainingMs <= 0)
116
+ throw new RevenueCatLookupError();
117
+ try {
118
+ return await axios_1.default.get(url.href, {
119
+ headers: { Authorization: `Bearer ${apiKey}` },
120
+ timeout: Math.min(timeoutMs, remainingMs),
121
+ signal: AbortSignal.timeout(remainingMs),
122
+ maxRedirects: 0,
123
+ maxContentLength: 1024 * 1024
124
+ });
125
+ }
126
+ catch (error) {
127
+ const delay = attempt < 2 ? requestRetryDelay(error, attempt) : null;
128
+ if (delay === null || delay >= deadline - Date.now())
129
+ throw new RevenueCatLookupError();
130
+ await new Promise(resolve => setTimeout(resolve, delay));
131
+ }
132
+ }
133
+ throw new RevenueCatLookupError();
134
+ }
135
+ function createRevenueCatClient(options) {
136
+ const apiKey = options.apiKey?.trim();
137
+ const projectId = options.projectId?.trim();
138
+ const timeoutMs = options.timeoutMs ?? 15000;
139
+ const totalTimeoutMs = options.totalTimeoutMs ?? 15000;
140
+ const maxPages = options.maxPages ?? 20;
141
+ if (!apiKey || /[\r\n]/.test(apiKey))
142
+ throw new Error('Configure the RevenueCat API key');
143
+ if (!projectId || projectId.length > 255 || !/^[a-zA-Z0-9_-]+$/.test(projectId))
144
+ throw new Error('Configure a valid RevenueCat project ID');
145
+ if (!positiveInteger(timeoutMs, 60000) ||
146
+ !positiveInteger(totalTimeoutMs, 120000) ||
147
+ !positiveInteger(maxPages, 100))
148
+ throw new Error('Configure valid RevenueCat request limits');
149
+ return {
150
+ async getActiveEntitlements(userId) {
151
+ if (!validUserId(userId))
152
+ throw new http_error_1.HttpError('Invalid DGC user ID', 400);
153
+ const endpoint = new URL(`${revenuecat_1.REVENUECAT_API_V2}/projects/${encodeURIComponent(projectId)}/customers/${encodeURIComponent(userId)}/active_entitlements`);
154
+ endpoint.searchParams.set('limit', '100');
155
+ const deadline = Date.now() + totalTimeoutMs;
156
+ const visited = new Set();
157
+ const entitlements = [];
158
+ let url = endpoint;
159
+ try {
160
+ while (url) {
161
+ const remainingMs = deadline - Date.now();
162
+ if (remainingMs <= 0 ||
163
+ visited.size >= maxPages ||
164
+ url.origin !== endpoint.origin ||
165
+ url.pathname !== endpoint.pathname ||
166
+ url.username ||
167
+ url.password ||
168
+ url.hash ||
169
+ visited.has(url.href))
170
+ throw new RevenueCatLookupError();
171
+ visited.add(url.href);
172
+ const response = await requestPage(url, apiKey, timeoutMs, deadline);
173
+ const page = parsePage(response.data);
174
+ entitlements.push(...page.items);
175
+ url = page.nextPage ? new URL(page.nextPage, endpoint) : null;
176
+ }
177
+ const now = Date.now();
178
+ if (now >= deadline)
179
+ throw new RevenueCatLookupError();
180
+ return entitlements.filter(item => item.expiresAt === null || item.expiresAt > now);
181
+ }
182
+ catch {
183
+ // Do not attach request/config/cause or log the customer payload.
184
+ throw new RevenueCatLookupError();
185
+ }
186
+ }
187
+ };
188
+ }
189
+ /** Resolve any eligible entitlement from one customer lookup; never infer Free from API failures. */
190
+ async function verifyCreditGiveawayEntitlement(userId, client, entitlementIds) {
191
+ const ids = (0, revenuecat_1.createRevenueCatEntitlementIds)(entitlementIds);
192
+ const active = await client.getActiveEntitlements(userId);
193
+ const checkedAt = new Date().toISOString();
194
+ const now = Date.parse(checkedAt);
195
+ const tiers = [
196
+ [user_1.UserCollectorType.Elite, ids.elite],
197
+ [user_1.UserCollectorType.Collector, ids.collector],
198
+ [user_1.UserCollectorType.Rookie, ids.rookie]
199
+ ];
200
+ for (const [collectorType, entitlementId] of tiers) {
201
+ const match = active.find(item => item.entitlementId === entitlementId && (item.expiresAt === null || item.expiresAt > now));
202
+ if (match)
203
+ return { userId, checkedAt, collectorType, ...match };
204
+ }
205
+ return null;
206
+ }
@@ -1,6 +1,7 @@
1
1
  import type { GiveawayStatus, GiveawayCategory, GiveawaySelectionMethod, GiveawayWinnerStatus, GiveawayNotificationKind, GiveawayNotificationAudience, GiveawayNotificationStatus, GiveawayTicketResult, GiveawayAction, GiveawayNotificationChannel } from '../enums/giveaway';
2
2
  import type { FbUser } from './user';
3
3
  import type { UserCollectorType } from '../enums/user';
4
+ import type { GiveawayCreditEligibilityEvidence } from './revenuecat';
4
5
  /** API identities are strings. MongoDB ObjectIds serialize as 24 lowercase hexadecimal characters. */
5
6
  export type GiveawayObjectId = string;
6
7
  /** New tickets use DGC- followed by twelve uppercase letters or digits. Existing numeric IDs remain readable. */
@@ -16,6 +17,7 @@ export type GiveawaySelection = {
16
17
  reason: string;
17
18
  selectedBy: string;
18
19
  selectedAt: string;
20
+ creditEligibility?: GiveawayCreditEligibilityEvidence;
19
21
  };
20
22
  export type GiveawayPrize = {
21
23
  _id: GiveawayObjectId;
@@ -72,10 +74,13 @@ export type GiveawayEntry = {
72
74
  userId: string;
73
75
  name: string;
74
76
  email: string;
75
- /** Historical entry addresses are retained; new entries collect delivery only at claim. */
77
+ /** Product delivery details are recorded only after the winner claims. */
76
78
  address?: GiveawayDeliveryAddress;
79
+ deliveryName?: string;
80
+ contactNumber?: string;
81
+ /** Retained only for historical entry records; new entries collect country at claim. */
77
82
  country?: string;
78
- /** Null only for legacy entries whose exact collector tier was not recorded. */
83
+ /** Null when the account tier is missing or unsupported; product entries remain eligible. */
79
84
  _collectorType: UserCollectorType | null;
80
85
  enteredAt: string;
81
86
  rulesAcceptedAt: string;
@@ -93,6 +98,7 @@ export type GiveawayWinner = {
93
98
  status: GiveawayWinnerStatus;
94
99
  userId?: string;
95
100
  selection?: GiveawaySelection;
101
+ creditEligibility?: GiveawayCreditEligibilityEvidence;
96
102
  category: GiveawayCategory;
97
103
  creditAmount?: number;
98
104
  claimDeadline?: string;
@@ -124,6 +130,14 @@ export type GiveawayNotificationJob = {
124
130
  /** Stable event key supports replacements without resending a prior event. */
125
131
  eventKey?: string;
126
132
  audience?: GiveawayNotificationAudience;
133
+ /** Snapshots keep targeting and expiry stable throughout a broadcast. */
134
+ category?: GiveawayCategory;
135
+ expiresAt?: string;
136
+ dispatchAfter?: string;
137
+ dispatchGeneration?: number;
138
+ recipientsQueued?: number;
139
+ recipientsSkipped?: number;
140
+ dispatchCompletedAt?: string;
127
141
  status: GiveawayNotificationStatus;
128
142
  attempts: number;
129
143
  error?: string;
@@ -170,6 +184,12 @@ export type GiveawayWinnerRecord = GiveawayWinner & {
170
184
  };
171
185
  export type GiveawayNotificationRecord = Omit<GiveawayNotificationJob, 'claimToken'> & {
172
186
  giveawayTitle: string;
187
+ /** Queue acceptance counts, not confirmation of device delivery. */
188
+ deliveryCounts?: {
189
+ queued: number;
190
+ skipped: number;
191
+ pending: number;
192
+ };
173
193
  };
174
194
  export type GiveawayOverviewData = {
175
195
  days: {
@@ -179,14 +199,20 @@ export type GiveawayOverviewData = {
179
199
  liveGiveaways: GiveawaySummary[];
180
200
  needsSelection: Giveaway[];
181
201
  };
182
- export type GiveawayUserProfile = Pick<FbUser, 'email' | 'firstName' | 'lastName' | '_collectorType'> & {
202
+ export type GiveawayUserProfile = Pick<FbUser, 'email' | 'firstName' | 'lastName'> & {
183
203
  userId: string;
204
+ _collectorType?: UserCollectorType | null;
184
205
  };
185
206
  export type GiveawayPhysicalClaim = {
186
207
  fullName: string;
187
208
  address: GiveawayDeliveryAddress;
188
209
  contactNumber: string;
189
210
  };
211
+ export type GiveawayPhysicalClaimInput = {
212
+ fullName?: string;
213
+ address: Omit<GiveawayDeliveryAddress, 'line2' | 'region'> & Partial<Pick<GiveawayDeliveryAddress, 'line2' | 'region'>>;
214
+ contactNumber: string;
215
+ };
190
216
  export type GiveawayTicket = {
191
217
  entry: Pick<GiveawayEntry, '_id' | 'giveawayId' | 'enteredAt' | 'rulesSnapshot'> & {
192
218
  tickets: 1;
@@ -197,6 +223,10 @@ export type GiveawayTicket = {
197
223
  };
198
224
  export type GiveawayClaim = Pick<GiveawayWinner, '_id' | 'giveawayId' | 'entryId' | 'creditAmount' | 'category' | 'status' | 'claimDeadline' | 'claimedAt' | 'claim' | 'courier' | 'tracking' | 'dispatchedAt' | 'creditedAt'> & {
199
225
  claimExpired: boolean;
226
+ /** Product delivery name defaults to the entrant name and may be edited. */
227
+ defaults?: {
228
+ fullName: string;
229
+ };
200
230
  };
201
231
  export type GiveawayPreset = {
202
232
  _id: GiveawayObjectId;
@@ -245,7 +275,6 @@ export type GiveawayTransitionInput = {
245
275
  revision: number;
246
276
  };
247
277
  export type GiveawayEntryInput = {
248
- country?: string;
249
278
  rulesAccepted: true;
250
279
  marketingConsent?: boolean;
251
280
  };
@@ -268,6 +297,8 @@ export type GiveawayWinnerPreview = {
268
297
  };
269
298
  /** Public app response; excludes admin settings, prize values and personal data. */
270
299
  export type GiveawayPublic = Pick<Giveaway, '_id' | 'title' | 'description' | 'image' | 'status' | 'startsAt' | 'closesAt' | 'countries' | 'rules' | 'category' | 'creditAmount' | 'deliveryInformation'> & {
300
+ /** Null permits all authenticated accounts regardless of their stored tier. */
301
+ eligibleCollectorTypes: UserCollectorType[] | null;
271
302
  entryCount: number;
272
303
  images: string[];
273
304
  prizes: Pick<GiveawayPrize, 'name' | 'quantity'>[];
@@ -19,3 +19,5 @@ export * from './user';
19
19
  export * from './x-credit';
20
20
  export * from './giveaway';
21
21
  export * from '../enums/giveaway';
22
+ export * from './revenuecat';
23
+ export * from '../constants/revenuecat';
@@ -35,3 +35,5 @@ __exportStar(require("./user"), exports);
35
35
  __exportStar(require("./x-credit"), exports);
36
36
  __exportStar(require("./giveaway"), exports);
37
37
  __exportStar(require("../enums/giveaway"), exports);
38
+ __exportStar(require("./revenuecat"), exports);
39
+ __exportStar(require("../constants/revenuecat"), exports);
@@ -0,0 +1,19 @@
1
+ import type { UserCollectorType } from '../enums/user';
2
+ export type RevenueCatActiveEntitlement = {
3
+ entitlementId: string;
4
+ /** RevenueCat uses milliseconds since epoch; null is non-expiring access. */
5
+ expiresAt: number | null;
6
+ };
7
+ export type RevenueCatEntitlementIds = Readonly<{
8
+ rookie: string;
9
+ collector: string;
10
+ elite: string;
11
+ }>;
12
+ export type RevenueCatClient = {
13
+ getActiveEntitlements(userId: string): Promise<RevenueCatActiveEntitlement[]>;
14
+ };
15
+ export type GiveawayCreditEligibilityEvidence = RevenueCatActiveEntitlement & {
16
+ userId: string;
17
+ checkedAt: string;
18
+ collectorType: UserCollectorType.Rookie | UserCollectorType.Collector | UserCollectorType.Elite;
19
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dgc-core",
3
- "version": "2.0.4",
3
+ "version": "2.0.7",
4
4
  "description": "Shared DGC contracts, business rules and server utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",