@timardex/cluemart-server-shared 1.0.299 → 1.0.301

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 * as _timardex_cluemart_shared from '@timardex/cluemart-shared';
2
- import { EnumAffiliateRewardType, AffiliateRewardType, PromoCodeType, UserLicenceType, EnumUserLicence, DateTimeType, EventListItemType, DateTimeWithPriceType } from '@timardex/cluemart-shared';
2
+ import { EnumAffiliateRewardType, AffiliateRewardType, PromoCodeType, UserLicenceType, UserType, EnumUserLicence, DateTimeType, EventListItemType, DateTimeWithPriceType } from '@timardex/cluemart-shared';
3
3
  import * as mongoose from 'mongoose';
4
- import { o as SchemaAffiliateResourceType, S as SchemaOwnerType, j as SchemaRelationType } from '../Affiliate-CiXqVASx.mjs';
4
+ import { n as SchemaAffiliateResourceType, S as SchemaOwnerType, i as SchemaRelationType } from '../Affiliate-BxNv9k41.mjs';
5
5
  import { G as GraphQLContext, O as ObjectId, f as SchemaCreateBulkNotificationInput } from '../Chat-Wn7yfc3v.mjs';
6
6
  import dayjs from 'dayjs';
7
7
  import { DateTimeType as DateTimeType$1 } from '@timardex/cluemart-shared/types';
@@ -31,8 +31,9 @@ type AwardAffiliateVendorSubscriptionRewardsResult = {
31
31
  * - Vendor is active, not deleted, and `promoCodes` includes the affiliate code
32
32
  * - Vendor owner has a non-expired Standard / Pro / Pro+ vendor licence
33
33
  *
34
- * Reward type follows the owner's highest valid licence:
35
- * - Pro / Pro+ → `ACTIVE_VENDOR_PRO_SUBSCRIPTION` (3 pts)
34
+ * Reward type follows the owner's highest valid licence + Stripe paying status:
35
+ * - Pro / Pro+ with active Stripe → `ACTIVE_VENDOR_PRO_SUBSCRIPTION` (3 pts)
36
+ * - Pro / Pro+ without paying Stripe → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)
36
37
  * - Standard → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)
37
38
  *
38
39
  * Idempotent per affiliate + vendor + reward type + calendar month.
@@ -113,9 +114,11 @@ declare function normalizeAffiliatePromoCodes(promoCodes: PromoCodeType[] | null
113
114
  * Maps a vendor owner's highest valid vendor licence to a monthly subscription reward.
114
115
  *
115
116
  * Prefer Pro / Pro+ over Standard when both are valid.
117
+ * Pro / Pro+ only yields the Pro reward when the owner has an active Stripe subscription;
118
+ * otherwise falls back to the Standard subscription reward.
116
119
  * Returns `null` when no non-expired vendor licence applies.
117
120
  */
118
- declare function mapVendorLicenceToSubscriptionRewardType(licences: UserLicenceType[] | null | undefined, now?: Date): EnumAffiliateRewardType | null;
121
+ declare function mapVendorLicenceToSubscriptionRewardType(licences: UserLicenceType[] | null | undefined, now?: Date, stripe?: UserType["stripe"] | null): EnumAffiliateRewardType | null;
119
122
  /** Inclusive start / exclusive end of the UTC calendar month containing `now`. */
120
123
  declare function getSubscriptionRewardPeriodBounds(now?: Date): {
121
124
  periodEnd: Date;
@@ -1,7 +1,7 @@
1
1
  import * as _timardex_cluemart_shared from '@timardex/cluemart-shared';
2
- import { EnumAffiliateRewardType, AffiliateRewardType, PromoCodeType, UserLicenceType, EnumUserLicence, DateTimeType, EventListItemType, DateTimeWithPriceType } from '@timardex/cluemart-shared';
2
+ import { EnumAffiliateRewardType, AffiliateRewardType, PromoCodeType, UserLicenceType, UserType, EnumUserLicence, DateTimeType, EventListItemType, DateTimeWithPriceType } from '@timardex/cluemart-shared';
3
3
  import * as mongoose from 'mongoose';
4
- import { o as SchemaAffiliateResourceType, S as SchemaOwnerType, j as SchemaRelationType } from '../Affiliate-B3kOq3DU.js';
4
+ import { n as SchemaAffiliateResourceType, S as SchemaOwnerType, i as SchemaRelationType } from '../Affiliate-DRuifOv4.js';
5
5
  import { G as GraphQLContext, O as ObjectId, f as SchemaCreateBulkNotificationInput } from '../Chat-Wn7yfc3v.js';
6
6
  import dayjs from 'dayjs';
7
7
  import { DateTimeType as DateTimeType$1 } from '@timardex/cluemart-shared/types';
@@ -31,8 +31,9 @@ type AwardAffiliateVendorSubscriptionRewardsResult = {
31
31
  * - Vendor is active, not deleted, and `promoCodes` includes the affiliate code
32
32
  * - Vendor owner has a non-expired Standard / Pro / Pro+ vendor licence
33
33
  *
34
- * Reward type follows the owner's highest valid licence:
35
- * - Pro / Pro+ → `ACTIVE_VENDOR_PRO_SUBSCRIPTION` (3 pts)
34
+ * Reward type follows the owner's highest valid licence + Stripe paying status:
35
+ * - Pro / Pro+ with active Stripe → `ACTIVE_VENDOR_PRO_SUBSCRIPTION` (3 pts)
36
+ * - Pro / Pro+ without paying Stripe → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)
36
37
  * - Standard → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)
37
38
  *
38
39
  * Idempotent per affiliate + vendor + reward type + calendar month.
@@ -113,9 +114,11 @@ declare function normalizeAffiliatePromoCodes(promoCodes: PromoCodeType[] | null
113
114
  * Maps a vendor owner's highest valid vendor licence to a monthly subscription reward.
114
115
  *
115
116
  * Prefer Pro / Pro+ over Standard when both are valid.
117
+ * Pro / Pro+ only yields the Pro reward when the owner has an active Stripe subscription;
118
+ * otherwise falls back to the Standard subscription reward.
116
119
  * Returns `null` when no non-expired vendor licence applies.
117
120
  */
118
- declare function mapVendorLicenceToSubscriptionRewardType(licences: UserLicenceType[] | null | undefined, now?: Date): EnumAffiliateRewardType | null;
121
+ declare function mapVendorLicenceToSubscriptionRewardType(licences: UserLicenceType[] | null | undefined, now?: Date, stripe?: UserType["stripe"] | null): EnumAffiliateRewardType | null;
119
122
  /** Inclusive start / exclusive end of the UTC calendar month containing `now`. */
120
123
  declare function getSubscriptionRewardPeriodBounds(now?: Date): {
121
124
  periodEnd: Date;
@@ -1,15 +1,14 @@
1
1
  import {
2
2
  AdModel,
3
3
  AffiliateModel,
4
- ChatModel,
5
4
  EnumAdStatus,
6
5
  EnumAffiliateRewardType,
7
- EnumChatType,
8
6
  EnumEventDateStatus,
9
7
  EnumInviteStatus,
10
8
  EnumNotificationResourceType,
11
9
  EnumNotificationType,
12
10
  EnumResourceType,
11
+ EnumSubscriptionStatus,
13
12
  EnumUserLicence,
14
13
  EventModel,
15
14
  GoogleImportedMarketModel,
@@ -19,7 +18,7 @@ import {
19
18
  VendorModel,
20
19
  dateFormat,
21
20
  timeFormat
22
- } from "../chunk-UEGYYYX5.mjs";
21
+ } from "../chunk-LOZFYYVW.mjs";
23
22
  import "../chunk-6PNG5BI5.mjs";
24
23
 
25
24
  // src/service/promoCode/constants.ts
@@ -215,14 +214,21 @@ function createAffiliateReward(rewardType, createdAt) {
215
214
  }
216
215
 
217
216
  // src/service/affiliate/vendorSubscriptionRewards.ts
218
- function mapVendorLicenceToSubscriptionRewardType(licences, now = /* @__PURE__ */ new Date()) {
217
+ function isPayingStripeSubscription(stripe) {
218
+ if (!stripe?.subscriptionId) return false;
219
+ return stripe.status === EnumSubscriptionStatus.ACTIVE || stripe.status === EnumSubscriptionStatus.TRIALING;
220
+ }
221
+ function mapVendorLicenceToSubscriptionRewardType(licences, now = /* @__PURE__ */ new Date(), stripe) {
219
222
  const validTypes = new Set(
220
223
  (licences ?? []).filter(
221
224
  (licence) => new Date(licence.expiryDate).getTime() > now.getTime()
222
225
  ).map((licence) => licence.licenceType)
223
226
  );
224
227
  if (validTypes.has(EnumUserLicence.PRO_VENDOR) || validTypes.has(EnumUserLicence.PRO_PLUS_VENDOR)) {
225
- return EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION;
228
+ if (isPayingStripeSubscription(stripe)) {
229
+ return EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION;
230
+ }
231
+ return EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION;
226
232
  }
227
233
  if (validTypes.has(EnumUserLicence.STANDARD_VENDOR)) {
228
234
  return EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION;
@@ -264,8 +270,12 @@ async function findEligibleReferredVendor(resourceId, affiliateCode) {
264
270
  }).select("_id name owner.userId").lean().exec();
265
271
  }
266
272
  async function resolveSubscriptionRewardTypeForVendorOwner(vendorOwnerUserId, now) {
267
- const vendorOwner = await UserModel.findById(vendorOwnerUserId).select("licences").lean().exec();
268
- return mapVendorLicenceToSubscriptionRewardType(vendorOwner?.licences, now);
273
+ const vendorOwner = await UserModel.findById(vendorOwnerUserId).select("licences stripe").lean().exec();
274
+ return mapVendorLicenceToSubscriptionRewardType(
275
+ vendorOwner?.licences,
276
+ now,
277
+ vendorOwner?.stripe
278
+ );
269
279
  }
270
280
  async function notifyAffiliateOwnerOfSubscriptionReward({
271
281
  affiliateId,
@@ -535,112 +545,6 @@ async function updateAdStatuses() {
535
545
  );
536
546
  }
537
547
 
538
- // src/service/associate.ts
539
- import mongoose2 from "mongoose";
540
- function normalizeObjectId(id) {
541
- return typeof id === "string" ? new mongoose2.Types.ObjectId(id) : id;
542
- }
543
- async function getAssociateEmailsForResource({
544
- normalizedResourceId,
545
- resourceType
546
- }) {
547
- const usersWithAssociates = await UserModel.find(
548
- {
549
- associates: {
550
- $elemMatch: {
551
- resourceId: normalizedResourceId,
552
- resourceType
553
- }
554
- }
555
- },
556
- {
557
- associates: 1
558
- }
559
- ).lean();
560
- return [
561
- ...new Set(
562
- usersWithAssociates.flatMap(
563
- (user) => (user.associates ?? []).filter(
564
- (associate) => associate.resourceId === normalizedResourceId && associate.resourceType === resourceType
565
- ).map((associate) => associate.email)
566
- )
567
- )
568
- ];
569
- }
570
- async function pullAssociatesFromUsers({
571
- normalizedResourceId,
572
- resourceType
573
- }) {
574
- await UserModel.updateMany(
575
- {
576
- associates: {
577
- $elemMatch: {
578
- resourceId: normalizedResourceId,
579
- resourceType
580
- }
581
- }
582
- },
583
- {
584
- $pull: {
585
- associates: {
586
- resourceId: normalizedResourceId,
587
- resourceType
588
- }
589
- }
590
- }
591
- );
592
- }
593
- async function pullAssociateParticipantsFromChats(resourceOwnerId, associateEmails) {
594
- await ChatModel.updateMany(
595
- {
596
- active: true,
597
- chatType: EnumChatType.RELATION,
598
- deletedAt: null,
599
- participants: {
600
- $elemMatch: {
601
- userId: resourceOwnerId
602
- }
603
- }
604
- },
605
- {
606
- $pull: {
607
- participants: {
608
- isAssociate: true,
609
- userEmail: {
610
- $in: associateEmails
611
- }
612
- }
613
- }
614
- }
615
- );
616
- }
617
- async function removeAssociateFromResource({
618
- resourceId,
619
- resourceOwnerId,
620
- resourceType
621
- }) {
622
- try {
623
- const scope = {
624
- normalizedResourceId: resourceId.toString(),
625
- resourceType
626
- };
627
- const associateEmails = await getAssociateEmailsForResource(scope);
628
- if (associateEmails.length === 0) {
629
- return;
630
- }
631
- await pullAssociatesFromUsers(scope);
632
- await pullAssociateParticipantsFromChats(
633
- normalizeObjectId(resourceOwnerId),
634
- associateEmails
635
- );
636
- } catch (error) {
637
- console.error(
638
- `[removeAssociateFromResource] Failed for resourceId=${resourceId}, resourceType=${resourceType}`,
639
- error
640
- );
641
- }
642
- }
643
-
644
548
  // src/service/vendor.ts
645
549
  async function updateVendorBasedOnUserLicense(userId, licenceType) {
646
550
  try {
@@ -657,7 +561,6 @@ async function updateVendorBasedOnUserLicense(userId, licenceType) {
657
561
  const updateData = {};
658
562
  const isStandardVendor = licenceType === EnumUserLicence.STANDARD_VENDOR;
659
563
  if (isStandardVendor) {
660
- updateData.associates = [];
661
564
  updateData.availability = {
662
565
  corporate: false,
663
566
  private: false,
@@ -677,25 +580,18 @@ async function updateVendorBasedOnUserLicense(userId, licenceType) {
677
580
  active: isStandardVendor ? index < 6 : true
678
581
  }));
679
582
  await VendorModel.updateOne({ _id: vendor._id }, { $set: updateData });
680
- if (isStandardVendor) {
681
- await removeAssociateFromResource({
682
- resourceId: vendor._id,
683
- resourceOwnerId: vendor.owner.userId,
684
- resourceType: EnumResourceType.VENDOR
685
- });
686
- }
687
583
  } catch (error) {
688
584
  console.error("[updateVendorBasedOnUserLicense] Failed:", error);
689
585
  }
690
586
  }
691
587
 
692
588
  // src/service/objectIdToString.ts
693
- import mongoose3 from "mongoose";
589
+ import mongoose2 from "mongoose";
694
590
  function convertObjectIdsToStrings(obj) {
695
591
  if (obj === null || obj === void 0) {
696
592
  return obj;
697
593
  }
698
- if (obj instanceof mongoose3.Types.ObjectId) {
594
+ if (obj instanceof mongoose2.Types.ObjectId) {
699
595
  return obj.toString();
700
596
  }
701
597
  if (obj instanceof Date) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/service/promoCode/constants.ts","../../src/service/affiliate/activeAffiliateCodeExists.ts","../../src/service/promoCode/normalizePromoCode.ts","../../src/service/saveNotificationsInDb.ts","../../src/service/sendPushNotifications.ts","../../src/service/affiliate/createAffiliateReward.ts","../../src/service/affiliate/vendorSubscriptionRewards.ts","../../src/service/affiliate/awardAffiliateVendorSubscriptionRewards.ts","../../src/service/affiliate/findAffiliateByPromoCode.ts","../../src/service/affiliate/normalizeAffiliatePromoCodes.ts","../../src/service/database.ts","../../src/service/notifyUsers.ts","../../src/service/updateAdStatus.ts","../../src/service/associate.ts","../../src/service/vendor.ts","../../src/service/objectIdToString.ts","../../src/service/event/updateAllEventDateTimeStatuses.ts","../../src/service/event/findEventOrImportedMarketById.ts","../../src/service/relations.ts"],"sourcesContent":["/** Matches partial unique indexes for promo/affiliate codes on active documents. */\nexport const ACTIVE_NOT_DELETED_FILTER = {\n active: true,\n deletedAt: null,\n} as const;\n","import { AffiliateModel } from \"src/mongoose\";\n\nimport { ACTIVE_NOT_DELETED_FILTER } from \"../promoCode/constants\";\n\n/**\n * Checks whether an affiliate code is already taken by an active, non-deleted affiliate.\n *\n * Used when generating new affiliate codes so collisions are avoided before insert.\n * Matches the partial unique index on `affiliateCode` for active documents.\n *\n * @param affiliateCode - Candidate affiliate promo code to check.\n * @returns `true` when an active, non-deleted affiliate already uses this code.\n */\nexport async function activeAffiliateCodeExists(\n affiliateCode: string,\n): Promise<boolean> {\n const existing = await AffiliateModel.exists({\n ...ACTIVE_NOT_DELETED_FILTER,\n affiliateCode,\n }).exec();\n\n return existing !== null;\n}\n","export function normalizePromoCode(\n decoratedPromoCode: string | null | undefined,\n): string | null {\n const normalized = decoratedPromoCode?.trim().toUpperCase();\n return normalized || null;\n}\n","import {\n SchemaCreateBulkNotificationInput,\n NotificationModel,\n} from \"src/mongoose/Notification\";\nimport { ObjectId } from \"src/types\";\n\n/**\n * Create notifications in the database for multiple users\n * This is typically called when sending push notifications\n */\nexport async function saveNotificationsInDb(\n payload: SchemaCreateBulkNotificationInput,\n): Promise<ObjectId[]> {\n const { data, message, title, type, userIds } = payload;\n console.log('NOTIFICATION DATA', JSON.stringify(payload, null, 2));\n try {\n const notifications = userIds.map((userId) => ({\n data,\n isRead: false,\n message,\n title,\n type,\n userId,\n }));\n\n // Save notifications to database\n await NotificationModel.insertMany(notifications);\n console.log(\n `Created ${notifications.length} notifications for ${userIds.length} users`,\n );\n\n return [...new Set(userIds)];\n } catch (error) {\n console.error(\"Failed to create notifications:\", error);\n return [];\n //throw new Error(`Failed to create notifications: ${error}`);\n }\n}\n","import { NotificationDataType } from \"@timardex/cluemart-shared\";\nimport { Expo, ExpoPushMessage, ExpoPushTicket } from \"expo-server-sdk\";\n\nimport { SchemaCreateBulkNotificationInput } from \"src/mongoose/Notification\";\nimport { PushTokenModel } from \"src/mongoose/PushToken\";\n\nconst expo = new Expo();\n\n/**\n * Safely extract tokens from ExpoPushMessage handling both string and array cases\n */\nfunction extractTokensFromMessage(message: ExpoPushMessage): string[] {\n return Array.isArray(message.to) ? message.to : [message.to];\n}\n\ninterface CreatePushMessagesOptions {\n tokens: string[];\n message: string;\n title: string;\n data: NotificationDataType;\n}\n\n/**\n * Create push messages from valid tokens\n */\nfunction createPushMessages({\n tokens,\n message,\n title,\n data,\n}: CreatePushMessagesOptions): {\n messages: ExpoPushMessage[];\n invalidTokens: string[];\n} {\n const messages: ExpoPushMessage[] = [];\n const invalidTokens: string[] = [];\n\n for (const token of tokens) {\n if (!Expo.isExpoPushToken(token)) {\n invalidTokens.push(token);\n continue;\n }\n\n messages.push({\n body: message,\n data: { ...data },\n sound: \"tui.wav\",\n title,\n to: token,\n });\n }\n\n return { invalidTokens, messages };\n}\n\n/**\n * Process chunk results and extract failed tokens\n */\nfunction processChunkResults(\n tickets: ExpoPushTicket[],\n chunk: ExpoPushMessage[],\n): { successCount: number; failedTokens: string[] } {\n let successCount = 0;\n const failedTokens: string[] = [];\n\n for (const [ticketIndex, ticket] of tickets.entries()) {\n if (ticket.status === \"error\") {\n const message = chunk[ticketIndex];\n if (message) {\n const tokens = extractTokensFromMessage(message);\n if (ticket.details?.error === \"DeviceNotRegistered\") {\n failedTokens.push(...tokens);\n }\n console.log(\"Push notification error\", {\n error: ticket.details?.error,\n tokens,\n });\n }\n } else {\n successCount++;\n }\n }\n\n return { failedTokens, successCount };\n}\n\n/**\n * Send a single chunk of push notifications\n */\nasync function sendChunk(\n chunk: ExpoPushMessage[],\n chunkIndex: number,\n): Promise<{ successCount: number; failedTokens: string[] }> {\n try {\n const tickets = await expo.sendPushNotificationsAsync(chunk);\n const { successCount, failedTokens } = processChunkResults(tickets, chunk);\n\n console.log(\n `Chunk ${chunkIndex + 1}: Sent ${successCount}/${chunk.length} notifications successfully`,\n );\n\n return { failedTokens, successCount };\n } catch (error) {\n console.log(\"Error sending Expo push notification chunk\", {\n chunkIndex,\n chunkSize: chunk.length,\n error: error instanceof Error ? error.message : String(error),\n });\n return { failedTokens: [], successCount: 0 };\n }\n}\n\nexport async function sendPushNotifications({\n data,\n message,\n title,\n userIds,\n}: SchemaCreateBulkNotificationInput) {\n const pushTokens = await PushTokenModel.find({ userId: { $in: userIds } });\n const expoTokens = pushTokens.map((token) => token.token);\n\n if (!data) return;\n\n const { messages, invalidTokens } = createPushMessages({\n data,\n message,\n title,\n tokens: expoTokens,\n });\n\n // Log invalid tokens\n if (invalidTokens.length > 0) {\n console.log(`Found ${invalidTokens.length} invalid push tokens`);\n }\n\n if (messages.length === 0) {\n console.log(\"No valid messages to send after filtering tokens\");\n return;\n }\n\n // Send notifications in chunks\n const chunks = expo.chunkPushNotifications(messages);\n let totalSuccessCount = 0;\n const allFailedTokens: string[] = [];\n\n for (const [chunkIndex, chunk] of chunks.entries()) {\n const { successCount, failedTokens } = await sendChunk(\n chunk,\n chunkIndex + 1,\n );\n totalSuccessCount += successCount;\n allFailedTokens.push(...failedTokens);\n }\n\n // Log final results\n console.log(\n `Sent push notification to ${totalSuccessCount}/${messages.length} tokens across ${chunks.length} chunks`,\n );\n\n if (allFailedTokens.length > 0) {\n console.log(`Found ${allFailedTokens.length} failed push tokens`);\n }\n}\n","import {\n AffiliateRewardType,\n EnumAffiliateRewardType,\n} from \"@timardex/cluemart-shared\";\n\ntype AffiliateRewardConfig = {\n description: string;\n value: number;\n};\n\nexport const AFFILIATE_REWARDS = {\n [EnumAffiliateRewardType.NEW_EVENT_REGISTRATION]: {\n description: \"10 points for new event registration (one-time reward)\",\n value: 10,\n },\n [EnumAffiliateRewardType.NEW_VENDOR_REGISTRATION]: {\n description: \"5 points for new vendor registration (one-time reward)\",\n value: 5,\n },\n [EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION]: {\n description: \"3 points for active vendor pro subscription (monthly reward)\",\n value: 3,\n },\n [EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION]: {\n description:\n \"1 point for active vendor standard subscription (monthly reward)\",\n value: 1,\n },\n [EnumAffiliateRewardType.ACTIVE_VENDOR_BONUS_REWARD]: {\n description:\n \"5 bonus points for every 10th active vendor (one-time reward)\",\n value: 5,\n },\n [EnumAffiliateRewardType.ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS]: {\n description:\n \"50 points for active event with vendor registrations (one-time reward)\",\n value: 50,\n },\n} satisfies Record<EnumAffiliateRewardType, AffiliateRewardConfig>;\n\n/**\n * Create an affiliate reward\n * @param rewardType - The type of reward\n * @param createdAt - The date the reward was received\n * @returns The affiliate reward\n * @example\n * const reward = createAffiliateReward(EnumAffiliateRewardType.NEW_EVENT_REGISTRATION, new Date());\n * console.log(reward);\n */\nexport function createAffiliateReward(\n rewardType: EnumAffiliateRewardType,\n createdAt: Date,\n): AffiliateRewardType {\n const reward = AFFILIATE_REWARDS[rewardType];\n\n return {\n createdAt,\n redeemedAt: null,\n rewardDescription: reward.description,\n rewardType,\n rewardValue: reward.value,\n };\n}\n","import {\n EnumAffiliateRewardType,\n EnumUserLicence,\n UserLicenceType,\n} from \"@timardex/cluemart-shared\";\n\n/**\n * Maps a vendor owner's highest valid vendor licence to a monthly subscription reward.\n *\n * Prefer Pro / Pro+ over Standard when both are valid.\n * Returns `null` when no non-expired vendor licence applies.\n */\nexport function mapVendorLicenceToSubscriptionRewardType(\n licences: UserLicenceType[] | null | undefined,\n now: Date = new Date(),\n): EnumAffiliateRewardType | null {\n const validTypes = new Set(\n (licences ?? [])\n .filter(\n (licence) => new Date(licence.expiryDate).getTime() > now.getTime(),\n )\n .map((licence) => licence.licenceType),\n );\n\n if (\n validTypes.has(EnumUserLicence.PRO_VENDOR) ||\n validTypes.has(EnumUserLicence.PRO_PLUS_VENDOR)\n ) {\n return EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION;\n }\n\n if (validTypes.has(EnumUserLicence.STANDARD_VENDOR)) {\n return EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION;\n }\n\n return null;\n}\n\n/** Inclusive start / exclusive end of the UTC calendar month containing `now`. */\nexport function getSubscriptionRewardPeriodBounds(now: Date = new Date()): {\n periodEnd: Date;\n periodStart: Date;\n} {\n const periodStart = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n );\n const periodEnd = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1),\n );\n return { periodEnd, periodStart };\n}\n","import {\n AffiliateResourceType,\n EnumAffiliateRewardType,\n EnumNotificationResourceType,\n EnumNotificationType,\n EnumResourceType,\n PromoCodeType,\n} from \"@timardex/cluemart-shared\";\n\nimport { AffiliateModel, UserModel, VendorModel } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\nimport { normalizePromoCode } from \"../promoCode/normalizePromoCode\";\nimport { saveNotificationsInDb } from \"../saveNotificationsInDb\";\nimport { sendPushNotifications } from \"../sendPushNotifications\";\n\nimport { createAffiliateReward } from \"./createAffiliateReward\";\nimport {\n getSubscriptionRewardPeriodBounds,\n mapVendorLicenceToSubscriptionRewardType,\n} from \"./vendorSubscriptionRewards\";\n\nexport type AwardAffiliateVendorSubscriptionRewardsResult = {\n awarded: number;\n skipped: number;\n};\n\ntype LeanAffiliateResource = Omit<\n AffiliateResourceType,\n \"resourceId\" | \"rewards\"\n> & {\n resourceId: ObjectId;\n};\n\ntype LeanAffiliate = {\n _id: ObjectId;\n affiliateCode: PromoCodeType;\n affiliateResources: LeanAffiliateResource[];\n owner?: { userId?: ObjectId | string } | null;\n};\n\ntype LeanVendor = {\n _id: ObjectId;\n name?: string;\n owner?: { userId?: ObjectId | string } | null;\n};\n\ntype PeriodBounds = {\n periodEnd: Date;\n periodStart: Date;\n};\n\nfunction isEligibleVendorResource(resource: LeanAffiliateResource): boolean {\n return (\n resource.resourceType === EnumResourceType.VENDOR &&\n resource.resourceActive === true &&\n resource.resourceDeletedAt == null\n );\n}\n\nasync function findAffiliatesWithActiveVendorReferrals(): Promise<\n LeanAffiliate[]\n> {\n return (await AffiliateModel.find({\n affiliateResources: {\n $elemMatch: {\n resourceActive: true,\n resourceDeletedAt: null,\n resourceType: EnumResourceType.VENDOR,\n },\n },\n deletedAt: null,\n })\n .select(\"_id affiliateCode affiliateResources owner\")\n .lean()\n .exec()) as LeanAffiliate[];\n}\n\nasync function findEligibleReferredVendor(\n resourceId: ObjectId,\n affiliateCode: string,\n): Promise<LeanVendor | null> {\n return (await VendorModel.findOne({\n _id: resourceId,\n active: true,\n deletedAt: null,\n promoCodes: affiliateCode,\n })\n .select(\"_id name owner.userId\")\n .lean()\n .exec()) as LeanVendor | null;\n}\n\nasync function resolveSubscriptionRewardTypeForVendorOwner(\n vendorOwnerUserId: ObjectId | string,\n now: Date,\n): Promise<EnumAffiliateRewardType | null> {\n const vendorOwner = await UserModel.findById(vendorOwnerUserId)\n .select(\"licences\")\n .lean()\n .exec();\n\n return mapVendorLicenceToSubscriptionRewardType(vendorOwner?.licences, now);\n}\n\nasync function notifyAffiliateOwnerOfSubscriptionReward({\n affiliateId,\n affiliateOwnerUserId,\n resourceName,\n rewardValue,\n}: {\n affiliateId: ObjectId | string;\n affiliateOwnerUserId: ObjectId | string;\n resourceName: string;\n rewardValue: number;\n}): Promise<void> {\n const payload = {\n data: {\n resourceId: String(affiliateId),\n resourceName,\n resourceType: EnumNotificationResourceType.AFFILIATE_REWARD_RECEIVED,\n },\n message: `You earned ${rewardValue} points! Your referred vendor \"${resourceName}\" has an active subscription.`,\n title: \"Affiliate points earned\",\n type: EnumNotificationType.SYSTEM,\n userIds: [affiliateOwnerUserId as ObjectId],\n };\n\n try {\n await saveNotificationsInDb(payload);\n await sendPushNotifications(payload);\n } catch (error) {\n console.error(\n `[affiliate] subscription reward notification failed for affiliate ${String(affiliateId)}:`,\n error instanceof Error ? error.message : error,\n );\n }\n}\n\nasync function tryAwardSubscriptionReward({\n affiliate,\n createdAt,\n period,\n resource,\n rewardType,\n vendor,\n}: {\n affiliate: LeanAffiliate;\n createdAt: Date;\n period: PeriodBounds;\n resource: LeanAffiliateResource;\n rewardType: EnumAffiliateRewardType;\n vendor: LeanVendor;\n}): Promise<\"awarded\" | \"skipped\"> {\n const reward = createAffiliateReward(rewardType, createdAt);\n\n const updateResult = await AffiliateModel.updateOne(\n {\n _id: affiliate._id,\n affiliateResources: {\n $elemMatch: {\n resourceActive: true,\n resourceDeletedAt: null,\n resourceId: vendor._id,\n resourceType: EnumResourceType.VENDOR,\n rewards: {\n $not: {\n $elemMatch: {\n createdAt: {\n $gte: period.periodStart,\n $lt: period.periodEnd,\n },\n rewardType,\n },\n },\n },\n },\n },\n deletedAt: null,\n },\n {\n $inc: { overallPoints: reward.rewardValue },\n $push: { \"affiliateResources.$.rewards\": reward },\n },\n ).exec();\n\n if (updateResult.modifiedCount === 0) {\n return \"skipped\";\n }\n\n const affiliateOwnerUserId = affiliate.owner?.userId;\n if (affiliateOwnerUserId) {\n await notifyAffiliateOwnerOfSubscriptionReward({\n affiliateId: affiliate._id,\n affiliateOwnerUserId,\n resourceName: resource.resourceName || vendor.name || \"vendor\",\n rewardValue: reward.rewardValue,\n });\n }\n\n return \"awarded\";\n}\n\nasync function processAffiliateVendorReferral({\n affiliate,\n affiliateCode,\n createdAt,\n now,\n period,\n resource,\n}: {\n affiliate: LeanAffiliate;\n affiliateCode: string;\n createdAt: Date;\n now: Date;\n period: PeriodBounds;\n resource: LeanAffiliateResource;\n}): Promise<\"awarded\" | \"skipped\"> {\n const vendor = await findEligibleReferredVendor(\n resource.resourceId,\n affiliateCode,\n );\n if (!vendor?.owner?.userId) {\n return \"skipped\";\n }\n\n const rewardType = await resolveSubscriptionRewardTypeForVendorOwner(\n vendor.owner.userId,\n now,\n );\n if (!rewardType) {\n return \"skipped\";\n }\n\n return tryAwardSubscriptionReward({\n affiliate,\n createdAt,\n period,\n resource,\n rewardType,\n vendor,\n });\n}\n\n/**\n * Awards monthly vendor subscription rewards to affiliates with active referred vendors.\n *\n * Eligibility per affiliate resource:\n * - Affiliate is not soft-deleted\n * - Linked `affiliateResources` entry is vendor, `resourceActive`, and not deleted\n * - Vendor is active, not deleted, and `promoCodes` includes the affiliate code\n * - Vendor owner has a non-expired Standard / Pro / Pro+ vendor licence\n *\n * Reward type follows the owner's highest valid licence:\n * - Pro / Pro+ → `ACTIVE_VENDOR_PRO_SUBSCRIPTION` (3 pts)\n * - Standard → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)\n *\n * Idempotent per affiliate + vendor + reward type + calendar month.\n * On successful award, notifies the affiliate owner (best-effort).\n *\n * @param now - Reference time for month bounds and licence expiry (defaults to now).\n * @returns Counts of awarded and skipped candidates.\n */\nexport async function awardAffiliateVendorSubscriptionRewards(\n now: Date = new Date(),\n): Promise<AwardAffiliateVendorSubscriptionRewardsResult> {\n const period = getSubscriptionRewardPeriodBounds(now);\n const createdAt = now;\n const affiliates = await findAffiliatesWithActiveVendorReferrals();\n\n let awarded = 0;\n let skipped = 0;\n const seenPairs = new Set<string>();\n\n for (const affiliate of affiliates) {\n const affiliateCode = normalizePromoCode(affiliate.affiliateCode);\n if (!affiliateCode) {\n continue;\n }\n\n for (const resource of affiliate.affiliateResources) {\n if (!isEligibleVendorResource(resource)) {\n continue;\n }\n\n const pairKey = `${String(affiliate._id)}:${String(resource.resourceId)}`;\n if (seenPairs.has(pairKey)) {\n continue;\n }\n seenPairs.add(pairKey);\n\n const outcome = await processAffiliateVendorReferral({\n affiliate,\n affiliateCode,\n createdAt,\n now,\n period,\n resource,\n });\n\n if (outcome === \"awarded\") {\n awarded += 1;\n } else {\n skipped += 1;\n }\n }\n }\n\n return { awarded, skipped };\n}\n","import { PromoCodeType } from \"@timardex/cluemart-shared\";\n\nimport { AffiliateModel } from \"src/mongoose\";\n\n/**\n * Loads the affiliate whose `affiliateCode` matches the given promo code.\n *\n * Affiliate codes are assigned on admin activation, so referrals only resolve after\n * a code exists. Soft-deleted affiliates (`deletedAt` set) are excluded.\n *\n * @param promoCode - Normalized affiliate promo code to resolve.\n * @returns The matching affiliate document, or `null` when not found.\n */\nexport async function findAffiliateByPromoCode(promoCode: PromoCodeType) {\n return AffiliateModel.findOne({\n affiliateCode: promoCode,\n deletedAt: null,\n }).exec();\n}\n","import { PromoCodeType } from \"@timardex/cluemart-shared\";\n\nimport { normalizePromoCode } from \"../promoCode/normalizePromoCode\";\n\n/**\n * Normalizes, trims, uppercases, and deduplicates resource promo codes.\n *\n * @param promoCodes - Raw promo codes from a create-resource mutation input.\n * @returns Unique normalized codes; empty strings and whitespace-only values are removed.\n */\nexport function normalizeAffiliatePromoCodes(\n promoCodes: PromoCodeType[] | null | undefined,\n): PromoCodeType[] {\n const normalized = (promoCodes ?? [])\n .map((code) => normalizePromoCode(code))\n .filter((code): code is PromoCodeType => code !== null);\n\n return [...new Set(normalized)];\n}\n","import mongoose from \"mongoose\";\n\n/**\n * Connect to MongoDB using Mongoose.\n * Supports both local MongoDB (via MONGODB_URI) and MongoDB Atlas (via individual env vars).\n */\nexport const connectToDatabase = async ({\n appName,\n dbName,\n dbPassword,\n dbUser,\n mongodbUri,\n}: {\n appName: string;\n dbName: string;\n dbPassword: string;\n dbUser: string;\n mongodbUri: string;\n}) => {\n try {\n // Check if MONGODB_URI is provided (for local Docker MongoDB)\n const mongoUri = mongodbUri\n ? mongodbUri\n : // Fallback to MongoDB Atlas connection string\n `mongodb+srv://${dbUser}:${dbPassword}@${dbName}.mongodb.net/?retryWrites=true&w=majority&appName=${appName}`;\n\n await mongoose.connect(mongoUri);\n\n const connectionType = mongodbUri ? \"Local MongoDB\" : \"MongoDB Atlas\";\n console.log(\n `${connectionType} connected from server/src/service/database.ts`,\n );\n } catch (err) {\n console.error(\"Error connecting to MongoDB:\", err);\n throw err; // You can throw the error if you want to stop the server in case of connection failure\n }\n};\n","import {\n NotificationModel,\n SchemaCreateBulkNotificationInput,\n} from \"src/mongoose/Notification\";\nimport { EnumPubSubEvents, GraphQLContext, ObjectId } from \"src/types\";\n\nimport { saveNotificationsInDb } from \"./saveNotificationsInDb\";\nimport { sendPushNotifications } from \"./sendPushNotifications\";\n\n/**\n * Publish-only pubsub used to emit GraphQL subscription events. Optional in\n * {@link notifyUsers} because non-GraphQL processes (e.g. the cron worker) have\n * no subscribers to publish to.\n */\nexport type NotificationPubSub = Pick<GraphQLContext[\"pubsub\"], \"publish\">;\n\n/** Publishes notification list/count subscription events for a user. */\nexport async function publishNotificationEvents(\n userId: ObjectId,\n pubsub: NotificationPubSub,\n) {\n try {\n // Get user's notifications for the subscription\n const userNotifications = await NotificationModel.find({\n userId,\n }).sort({ createdAt: -1 });\n\n // Get notification count\n const [total, unread] = await Promise.all([\n NotificationModel.countDocuments({ userId }),\n NotificationModel.countDocuments({ isRead: false, userId }),\n ]);\n\n // Publish both events\n pubsub.publish(EnumPubSubEvents.GET_NOTIFICATIONS, {\n getNotifications: userNotifications,\n getNotificationsUserId: userId,\n });\n\n pubsub.publish(EnumPubSubEvents.GET_NOTIFICATIONS_COUNT, {\n getNotificationsCount: { total, unread, userId },\n });\n\n console.log(`Published notification events for user: ${String(userId)}`);\n } catch (error) {\n console.error(\n `Failed to publish notification events for user ${String(userId)}:`,\n error,\n );\n }\n}\n\n/**\n * Sends push notifications, saves them in the database, and (when a `pubsub`\n * is provided) publishes GraphQL subscription events for each affected user.\n *\n * Errors are logged and swallowed; this function never throws. Instead it\n * returns `false` when notifications were not confirmed (an error was thrown,\n * or `saveNotificationsInDb` failed to persist any rows for a non-empty\n * `userIds`), so callers can gate success logs/metrics on the result.\n */\nexport async function notifyUsers({\n payload,\n pubsub,\n}: {\n payload: SchemaCreateBulkNotificationInput;\n pubsub?: NotificationPubSub;\n}): Promise<boolean> {\n try {\n await sendPushNotifications(payload);\n\n const uniqueUserIds = await saveNotificationsInDb(payload);\n // saveNotificationsInDb swallows its own errors and returns []; treat an\n // empty result for a non-empty input as a persistence failure.\n const persisted = payload.userIds.length === 0 || uniqueUserIds.length > 0;\n\n if (pubsub) {\n for (const userId of uniqueUserIds) {\n await publishNotificationEvents(userId, pubsub);\n }\n }\n\n return persisted;\n } catch (error) {\n console.error(\"Error in notifyUsers:\", error);\n return false;\n }\n}\n","import { EnumAdStatus } from \"@timardex/cluemart-shared\";\n\nimport { AdModel } from \"src/mongoose\";\n\n/**\n * Updates ad statuses based on start/end dates and validity\n */\nexport async function updateAdStatuses(): Promise<void> {\n const now = new Date();\n\n // invalid\n const invalidResult = await AdModel.updateMany(\n {\n $or: [\n { start: { $exists: false } },\n { end: { $exists: false } },\n { $expr: { $gt: [\"$start\", \"$end\"] } },\n ],\n status: { $ne: EnumAdStatus.PAUSED },\n },\n { $set: { status: EnumAdStatus.PAUSED } },\n );\n\n // expired\n const expiredResult = await AdModel.updateMany(\n { end: { $lte: now }, status: { $ne: EnumAdStatus.EXPIRED } },\n { $set: { status: EnumAdStatus.EXPIRED } },\n );\n\n // active\n const activeResult = await AdModel.updateMany(\n {\n end: { $gt: now },\n start: { $lte: now },\n status: { $ne: EnumAdStatus.ACTIVE },\n },\n { $set: { status: EnumAdStatus.ACTIVE } },\n );\n\n // paused\n const pausedResult = await AdModel.updateMany(\n { start: { $gt: now }, status: { $ne: EnumAdStatus.PAUSED } },\n { $set: { status: EnumAdStatus.PAUSED } },\n );\n\n console.log(\n `✅ Ad statuses updated: invalid=${invalidResult.modifiedCount}, expired=${expiredResult.modifiedCount}, active=${activeResult.modifiedCount}, paused=${pausedResult.modifiedCount}`,\n );\n}\n","import { EnumChatType, EnumResourceType } from \"@timardex/cluemart-shared\";\nimport mongoose from \"mongoose\";\n\nimport { ChatModel, UserModel } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\ninterface RemoveAssociateFromResourceParams {\n resourceId: string | ObjectId;\n resourceOwnerId: string | ObjectId;\n resourceType: EnumResourceType;\n}\n\ninterface ResourceAssociateScope {\n normalizedResourceId: string;\n resourceType: EnumResourceType;\n}\n\nfunction normalizeObjectId(id: string | ObjectId): ObjectId {\n return typeof id === \"string\" ? new mongoose.Types.ObjectId(id) : id;\n}\n\nasync function getAssociateEmailsForResource({\n normalizedResourceId,\n resourceType,\n}: ResourceAssociateScope): Promise<string[]> {\n const usersWithAssociates = await UserModel.find(\n {\n associates: {\n $elemMatch: {\n resourceId: normalizedResourceId,\n resourceType,\n },\n },\n },\n {\n associates: 1,\n },\n ).lean();\n\n return [\n ...new Set(\n usersWithAssociates.flatMap((user) =>\n (user.associates ?? [])\n .filter(\n (associate) =>\n associate.resourceId === normalizedResourceId &&\n associate.resourceType === resourceType,\n )\n .map((associate) => associate.email),\n ),\n ),\n ];\n}\n\nasync function pullAssociatesFromUsers({\n normalizedResourceId,\n resourceType,\n}: ResourceAssociateScope): Promise<void> {\n await UserModel.updateMany(\n {\n associates: {\n $elemMatch: {\n resourceId: normalizedResourceId,\n resourceType,\n },\n },\n },\n {\n $pull: {\n associates: {\n resourceId: normalizedResourceId,\n resourceType,\n },\n },\n },\n );\n}\n\nasync function pullAssociateParticipantsFromChats(\n resourceOwnerId: ObjectId,\n associateEmails: string[],\n): Promise<void> {\n await ChatModel.updateMany(\n {\n active: true,\n chatType: EnumChatType.RELATION,\n deletedAt: null,\n participants: {\n $elemMatch: {\n userId: resourceOwnerId,\n },\n },\n },\n {\n $pull: {\n participants: {\n isAssociate: true,\n userEmail: {\n $in: associateEmails,\n },\n },\n },\n },\n );\n}\n\n/**\n * Removes all associates linked to a resource\n * and removes related associate chat participants.\n */\nexport async function removeAssociateFromResource({\n resourceId,\n resourceOwnerId,\n resourceType,\n}: RemoveAssociateFromResourceParams): Promise<void> {\n try {\n const scope: ResourceAssociateScope = {\n normalizedResourceId: resourceId.toString(),\n resourceType,\n };\n\n const associateEmails = await getAssociateEmailsForResource(scope);\n\n if (associateEmails.length === 0) {\n return;\n }\n\n await pullAssociatesFromUsers(scope);\n await pullAssociateParticipantsFromChats(\n normalizeObjectId(resourceOwnerId),\n associateEmails,\n );\n } catch (error) {\n console.error(\n `[removeAssociateFromResource] Failed for resourceId=${resourceId}, resourceType=${resourceType}`,\n error,\n );\n }\n}\n","import { EnumResourceType, EnumUserLicence } from \"@timardex/cluemart-shared\";\n\nimport { UserModel, VendorModel, SchemaVendorType } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\nimport { removeAssociateFromResource } from \"./associate\";\n\nexport async function updateVendorBasedOnUserLicense(\n userId: ObjectId,\n licenceType: EnumUserLicence,\n): Promise<void> {\n try {\n /**\n * Fetch user vendor reference\n */\n const user = await UserModel.findById(userId)\n .select(\"vendor\")\n .lean()\n .exec();\n\n if (!user?.vendor) {\n console.warn(`[updateVendor] No vendor found for userId=${userId}`);\n return;\n }\n\n /**\n * Fetch vendor\n */\n const vendor = await VendorModel.findById(user.vendor)\n .lean<SchemaVendorType>()\n .exec();\n\n if (!vendor) {\n console.warn(`[updateVendor] Vendor not found for id=${user.vendor}`);\n return;\n }\n\n /**\n * Build vendor update payload\n */\n const updateData: Partial<SchemaVendorType> = {};\n\n const isStandardVendor = licenceType === EnumUserLicence.STANDARD_VENDOR;\n\n if (isStandardVendor) {\n updateData.associates = [];\n\n updateData.availability = {\n corporate: false,\n private: false,\n school: false,\n };\n\n updateData.products = {\n active: false,\n productsList: vendor.products?.productsList ?? [],\n };\n\n updateData.calendar = {\n active: false,\n calendarData: vendor.calendar?.calendarData ?? [],\n };\n }\n\n /**\n * Image rules\n * STANDARD_VENDOR => only first 6 active, 6 is the default image limit for standard vendors\n * PRO_VENDOR => all active\n */\n updateData.images = (vendor.images ?? []).map((image, index) => ({\n ...image,\n active: isStandardVendor ? index < 6 : true,\n }));\n\n /**\n * Persist vendor updates\n */\n await VendorModel.updateOne({ _id: vendor._id }, { $set: updateData });\n\n /**\n * Cleanup associates for STANDARD licence\n */\n if (isStandardVendor) {\n await removeAssociateFromResource({\n resourceId: vendor._id,\n resourceOwnerId: vendor.owner.userId,\n resourceType: EnumResourceType.VENDOR,\n });\n }\n } catch (error) {\n console.error(\"[updateVendorBasedOnUserLicense] Failed:\", error);\n }\n}\n","import mongoose from \"mongoose\";\n\n/**\n * Recursively converts all ObjectId fields to strings in an object\n * This is needed because GraphQL expects string IDs, not ObjectIds\n */\nexport function convertObjectIdsToStrings(obj: any): any {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (obj instanceof mongoose.Types.ObjectId) {\n return obj.toString();\n }\n\n if (obj instanceof Date) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(convertObjectIdsToStrings);\n }\n\n if (typeof obj === \"object\") {\n const converted: any = {};\n for (const [key, value] of Object.entries(obj)) {\n converted[key] = convertObjectIdsToStrings(value);\n }\n return converted;\n }\n\n return obj;\n}\n","import {\n dateFormat,\n DateTimeType,\n EnumEventDateStatus,\n timeFormat,\n} from \"@timardex/cluemart-shared\";\nimport dayjs from \"dayjs\";\nimport customParseFormat from \"dayjs/plugin/customParseFormat\";\nimport isoWeek from \"dayjs/plugin/isoWeek\";\n\nimport { EventModel, GoogleImportedMarketModel } from \"src/mongoose\";\n\n// Enable dayjs plugins used by event date parsing/status logic\ndayjs.extend(customParseFormat);\ndayjs.extend(isoWeek);\n\n/**\n * Determines the dateStatus for a future event date\n * @param startDateTime The start date-time of the event\n * @param now The current date-time\n * @returns The appropriate EnumEventDateStatus\n */\nexport function getFutureEventStatus(\n startDateTime: dayjs.Dayjs,\n now: dayjs.Dayjs,\n): EnumEventDateStatus {\n const hoursUntilStart = startDateTime.diff(now, \"hour\", true);\n if (hoursUntilStart > 0 && hoursUntilStart <= 4) {\n return EnumEventDateStatus.STARTING_SOON;\n }\n\n if (startDateTime.isSame(now, \"day\")) {\n return EnumEventDateStatus.TODAY;\n }\n\n if (startDateTime.isSame(now.add(1, \"day\"), \"day\")) {\n return EnumEventDateStatus.TOMORROW;\n }\n\n if (startDateTime.isSame(now, \"isoWeek\")) {\n return EnumEventDateStatus.THIS_WEEK;\n }\n\n if (startDateTime.isSame(now.add(1, \"week\"), \"isoWeek\")) {\n return EnumEventDateStatus.NEXT_WEEK;\n }\n\n return EnumEventDateStatus.UPCOMING;\n}\n\n/**\n * Updates the dateStatus field for a single DateTimeType object based on the current date/time\n * @param dateTime The date-time object to update\n * @returns A new object with updated dateStatus value\n */\nexport function updateSingleDateTimeStatus<T extends DateTimeType>(\n dateTime: T,\n now: dayjs.Dayjs = dayjs(),\n): T {\n // Parse start and end date-time\n const dateTimeFormat = `${dateFormat} ${timeFormat}`;\n const startDateTime = dayjs(\n `${dateTime.startDate} ${dateTime.startTime}`,\n dateTimeFormat,\n true,\n );\n const endDateTime = dayjs(\n `${dateTime.endDate} ${dateTime.endTime}`,\n dateTimeFormat,\n true,\n );\n\n // Skip if dates are invalid\n if (!startDateTime.isValid() || !endDateTime.isValid()) {\n return {\n ...dateTime,\n dateStatus: EnumEventDateStatus.INVALID,\n };\n }\n\n if (endDateTime.isBefore(startDateTime)) {\n return {\n ...dateTime,\n dateStatus: EnumEventDateStatus.INVALID,\n };\n }\n\n let dateStatus: EnumEventDateStatus;\n\n if (endDateTime.isAfter(now)) {\n if (startDateTime.isAfter(now)) {\n // Event has not started yet\n dateStatus = getFutureEventStatus(startDateTime, now);\n } else {\n // Event is in progress\n dateStatus = EnumEventDateStatus.STARTED;\n }\n } else {\n // Event has ended\n dateStatus = EnumEventDateStatus.ENDED;\n }\n\n return {\n ...dateTime,\n dateStatus,\n };\n}\n\nconst dateTimeStatusFilter = {\n dateTime: { $ne: [], $type: \"array\" },\n deletedAt: null,\n} as const;\n\nconst CURSOR_BATCH_SIZE = 50;\n\ntype DocWithDateTime = {\n _id: unknown;\n dateTime: DateTimeType[];\n};\n\ntype DateTimeBulkWriteOp = {\n updateOne: {\n filter: { _id: unknown };\n update: { $set: { dateTime: DateTimeType[] } };\n };\n};\n\ntype DateTimeCursor = AsyncIterable<DocWithDateTime> & {\n close: () => Promise<unknown>;\n};\n\ntype DateTimeUpdatableModel = {\n bulkWrite: (ops: DateTimeBulkWriteOp[]) => Promise<unknown>;\n find: (filter: typeof dateTimeStatusFilter) => {\n select: (fields: { _id: 1; dateTime: 1 }) => {\n lean: () => {\n cursor: (opts: { batchSize: number }) => DateTimeCursor;\n };\n };\n };\n};\n\nfunction hasDateTimeStatusChanges(\n stored: DateTimeType[],\n updated: DateTimeType[],\n): boolean {\n if (stored.length !== updated.length) {\n return true;\n }\n\n return stored.some(\n (slot, index) => slot.dateStatus !== updated[index].dateStatus,\n );\n}\n\nasync function updateModelDateTimeStatuses(\n model: DateTimeUpdatableModel,\n now: dayjs.Dayjs,\n): Promise<number> {\n let updated = 0;\n const cursor = model\n .find(dateTimeStatusFilter)\n .select({ _id: 1, dateTime: 1 })\n .lean()\n .cursor({ batchSize: CURSOR_BATCH_SIZE });\n\n let bulkOps: DateTimeBulkWriteOp[] = [];\n\n const flushBulkWrites = async (): Promise<void> => {\n if (!bulkOps.length) {\n return;\n }\n\n await model.bulkWrite(bulkOps);\n updated += bulkOps.length;\n bulkOps = [];\n };\n\n try {\n for await (const doc of cursor) {\n const dateTime = doc.dateTime.map((slot) =>\n updateSingleDateTimeStatus(slot, now),\n );\n\n if (!hasDateTimeStatusChanges(doc.dateTime, dateTime)) {\n continue;\n }\n\n bulkOps.push({\n updateOne: {\n filter: { _id: doc._id },\n update: { $set: { dateTime } },\n },\n });\n\n if (bulkOps.length >= CURSOR_BATCH_SIZE) {\n await flushBulkWrites();\n }\n }\n\n await flushBulkWrites();\n } finally {\n await cursor.close().catch(() => undefined);\n }\n\n return updated;\n}\n\n/**\n * Recomputes dateStatus for every dateTime slot on events and google imported markets.\n */\nexport async function updateAllEventDateTimeStatuses(): Promise<void> {\n const now = dayjs();\n const [eventCount, marketCount] = await Promise.all([\n updateModelDateTimeStatuses(EventModel as DateTimeUpdatableModel, now),\n updateModelDateTimeStatuses(\n GoogleImportedMarketModel as DateTimeUpdatableModel,\n now,\n ),\n ]);\n\n console.log(\n `✅ Event dateTime statuses updated: events=${eventCount} changed, google imported markets=${marketCount} changed`,\n );\n}\n","import { EventListItemType } from \"@timardex/cluemart-shared\";\n\nimport { EventModel, GoogleImportedMarketModel } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\nimport { convertObjectIdsToStrings } from \"../objectIdToString\";\n\ntype EventOrMarket = Pick<EventListItemType, \"_id\" | \"name\"> | null;\n\n/**\n * This function attempts to find an Event or a Google Imported Market by the given resource ID.\n * It first normalizes the resource ID to a string format, then performs parallel queries to both collections.\n * If an Event is found, it returns that; otherwise, it checks for a Google Imported Market and returns it if found.\n * If neither is found, it returns null.\n * @param resourceId - The ID of the resource to find, which can be an ObjectId, string, null, or undefined.\n * @returns A promise that resolves to either an Event or a Google Imported Market object containing _id and name, or null if not found.\n */\n\nexport async function findEventOrImportedMarketById(\n resourceId: ObjectId | string | null | undefined,\n): Promise<EventOrMarket> {\n if (!resourceId) {\n return null;\n }\n\n const normalizedId = convertObjectIdsToStrings(resourceId) as string;\n\n const [eventDoc, googleImportedDoc] = await Promise.all([\n EventModel.findById(normalizedId)\n .select(\"_id name\")\n .lean<Pick<EventListItemType, \"_id\" | \"name\">>()\n .exec(),\n GoogleImportedMarketModel.findById(normalizedId)\n .select(\"_id name\")\n .lean<Pick<EventListItemType, \"_id\" | \"name\">>()\n .exec(),\n ]);\n\n return eventDoc ?? googleImportedDoc;\n}\n","import {\n DateTimeWithPriceType,\n EnumInviteStatus,\n} from \"@timardex/cluemart-shared\";\nimport { DateTimeType } from \"@timardex/cluemart-shared/types\";\n\nimport { SchemaRelationType } from \"src/mongoose/Relation\";\n\ntype EventDateSlot = Pick<DateTimeType, \"startDate\" | \"startTime\">;\n\n/**\n * Returns true when at least one startDate/startTime slot from the previous\n * schedule is absent from the next schedule (i.e. a date was removed).\n */\nexport function didRemoveAnyEventDates(\n previousDateTime: EventDateSlot[] | undefined,\n nextDateTime: EventDateSlot[] | undefined,\n): boolean {\n if (!previousDateTime?.length) {\n return false;\n }\n\n return previousDateTime.some(\n (prev) =>\n !nextDateTime?.some(\n (next) =>\n next.startDate === prev.startDate &&\n next.startTime === prev.startTime,\n ),\n );\n}\n\n/**\n * Helper: Update relationDates based on event's dateTime\n * Marks dates as UNAVAILABLE if they no longer exist in the event's dateTime.\n */\nexport function updateRelationDatesToUnavailable(\n relationDates: SchemaRelationType[\"relationDates\"],\n eventDateTime: DateTimeWithPriceType[] | undefined,\n): SchemaRelationType[\"relationDates\"] {\n return relationDates.map((relationDate) => {\n // Check if this relationDate exists in the event's dateTime\n const existsInEvent =\n eventDateTime?.some(\n (dt) =>\n dt.startDate === relationDate.dateTime.startDate &&\n dt.startTime === relationDate.dateTime.startTime,\n ) ?? false;\n\n return {\n ...relationDate,\n status: existsInEvent\n ? relationDate.status\n : EnumInviteStatus.UNAVAILABLE,\n };\n });\n}\n"],"mappings":"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","names":["mongoose","mongoose","mongoose"]}
1
+ {"version":3,"sources":["../../src/service/promoCode/constants.ts","../../src/service/affiliate/activeAffiliateCodeExists.ts","../../src/service/promoCode/normalizePromoCode.ts","../../src/service/saveNotificationsInDb.ts","../../src/service/sendPushNotifications.ts","../../src/service/affiliate/createAffiliateReward.ts","../../src/service/affiliate/vendorSubscriptionRewards.ts","../../src/service/affiliate/awardAffiliateVendorSubscriptionRewards.ts","../../src/service/affiliate/findAffiliateByPromoCode.ts","../../src/service/affiliate/normalizeAffiliatePromoCodes.ts","../../src/service/database.ts","../../src/service/notifyUsers.ts","../../src/service/updateAdStatus.ts","../../src/service/vendor.ts","../../src/service/objectIdToString.ts","../../src/service/event/updateAllEventDateTimeStatuses.ts","../../src/service/event/findEventOrImportedMarketById.ts","../../src/service/relations.ts"],"sourcesContent":["/** Matches partial unique indexes for promo/affiliate codes on active documents. */\nexport const ACTIVE_NOT_DELETED_FILTER = {\n active: true,\n deletedAt: null,\n} as const;\n","import { AffiliateModel } from \"src/mongoose\";\n\nimport { ACTIVE_NOT_DELETED_FILTER } from \"../promoCode/constants\";\n\n/**\n * Checks whether an affiliate code is already taken by an active, non-deleted affiliate.\n *\n * Used when generating new affiliate codes so collisions are avoided before insert.\n * Matches the partial unique index on `affiliateCode` for active documents.\n *\n * @param affiliateCode - Candidate affiliate promo code to check.\n * @returns `true` when an active, non-deleted affiliate already uses this code.\n */\nexport async function activeAffiliateCodeExists(\n affiliateCode: string,\n): Promise<boolean> {\n const existing = await AffiliateModel.exists({\n ...ACTIVE_NOT_DELETED_FILTER,\n affiliateCode,\n }).exec();\n\n return existing !== null;\n}\n","export function normalizePromoCode(\n decoratedPromoCode: string | null | undefined,\n): string | null {\n const normalized = decoratedPromoCode?.trim().toUpperCase();\n return normalized || null;\n}\n","import {\n SchemaCreateBulkNotificationInput,\n NotificationModel,\n} from \"src/mongoose/Notification\";\nimport { ObjectId } from \"src/types\";\n\n/**\n * Create notifications in the database for multiple users\n * This is typically called when sending push notifications\n */\nexport async function saveNotificationsInDb(\n payload: SchemaCreateBulkNotificationInput,\n): Promise<ObjectId[]> {\n const { data, message, title, type, userIds } = payload;\n console.log('NOTIFICATION DATA', JSON.stringify(payload, null, 2));\n try {\n const notifications = userIds.map((userId) => ({\n data,\n isRead: false,\n message,\n title,\n type,\n userId,\n }));\n\n // Save notifications to database\n await NotificationModel.insertMany(notifications);\n console.log(\n `Created ${notifications.length} notifications for ${userIds.length} users`,\n );\n\n return [...new Set(userIds)];\n } catch (error) {\n console.error(\"Failed to create notifications:\", error);\n return [];\n //throw new Error(`Failed to create notifications: ${error}`);\n }\n}\n","import { NotificationDataType } from \"@timardex/cluemart-shared\";\nimport { Expo, ExpoPushMessage, ExpoPushTicket } from \"expo-server-sdk\";\n\nimport { SchemaCreateBulkNotificationInput } from \"src/mongoose/Notification\";\nimport { PushTokenModel } from \"src/mongoose/PushToken\";\n\nconst expo = new Expo();\n\n/**\n * Safely extract tokens from ExpoPushMessage handling both string and array cases\n */\nfunction extractTokensFromMessage(message: ExpoPushMessage): string[] {\n return Array.isArray(message.to) ? message.to : [message.to];\n}\n\ninterface CreatePushMessagesOptions {\n tokens: string[];\n message: string;\n title: string;\n data: NotificationDataType;\n}\n\n/**\n * Create push messages from valid tokens\n */\nfunction createPushMessages({\n tokens,\n message,\n title,\n data,\n}: CreatePushMessagesOptions): {\n messages: ExpoPushMessage[];\n invalidTokens: string[];\n} {\n const messages: ExpoPushMessage[] = [];\n const invalidTokens: string[] = [];\n\n for (const token of tokens) {\n if (!Expo.isExpoPushToken(token)) {\n invalidTokens.push(token);\n continue;\n }\n\n messages.push({\n body: message,\n data: { ...data },\n sound: \"tui.wav\",\n title,\n to: token,\n });\n }\n\n return { invalidTokens, messages };\n}\n\n/**\n * Process chunk results and extract failed tokens\n */\nfunction processChunkResults(\n tickets: ExpoPushTicket[],\n chunk: ExpoPushMessage[],\n): { successCount: number; failedTokens: string[] } {\n let successCount = 0;\n const failedTokens: string[] = [];\n\n for (const [ticketIndex, ticket] of tickets.entries()) {\n if (ticket.status === \"error\") {\n const message = chunk[ticketIndex];\n if (message) {\n const tokens = extractTokensFromMessage(message);\n if (ticket.details?.error === \"DeviceNotRegistered\") {\n failedTokens.push(...tokens);\n }\n console.log(\"Push notification error\", {\n error: ticket.details?.error,\n tokens,\n });\n }\n } else {\n successCount++;\n }\n }\n\n return { failedTokens, successCount };\n}\n\n/**\n * Send a single chunk of push notifications\n */\nasync function sendChunk(\n chunk: ExpoPushMessage[],\n chunkIndex: number,\n): Promise<{ successCount: number; failedTokens: string[] }> {\n try {\n const tickets = await expo.sendPushNotificationsAsync(chunk);\n const { successCount, failedTokens } = processChunkResults(tickets, chunk);\n\n console.log(\n `Chunk ${chunkIndex + 1}: Sent ${successCount}/${chunk.length} notifications successfully`,\n );\n\n return { failedTokens, successCount };\n } catch (error) {\n console.log(\"Error sending Expo push notification chunk\", {\n chunkIndex,\n chunkSize: chunk.length,\n error: error instanceof Error ? error.message : String(error),\n });\n return { failedTokens: [], successCount: 0 };\n }\n}\n\nexport async function sendPushNotifications({\n data,\n message,\n title,\n userIds,\n}: SchemaCreateBulkNotificationInput) {\n const pushTokens = await PushTokenModel.find({ userId: { $in: userIds } });\n const expoTokens = pushTokens.map((token) => token.token);\n\n if (!data) return;\n\n const { messages, invalidTokens } = createPushMessages({\n data,\n message,\n title,\n tokens: expoTokens,\n });\n\n // Log invalid tokens\n if (invalidTokens.length > 0) {\n console.log(`Found ${invalidTokens.length} invalid push tokens`);\n }\n\n if (messages.length === 0) {\n console.log(\"No valid messages to send after filtering tokens\");\n return;\n }\n\n // Send notifications in chunks\n const chunks = expo.chunkPushNotifications(messages);\n let totalSuccessCount = 0;\n const allFailedTokens: string[] = [];\n\n for (const [chunkIndex, chunk] of chunks.entries()) {\n const { successCount, failedTokens } = await sendChunk(\n chunk,\n chunkIndex + 1,\n );\n totalSuccessCount += successCount;\n allFailedTokens.push(...failedTokens);\n }\n\n // Log final results\n console.log(\n `Sent push notification to ${totalSuccessCount}/${messages.length} tokens across ${chunks.length} chunks`,\n );\n\n if (allFailedTokens.length > 0) {\n console.log(`Found ${allFailedTokens.length} failed push tokens`);\n }\n}\n","import {\n AffiliateRewardType,\n EnumAffiliateRewardType,\n} from \"@timardex/cluemart-shared\";\n\ntype AffiliateRewardConfig = {\n description: string;\n value: number;\n};\n\nexport const AFFILIATE_REWARDS = {\n [EnumAffiliateRewardType.NEW_EVENT_REGISTRATION]: {\n description: \"10 points for new event registration (one-time reward)\",\n value: 10,\n },\n [EnumAffiliateRewardType.NEW_VENDOR_REGISTRATION]: {\n description: \"5 points for new vendor registration (one-time reward)\",\n value: 5,\n },\n [EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION]: {\n description: \"3 points for active vendor pro subscription (monthly reward)\",\n value: 3,\n },\n [EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION]: {\n description:\n \"1 point for active vendor standard subscription (monthly reward)\",\n value: 1,\n },\n [EnumAffiliateRewardType.ACTIVE_VENDOR_BONUS_REWARD]: {\n description:\n \"5 bonus points for every 10th active vendor (one-time reward)\",\n value: 5,\n },\n [EnumAffiliateRewardType.ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS]: {\n description:\n \"50 points for active event with vendor registrations (one-time reward)\",\n value: 50,\n },\n} satisfies Record<EnumAffiliateRewardType, AffiliateRewardConfig>;\n\n/**\n * Create an affiliate reward\n * @param rewardType - The type of reward\n * @param createdAt - The date the reward was received\n * @returns The affiliate reward\n * @example\n * const reward = createAffiliateReward(EnumAffiliateRewardType.NEW_EVENT_REGISTRATION, new Date());\n * console.log(reward);\n */\nexport function createAffiliateReward(\n rewardType: EnumAffiliateRewardType,\n createdAt: Date,\n): AffiliateRewardType {\n const reward = AFFILIATE_REWARDS[rewardType];\n\n return {\n createdAt,\n redeemedAt: null,\n rewardDescription: reward.description,\n rewardType,\n rewardValue: reward.value,\n };\n}\n","import {\n EnumAffiliateRewardType,\n EnumSubscriptionStatus,\n EnumUserLicence,\n UserLicenceType,\n UserType,\n} from \"@timardex/cluemart-shared\";\n\n/**\n * Paying = Stripe subscriptionId present and status active or trialing.\n * Matches `hasActiveStripeSubscription` in cluemart-server User utils.\n */\nfunction isPayingStripeSubscription(\n stripe?: UserType[\"stripe\"] | null,\n): boolean {\n if (!stripe?.subscriptionId) return false;\n\n return (\n stripe.status === EnumSubscriptionStatus.ACTIVE ||\n stripe.status === EnumSubscriptionStatus.TRIALING\n );\n}\n\n/**\n * Maps a vendor owner's highest valid vendor licence to a monthly subscription reward.\n *\n * Prefer Pro / Pro+ over Standard when both are valid.\n * Pro / Pro+ only yields the Pro reward when the owner has an active Stripe subscription;\n * otherwise falls back to the Standard subscription reward.\n * Returns `null` when no non-expired vendor licence applies.\n */\nexport function mapVendorLicenceToSubscriptionRewardType(\n licences: UserLicenceType[] | null | undefined,\n now: Date = new Date(),\n stripe?: UserType[\"stripe\"] | null,\n): EnumAffiliateRewardType | null {\n const validTypes = new Set(\n (licences ?? [])\n .filter(\n (licence) => new Date(licence.expiryDate).getTime() > now.getTime(),\n )\n .map((licence) => licence.licenceType),\n );\n\n if (\n validTypes.has(EnumUserLicence.PRO_VENDOR) ||\n validTypes.has(EnumUserLicence.PRO_PLUS_VENDOR)\n ) {\n if (isPayingStripeSubscription(stripe)) {\n return EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION;\n }\n return EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION;\n }\n\n if (validTypes.has(EnumUserLicence.STANDARD_VENDOR)) {\n return EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION;\n }\n\n return null;\n}\n\n/** Inclusive start / exclusive end of the UTC calendar month containing `now`. */\nexport function getSubscriptionRewardPeriodBounds(now: Date = new Date()): {\n periodEnd: Date;\n periodStart: Date;\n} {\n const periodStart = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n );\n const periodEnd = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1),\n );\n return { periodEnd, periodStart };\n}\n","import {\n AffiliateResourceType,\n EnumAffiliateRewardType,\n EnumNotificationResourceType,\n EnumNotificationType,\n EnumResourceType,\n PromoCodeType,\n} from \"@timardex/cluemart-shared\";\n\nimport { AffiliateModel, UserModel, VendorModel } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\nimport { normalizePromoCode } from \"../promoCode/normalizePromoCode\";\nimport { saveNotificationsInDb } from \"../saveNotificationsInDb\";\nimport { sendPushNotifications } from \"../sendPushNotifications\";\n\nimport { createAffiliateReward } from \"./createAffiliateReward\";\nimport {\n getSubscriptionRewardPeriodBounds,\n mapVendorLicenceToSubscriptionRewardType,\n} from \"./vendorSubscriptionRewards\";\n\nexport type AwardAffiliateVendorSubscriptionRewardsResult = {\n awarded: number;\n skipped: number;\n};\n\ntype LeanAffiliateResource = Omit<\n AffiliateResourceType,\n \"resourceId\" | \"rewards\"\n> & {\n resourceId: ObjectId;\n};\n\ntype LeanAffiliate = {\n _id: ObjectId;\n affiliateCode: PromoCodeType;\n affiliateResources: LeanAffiliateResource[];\n owner?: { userId?: ObjectId | string } | null;\n};\n\ntype LeanVendor = {\n _id: ObjectId;\n name?: string;\n owner?: { userId?: ObjectId | string } | null;\n};\n\ntype PeriodBounds = {\n periodEnd: Date;\n periodStart: Date;\n};\n\nfunction isEligibleVendorResource(resource: LeanAffiliateResource): boolean {\n return (\n resource.resourceType === EnumResourceType.VENDOR &&\n resource.resourceActive === true &&\n resource.resourceDeletedAt == null\n );\n}\n\nasync function findAffiliatesWithActiveVendorReferrals(): Promise<\n LeanAffiliate[]\n> {\n return (await AffiliateModel.find({\n affiliateResources: {\n $elemMatch: {\n resourceActive: true,\n resourceDeletedAt: null,\n resourceType: EnumResourceType.VENDOR,\n },\n },\n deletedAt: null,\n })\n .select(\"_id affiliateCode affiliateResources owner\")\n .lean()\n .exec()) as LeanAffiliate[];\n}\n\nasync function findEligibleReferredVendor(\n resourceId: ObjectId,\n affiliateCode: string,\n): Promise<LeanVendor | null> {\n return (await VendorModel.findOne({\n _id: resourceId,\n active: true,\n deletedAt: null,\n promoCodes: affiliateCode,\n })\n .select(\"_id name owner.userId\")\n .lean()\n .exec()) as LeanVendor | null;\n}\n\nasync function resolveSubscriptionRewardTypeForVendorOwner(\n vendorOwnerUserId: ObjectId | string,\n now: Date,\n): Promise<EnumAffiliateRewardType | null> {\n const vendorOwner = await UserModel.findById(vendorOwnerUserId)\n .select(\"licences stripe\")\n .lean()\n .exec();\n\n return mapVendorLicenceToSubscriptionRewardType(\n vendorOwner?.licences,\n now,\n vendorOwner?.stripe,\n );\n}\n\nasync function notifyAffiliateOwnerOfSubscriptionReward({\n affiliateId,\n affiliateOwnerUserId,\n resourceName,\n rewardValue,\n}: {\n affiliateId: ObjectId | string;\n affiliateOwnerUserId: ObjectId | string;\n resourceName: string;\n rewardValue: number;\n}): Promise<void> {\n const payload = {\n data: {\n resourceId: String(affiliateId),\n resourceName,\n resourceType: EnumNotificationResourceType.AFFILIATE_REWARD_RECEIVED,\n },\n message: `You earned ${rewardValue} points! Your referred vendor \"${resourceName}\" has an active subscription.`,\n title: \"Affiliate points earned\",\n type: EnumNotificationType.SYSTEM,\n userIds: [affiliateOwnerUserId as ObjectId],\n };\n\n try {\n await saveNotificationsInDb(payload);\n await sendPushNotifications(payload);\n } catch (error) {\n console.error(\n `[affiliate] subscription reward notification failed for affiliate ${String(affiliateId)}:`,\n error instanceof Error ? error.message : error,\n );\n }\n}\n\nasync function tryAwardSubscriptionReward({\n affiliate,\n createdAt,\n period,\n resource,\n rewardType,\n vendor,\n}: {\n affiliate: LeanAffiliate;\n createdAt: Date;\n period: PeriodBounds;\n resource: LeanAffiliateResource;\n rewardType: EnumAffiliateRewardType;\n vendor: LeanVendor;\n}): Promise<\"awarded\" | \"skipped\"> {\n const reward = createAffiliateReward(rewardType, createdAt);\n\n const updateResult = await AffiliateModel.updateOne(\n {\n _id: affiliate._id,\n affiliateResources: {\n $elemMatch: {\n resourceActive: true,\n resourceDeletedAt: null,\n resourceId: vendor._id,\n resourceType: EnumResourceType.VENDOR,\n rewards: {\n $not: {\n $elemMatch: {\n createdAt: {\n $gte: period.periodStart,\n $lt: period.periodEnd,\n },\n rewardType,\n },\n },\n },\n },\n },\n deletedAt: null,\n },\n {\n $inc: { overallPoints: reward.rewardValue },\n $push: { \"affiliateResources.$.rewards\": reward },\n },\n ).exec();\n\n if (updateResult.modifiedCount === 0) {\n return \"skipped\";\n }\n\n const affiliateOwnerUserId = affiliate.owner?.userId;\n if (affiliateOwnerUserId) {\n await notifyAffiliateOwnerOfSubscriptionReward({\n affiliateId: affiliate._id,\n affiliateOwnerUserId,\n resourceName: resource.resourceName || vendor.name || \"vendor\",\n rewardValue: reward.rewardValue,\n });\n }\n\n return \"awarded\";\n}\n\nasync function processAffiliateVendorReferral({\n affiliate,\n affiliateCode,\n createdAt,\n now,\n period,\n resource,\n}: {\n affiliate: LeanAffiliate;\n affiliateCode: string;\n createdAt: Date;\n now: Date;\n period: PeriodBounds;\n resource: LeanAffiliateResource;\n}): Promise<\"awarded\" | \"skipped\"> {\n const vendor = await findEligibleReferredVendor(\n resource.resourceId,\n affiliateCode,\n );\n if (!vendor?.owner?.userId) {\n return \"skipped\";\n }\n\n const rewardType = await resolveSubscriptionRewardTypeForVendorOwner(\n vendor.owner.userId,\n now,\n );\n if (!rewardType) {\n return \"skipped\";\n }\n\n return tryAwardSubscriptionReward({\n affiliate,\n createdAt,\n period,\n resource,\n rewardType,\n vendor,\n });\n}\n\n/**\n * Awards monthly vendor subscription rewards to affiliates with active referred vendors.\n *\n * Eligibility per affiliate resource:\n * - Affiliate is not soft-deleted\n * - Linked `affiliateResources` entry is vendor, `resourceActive`, and not deleted\n * - Vendor is active, not deleted, and `promoCodes` includes the affiliate code\n * - Vendor owner has a non-expired Standard / Pro / Pro+ vendor licence\n *\n * Reward type follows the owner's highest valid licence + Stripe paying status:\n * - Pro / Pro+ with active Stripe → `ACTIVE_VENDOR_PRO_SUBSCRIPTION` (3 pts)\n * - Pro / Pro+ without paying Stripe → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)\n * - Standard → `ACTIVE_VENDOR_STANDARD_SUBSCRIPTION` (1 pt)\n *\n * Idempotent per affiliate + vendor + reward type + calendar month.\n * On successful award, notifies the affiliate owner (best-effort).\n *\n * @param now - Reference time for month bounds and licence expiry (defaults to now).\n * @returns Counts of awarded and skipped candidates.\n */\nexport async function awardAffiliateVendorSubscriptionRewards(\n now: Date = new Date(),\n): Promise<AwardAffiliateVendorSubscriptionRewardsResult> {\n const period = getSubscriptionRewardPeriodBounds(now);\n const createdAt = now;\n const affiliates = await findAffiliatesWithActiveVendorReferrals();\n\n let awarded = 0;\n let skipped = 0;\n const seenPairs = new Set<string>();\n\n for (const affiliate of affiliates) {\n const affiliateCode = normalizePromoCode(affiliate.affiliateCode);\n if (!affiliateCode) {\n continue;\n }\n\n for (const resource of affiliate.affiliateResources) {\n if (!isEligibleVendorResource(resource)) {\n continue;\n }\n\n const pairKey = `${String(affiliate._id)}:${String(resource.resourceId)}`;\n if (seenPairs.has(pairKey)) {\n continue;\n }\n seenPairs.add(pairKey);\n\n const outcome = await processAffiliateVendorReferral({\n affiliate,\n affiliateCode,\n createdAt,\n now,\n period,\n resource,\n });\n\n if (outcome === \"awarded\") {\n awarded += 1;\n } else {\n skipped += 1;\n }\n }\n }\n\n return { awarded, skipped };\n}\n","import { PromoCodeType } from \"@timardex/cluemart-shared\";\n\nimport { AffiliateModel } from \"src/mongoose\";\n\n/**\n * Loads the affiliate whose `affiliateCode` matches the given promo code.\n *\n * Affiliate codes are assigned on admin activation, so referrals only resolve after\n * a code exists. Soft-deleted affiliates (`deletedAt` set) are excluded.\n *\n * @param promoCode - Normalized affiliate promo code to resolve.\n * @returns The matching affiliate document, or `null` when not found.\n */\nexport async function findAffiliateByPromoCode(promoCode: PromoCodeType) {\n return AffiliateModel.findOne({\n affiliateCode: promoCode,\n deletedAt: null,\n }).exec();\n}\n","import { PromoCodeType } from \"@timardex/cluemart-shared\";\n\nimport { normalizePromoCode } from \"../promoCode/normalizePromoCode\";\n\n/**\n * Normalizes, trims, uppercases, and deduplicates resource promo codes.\n *\n * @param promoCodes - Raw promo codes from a create-resource mutation input.\n * @returns Unique normalized codes; empty strings and whitespace-only values are removed.\n */\nexport function normalizeAffiliatePromoCodes(\n promoCodes: PromoCodeType[] | null | undefined,\n): PromoCodeType[] {\n const normalized = (promoCodes ?? [])\n .map((code) => normalizePromoCode(code))\n .filter((code): code is PromoCodeType => code !== null);\n\n return [...new Set(normalized)];\n}\n","import mongoose from \"mongoose\";\n\n/**\n * Connect to MongoDB using Mongoose.\n * Supports both local MongoDB (via MONGODB_URI) and MongoDB Atlas (via individual env vars).\n */\nexport const connectToDatabase = async ({\n appName,\n dbName,\n dbPassword,\n dbUser,\n mongodbUri,\n}: {\n appName: string;\n dbName: string;\n dbPassword: string;\n dbUser: string;\n mongodbUri: string;\n}) => {\n try {\n // Check if MONGODB_URI is provided (for local Docker MongoDB)\n const mongoUri = mongodbUri\n ? mongodbUri\n : // Fallback to MongoDB Atlas connection string\n `mongodb+srv://${dbUser}:${dbPassword}@${dbName}.mongodb.net/?retryWrites=true&w=majority&appName=${appName}`;\n\n await mongoose.connect(mongoUri);\n\n const connectionType = mongodbUri ? \"Local MongoDB\" : \"MongoDB Atlas\";\n console.log(\n `${connectionType} connected from server/src/service/database.ts`,\n );\n } catch (err) {\n console.error(\"Error connecting to MongoDB:\", err);\n throw err; // You can throw the error if you want to stop the server in case of connection failure\n }\n};\n","import {\n NotificationModel,\n SchemaCreateBulkNotificationInput,\n} from \"src/mongoose/Notification\";\nimport { EnumPubSubEvents, GraphQLContext, ObjectId } from \"src/types\";\n\nimport { saveNotificationsInDb } from \"./saveNotificationsInDb\";\nimport { sendPushNotifications } from \"./sendPushNotifications\";\n\n/**\n * Publish-only pubsub used to emit GraphQL subscription events. Optional in\n * {@link notifyUsers} because non-GraphQL processes (e.g. the cron worker) have\n * no subscribers to publish to.\n */\nexport type NotificationPubSub = Pick<GraphQLContext[\"pubsub\"], \"publish\">;\n\n/** Publishes notification list/count subscription events for a user. */\nexport async function publishNotificationEvents(\n userId: ObjectId,\n pubsub: NotificationPubSub,\n) {\n try {\n // Get user's notifications for the subscription\n const userNotifications = await NotificationModel.find({\n userId,\n }).sort({ createdAt: -1 });\n\n // Get notification count\n const [total, unread] = await Promise.all([\n NotificationModel.countDocuments({ userId }),\n NotificationModel.countDocuments({ isRead: false, userId }),\n ]);\n\n // Publish both events\n pubsub.publish(EnumPubSubEvents.GET_NOTIFICATIONS, {\n getNotifications: userNotifications,\n getNotificationsUserId: userId,\n });\n\n pubsub.publish(EnumPubSubEvents.GET_NOTIFICATIONS_COUNT, {\n getNotificationsCount: { total, unread, userId },\n });\n\n console.log(`Published notification events for user: ${String(userId)}`);\n } catch (error) {\n console.error(\n `Failed to publish notification events for user ${String(userId)}:`,\n error,\n );\n }\n}\n\n/**\n * Sends push notifications, saves them in the database, and (when a `pubsub`\n * is provided) publishes GraphQL subscription events for each affected user.\n *\n * Errors are logged and swallowed; this function never throws. Instead it\n * returns `false` when notifications were not confirmed (an error was thrown,\n * or `saveNotificationsInDb` failed to persist any rows for a non-empty\n * `userIds`), so callers can gate success logs/metrics on the result.\n */\nexport async function notifyUsers({\n payload,\n pubsub,\n}: {\n payload: SchemaCreateBulkNotificationInput;\n pubsub?: NotificationPubSub;\n}): Promise<boolean> {\n try {\n await sendPushNotifications(payload);\n\n const uniqueUserIds = await saveNotificationsInDb(payload);\n // saveNotificationsInDb swallows its own errors and returns []; treat an\n // empty result for a non-empty input as a persistence failure.\n const persisted = payload.userIds.length === 0 || uniqueUserIds.length > 0;\n\n if (pubsub) {\n for (const userId of uniqueUserIds) {\n await publishNotificationEvents(userId, pubsub);\n }\n }\n\n return persisted;\n } catch (error) {\n console.error(\"Error in notifyUsers:\", error);\n return false;\n }\n}\n","import { EnumAdStatus } from \"@timardex/cluemart-shared\";\n\nimport { AdModel } from \"src/mongoose\";\n\n/**\n * Updates ad statuses based on start/end dates and validity\n */\nexport async function updateAdStatuses(): Promise<void> {\n const now = new Date();\n\n // invalid\n const invalidResult = await AdModel.updateMany(\n {\n $or: [\n { start: { $exists: false } },\n { end: { $exists: false } },\n { $expr: { $gt: [\"$start\", \"$end\"] } },\n ],\n status: { $ne: EnumAdStatus.PAUSED },\n },\n { $set: { status: EnumAdStatus.PAUSED } },\n );\n\n // expired\n const expiredResult = await AdModel.updateMany(\n { end: { $lte: now }, status: { $ne: EnumAdStatus.EXPIRED } },\n { $set: { status: EnumAdStatus.EXPIRED } },\n );\n\n // active\n const activeResult = await AdModel.updateMany(\n {\n end: { $gt: now },\n start: { $lte: now },\n status: { $ne: EnumAdStatus.ACTIVE },\n },\n { $set: { status: EnumAdStatus.ACTIVE } },\n );\n\n // paused\n const pausedResult = await AdModel.updateMany(\n { start: { $gt: now }, status: { $ne: EnumAdStatus.PAUSED } },\n { $set: { status: EnumAdStatus.PAUSED } },\n );\n\n console.log(\n `✅ Ad statuses updated: invalid=${invalidResult.modifiedCount}, expired=${expiredResult.modifiedCount}, active=${activeResult.modifiedCount}, paused=${pausedResult.modifiedCount}`,\n );\n}\n","import { EnumUserLicence } from \"@timardex/cluemart-shared\";\n\nimport { UserModel, VendorModel, SchemaVendorType } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\nexport async function updateVendorBasedOnUserLicense(\n userId: ObjectId,\n licenceType: EnumUserLicence,\n): Promise<void> {\n try {\n /**\n * Fetch user vendor reference\n */\n const user = await UserModel.findById(userId)\n .select(\"vendor\")\n .lean()\n .exec();\n\n if (!user?.vendor) {\n console.warn(`[updateVendor] No vendor found for userId=${userId}`);\n return;\n }\n\n /**\n * Fetch vendor\n */\n const vendor = await VendorModel.findById(user.vendor)\n .lean<SchemaVendorType>()\n .exec();\n\n if (!vendor) {\n console.warn(`[updateVendor] Vendor not found for id=${user.vendor}`);\n return;\n }\n\n /**\n * Build vendor update payload\n */\n const updateData: Partial<SchemaVendorType> = {};\n\n const isStandardVendor = licenceType === EnumUserLicence.STANDARD_VENDOR;\n\n if (isStandardVendor) {\n updateData.availability = {\n corporate: false,\n private: false,\n school: false,\n };\n\n updateData.products = {\n active: false,\n productsList: vendor.products?.productsList ?? [],\n };\n\n updateData.calendar = {\n active: false,\n calendarData: vendor.calendar?.calendarData ?? [],\n };\n }\n\n /**\n * Image rules\n * STANDARD_VENDOR => only first 6 active, 6 is the default image limit for standard vendors\n * PRO_VENDOR => all active\n */\n updateData.images = (vendor.images ?? []).map((image, index) => ({\n ...image,\n active: isStandardVendor ? index < 6 : true,\n }));\n\n /**\n * Persist vendor updates\n */\n await VendorModel.updateOne({ _id: vendor._id }, { $set: updateData });\n } catch (error) {\n console.error(\"[updateVendorBasedOnUserLicense] Failed:\", error);\n }\n}\n","import mongoose from \"mongoose\";\n\n/**\n * Recursively converts all ObjectId fields to strings in an object\n * This is needed because GraphQL expects string IDs, not ObjectIds\n */\nexport function convertObjectIdsToStrings(obj: any): any {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (obj instanceof mongoose.Types.ObjectId) {\n return obj.toString();\n }\n\n if (obj instanceof Date) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(convertObjectIdsToStrings);\n }\n\n if (typeof obj === \"object\") {\n const converted: any = {};\n for (const [key, value] of Object.entries(obj)) {\n converted[key] = convertObjectIdsToStrings(value);\n }\n return converted;\n }\n\n return obj;\n}\n","import {\n dateFormat,\n DateTimeType,\n EnumEventDateStatus,\n timeFormat,\n} from \"@timardex/cluemart-shared\";\nimport dayjs from \"dayjs\";\nimport customParseFormat from \"dayjs/plugin/customParseFormat\";\nimport isoWeek from \"dayjs/plugin/isoWeek\";\n\nimport { EventModel, GoogleImportedMarketModel } from \"src/mongoose\";\n\n// Enable dayjs plugins used by event date parsing/status logic\ndayjs.extend(customParseFormat);\ndayjs.extend(isoWeek);\n\n/**\n * Determines the dateStatus for a future event date\n * @param startDateTime The start date-time of the event\n * @param now The current date-time\n * @returns The appropriate EnumEventDateStatus\n */\nexport function getFutureEventStatus(\n startDateTime: dayjs.Dayjs,\n now: dayjs.Dayjs,\n): EnumEventDateStatus {\n const hoursUntilStart = startDateTime.diff(now, \"hour\", true);\n if (hoursUntilStart > 0 && hoursUntilStart <= 4) {\n return EnumEventDateStatus.STARTING_SOON;\n }\n\n if (startDateTime.isSame(now, \"day\")) {\n return EnumEventDateStatus.TODAY;\n }\n\n if (startDateTime.isSame(now.add(1, \"day\"), \"day\")) {\n return EnumEventDateStatus.TOMORROW;\n }\n\n if (startDateTime.isSame(now, \"isoWeek\")) {\n return EnumEventDateStatus.THIS_WEEK;\n }\n\n if (startDateTime.isSame(now.add(1, \"week\"), \"isoWeek\")) {\n return EnumEventDateStatus.NEXT_WEEK;\n }\n\n return EnumEventDateStatus.UPCOMING;\n}\n\n/**\n * Updates the dateStatus field for a single DateTimeType object based on the current date/time\n * @param dateTime The date-time object to update\n * @returns A new object with updated dateStatus value\n */\nexport function updateSingleDateTimeStatus<T extends DateTimeType>(\n dateTime: T,\n now: dayjs.Dayjs = dayjs(),\n): T {\n // Parse start and end date-time\n const dateTimeFormat = `${dateFormat} ${timeFormat}`;\n const startDateTime = dayjs(\n `${dateTime.startDate} ${dateTime.startTime}`,\n dateTimeFormat,\n true,\n );\n const endDateTime = dayjs(\n `${dateTime.endDate} ${dateTime.endTime}`,\n dateTimeFormat,\n true,\n );\n\n // Skip if dates are invalid\n if (!startDateTime.isValid() || !endDateTime.isValid()) {\n return {\n ...dateTime,\n dateStatus: EnumEventDateStatus.INVALID,\n };\n }\n\n if (endDateTime.isBefore(startDateTime)) {\n return {\n ...dateTime,\n dateStatus: EnumEventDateStatus.INVALID,\n };\n }\n\n let dateStatus: EnumEventDateStatus;\n\n if (endDateTime.isAfter(now)) {\n if (startDateTime.isAfter(now)) {\n // Event has not started yet\n dateStatus = getFutureEventStatus(startDateTime, now);\n } else {\n // Event is in progress\n dateStatus = EnumEventDateStatus.STARTED;\n }\n } else {\n // Event has ended\n dateStatus = EnumEventDateStatus.ENDED;\n }\n\n return {\n ...dateTime,\n dateStatus,\n };\n}\n\nconst dateTimeStatusFilter = {\n dateTime: { $ne: [], $type: \"array\" },\n deletedAt: null,\n} as const;\n\nconst CURSOR_BATCH_SIZE = 50;\n\ntype DocWithDateTime = {\n _id: unknown;\n dateTime: DateTimeType[];\n};\n\ntype DateTimeBulkWriteOp = {\n updateOne: {\n filter: { _id: unknown };\n update: { $set: { dateTime: DateTimeType[] } };\n };\n};\n\ntype DateTimeCursor = AsyncIterable<DocWithDateTime> & {\n close: () => Promise<unknown>;\n};\n\ntype DateTimeUpdatableModel = {\n bulkWrite: (ops: DateTimeBulkWriteOp[]) => Promise<unknown>;\n find: (filter: typeof dateTimeStatusFilter) => {\n select: (fields: { _id: 1; dateTime: 1 }) => {\n lean: () => {\n cursor: (opts: { batchSize: number }) => DateTimeCursor;\n };\n };\n };\n};\n\nfunction hasDateTimeStatusChanges(\n stored: DateTimeType[],\n updated: DateTimeType[],\n): boolean {\n if (stored.length !== updated.length) {\n return true;\n }\n\n return stored.some(\n (slot, index) => slot.dateStatus !== updated[index].dateStatus,\n );\n}\n\nasync function updateModelDateTimeStatuses(\n model: DateTimeUpdatableModel,\n now: dayjs.Dayjs,\n): Promise<number> {\n let updated = 0;\n const cursor = model\n .find(dateTimeStatusFilter)\n .select({ _id: 1, dateTime: 1 })\n .lean()\n .cursor({ batchSize: CURSOR_BATCH_SIZE });\n\n let bulkOps: DateTimeBulkWriteOp[] = [];\n\n const flushBulkWrites = async (): Promise<void> => {\n if (!bulkOps.length) {\n return;\n }\n\n await model.bulkWrite(bulkOps);\n updated += bulkOps.length;\n bulkOps = [];\n };\n\n try {\n for await (const doc of cursor) {\n const dateTime = doc.dateTime.map((slot) =>\n updateSingleDateTimeStatus(slot, now),\n );\n\n if (!hasDateTimeStatusChanges(doc.dateTime, dateTime)) {\n continue;\n }\n\n bulkOps.push({\n updateOne: {\n filter: { _id: doc._id },\n update: { $set: { dateTime } },\n },\n });\n\n if (bulkOps.length >= CURSOR_BATCH_SIZE) {\n await flushBulkWrites();\n }\n }\n\n await flushBulkWrites();\n } finally {\n await cursor.close().catch(() => undefined);\n }\n\n return updated;\n}\n\n/**\n * Recomputes dateStatus for every dateTime slot on events and google imported markets.\n */\nexport async function updateAllEventDateTimeStatuses(): Promise<void> {\n const now = dayjs();\n const [eventCount, marketCount] = await Promise.all([\n updateModelDateTimeStatuses(EventModel as DateTimeUpdatableModel, now),\n updateModelDateTimeStatuses(\n GoogleImportedMarketModel as DateTimeUpdatableModel,\n now,\n ),\n ]);\n\n console.log(\n `✅ Event dateTime statuses updated: events=${eventCount} changed, google imported markets=${marketCount} changed`,\n );\n}\n","import { EventListItemType } from \"@timardex/cluemart-shared\";\n\nimport { EventModel, GoogleImportedMarketModel } from \"src/mongoose\";\nimport { ObjectId } from \"src/types\";\n\nimport { convertObjectIdsToStrings } from \"../objectIdToString\";\n\ntype EventOrMarket = Pick<EventListItemType, \"_id\" | \"name\"> | null;\n\n/**\n * This function attempts to find an Event or a Google Imported Market by the given resource ID.\n * It first normalizes the resource ID to a string format, then performs parallel queries to both collections.\n * If an Event is found, it returns that; otherwise, it checks for a Google Imported Market and returns it if found.\n * If neither is found, it returns null.\n * @param resourceId - The ID of the resource to find, which can be an ObjectId, string, null, or undefined.\n * @returns A promise that resolves to either an Event or a Google Imported Market object containing _id and name, or null if not found.\n */\n\nexport async function findEventOrImportedMarketById(\n resourceId: ObjectId | string | null | undefined,\n): Promise<EventOrMarket> {\n if (!resourceId) {\n return null;\n }\n\n const normalizedId = convertObjectIdsToStrings(resourceId) as string;\n\n const [eventDoc, googleImportedDoc] = await Promise.all([\n EventModel.findById(normalizedId)\n .select(\"_id name\")\n .lean<Pick<EventListItemType, \"_id\" | \"name\">>()\n .exec(),\n GoogleImportedMarketModel.findById(normalizedId)\n .select(\"_id name\")\n .lean<Pick<EventListItemType, \"_id\" | \"name\">>()\n .exec(),\n ]);\n\n return eventDoc ?? googleImportedDoc;\n}\n","import {\n DateTimeWithPriceType,\n EnumInviteStatus,\n} from \"@timardex/cluemart-shared\";\nimport { DateTimeType } from \"@timardex/cluemart-shared/types\";\n\nimport { SchemaRelationType } from \"src/mongoose/Relation\";\n\ntype EventDateSlot = Pick<DateTimeType, \"startDate\" | \"startTime\">;\n\n/**\n * Returns true when at least one startDate/startTime slot from the previous\n * schedule is absent from the next schedule (i.e. a date was removed).\n */\nexport function didRemoveAnyEventDates(\n previousDateTime: EventDateSlot[] | undefined,\n nextDateTime: EventDateSlot[] | undefined,\n): boolean {\n if (!previousDateTime?.length) {\n return false;\n }\n\n return previousDateTime.some(\n (prev) =>\n !nextDateTime?.some(\n (next) =>\n next.startDate === prev.startDate &&\n next.startTime === prev.startTime,\n ),\n );\n}\n\n/**\n * Helper: Update relationDates based on event's dateTime\n * Marks dates as UNAVAILABLE if they no longer exist in the event's dateTime.\n */\nexport function updateRelationDatesToUnavailable(\n relationDates: SchemaRelationType[\"relationDates\"],\n eventDateTime: DateTimeWithPriceType[] | undefined,\n): SchemaRelationType[\"relationDates\"] {\n return relationDates.map((relationDate) => {\n // Check if this relationDate exists in the event's dateTime\n const existsInEvent =\n eventDateTime?.some(\n (dt) =>\n dt.startDate === relationDate.dateTime.startDate &&\n dt.startTime === relationDate.dateTime.startTime,\n ) ?? false;\n\n return {\n ...relationDate,\n status: existsInEvent\n ? relationDate.status\n : EnumInviteStatus.UNAVAILABLE,\n };\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AACO,IAAM,4BAA4B;AAAA,EACvC,QAAQ;AAAA,EACR,WAAW;AACb;;;ACSA,eAAsB,0BACpB,eACkB;AAClB,QAAM,WAAW,MAAM,eAAe,OAAO;AAAA,IAC3C,GAAG;AAAA,IACH;AAAA,EACF,CAAC,EAAE,KAAK;AAER,SAAO,aAAa;AACtB;;;ACtBO,SAAS,mBACd,oBACe;AACf,QAAM,aAAa,oBAAoB,KAAK,EAAE,YAAY;AAC1D,SAAO,cAAc;AACvB;;;ACKA,eAAsB,sBACpB,SACqB;AACrB,QAAM,EAAE,MAAM,SAAS,OAAO,MAAM,QAAQ,IAAI;AAChD,UAAQ,IAAI,qBAAqB,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AACjE,MAAI;AACF,UAAM,gBAAgB,QAAQ,IAAI,CAAC,YAAY;AAAA,MAC7C;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE;AAGF,UAAM,kBAAkB,WAAW,aAAa;AAChD,YAAQ;AAAA,MACN,WAAW,cAAc,MAAM,sBAAsB,QAAQ,MAAM;AAAA,IACrE;AAEA,WAAO,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC;AAAA,EAC7B,SAAS,OAAO;AACd,YAAQ,MAAM,mCAAmC,KAAK;AACtD,WAAO,CAAC;AAAA,EAEV;AACF;;;ACpCA,SAAS,YAA6C;AAKtD,IAAM,OAAO,IAAI,KAAK;AAKtB,SAAS,yBAAyB,SAAoC;AACpE,SAAO,MAAM,QAAQ,QAAQ,EAAE,IAAI,QAAQ,KAAK,CAAC,QAAQ,EAAE;AAC7D;AAYA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAGE;AACA,QAAM,WAA8B,CAAC;AACrC,QAAM,gBAA0B,CAAC;AAEjC,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,KAAK,gBAAgB,KAAK,GAAG;AAChC,oBAAc,KAAK,KAAK;AACxB;AAAA,IACF;AAEA,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,MAAM,EAAE,GAAG,KAAK;AAAA,MAChB,OAAO;AAAA,MACP;AAAA,MACA,IAAI;AAAA,IACN,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,eAAe,SAAS;AACnC;AAKA,SAAS,oBACP,SACA,OACkD;AAClD,MAAI,eAAe;AACnB,QAAM,eAAyB,CAAC;AAEhC,aAAW,CAAC,aAAa,MAAM,KAAK,QAAQ,QAAQ,GAAG;AACrD,QAAI,OAAO,WAAW,SAAS;AAC7B,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,SAAS;AACX,cAAM,SAAS,yBAAyB,OAAO;AAC/C,YAAI,OAAO,SAAS,UAAU,uBAAuB;AACnD,uBAAa,KAAK,GAAG,MAAM;AAAA,QAC7B;AACA,gBAAQ,IAAI,2BAA2B;AAAA,UACrC,OAAO,OAAO,SAAS;AAAA,UACvB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,cAAc,aAAa;AACtC;AAKA,eAAe,UACb,OACA,YAC2D;AAC3D,MAAI;AACF,UAAM,UAAU,MAAM,KAAK,2BAA2B,KAAK;AAC3D,UAAM,EAAE,cAAc,aAAa,IAAI,oBAAoB,SAAS,KAAK;AAEzE,YAAQ;AAAA,MACN,SAAS,aAAa,CAAC,UAAU,YAAY,IAAI,MAAM,MAAM;AAAA,IAC/D;AAEA,WAAO,EAAE,cAAc,aAAa;AAAA,EACtC,SAAS,OAAO;AACd,YAAQ,IAAI,8CAA8C;AAAA,MACxD;AAAA,MACA,WAAW,MAAM;AAAA,MACjB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AACD,WAAO,EAAE,cAAc,CAAC,GAAG,cAAc,EAAE;AAAA,EAC7C;AACF;AAEA,eAAsB,sBAAsB;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsC;AACpC,QAAM,aAAa,MAAM,eAAe,KAAK,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,CAAC;AACzE,QAAM,aAAa,WAAW,IAAI,CAAC,UAAU,MAAM,KAAK;AAExD,MAAI,CAAC,KAAM;AAEX,QAAM,EAAE,UAAU,cAAc,IAAI,mBAAmB;AAAA,IACrD;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACV,CAAC;AAGD,MAAI,cAAc,SAAS,GAAG;AAC5B,YAAQ,IAAI,SAAS,cAAc,MAAM,sBAAsB;AAAA,EACjE;AAEA,MAAI,SAAS,WAAW,GAAG;AACzB,YAAQ,IAAI,kDAAkD;AAC9D;AAAA,EACF;AAGA,QAAM,SAAS,KAAK,uBAAuB,QAAQ;AACnD,MAAI,oBAAoB;AACxB,QAAM,kBAA4B,CAAC;AAEnC,aAAW,CAAC,YAAY,KAAK,KAAK,OAAO,QAAQ,GAAG;AAClD,UAAM,EAAE,cAAc,aAAa,IAAI,MAAM;AAAA,MAC3C;AAAA,MACA,aAAa;AAAA,IACf;AACA,yBAAqB;AACrB,oBAAgB,KAAK,GAAG,YAAY;AAAA,EACtC;AAGA,UAAQ;AAAA,IACN,6BAA6B,iBAAiB,IAAI,SAAS,MAAM,kBAAkB,OAAO,MAAM;AAAA,EAClG;AAEA,MAAI,gBAAgB,SAAS,GAAG;AAC9B,YAAQ,IAAI,SAAS,gBAAgB,MAAM,qBAAqB;AAAA,EAClE;AACF;;;ACxJO,IAAM,oBAAoB;AAAA,EAC/B,CAAC,wBAAwB,sBAAsB,GAAG;AAAA,IAChD,aAAa;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EACA,CAAC,wBAAwB,uBAAuB,GAAG;AAAA,IACjD,aAAa;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EACA,CAAC,wBAAwB,8BAA8B,GAAG;AAAA,IACxD,aAAa;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EACA,CAAC,wBAAwB,mCAAmC,GAAG;AAAA,IAC7D,aACE;AAAA,IACF,OAAO;AAAA,EACT;AAAA,EACA,CAAC,wBAAwB,0BAA0B,GAAG;AAAA,IACpD,aACE;AAAA,IACF,OAAO;AAAA,EACT;AAAA,EACA,CAAC,wBAAwB,sCAAsC,GAAG;AAAA,IAChE,aACE;AAAA,IACF,OAAO;AAAA,EACT;AACF;AAWO,SAAS,sBACd,YACA,WACqB;AACrB,QAAM,SAAS,kBAAkB,UAAU;AAE3C,SAAO;AAAA,IACL;AAAA,IACA,YAAY;AAAA,IACZ,mBAAmB,OAAO;AAAA,IAC1B;AAAA,IACA,aAAa,OAAO;AAAA,EACtB;AACF;;;AClDA,SAAS,2BACP,QACS;AACT,MAAI,CAAC,QAAQ,eAAgB,QAAO;AAEpC,SACE,OAAO,WAAW,uBAAuB,UACzC,OAAO,WAAW,uBAAuB;AAE7C;AAUO,SAAS,yCACd,UACA,MAAY,oBAAI,KAAK,GACrB,QACgC;AAChC,QAAM,aAAa,IAAI;AAAA,KACpB,YAAY,CAAC,GACX;AAAA,MACC,CAAC,YAAY,IAAI,KAAK,QAAQ,UAAU,EAAE,QAAQ,IAAI,IAAI,QAAQ;AAAA,IACpE,EACC,IAAI,CAAC,YAAY,QAAQ,WAAW;AAAA,EACzC;AAEA,MACE,WAAW,IAAI,gBAAgB,UAAU,KACzC,WAAW,IAAI,gBAAgB,eAAe,GAC9C;AACA,QAAI,2BAA2B,MAAM,GAAG;AACtC,aAAO,wBAAwB;AAAA,IACjC;AACA,WAAO,wBAAwB;AAAA,EACjC;AAEA,MAAI,WAAW,IAAI,gBAAgB,eAAe,GAAG;AACnD,WAAO,wBAAwB;AAAA,EACjC;AAEA,SAAO;AACT;AAGO,SAAS,kCAAkC,MAAY,oBAAI,KAAK,GAGrE;AACA,QAAM,cAAc,IAAI;AAAA,IACtB,KAAK,IAAI,IAAI,eAAe,GAAG,IAAI,YAAY,GAAG,CAAC;AAAA,EACrD;AACA,QAAM,YAAY,IAAI;AAAA,IACpB,KAAK,IAAI,IAAI,eAAe,GAAG,IAAI,YAAY,IAAI,GAAG,CAAC;AAAA,EACzD;AACA,SAAO,EAAE,WAAW,YAAY;AAClC;;;ACrBA,SAAS,yBAAyB,UAA0C;AAC1E,SACE,SAAS,iBAAiB,iBAAiB,UAC3C,SAAS,mBAAmB,QAC5B,SAAS,qBAAqB;AAElC;AAEA,eAAe,0CAEb;AACA,SAAQ,MAAM,eAAe,KAAK;AAAA,IAChC,oBAAoB;AAAA,MAClB,YAAY;AAAA,QACV,gBAAgB;AAAA,QAChB,mBAAmB;AAAA,QACnB,cAAc,iBAAiB;AAAA,MACjC;AAAA,IACF;AAAA,IACA,WAAW;AAAA,EACb,CAAC,EACE,OAAO,4CAA4C,EACnD,KAAK,EACL,KAAK;AACV;AAEA,eAAe,2BACb,YACA,eAC4B;AAC5B,SAAQ,MAAM,YAAY,QAAQ;AAAA,IAChC,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,YAAY;AAAA,EACd,CAAC,EACE,OAAO,uBAAuB,EAC9B,KAAK,EACL,KAAK;AACV;AAEA,eAAe,4CACb,mBACA,KACyC;AACzC,QAAM,cAAc,MAAM,UAAU,SAAS,iBAAiB,EAC3D,OAAO,iBAAiB,EACxB,KAAK,EACL,KAAK;AAER,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IACA,aAAa;AAAA,EACf;AACF;AAEA,eAAe,yCAAyC;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKkB;AAChB,QAAM,UAAU;AAAA,IACd,MAAM;AAAA,MACJ,YAAY,OAAO,WAAW;AAAA,MAC9B;AAAA,MACA,cAAc,6BAA6B;AAAA,IAC7C;AAAA,IACA,SAAS,cAAc,WAAW,kCAAkC,YAAY;AAAA,IAChF,OAAO;AAAA,IACP,MAAM,qBAAqB;AAAA,IAC3B,SAAS,CAAC,oBAAgC;AAAA,EAC5C;AAEA,MAAI;AACF,UAAM,sBAAsB,OAAO;AACnC,UAAM,sBAAsB,OAAO;AAAA,EACrC,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,qEAAqE,OAAO,WAAW,CAAC;AAAA,MACxF,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,eAAe,2BAA2B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOmC;AACjC,QAAM,SAAS,sBAAsB,YAAY,SAAS;AAE1D,QAAM,eAAe,MAAM,eAAe;AAAA,IACxC;AAAA,MACE,KAAK,UAAU;AAAA,MACf,oBAAoB;AAAA,QAClB,YAAY;AAAA,UACV,gBAAgB;AAAA,UAChB,mBAAmB;AAAA,UACnB,YAAY,OAAO;AAAA,UACnB,cAAc,iBAAiB;AAAA,UAC/B,SAAS;AAAA,YACP,MAAM;AAAA,cACJ,YAAY;AAAA,gBACV,WAAW;AAAA,kBACT,MAAM,OAAO;AAAA,kBACb,KAAK,OAAO;AAAA,gBACd;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,WAAW;AAAA,IACb;AAAA,IACA;AAAA,MACE,MAAM,EAAE,eAAe,OAAO,YAAY;AAAA,MAC1C,OAAO,EAAE,gCAAgC,OAAO;AAAA,IAClD;AAAA,EACF,EAAE,KAAK;AAEP,MAAI,aAAa,kBAAkB,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,QAAM,uBAAuB,UAAU,OAAO;AAC9C,MAAI,sBAAsB;AACxB,UAAM,yCAAyC;AAAA,MAC7C,aAAa,UAAU;AAAA,MACvB;AAAA,MACA,cAAc,SAAS,gBAAgB,OAAO,QAAQ;AAAA,MACtD,aAAa,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,eAAe,+BAA+B;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOmC;AACjC,QAAM,SAAS,MAAM;AAAA,IACnB,SAAS;AAAA,IACT;AAAA,EACF;AACA,MAAI,CAAC,QAAQ,OAAO,QAAQ;AAC1B,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM;AAAA,IACvB,OAAO,MAAM;AAAA,IACb;AAAA,EACF;AACA,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,SAAO,2BAA2B;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAsBA,eAAsB,wCACpB,MAAY,oBAAI,KAAK,GACmC;AACxD,QAAM,SAAS,kCAAkC,GAAG;AACpD,QAAM,YAAY;AAClB,QAAM,aAAa,MAAM,wCAAwC;AAEjE,MAAI,UAAU;AACd,MAAI,UAAU;AACd,QAAM,YAAY,oBAAI,IAAY;AAElC,aAAW,aAAa,YAAY;AAClC,UAAM,gBAAgB,mBAAmB,UAAU,aAAa;AAChE,QAAI,CAAC,eAAe;AAClB;AAAA,IACF;AAEA,eAAW,YAAY,UAAU,oBAAoB;AACnD,UAAI,CAAC,yBAAyB,QAAQ,GAAG;AACvC;AAAA,MACF;AAEA,YAAM,UAAU,GAAG,OAAO,UAAU,GAAG,CAAC,IAAI,OAAO,SAAS,UAAU,CAAC;AACvE,UAAI,UAAU,IAAI,OAAO,GAAG;AAC1B;AAAA,MACF;AACA,gBAAU,IAAI,OAAO;AAErB,YAAM,UAAU,MAAM,+BAA+B;AAAA,QACnD;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,UAAI,YAAY,WAAW;AACzB,mBAAW;AAAA,MACb,OAAO;AACL,mBAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,QAAQ;AAC5B;;;AC7SA,eAAsB,yBAAyB,WAA0B;AACvE,SAAO,eAAe,QAAQ;AAAA,IAC5B,eAAe;AAAA,IACf,WAAW;AAAA,EACb,CAAC,EAAE,KAAK;AACV;;;ACRO,SAAS,6BACd,YACiB;AACjB,QAAM,cAAc,cAAc,CAAC,GAChC,IAAI,CAAC,SAAS,mBAAmB,IAAI,CAAC,EACtC,OAAO,CAAC,SAAgC,SAAS,IAAI;AAExD,SAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;;;AClBA,OAAO,cAAc;AAMd,IAAM,oBAAoB,OAAO;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMM;AACJ,MAAI;AAEF,UAAM,WAAW,aACb;AAAA;AAAA,MAEA,iBAAiB,MAAM,IAAI,UAAU,IAAI,MAAM,qDAAqD,OAAO;AAAA;AAE/G,UAAM,SAAS,QAAQ,QAAQ;AAE/B,UAAM,iBAAiB,aAAa,kBAAkB;AACtD,YAAQ;AAAA,MACN,GAAG,cAAc;AAAA,IACnB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,gCAAgC,GAAG;AACjD,UAAM;AAAA,EACR;AACF;;;ACnBA,eAAsB,0BACpB,QACA,QACA;AACA,MAAI;AAEF,UAAM,oBAAoB,MAAM,kBAAkB,KAAK;AAAA,MACrD;AAAA,IACF,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,CAAC;AAGzB,UAAM,CAAC,OAAO,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MACxC,kBAAkB,eAAe,EAAE,OAAO,CAAC;AAAA,MAC3C,kBAAkB,eAAe,EAAE,QAAQ,OAAO,OAAO,CAAC;AAAA,IAC5D,CAAC;AAGD,WAAO,qDAA4C;AAAA,MACjD,kBAAkB;AAAA,MAClB,wBAAwB;AAAA,IAC1B,CAAC;AAED,WAAO,iEAAkD;AAAA,MACvD,uBAAuB,EAAE,OAAO,QAAQ,OAAO;AAAA,IACjD,CAAC;AAED,YAAQ,IAAI,2CAA2C,OAAO,MAAM,CAAC,EAAE;AAAA,EACzE,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,kDAAkD,OAAO,MAAM,CAAC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACF;AAWA,eAAsB,YAAY;AAAA,EAChC;AAAA,EACA;AACF,GAGqB;AACnB,MAAI;AACF,UAAM,sBAAsB,OAAO;AAEnC,UAAM,gBAAgB,MAAM,sBAAsB,OAAO;AAGzD,UAAM,YAAY,QAAQ,QAAQ,WAAW,KAAK,cAAc,SAAS;AAEzE,QAAI,QAAQ;AACV,iBAAW,UAAU,eAAe;AAClC,cAAM,0BAA0B,QAAQ,MAAM;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,yBAAyB,KAAK;AAC5C,WAAO;AAAA,EACT;AACF;;;AChFA,eAAsB,mBAAkC;AACtD,QAAM,MAAM,oBAAI,KAAK;AAGrB,QAAM,gBAAgB,MAAM,QAAQ;AAAA,IAClC;AAAA,MACE,KAAK;AAAA,QACH,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE;AAAA,QAC5B,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE;AAAA,QAC1B,EAAE,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,EAAE,EAAE;AAAA,MACvC;AAAA,MACA,QAAQ,EAAE,KAAK,aAAa,OAAO;AAAA,IACrC;AAAA,IACA,EAAE,MAAM,EAAE,QAAQ,aAAa,OAAO,EAAE;AAAA,EAC1C;AAGA,QAAM,gBAAgB,MAAM,QAAQ;AAAA,IAClC,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,QAAQ,EAAE,KAAK,aAAa,QAAQ,EAAE;AAAA,IAC5D,EAAE,MAAM,EAAE,QAAQ,aAAa,QAAQ,EAAE;AAAA,EAC3C;AAGA,QAAM,eAAe,MAAM,QAAQ;AAAA,IACjC;AAAA,MACE,KAAK,EAAE,KAAK,IAAI;AAAA,MAChB,OAAO,EAAE,MAAM,IAAI;AAAA,MACnB,QAAQ,EAAE,KAAK,aAAa,OAAO;AAAA,IACrC;AAAA,IACA,EAAE,MAAM,EAAE,QAAQ,aAAa,OAAO,EAAE;AAAA,EAC1C;AAGA,QAAM,eAAe,MAAM,QAAQ;AAAA,IACjC,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,QAAQ,EAAE,KAAK,aAAa,OAAO,EAAE;AAAA,IAC5D,EAAE,MAAM,EAAE,QAAQ,aAAa,OAAO,EAAE;AAAA,EAC1C;AAEA,UAAQ;AAAA,IACN,uCAAkC,cAAc,aAAa,aAAa,cAAc,aAAa,YAAY,aAAa,aAAa,YAAY,aAAa,aAAa;AAAA,EACnL;AACF;;;AC3CA,eAAsB,+BACpB,QACA,aACe;AACf,MAAI;AAIF,UAAM,OAAO,MAAM,UAAU,SAAS,MAAM,EACzC,OAAO,QAAQ,EACf,KAAK,EACL,KAAK;AAER,QAAI,CAAC,MAAM,QAAQ;AACjB,cAAQ,KAAK,6CAA6C,MAAM,EAAE;AAClE;AAAA,IACF;AAKA,UAAM,SAAS,MAAM,YAAY,SAAS,KAAK,MAAM,EAClD,KAAuB,EACvB,KAAK;AAER,QAAI,CAAC,QAAQ;AACX,cAAQ,KAAK,0CAA0C,KAAK,MAAM,EAAE;AACpE;AAAA,IACF;AAKA,UAAM,aAAwC,CAAC;AAE/C,UAAM,mBAAmB,gBAAgB,gBAAgB;AAEzD,QAAI,kBAAkB;AACpB,iBAAW,eAAe;AAAA,QACxB,WAAW;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,MACV;AAEA,iBAAW,WAAW;AAAA,QACpB,QAAQ;AAAA,QACR,cAAc,OAAO,UAAU,gBAAgB,CAAC;AAAA,MAClD;AAEA,iBAAW,WAAW;AAAA,QACpB,QAAQ;AAAA,QACR,cAAc,OAAO,UAAU,gBAAgB,CAAC;AAAA,MAClD;AAAA,IACF;AAOA,eAAW,UAAU,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,WAAW;AAAA,MAC/D,GAAG;AAAA,MACH,QAAQ,mBAAmB,QAAQ,IAAI;AAAA,IACzC,EAAE;AAKF,UAAM,YAAY,UAAU,EAAE,KAAK,OAAO,IAAI,GAAG,EAAE,MAAM,WAAW,CAAC;AAAA,EACvE,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAAA,EACjE;AACF;;;AC7EA,OAAOA,eAAc;AAMd,SAAS,0BAA0B,KAAe;AACvD,MAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,WAAO;AAAA,EACT;AAEA,MAAI,eAAeA,UAAS,MAAM,UAAU;AAC1C,WAAO,IAAI,SAAS;AAAA,EACtB;AAEA,MAAI,eAAe,MAAM;AACvB,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,yBAAyB;AAAA,EAC1C;AAEA,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,YAAiB,CAAC;AACxB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,gBAAU,GAAG,IAAI,0BAA0B,KAAK;AAAA,IAClD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;AC1BA,OAAO,WAAW;AAClB,OAAO,uBAAuB;AAC9B,OAAO,aAAa;AAKpB,MAAM,OAAO,iBAAiB;AAC9B,MAAM,OAAO,OAAO;AAQb,SAAS,qBACd,eACA,KACqB;AACrB,QAAM,kBAAkB,cAAc,KAAK,KAAK,QAAQ,IAAI;AAC5D,MAAI,kBAAkB,KAAK,mBAAmB,GAAG;AAC/C,WAAO,oBAAoB;AAAA,EAC7B;AAEA,MAAI,cAAc,OAAO,KAAK,KAAK,GAAG;AACpC,WAAO,oBAAoB;AAAA,EAC7B;AAEA,MAAI,cAAc,OAAO,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG;AAClD,WAAO,oBAAoB;AAAA,EAC7B;AAEA,MAAI,cAAc,OAAO,KAAK,SAAS,GAAG;AACxC,WAAO,oBAAoB;AAAA,EAC7B;AAEA,MAAI,cAAc,OAAO,IAAI,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG;AACvD,WAAO,oBAAoB;AAAA,EAC7B;AAEA,SAAO,oBAAoB;AAC7B;AAOO,SAAS,2BACd,UACA,MAAmB,MAAM,GACtB;AAEH,QAAM,iBAAiB,GAAG,UAAU,IAAI,UAAU;AAClD,QAAM,gBAAgB;AAAA,IACpB,GAAG,SAAS,SAAS,IAAI,SAAS,SAAS;AAAA,IAC3C;AAAA,IACA;AAAA,EACF;AACA,QAAM,cAAc;AAAA,IAClB,GAAG,SAAS,OAAO,IAAI,SAAS,OAAO;AAAA,IACvC;AAAA,IACA;AAAA,EACF;AAGA,MAAI,CAAC,cAAc,QAAQ,KAAK,CAAC,YAAY,QAAQ,GAAG;AACtD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY,oBAAoB;AAAA,IAClC;AAAA,EACF;AAEA,MAAI,YAAY,SAAS,aAAa,GAAG;AACvC,WAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY,oBAAoB;AAAA,IAClC;AAAA,EACF;AAEA,MAAI;AAEJ,MAAI,YAAY,QAAQ,GAAG,GAAG;AAC5B,QAAI,cAAc,QAAQ,GAAG,GAAG;AAE9B,mBAAa,qBAAqB,eAAe,GAAG;AAAA,IACtD,OAAO;AAEL,mBAAa,oBAAoB;AAAA,IACnC;AAAA,EACF,OAAO;AAEL,iBAAa,oBAAoB;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAM,uBAAuB;AAAA,EAC3B,UAAU,EAAE,KAAK,CAAC,GAAG,OAAO,QAAQ;AAAA,EACpC,WAAW;AACb;AAEA,IAAM,oBAAoB;AA6B1B,SAAS,yBACP,QACA,SACS;AACT,MAAI,OAAO,WAAW,QAAQ,QAAQ;AACpC,WAAO;AAAA,EACT;AAEA,SAAO,OAAO;AAAA,IACZ,CAAC,MAAM,UAAU,KAAK,eAAe,QAAQ,KAAK,EAAE;AAAA,EACtD;AACF;AAEA,eAAe,4BACb,OACA,KACiB;AACjB,MAAI,UAAU;AACd,QAAM,SAAS,MACZ,KAAK,oBAAoB,EACzB,OAAO,EAAE,KAAK,GAAG,UAAU,EAAE,CAAC,EAC9B,KAAK,EACL,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAE1C,MAAI,UAAiC,CAAC;AAEtC,QAAM,kBAAkB,YAA2B;AACjD,QAAI,CAAC,QAAQ,QAAQ;AACnB;AAAA,IACF;AAEA,UAAM,MAAM,UAAU,OAAO;AAC7B,eAAW,QAAQ;AACnB,cAAU,CAAC;AAAA,EACb;AAEA,MAAI;AACF,qBAAiB,OAAO,QAAQ;AAC9B,YAAM,WAAW,IAAI,SAAS;AAAA,QAAI,CAAC,SACjC,2BAA2B,MAAM,GAAG;AAAA,MACtC;AAEA,UAAI,CAAC,yBAAyB,IAAI,UAAU,QAAQ,GAAG;AACrD;AAAA,MACF;AAEA,cAAQ,KAAK;AAAA,QACX,WAAW;AAAA,UACT,QAAQ,EAAE,KAAK,IAAI,IAAI;AAAA,UACvB,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE;AAAA,QAC/B;AAAA,MACF,CAAC;AAED,UAAI,QAAQ,UAAU,mBAAmB;AACvC,cAAM,gBAAgB;AAAA,MACxB;AAAA,IACF;AAEA,UAAM,gBAAgB;AAAA,EACxB,UAAE;AACA,UAAM,OAAO,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAC5C;AAEA,SAAO;AACT;AAKA,eAAsB,iCAAgD;AACpE,QAAM,MAAM,MAAM;AAClB,QAAM,CAAC,YAAY,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClD,4BAA4B,YAAsC,GAAG;AAAA,IACrE;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ;AAAA,IACN,kDAA6C,UAAU,qCAAqC,WAAW;AAAA,EACzG;AACF;;;AC9MA,eAAsB,8BACpB,YACwB;AACxB,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,0BAA0B,UAAU;AAEzD,QAAM,CAAC,UAAU,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,IACtD,WAAW,SAAS,YAAY,EAC7B,OAAO,UAAU,EACjB,KAA8C,EAC9C,KAAK;AAAA,IACR,0BAA0B,SAAS,YAAY,EAC5C,OAAO,UAAU,EACjB,KAA8C,EAC9C,KAAK;AAAA,EACV,CAAC;AAED,SAAO,YAAY;AACrB;;;ACzBO,SAAS,uBACd,kBACA,cACS;AACT,MAAI,CAAC,kBAAkB,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB;AAAA,IACtB,CAAC,SACC,CAAC,cAAc;AAAA,MACb,CAAC,SACC,KAAK,cAAc,KAAK,aACxB,KAAK,cAAc,KAAK;AAAA,IAC5B;AAAA,EACJ;AACF;AAMO,SAAS,iCACd,eACA,eACqC;AACrC,SAAO,cAAc,IAAI,CAAC,iBAAiB;AAEzC,UAAM,gBACJ,eAAe;AAAA,MACb,CAAC,OACC,GAAG,cAAc,aAAa,SAAS,aACvC,GAAG,cAAc,aAAa,SAAS;AAAA,IAC3C,KAAK;AAEP,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ,gBACJ,aAAa,SACb,iBAAiB;AAAA,IACvB;AAAA,EACF,CAAC;AACH;","names":["mongoose"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timardex/cluemart-server-shared",
3
- "version": "1.0.299",
3
+ "version": "1.0.301",
4
4
  "description": "",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -44,7 +44,7 @@
44
44
  "author": "Csaba Timár",
45
45
  "license": "ISC",
46
46
  "devDependencies": {
47
- "@timardex/cluemart-shared": "^1.5.827",
47
+ "@timardex/cluemart-shared": "^1.5.831",
48
48
  "@types/express": "^5.0.5",
49
49
  "@types/node": "^22.13.4",
50
50
  "@typescript-eslint/eslint-plugin": "^8.20.0",