dgc-core 2.0.0 → 2.0.3

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.
@@ -2,6 +2,6 @@ export declare enum UserCollectorType {
2
2
  'Non-member' = -1,
3
3
  Free = 2,
4
4
  Rookie = 1,
5
- Collector = 0,
6
- Elite = 3
5
+ Collector = 3,
6
+ Elite = 0
7
7
  }
@@ -6,6 +6,6 @@ var UserCollectorType;
6
6
  UserCollectorType[UserCollectorType["Non-member"] = -1] = "Non-member";
7
7
  UserCollectorType[UserCollectorType["Free"] = 2] = "Free";
8
8
  UserCollectorType[UserCollectorType["Rookie"] = 1] = "Rookie";
9
- UserCollectorType[UserCollectorType["Collector"] = 0] = "Collector";
10
- UserCollectorType[UserCollectorType["Elite"] = 3] = "Elite";
9
+ UserCollectorType[UserCollectorType["Collector"] = 3] = "Collector";
10
+ UserCollectorType[UserCollectorType["Elite"] = 0] = "Elite";
11
11
  })(UserCollectorType || (exports.UserCollectorType = UserCollectorType = {}));
@@ -0,0 +1,6 @@
1
+ /** Mobile giveaway pagination; private dashboard pagination keeps its own contract. */
2
+ export declare function parseGiveawayApiPagination(params: URLSearchParams): {
3
+ page: number;
4
+ pageSize: number;
5
+ skip: number;
6
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseGiveawayApiPagination = parseGiveawayApiPagination;
4
+ const http_error_1 = require("../http-error");
5
+ /** Mobile giveaway pagination; private dashboard pagination keeps its own contract. */
6
+ function parseGiveawayApiPagination(params) {
7
+ const invalid = () => new http_error_1.HttpError('Invalid page or size parameter', 400);
8
+ const integer = (key, fallback) => {
9
+ const values = params.getAll(key);
10
+ if (!values.length)
11
+ return fallback;
12
+ if (values.length !== 1 || !/^\d+$/.test(values[0]))
13
+ throw invalid();
14
+ const value = Number(values[0]);
15
+ if (!Number.isSafeInteger(value) || value < 1)
16
+ throw invalid();
17
+ return value;
18
+ };
19
+ const page = integer('page', 1);
20
+ const pageSize = integer('size', 20);
21
+ const skip = (page - 1) * pageSize;
22
+ if (pageSize > 50 || !Number.isSafeInteger(skip))
23
+ throw invalid();
24
+ return { page, pageSize, skip };
25
+ }
@@ -177,14 +177,14 @@ function submitEntry(g, account, raw) {
177
177
  new Date(g.startsAt) > now ||
178
178
  new Date(g.closesAt) <= now)
179
179
  throw new http_error_1.HttpError('This giveaway is not accepting entries', 409);
180
- const country = input.country || account.country;
180
+ const country = input.country;
181
181
  if (g.countries.length && (!country || !g.countries.includes(country)))
182
182
  throw new http_error_1.HttpError('This country is not eligible', 422);
183
183
  const at = now.toISOString();
184
184
  return {
185
185
  giveawayId: g._id,
186
186
  userId: account.userId,
187
- fullName: [account.firstName, account.lastName].filter(Boolean).join(' ').trim() || account.email,
187
+ name: account.name,
188
188
  email: account.email,
189
189
  username: account.username,
190
190
  country,
@@ -1,7 +1,7 @@
1
1
  import { GiveawayAction, GiveawayCategory, GiveawaySelectionMethod, GiveawayStatus, GiveawayWinnerStatus } from '../enums/giveaway';
2
2
  import { UserCollectorType } from '../enums/user';
3
3
  import type { ClientSession, Db, MongoClient } from 'mongodb';
4
- import type { GiveawayPreset, GiveawayTicket, GiveawayClaim } from '../types';
4
+ import type { GiveawayPreset, GiveawayTicket, GiveawayClaim, PaginatedApiResponse } from '../types';
5
5
  import type { UserProfile, AuditEvent, DashboardData, Entry, EntryRecord, Giveaway, GiveawayDetailData, GiveawaySummary, NotificationJob, NotificationRecord, OverviewData, PageResult, Winner, WinnerRecord } from './types';
6
6
  type Stored<T extends {
7
7
  _id: string;
@@ -26,7 +26,7 @@ export declare class GiveawayRepository {
26
26
  private audit;
27
27
  getGiveaway(id: string, session?: ClientSession): Promise<Giveaway>;
28
28
  getPublicGiveaway(id: string): Promise<GiveawaySummary>;
29
- listPublicGiveaways(params?: URLSearchParams): Promise<PageResult<GiveawaySummary>>;
29
+ listPublicGiveaways(params?: URLSearchParams): Promise<PaginatedApiResponse<GiveawaySummary>>;
30
30
  listGiveaways(params?: URLSearchParams): Promise<PageResult<GiveawaySummary>>;
31
31
  getDashboard(admin: string): Promise<DashboardData>;
32
32
  getOverview(days?: number): Promise<OverviewData>;
@@ -53,12 +53,12 @@ export declare class GiveawayRepository {
53
53
  _id: string;
54
54
  email: string;
55
55
  _collectorType: UserCollectorType | null;
56
+ name: string;
56
57
  giveawayId: import("../types").GiveawayObjectId;
57
58
  enteredAt: string;
58
59
  username?: string | undefined;
59
60
  rulesSnapshot: string;
60
61
  userId: string;
61
- fullName: string;
62
62
  address?: import("../types").GiveawayDeliveryAddress | undefined;
63
63
  country?: string | undefined;
64
64
  rulesAcceptedAt: string;
@@ -233,12 +233,7 @@ export declare class GiveawayRepository {
233
233
  _id: string;
234
234
  }>;
235
235
  getTicket(userId: string, entryId: string): Promise<GiveawayTicket>;
236
- listTickets(userId: string, params: URLSearchParams): Promise<{
237
- data: GiveawayTicket[];
238
- total: number;
239
- page: number;
240
- limit: number;
241
- }>;
236
+ listTickets(userId: string, params: URLSearchParams): Promise<PaginatedApiResponse<GiveawayTicket>>;
242
237
  getUserClaim(giveawayId: string, userId: string): Promise<GiveawayClaim>;
243
238
  claimPrize(giveawayId: string, userId: string, raw: unknown): Promise<Omit<import("mongodb").WithId<Stored<Winner>>, "_id"> & {
244
239
  _id: string;
@@ -8,6 +8,7 @@ const user_1 = require("../enums/user");
8
8
  const node_crypto_1 = require("node:crypto");
9
9
  const mongodb_1 = require("mongodb");
10
10
  const entry_id_1 = require("./entry-id");
11
+ const api_pagination_1 = require("./api-pagination");
11
12
  const domain_1 = require("./domain");
12
13
  const schemas_1 = require("./schemas");
13
14
  const projection = { entryVersion: 0 };
@@ -21,6 +22,11 @@ function serialize(record) {
21
22
  return { ...record, _id: record._id.toHexString() };
22
23
  }
23
24
  const statuses = Object.values(giveaway_2.GiveawayStatus);
25
+ /** Normalize historical entry names at read boundaries without rewriting stored records. */
26
+ function normalizeEntry(record) {
27
+ const { fullName, ...fields } = record;
28
+ return { ...fields, name: record.name ?? fullName ?? '' };
29
+ }
24
30
  function pagination(params) {
25
31
  const integer = (key, fallback, max) => {
26
32
  const value = Number(params.get(key));
@@ -179,9 +185,7 @@ class GiveawayRepository {
179
185
  return { ...g, status, entryCount: await this.entries.countDocuments({ giveawayId: id }) };
180
186
  }
181
187
  async listPublicGiveaways(params = new URLSearchParams()) {
182
- const { page, limit: requestedLimit } = pagination(params);
183
- const limit = Math.min(requestedLimit, 50);
184
- const skip = (page - 1) * limit;
188
+ const { page, pageSize, skip } = (0, api_pagination_1.parseGiveawayApiPagination)(params);
185
189
  const filter = {
186
190
  $or: [
187
191
  {
@@ -204,20 +208,21 @@ class GiveawayRepository {
204
208
  { $match: filter },
205
209
  { $sort: { createdAt: -1, _id: 1 } },
206
210
  { $skip: skip },
207
- { $limit: limit },
211
+ { $limit: pageSize },
208
212
  ...entryCountLookup
209
213
  ])
210
214
  .toArray(),
211
215
  this.giveaways.countDocuments(filter)
212
216
  ]);
213
217
  return {
218
+ page,
219
+ pageSize,
220
+ count: rows.length,
221
+ totalCount: total,
214
222
  data: rows.map(row => {
215
223
  const g = row;
216
224
  return { ...g, status: (0, domain_1.effectiveStatus)(g) };
217
- }),
218
- total,
219
- page,
220
- limit
225
+ })
221
226
  };
222
227
  }
223
228
  async listGiveaways(params = new URLSearchParams()) {
@@ -411,6 +416,7 @@ class GiveawayRepository {
411
416
  const q = search(params);
412
417
  if (q)
413
418
  filter.$or = [
419
+ 'name',
414
420
  'fullName',
415
421
  '_id',
416
422
  'username',
@@ -470,7 +476,7 @@ class GiveawayRepository {
470
476
  .aggregate([...pipeline, { $facet: { data, total: [{ $count: 'count' }] } }])
471
477
  .next();
472
478
  return {
473
- data: result?.data || [],
479
+ data: (result?.data || []).map(normalizeEntry),
474
480
  total: result?.total[0]?.count || 0,
475
481
  page,
476
482
  limit
@@ -542,6 +548,7 @@ class GiveawayRepository {
542
548
  return {
543
549
  data: data.map(w => ({
544
550
  ...w,
551
+ entry: normalizeEntry(w.entry),
545
552
  giveaway: { ...w.giveaway, status: (0, domain_1.effectiveStatus)(w.giveaway) }
546
553
  })),
547
554
  total,
@@ -716,7 +723,7 @@ class GiveawayRepository {
716
723
  })
717
724
  .limit(1000)
718
725
  .toArray();
719
- const entries = rows;
726
+ const entries = rows.map(normalizeEntry);
720
727
  const existing = await this.winners.findOne({ giveawayId: id, replacedAt: null }, { projection: { _id: 1 } });
721
728
  (0, domain_1.confirmWinners)(g, entries, !!existing, 'preview', input);
722
729
  return { entries, giveaway: { ...g, status: (0, domain_1.effectiveStatus)(g) } };
@@ -851,7 +858,7 @@ class GiveawayRepository {
851
858
  const updated = { ...g, selection, revision: g.revision + 1 };
852
859
  await this.updateGiveaway(updated, g.revision, session);
853
860
  await this.audit(actor, 'Winner selected: ' + input.method, id, entry._id + (selection.reason ? ': ' + selection.reason : ''), session);
854
- return { giveaway: updated, entry };
861
+ return { giveaway: updated, entry: normalizeEntry(entry) };
855
862
  });
856
863
  }
857
864
  async resetSelection(actor, id, revision, reason) {
@@ -978,24 +985,23 @@ class GiveawayRepository {
978
985
  };
979
986
  }
980
987
  async listTickets(userId, params) {
981
- const { page, limit: requestedLimit } = pagination(params);
982
- const limit = Math.min(requestedLimit, 50);
983
- const skip = (page - 1) * limit;
988
+ const { page, pageSize, skip } = (0, api_pagination_1.parseGiveawayApiPagination)(params);
984
989
  const filter = { userId };
985
990
  const [entries, total] = await Promise.all([
986
991
  this.entries
987
992
  .find(filter, { projection: { _id: 1 } })
988
993
  .sort({ enteredAt: -1, _id: 1 })
989
994
  .skip(skip)
990
- .limit(limit)
995
+ .limit(pageSize)
991
996
  .toArray(),
992
997
  this.entries.countDocuments(filter)
993
998
  ]);
994
999
  return {
995
- data: await Promise.all(entries.map(e => this.getTicket(userId, e._id))),
996
- total,
997
1000
  page,
998
- limit
1001
+ pageSize,
1002
+ count: entries.length,
1003
+ totalCount: total,
1004
+ data: await Promise.all(entries.map(e => this.getTicket(userId, e._id)))
999
1005
  };
1000
1006
  }
1001
1007
  async getUserClaim(giveawayId, userId) {
@@ -1251,7 +1257,7 @@ class GiveawayRepository {
1251
1257
  ...marketingLookup
1252
1258
  ];
1253
1259
  if (marketingOnly)
1254
- pipeline.push({ $match: { marketingAllowed: true } }, { $project: { fullName: 1, email: 1 } });
1260
+ pipeline.push({ $match: { marketingAllowed: true } }, { $project: { name: 1, fullName: 1, email: 1 } });
1255
1261
  const cursor = this.entries.aggregate(pipeline, {
1256
1262
  batchSize: 100
1257
1263
  });
@@ -1277,11 +1283,12 @@ class GiveawayRepository {
1277
1283
  'Rules accepted'
1278
1284
  ]
1279
1285
  ]);
1280
- for await (const e of cursor) {
1286
+ for await (const record of cursor) {
1287
+ const e = normalizeEntry(record);
1281
1288
  const row = marketingOnly
1282
- ? [e.fullName, e.email]
1289
+ ? [e.name, e.email]
1283
1290
  : [
1284
- e.fullName,
1291
+ e.name,
1285
1292
  e.email,
1286
1293
  e.address?.line1 || '',
1287
1294
  e.address?.line2 || '',
@@ -1,5 +1,6 @@
1
1
  /** Server-only giveaway rules and persistence shared by the admin dashboard and cloud APIs. */
2
2
  export * from './domain';
3
+ export * from './api-pagination';
3
4
  export * from './entry-id';
4
5
  export * from './giveaway-repository';
5
6
  export * from './schemas';
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  /** Server-only giveaway rules and persistence shared by the admin dashboard and cloud APIs. */
18
18
  __exportStar(require("./domain"), exports);
19
+ __exportStar(require("./api-pagination"), exports);
19
20
  __exportStar(require("./entry-id"), exports);
20
21
  __exportStar(require("./giveaway-repository"), exports);
21
22
  __exportStar(require("./schemas"), exports);
@@ -0,0 +1,12 @@
1
+ /** Successful non-paginated DGC API response. */
2
+ export type ApiResponse<T> = {
3
+ data: T;
4
+ };
5
+ /** Successful paginated DGC API response. count is the number of returned records. */
6
+ export type PaginatedApiResponse<T> = {
7
+ page: number;
8
+ pageSize: number;
9
+ count: number;
10
+ totalCount: number;
11
+ data: T[];
12
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -70,7 +70,7 @@ export type GiveawayEntry = {
70
70
  _id: GiveawayEntryId;
71
71
  giveawayId: GiveawayObjectId;
72
72
  userId: string;
73
- fullName: string;
73
+ name: string;
74
74
  email: string;
75
75
  /** Historical entry addresses are retained; new entries collect delivery only at claim. */
76
76
  address?: GiveawayDeliveryAddress;
@@ -180,10 +180,9 @@ export type GiveawayOverviewData = {
180
180
  liveGiveaways: GiveawaySummary[];
181
181
  needsSelection: Giveaway[];
182
182
  };
183
- export type GiveawayUserProfile = Pick<FbUser, 'email' | 'firstName' | 'lastName' | '_collectorType'> & {
183
+ export type GiveawayUserProfile = Pick<FbUser, 'email' | 'name' | '_collectorType'> & {
184
184
  userId: string;
185
185
  username?: string;
186
- country?: string;
187
186
  };
188
187
  export type GiveawayPhysicalClaim = {
189
188
  fullName: string;
@@ -5,6 +5,7 @@ export * from '../enums/card';
5
5
  export * from '../enums/rc';
6
6
  export * from '../enums/user';
7
7
  export * from './blockchain';
8
+ export * from './api';
8
9
  export * from './card';
9
10
  export * from './embedding';
10
11
  export * from './env';
@@ -21,6 +21,7 @@ __exportStar(require("../enums/card"), exports);
21
21
  __exportStar(require("../enums/rc"), exports);
22
22
  __exportStar(require("../enums/user"), exports);
23
23
  __exportStar(require("./blockchain"), exports);
24
+ __exportStar(require("./api"), exports);
24
25
  __exportStar(require("./card"), exports);
25
26
  __exportStar(require("./embedding"), exports);
26
27
  __exportStar(require("./env"), exports);
@@ -8,6 +8,8 @@ export type FbUser = {
8
8
  count: number;
9
9
  };
10
10
  email: string;
11
+ /** Full name stored on the Firebase account. */
12
+ name: string;
11
13
  fcmTokens: Record<string, boolean>;
12
14
  firstName: string;
13
15
  fundAmount: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dgc-core",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "Shared DGC contracts, business rules and server utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",