@lyxa.ai/marketing 1.0.54 → 1.0.55

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.
Files changed (94) hide show
  1. package/dist/lib/index.d.ts +4 -0
  2. package/dist/lib/index.d.ts.map +1 -1
  3. package/dist/lib/index.js +2 -2
  4. package/dist/lib/index.js.map +1 -1
  5. package/dist/lib/modules/coupon/routers/coupon.router.d.ts +4 -0
  6. package/dist/lib/modules/coupon/routers/coupon.router.d.ts.map +1 -1
  7. package/dist/lib/modules/coupon/services/coupon.service.d.ts.map +1 -1
  8. package/dist/lib/modules/coupon/services/coupon.service.js +4 -1
  9. package/dist/lib/modules/coupon/services/coupon.service.js.map +1 -1
  10. package/dist/lib/modules/coupon/services/custom-coupon.service.d.ts +5 -0
  11. package/dist/lib/modules/coupon/services/custom-coupon.service.d.ts.map +1 -1
  12. package/dist/lib/modules/coupon/services/helper.service.d.ts +5 -0
  13. package/dist/lib/modules/coupon/services/helper.service.d.ts.map +1 -1
  14. package/dist/lib/modules/coupon/services/individual-store-coupon.service.d.ts +5 -0
  15. package/dist/lib/modules/coupon/services/individual-store-coupon.service.d.ts.map +1 -1
  16. package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts +1 -0
  17. package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts.map +1 -1
  18. package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.js +25 -7
  19. package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.js.map +1 -1
  20. package/dist/lib/modules/coupon/services/shops-categories-coupon.service.d.ts +5 -0
  21. package/dist/lib/modules/coupon/services/shops-categories-coupon.service.d.ts.map +1 -1
  22. package/dist/lib/modules/coupon/validations/coupon.validation.d.ts +18 -0
  23. package/dist/lib/modules/coupon/validations/coupon.validation.d.ts.map +1 -1
  24. package/dist/lib/modules/coupon/validations/coupon.validation.js +6 -0
  25. package/dist/lib/modules/coupon/validations/coupon.validation.js.map +1 -1
  26. package/dist/lib/modules/coupon/validations/create-coupon.validation.d.ts.map +1 -1
  27. package/dist/lib/modules/coupon/validations/create-coupon.validation.js +4 -1
  28. package/dist/lib/modules/coupon/validations/create-coupon.validation.js.map +1 -1
  29. package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts +9 -1
  30. package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts.map +1 -1
  31. package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.js +26 -3
  32. package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.js.map +1 -1
  33. package/dist/lib/modules/tournament/match/events/evaluate-predictions.consumer.js +1 -1
  34. package/dist/lib/modules/tournament/match/events/evaluate-predictions.consumer.js.map +1 -1
  35. package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.d.ts +2 -0
  36. package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.d.ts.map +1 -0
  37. package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.js +266 -0
  38. package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.js.map +1 -0
  39. package/dist/lib/modules/tournament/notification/notification.service.d.ts +14 -3
  40. package/dist/lib/modules/tournament/notification/notification.service.d.ts.map +1 -1
  41. package/dist/lib/modules/tournament/notification/notification.service.js +89 -31
  42. package/dist/lib/modules/tournament/notification/notification.service.js.map +1 -1
  43. package/dist/lib/modules/tournament/participant/participant.repository.d.ts +1 -0
  44. package/dist/lib/modules/tournament/participant/participant.repository.d.ts.map +1 -1
  45. package/dist/lib/modules/tournament/participant/participant.repository.js +9 -0
  46. package/dist/lib/modules/tournament/participant/participant.repository.js.map +1 -1
  47. package/dist/lib/modules/tournament/participant/participant.service.d.ts +1 -0
  48. package/dist/lib/modules/tournament/participant/participant.service.d.ts.map +1 -1
  49. package/dist/lib/modules/tournament/participant/participant.service.js +6 -0
  50. package/dist/lib/modules/tournament/participant/participant.service.js.map +1 -1
  51. package/dist/lib/modules/tournament/reward/reward.builder.d.ts.map +1 -1
  52. package/dist/lib/modules/tournament/reward/reward.builder.js +3 -3
  53. package/dist/lib/modules/tournament/reward/reward.builder.js.map +1 -1
  54. package/dist/lib/modules/tournament/reward/reward.types.d.ts +1 -0
  55. package/dist/lib/modules/tournament/reward/reward.types.d.ts.map +1 -1
  56. package/dist/lib/modules/tournament/reward/reward.types.js.map +1 -1
  57. package/dist/lib/modules/tournament/schedule/schedule.service.d.ts +7 -0
  58. package/dist/lib/modules/tournament/schedule/schedule.service.d.ts.map +1 -1
  59. package/dist/lib/modules/tournament/schedule/schedule.service.js +52 -19
  60. package/dist/lib/modules/tournament/schedule/schedule.service.js.map +1 -1
  61. package/dist/lib/utility/utils.js +2 -2
  62. package/dist/lib/utility/utils.js.map +1 -1
  63. package/dist/types/index.d.ts +4 -0
  64. package/dist/types/index.d.ts.map +1 -1
  65. package/dist/types/modules/coupon/routers/coupon.router.d.ts +4 -0
  66. package/dist/types/modules/coupon/routers/coupon.router.d.ts.map +1 -1
  67. package/dist/types/modules/coupon/services/coupon.service.d.ts.map +1 -1
  68. package/dist/types/modules/coupon/services/custom-coupon.service.d.ts +5 -0
  69. package/dist/types/modules/coupon/services/custom-coupon.service.d.ts.map +1 -1
  70. package/dist/types/modules/coupon/services/helper.service.d.ts +5 -0
  71. package/dist/types/modules/coupon/services/helper.service.d.ts.map +1 -1
  72. package/dist/types/modules/coupon/services/individual-store-coupon.service.d.ts +5 -0
  73. package/dist/types/modules/coupon/services/individual-store-coupon.service.d.ts.map +1 -1
  74. package/dist/types/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts +1 -0
  75. package/dist/types/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts.map +1 -1
  76. package/dist/types/modules/coupon/services/shops-categories-coupon.service.d.ts +5 -0
  77. package/dist/types/modules/coupon/services/shops-categories-coupon.service.d.ts.map +1 -1
  78. package/dist/types/modules/coupon/validations/coupon.validation.d.ts +18 -0
  79. package/dist/types/modules/coupon/validations/coupon.validation.d.ts.map +1 -1
  80. package/dist/types/modules/coupon/validations/create-coupon.validation.d.ts.map +1 -1
  81. package/dist/types/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts +9 -1
  82. package/dist/types/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts.map +1 -1
  83. package/dist/types/modules/tournament/notification/notification.service.d.ts +14 -3
  84. package/dist/types/modules/tournament/notification/notification.service.d.ts.map +1 -1
  85. package/dist/types/modules/tournament/participant/participant.repository.d.ts +1 -0
  86. package/dist/types/modules/tournament/participant/participant.repository.d.ts.map +1 -1
  87. package/dist/types/modules/tournament/participant/participant.service.d.ts +1 -0
  88. package/dist/types/modules/tournament/participant/participant.service.d.ts.map +1 -1
  89. package/dist/types/modules/tournament/reward/reward.builder.d.ts.map +1 -1
  90. package/dist/types/modules/tournament/reward/reward.types.d.ts +1 -0
  91. package/dist/types/modules/tournament/reward/reward.types.d.ts.map +1 -1
  92. package/dist/types/modules/tournament/schedule/schedule.service.d.ts +7 -0
  93. package/dist/types/modules/tournament/schedule/schedule.service.d.ts.map +1 -1
  94. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"evaluate-predictions.consumer.js","sourceRoot":"/","sources":["modules/tournament/match/events/evaluate-predictions.consumer.ts"],"names":[],"mappings":"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","sourcesContent":["import {mongoose} from '@typegoose/typegoose';\nimport {EvaluatePredictionsPayload, TOURNAMENT_EVALUATE_PREDICTIONS_EVENT} from './evaluate-predictions.event';\nimport {predictionService} from '../../prediction/prediction.service';\nimport {rewardService} from '../../reward/reward.service';\nimport {tournamentNotificationService} from '../../notification/notification.service';\nimport {notificationTemplateRepository} from '../../notification/notification-template.repository';\nimport {TournamentNotificationTag} from '@lyxa.ai/core/dist/utilities/enum';\nimport {RewardBuilder} from '../../reward/reward.builder';\nimport {CouponRewardResult, RewardType} from '../../reward/reward.types';\nimport {subscribeToEvent} from '@lyxa.ai/core/dist/libraries/event';\nimport {tournamentConfigRepository} from '@modules/tournament/config/config.repository';\nimport {notFoundError} from '@lyxa.ai/core/dist/utilities/error-common';\nimport {matchEvaluationOrchestrator} from \"@modules/tournament/match/events/match-evaluation.orchestrator\";\nimport {getLibraries} from '@lyxa.ai/core';\n\nexport async function EvaluatePredictionsConsumer() {\n await subscribeToEvent(TOURNAMENT_EVALUATE_PREDICTIONS_EVENT, async (payload: EvaluatePredictionsPayload) => {\n if (!payload.matches?.length) return;\n const tournamentId = new mongoose.Types.ObjectId(payload.tournamentId.toString());\n const logger = getLibraries().getLoggerKit();\n\n logger.info('Evaluate predictions event received', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchCount: payload.matches.length, matches: payload.matches },\n });\n\n const tournament = await tournamentConfigRepository.findById(tournamentId);\n if (!tournament) {\n notFoundError('Tournament not found');\n }\n\n const templates = await notificationTemplateRepository.findTemplates(tournamentId);\n const winner = templates.get(TournamentNotificationTag.MATCH_RESULT_WINNER);\n const config = await matchEvaluationOrchestrator.resolveCouponConfig(tournamentId);\n\t\tif (!config) return;\n\n for (const m of payload.matches) {\n const matchId = new mongoose.Types.ObjectId(m.matchId.toString());\n const { winnerIds } = await predictionService.evaluate(tournamentId, matchId, m.finalScore);\n\n logger.info('Predictions evaluated', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), matchNumber: m.matchNumber, finalScore: m.finalScore, winnerCount: winnerIds.length, winnerIds },\n });\n\n if (config && winnerIds.length > 0) {\n const couponDetails = await rewardService.grant(\n RewardType.COUPON,\n RewardBuilder.buildCouponRewardContext(matchId, tournamentId, winnerIds, m.matchNumber, config)\n );\n\n logger.info('Coupon reward granted', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), winnerCount: winnerIds.length, couponDetails },\n });\n\n await tournamentNotificationService.notifyUsers(\n {\n title: winner?.title ?? '🎉 Your prediction was currect.',\n body: winner?.body ?? 'Your coupon is now available',\n tournamentId: tournamentId.toString(),\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n deepLinkRoute: tournament.deepLinkRoute,\n matchId: matchId.toString(),\n couponCode: (couponDetails as CouponRewardResult).code,\n couponOfferLabel: (couponDetails as CouponRewardResult).couponOfferLabel,\n couponValidityLabel: (couponDetails as CouponRewardResult).couponValidityLabel,\n couponStartDate: (couponDetails as CouponRewardResult).duration.start,\n couponEndDate: (couponDetails as CouponRewardResult).duration.end,\n },\n winnerIds\n );\n }\n }\n });\n}\n"]}
1
+ {"version":3,"file":"evaluate-predictions.consumer.js","sourceRoot":"/","sources":["modules/tournament/match/events/evaluate-predictions.consumer.ts"],"names":[],"mappings":"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","sourcesContent":["import {mongoose} from '@typegoose/typegoose';\nimport {EvaluatePredictionsPayload, TOURNAMENT_EVALUATE_PREDICTIONS_EVENT} from './evaluate-predictions.event';\nimport {predictionService} from '../../prediction/prediction.service';\nimport {rewardService} from '../../reward/reward.service';\nimport {tournamentNotificationService} from '../../notification/notification.service';\nimport {notificationTemplateRepository} from '../../notification/notification-template.repository';\nimport {TournamentNotificationTag} from '@lyxa.ai/core/dist/utilities/enum';\nimport {RewardBuilder} from '../../reward/reward.builder';\nimport {CouponRewardResult, RewardType} from '../../reward/reward.types';\nimport {subscribeToEvent} from '@lyxa.ai/core/dist/libraries/event';\nimport {tournamentConfigRepository} from '@modules/tournament/config/config.repository';\nimport {notFoundError} from '@lyxa.ai/core/dist/utilities/error-common';\nimport {matchEvaluationOrchestrator} from \"@modules/tournament/match/events/match-evaluation.orchestrator\";\nimport {getLibraries} from '@lyxa.ai/core';\n\nexport async function EvaluatePredictionsConsumer() {\n await subscribeToEvent(TOURNAMENT_EVALUATE_PREDICTIONS_EVENT, async (payload: EvaluatePredictionsPayload) => {\n if (!payload.matches?.length) return;\n const tournamentId = new mongoose.Types.ObjectId(payload.tournamentId.toString());\n const logger = getLibraries().getLoggerKit();\n\n logger.info('Evaluate predictions event received', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchCount: payload.matches.length, matches: payload.matches },\n });\n\n const tournament = await tournamentConfigRepository.findById(tournamentId);\n if (!tournament) {\n notFoundError('Tournament not found');\n }\n\n const templates = await notificationTemplateRepository.findTemplates(tournamentId);\n const winner = templates.get(TournamentNotificationTag.MATCH_RESULT_WINNER);\n const config = await matchEvaluationOrchestrator.resolveCouponConfig(tournamentId);\n\t\tif (!config) return;\n\n for (const m of payload.matches) {\n const matchId = new mongoose.Types.ObjectId(m.matchId.toString());\n const { winnerIds } = await predictionService.evaluate(tournamentId, matchId, m.finalScore);\n\n logger.info('Predictions evaluated', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), matchNumber: m.matchNumber, finalScore: m.finalScore, winnerCount: winnerIds.length, winnerIds },\n });\n\n if (config && winnerIds.length > 0) {\n const couponDetails = await rewardService.grant(\n RewardType.COUPON,\n RewardBuilder.buildCouponRewardContext(matchId, tournamentId, winnerIds, m.matchNumber, config)\n );\n\n logger.info('Coupon reward granted', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), winnerCount: winnerIds.length, couponDetails },\n });\n\n await tournamentNotificationService.notifyUsers(\n {\n title: winner?.title ?? '🎉 Your prediction was correct.',\n body: winner?.body ?? 'Your coupon is now available',\n tournamentId: tournamentId.toString(),\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n deepLinkRoute: tournament.deepLinkRoute,\n matchId: matchId.toString(),\n couponCode: (couponDetails as CouponRewardResult).code,\n couponOfferLabel: (couponDetails as CouponRewardResult).couponOfferLabel,\n couponValidityLabel: (couponDetails as CouponRewardResult).couponValidityLabel,\n couponStartDate: (couponDetails as CouponRewardResult).duration.start,\n couponEndDate: (couponDetails as CouponRewardResult).duration.end,\n },\n winnerIds\n );\n }\n }\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=notification.service.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.service.spec.d.ts","sourceRoot":"/","sources":["modules/tournament/notification/__tests__/notification.service.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ jest.mock('typedi', () => ({
7
+ __esModule: true,
8
+ default: { get: jest.fn() },
9
+ Container: { get: jest.fn() },
10
+ Service: () => (target) => target,
11
+ }));
12
+ jest.mock('../../participant/participant.service', () => ({
13
+ ParticipantService: class {
14
+ },
15
+ participantService: {},
16
+ }));
17
+ const publishEventMock = jest.fn().mockResolvedValue(undefined);
18
+ jest.mock('@lyxa.ai/core/dist/libraries/event', () => ({
19
+ publishEvent: (...args) => publishEventMock(...args),
20
+ }));
21
+ const loggerMock = { info: jest.fn(), error: jest.fn() };
22
+ jest.mock('@lyxa.ai/core', () => ({
23
+ getLibraries: () => ({ getLoggerKit: () => loggerMock }),
24
+ }));
25
+ const mongoose_1 = require("mongoose");
26
+ const typedi_1 = __importDefault(require("typedi"));
27
+ const enum_1 = require("@lyxa.ai/core/dist/utilities/enum");
28
+ const notification_service_1 = require("../notification.service");
29
+ describe('TournamentNotificationService', () => {
30
+ const participantService = {
31
+ getPredicted: jest.fn(),
32
+ getUnpredicted: jest.fn(),
33
+ };
34
+ let service;
35
+ beforeEach(() => {
36
+ jest.clearAllMocks();
37
+ typedi_1.default.get.mockReturnValue(participantService);
38
+ service = new notification_service_1.TournamentNotificationService();
39
+ });
40
+ function makeMatch(overrides = {}) {
41
+ return {
42
+ _id: new mongoose_1.Types.ObjectId(),
43
+ stage: 'group',
44
+ team1: { teamId: new mongoose_1.Types.ObjectId(), name: 'Lions' },
45
+ team2: { teamId: new mongoose_1.Types.ObjectId(), name: 'Tigers' },
46
+ score: { team1: null, team2: null },
47
+ winnerId: null,
48
+ startingDate: new Date('2026-06-20T18:00:00Z'),
49
+ matchEnded: false,
50
+ notificationFlags: new Map(),
51
+ ...overrides,
52
+ };
53
+ }
54
+ function makeTournament(overrides = {}) {
55
+ return {
56
+ _id: new mongoose_1.Types.ObjectId(),
57
+ deepLinkRoute: '/tournament/123',
58
+ ...overrides,
59
+ };
60
+ }
61
+ function makeParticipant(overrides = {}) {
62
+ return {
63
+ _id: new mongoose_1.Types.ObjectId(),
64
+ userId: new mongoose_1.Types.ObjectId(),
65
+ tournamentId: new mongoose_1.Types.ObjectId(),
66
+ status: 'joined',
67
+ predictions: [],
68
+ ...overrides,
69
+ };
70
+ }
71
+ function makeTemplates(tag, title, body) {
72
+ return new Map([[tag, { title, body }]]);
73
+ }
74
+ describe('notifyUsers', () => {
75
+ it('publishes one notification per recipient and logs success', async () => {
76
+ const userIds = [new mongoose_1.Types.ObjectId(), new mongoose_1.Types.ObjectId()];
77
+ await service.notifyUsers({
78
+ title: 'Prediction reminder',
79
+ body: 'You have not predicted yet',
80
+ tournamentNotificationType: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
81
+ tournamentId: 'tournament-1',
82
+ }, userIds);
83
+ expect(publishEventMock).toHaveBeenCalledTimes(userIds.length);
84
+ const recipientIds = publishEventMock.mock.calls.map(([event]) => event.payload.data.recipientId);
85
+ expect(recipientIds).toEqual(userIds);
86
+ expect(loggerMock.info).toHaveBeenCalledWith('Tournament user notifications dispatched', expect.objectContaining({ data: expect.objectContaining({ recipientCount: userIds.length }) }));
87
+ expect(loggerMock.error).not.toHaveBeenCalled();
88
+ });
89
+ it('does not publish anything for an empty recipient list but still logs', async () => {
90
+ await service.notifyUsers({
91
+ title: 'Prediction reminder',
92
+ body: 'You have not predicted yet',
93
+ tournamentNotificationType: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
94
+ tournamentId: 'tournament-1',
95
+ }, []);
96
+ expect(publishEventMock).not.toHaveBeenCalled();
97
+ expect(loggerMock.info).toHaveBeenCalledWith('Tournament user notifications dispatched', expect.objectContaining({ data: expect.objectContaining({ recipientCount: 0 }) }));
98
+ });
99
+ it('catches publish failures and logs the error instead of throwing', async () => {
100
+ publishEventMock.mockRejectedValueOnce(new Error('broker unreachable'));
101
+ await expect(service.notifyUsers({
102
+ title: 'Prediction reminder',
103
+ body: 'You have not predicted yet',
104
+ tournamentNotificationType: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
105
+ tournamentId: 'tournament-1',
106
+ }, [new mongoose_1.Types.ObjectId()])).resolves.toBeUndefined();
107
+ expect(loggerMock.error).toHaveBeenCalledWith('Failed to dispatch tournament user notifications', expect.any(Error), expect.anything());
108
+ expect(loggerMock.info).not.toHaveBeenCalled();
109
+ });
110
+ });
111
+ describe('dispatchScheduledReminder', () => {
112
+ it('sends a single global notification for GLOBAL target without resolving recipients', async () => {
113
+ const match = makeMatch();
114
+ const tournament = makeTournament();
115
+ const ctx = {
116
+ type: enum_1.TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY,
117
+ target: enum_1.TournamentScheduledNotificationTarget.GLOBAL,
118
+ templates: makeTemplates(enum_1.TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY, '{team1} vs {team2} kicks off soon', 'Pick your team for {team1} vs {team2}'),
119
+ tournament,
120
+ match,
121
+ };
122
+ await service.dispatchScheduledReminder(ctx);
123
+ expect(participantService.getPredicted).not.toHaveBeenCalled();
124
+ expect(participantService.getUnpredicted).not.toHaveBeenCalled();
125
+ expect(publishEventMock).toHaveBeenCalledTimes(1);
126
+ const event = publishEventMock.mock.calls[0][0];
127
+ expect(event.payload.title).toBe('Lions vs Tigers kicks off soon');
128
+ expect(event.payload.body).toBe('Pick your team for Lions vs Tigers');
129
+ expect(event.payload.data.isGlobal).toBe(true);
130
+ expect(event.payload.data.matchId).toBeUndefined();
131
+ expect(event.payload.data.predictionExpiryTime).toBeUndefined();
132
+ });
133
+ it('resolves predicted participants and notifies them for PREDICTION_REMINDER_1_HOUR', async () => {
134
+ const match = makeMatch();
135
+ const tournament = makeTournament();
136
+ const predicted = [makeParticipant(), makeParticipant()];
137
+ participantService.getPredicted.mockResolvedValue(predicted);
138
+ const ctx = {
139
+ type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR,
140
+ target: enum_1.TournamentScheduledNotificationTarget.PREDICTED_PARTICIPANTS,
141
+ templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR, '{team1} vs {team2} starts in 1 hour', 'Your prediction for {team1} vs {team2} locks soon'),
142
+ tournament,
143
+ match,
144
+ };
145
+ await service.dispatchScheduledReminder(ctx);
146
+ expect(participantService.getPredicted).toHaveBeenCalledWith(tournament._id, match._id);
147
+ expect(participantService.getUnpredicted).not.toHaveBeenCalled();
148
+ expect(publishEventMock).toHaveBeenCalledTimes(predicted.length);
149
+ const event = publishEventMock.mock.calls[0][0];
150
+ expect(event.payload.title).toBe('Lions vs Tigers starts in 1 hour');
151
+ expect(event.payload.data.matchId).toBe(match._id.toString());
152
+ expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);
153
+ });
154
+ it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_3_HOURS', async () => {
155
+ const match = makeMatch();
156
+ const tournament = makeTournament();
157
+ const unpredicted = [makeParticipant(), makeParticipant()];
158
+ participantService.getUnpredicted.mockResolvedValue(unpredicted);
159
+ const ctx = {
160
+ type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS,
161
+ target: enum_1.TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,
162
+ templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS, '{team1} vs {team2} starts in 3 hours', 'Predict {team1} vs {team2} now'),
163
+ tournament,
164
+ match,
165
+ };
166
+ await service.dispatchScheduledReminder(ctx);
167
+ expect(participantService.getUnpredicted).toHaveBeenCalledWith(tournament._id, [match.team1.teamId, match.team2.teamId], match._id);
168
+ expect(participantService.getPredicted).not.toHaveBeenCalled();
169
+ expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);
170
+ const event = publishEventMock.mock.calls[0][0];
171
+ expect(event.payload.title).toBe('Lions vs Tigers starts in 3 hours');
172
+ expect(event.payload.data.matchId).toBe(match._id.toString());
173
+ expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);
174
+ });
175
+ it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_15_MINUTES', async () => {
176
+ const match = makeMatch();
177
+ const tournament = makeTournament();
178
+ const unpredicted = [makeParticipant()];
179
+ participantService.getUnpredicted.mockResolvedValue(unpredicted);
180
+ const ctx = {
181
+ type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
182
+ target: enum_1.TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,
183
+ templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES, '{team1} vs {team2} starts in 15 minutes', 'Last call to predict {team1} vs {team2}'),
184
+ tournament,
185
+ match,
186
+ };
187
+ await service.dispatchScheduledReminder(ctx);
188
+ expect(participantService.getUnpredicted).toHaveBeenCalledWith(tournament._id, [match.team1.teamId, match.team2.teamId], match._id);
189
+ expect(participantService.getPredicted).not.toHaveBeenCalled();
190
+ expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);
191
+ const event = publishEventMock.mock.calls[0][0];
192
+ expect(event.payload.title).toBe('Lions vs Tigers starts in 15 minutes');
193
+ expect(event.payload.data.recipientId).toEqual(unpredicted[0].userId);
194
+ });
195
+ it('short-circuits without notifying when there are no recipients', async () => {
196
+ const match = makeMatch();
197
+ const tournament = makeTournament();
198
+ participantService.getUnpredicted.mockResolvedValue([]);
199
+ const ctx = {
200
+ type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
201
+ target: enum_1.TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,
202
+ templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES, 'Last call for {team1} vs {team2}', 'Predict now'),
203
+ tournament,
204
+ match,
205
+ };
206
+ await service.dispatchScheduledReminder(ctx);
207
+ expect(publishEventMock).not.toHaveBeenCalled();
208
+ expect(loggerMock.info).not.toHaveBeenCalled();
209
+ });
210
+ it('throws when no template exists for the requested tag', async () => {
211
+ const match = makeMatch();
212
+ const tournament = makeTournament();
213
+ const ctx = {
214
+ type: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
215
+ target: enum_1.TournamentScheduledNotificationTarget.GLOBAL,
216
+ templates: new Map(),
217
+ tournament,
218
+ match,
219
+ };
220
+ await expect(service.dispatchScheduledReminder(ctx)).rejects.toThrow(`No notification template for tag: ${enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER}`);
221
+ });
222
+ });
223
+ describe('match result notifications', () => {
224
+ it('notifies winners with their coupon reward details attached', async () => {
225
+ const match = makeMatch();
226
+ const winnerIds = [new mongoose_1.Types.ObjectId(), new mongoose_1.Types.ObjectId()];
227
+ await service.notifyUsers({
228
+ title: 'Your prediction was correct',
229
+ body: 'Your coupon is now available',
230
+ tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
231
+ tournamentId: 'tournament-1',
232
+ deepLinkRoute: '/deep/link',
233
+ matchId: match._id.toString(),
234
+ couponCode: 'WIN-50',
235
+ couponOfferLabel: '50% off',
236
+ }, winnerIds);
237
+ expect(publishEventMock).toHaveBeenCalledTimes(winnerIds.length);
238
+ const payloads = publishEventMock.mock.calls.map(([event]) => event.payload);
239
+ expect(payloads.map(p => p.data.recipientId)).toEqual(winnerIds);
240
+ payloads.forEach(payload => {
241
+ expect(payload.data).toMatchObject({
242
+ tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
243
+ matchId: match._id.toString(),
244
+ couponCode: 'WIN-50',
245
+ couponOfferLabel: '50% off',
246
+ });
247
+ });
248
+ });
249
+ it('notifies losers without coupon reward details', async () => {
250
+ const match = makeMatch();
251
+ const loserIds = [new mongoose_1.Types.ObjectId()];
252
+ await service.notifyUsers({
253
+ title: 'Better luck next time',
254
+ body: 'Your prediction did not match the result',
255
+ tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_LOSER,
256
+ tournamentId: 'tournament-1',
257
+ matchId: match._id.toString(),
258
+ }, loserIds);
259
+ expect(publishEventMock).toHaveBeenCalledTimes(loserIds.length);
260
+ const event = publishEventMock.mock.calls[0][0];
261
+ expect(event.payload.data.tournamentNotificationType).toBe(enum_1.TournamentNotificationTag.MATCH_RESULT_LOSER);
262
+ expect(event.payload.data.couponCode).toBeUndefined();
263
+ });
264
+ });
265
+ });
266
+ //# sourceMappingURL=notification.service.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.service.spec.js","sourceRoot":"/","sources":["modules/tournament/notification/__tests__/notification.service.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TournamentNotificationService unit tests\n *\n * Covers:\n * - notifyGlobal: builds and publishes a NotificationGlobalEvent with tournament-specific overrides\n * - notifyUsers: publishes one NotificationEvent per recipient, logs success, and on failure\n * catches the error and logs it instead of throwing\n * - dispatchScheduledReminder:\n * - GLOBAL target dispatches a single global notification without resolving recipients\n * - PREDICTION_REMINDER_1_HOUR resolves recipients via getPredicted (predicted participants)\n * - any other tag resolves recipients via getUnpredicted (unpredicted participants)\n * - empty recipient list short-circuits without calling notifyUsers\n * - targeted messages include matchId + predictionExpiryTime, global/base messages don't\n * - {team1}/{team2} placeholders are substituted in both title and body\n * - missing template for the requested tag throws\n */\n\njest.mock('typedi', () => ({\n __esModule: true,\n default: {get: jest.fn()},\n Container: {get: jest.fn()},\n Service: () => (target: any) => target,\n}));\n\njest.mock('../../participant/participant.service', () => ({\n ParticipantService: class {},\n participantService: {},\n}));\n\nconst publishEventMock = jest.fn().mockResolvedValue(undefined);\njest.mock('@lyxa.ai/core/dist/libraries/event', () => ({\n publishEvent: (...args: any[]) => publishEventMock(...args),\n}));\n\nconst loggerMock = {info: jest.fn(), error: jest.fn()};\njest.mock('@lyxa.ai/core', () => ({\n getLibraries: () => ({getLoggerKit: () => loggerMock}),\n}));\n\nimport {Types} from 'mongoose';\nimport Container from 'typedi';\nimport {TournamentNotificationTag, TournamentScheduledNotificationTarget} from '@lyxa.ai/core/dist/utilities/enum';\nimport {NotificationMessage, TournamentMatch} from '@lyxa.ai/core/dist/libraries/mongo/models';\nimport {TournamentNotificationService, ScheduledReminderContext} from '../notification.service';\nimport {TournamentParticipantOutputDTO} from '../../participant/participant.validation';\nimport {TournamentDocument} from '../../config/config.types';\n\ndescribe('TournamentNotificationService', () => {\n const participantService = {\n getPredicted: jest.fn(),\n getUnpredicted: jest.fn(),\n };\n\n let service: TournamentNotificationService;\n\n beforeEach(() => {\n jest.clearAllMocks();\n (Container.get as jest.Mock).mockReturnValue(participantService);\n service = new TournamentNotificationService();\n });\n\n function makeMatch(overrides: Partial<TournamentMatch> = {}): TournamentMatch {\n return {\n _id: new Types.ObjectId(),\n stage: 'group',\n team1: {teamId: new Types.ObjectId(), name: 'Lions'},\n team2: {teamId: new Types.ObjectId(), name: 'Tigers'},\n score: {team1: null, team2: null},\n winnerId: null,\n startingDate: new Date('2026-06-20T18:00:00Z'),\n matchEnded: false,\n notificationFlags: new Map(),\n ...overrides,\n } as TournamentMatch;\n }\n\n function makeTournament(overrides: Partial<TournamentDocument> = {}): TournamentDocument {\n return {\n _id: new Types.ObjectId(),\n deepLinkRoute: '/tournament/123',\n ...overrides,\n } as TournamentDocument;\n }\n\n function makeParticipant(overrides: Partial<TournamentParticipantOutputDTO> = {}): TournamentParticipantOutputDTO {\n return {\n _id: new Types.ObjectId(),\n userId: new Types.ObjectId(),\n tournamentId: new Types.ObjectId(),\n status: 'joined',\n predictions: [],\n ...overrides,\n } as TournamentParticipantOutputDTO;\n }\n\n function makeTemplates(tag: TournamentNotificationTag, title: string, body: string): Map<TournamentNotificationTag, NotificationMessage> {\n return new Map([[tag, {title, body} as NotificationMessage]]);\n }\n\n describe('notifyUsers', () => {\n it('publishes one notification per recipient and logs success', async () => {\n const userIds = [new Types.ObjectId(), new Types.ObjectId()];\n\n await service.notifyUsers(\n {\n title: 'Prediction reminder',\n body: 'You have not predicted yet',\n tournamentNotificationType: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n tournamentId: 'tournament-1',\n },\n userIds\n );\n\n expect(publishEventMock).toHaveBeenCalledTimes(userIds.length);\n const recipientIds = publishEventMock.mock.calls.map(([event]) => event.payload.data.recipientId);\n expect(recipientIds).toEqual(userIds);\n expect(loggerMock.info).toHaveBeenCalledWith(\n 'Tournament user notifications dispatched',\n expect.objectContaining({data: expect.objectContaining({recipientCount: userIds.length})})\n );\n expect(loggerMock.error).not.toHaveBeenCalled();\n });\n\n it('does not publish anything for an empty recipient list but still logs', async () => {\n await service.notifyUsers(\n {\n title: 'Prediction reminder',\n body: 'You have not predicted yet',\n tournamentNotificationType: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n tournamentId: 'tournament-1',\n },\n []\n );\n\n expect(publishEventMock).not.toHaveBeenCalled();\n expect(loggerMock.info).toHaveBeenCalledWith(\n 'Tournament user notifications dispatched',\n expect.objectContaining({data: expect.objectContaining({recipientCount: 0})})\n );\n });\n\n it('catches publish failures and logs the error instead of throwing', async () => {\n publishEventMock.mockRejectedValueOnce(new Error('broker unreachable'));\n\n await expect(\n service.notifyUsers(\n {\n title: 'Prediction reminder',\n body: 'You have not predicted yet',\n tournamentNotificationType: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n tournamentId: 'tournament-1',\n },\n [new Types.ObjectId()]\n )\n ).resolves.toBeUndefined();\n\n expect(loggerMock.error).toHaveBeenCalledWith(\n 'Failed to dispatch tournament user notifications',\n expect.any(Error),\n expect.anything()\n );\n expect(loggerMock.info).not.toHaveBeenCalled();\n });\n });\n\n describe('dispatchScheduledReminder', () => {\n it('sends a single global notification for GLOBAL target without resolving recipients', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY,\n target: TournamentScheduledNotificationTarget.GLOBAL,\n templates: makeTemplates(\n TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY,\n '{team1} vs {team2} kicks off soon',\n 'Pick your team for {team1} vs {team2}'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getPredicted).not.toHaveBeenCalled();\n expect(participantService.getUnpredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(1);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers kicks off soon');\n expect(event.payload.body).toBe('Pick your team for Lions vs Tigers');\n expect(event.payload.data.isGlobal).toBe(true);\n expect(event.payload.data.matchId).toBeUndefined();\n expect(event.payload.data.predictionExpiryTime).toBeUndefined();\n });\n\n it('resolves predicted participants and notifies them for PREDICTION_REMINDER_1_HOUR', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const predicted = [makeParticipant(), makeParticipant()];\n participantService.getPredicted.mockResolvedValue(predicted);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR,\n target: TournamentScheduledNotificationTarget.PREDICTED_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR,\n '{team1} vs {team2} starts in 1 hour',\n 'Your prediction for {team1} vs {team2} locks soon'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getPredicted).toHaveBeenCalledWith(tournament._id, match._id);\n expect(participantService.getUnpredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(predicted.length);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers starts in 1 hour');\n expect(event.payload.data.matchId).toBe(match._id.toString());\n expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);\n });\n\n it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_3_HOURS', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const unpredicted = [makeParticipant(), makeParticipant()];\n participantService.getUnpredicted.mockResolvedValue(unpredicted);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS,\n target: TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS,\n '{team1} vs {team2} starts in 3 hours',\n 'Predict {team1} vs {team2} now'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getUnpredicted).toHaveBeenCalledWith(\n tournament._id,\n [match.team1.teamId, match.team2.teamId],\n match._id\n );\n expect(participantService.getPredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers starts in 3 hours');\n expect(event.payload.data.matchId).toBe(match._id.toString());\n expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);\n });\n\n it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_15_MINUTES', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const unpredicted = [makeParticipant()];\n participantService.getUnpredicted.mockResolvedValue(unpredicted);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n target: TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n '{team1} vs {team2} starts in 15 minutes',\n 'Last call to predict {team1} vs {team2}'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getUnpredicted).toHaveBeenCalledWith(\n tournament._id,\n [match.team1.teamId, match.team2.teamId],\n match._id\n );\n expect(participantService.getPredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers starts in 15 minutes');\n expect(event.payload.data.recipientId).toEqual(unpredicted[0].userId);\n });\n\n it('short-circuits without notifying when there are no recipients', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n participantService.getUnpredicted.mockResolvedValue([]);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n target: TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n 'Last call for {team1} vs {team2}',\n 'Predict now'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(publishEventMock).not.toHaveBeenCalled();\n expect(loggerMock.info).not.toHaveBeenCalled();\n });\n\n it('throws when no template exists for the requested tag', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.MATCH_RESULT_WINNER,\n target: TournamentScheduledNotificationTarget.GLOBAL,\n templates: new Map(),\n tournament,\n match,\n };\n\n await expect(service.dispatchScheduledReminder(ctx)).rejects.toThrow(\n `No notification template for tag: ${TournamentNotificationTag.MATCH_RESULT_WINNER}`\n );\n });\n });\n\n // Match-result notifications (winner/loser) aren't dispatched through dispatchScheduledReminder -\n // evaluate-predictions.consumer.ts calls notifyUsers directly once a match is evaluated, attaching\n // reward-specific extra fields via the NotificationObject index signature.\n describe('match result notifications', () => {\n it('notifies winners with their coupon reward details attached', async () => {\n const match = makeMatch();\n const winnerIds = [new Types.ObjectId(), new Types.ObjectId()];\n\n await service.notifyUsers(\n {\n title: 'Your prediction was correct',\n body: 'Your coupon is now available',\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n tournamentId: 'tournament-1',\n deepLinkRoute: '/deep/link',\n matchId: match._id.toString(),\n couponCode: 'WIN-50',\n couponOfferLabel: '50% off',\n },\n winnerIds\n );\n\n expect(publishEventMock).toHaveBeenCalledTimes(winnerIds.length);\n const payloads = publishEventMock.mock.calls.map(([event]) => event.payload);\n expect(payloads.map(p => p.data.recipientId)).toEqual(winnerIds);\n payloads.forEach(payload => {\n expect(payload.data).toMatchObject({\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n matchId: match._id.toString(),\n couponCode: 'WIN-50',\n couponOfferLabel: '50% off',\n });\n });\n });\n\n it('notifies losers without coupon reward details', async () => {\n const match = makeMatch();\n const loserIds = [new Types.ObjectId()];\n\n await service.notifyUsers(\n {\n title: 'Better luck next time',\n body: 'Your prediction did not match the result',\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_LOSER,\n tournamentId: 'tournament-1',\n matchId: match._id.toString(),\n },\n loserIds\n );\n\n expect(publishEventMock).toHaveBeenCalledTimes(loserIds.length);\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.data.tournamentNotificationType).toBe(TournamentNotificationTag.MATCH_RESULT_LOSER);\n expect(event.payload.data.couponCode).toBeUndefined();\n });\n });\n});\n"]}
@@ -1,15 +1,26 @@
1
1
  import { mongoose } from '@typegoose/typegoose';
2
2
  import { TournamentNotificationTag, TournamentScheduledNotificationTarget } from '@lyxa.ai/core/dist/utilities/enum';
3
- import { NotificationMessage } from '@lyxa.ai/core/dist/libraries/mongo/models';
3
+ import { NotificationMessage, TournamentMatch } from '@lyxa.ai/core/dist/libraries/mongo/models';
4
+ import { TournamentDocument } from '../config/config.types';
4
5
  import { NotificationObject } from './notification.config';
6
+ export interface ScheduledReminderContext {
7
+ type: TournamentNotificationTag;
8
+ target: TournamentScheduledNotificationTarget;
9
+ templates: Map<TournamentNotificationTag, NotificationMessage>;
10
+ tournament: TournamentDocument;
11
+ match: TournamentMatch;
12
+ }
5
13
  export declare class TournamentNotificationService {
6
14
  private participantService;
7
15
  constructor();
8
16
  private getTemplate;
9
- private hasNotPredicted;
10
17
  notifyGlobal(notification: NotificationObject): Promise<void>;
11
18
  notifyUsers(notification: NotificationObject, userIds: mongoose.Types.ObjectId[]): Promise<void>;
12
- dispatchScheduledJob(type: TournamentNotificationTag, target: TournamentScheduledNotificationTarget, templates: Map<TournamentNotificationTag, NotificationMessage>, tournamentId: mongoose.Types.ObjectId, teamIds: mongoose.Types.ObjectId[], matchId: mongoose.Types.ObjectId, matchStartTime: Date, deepLinkRoute?: string): Promise<void>;
19
+ dispatchScheduledReminder(ctx: ScheduledReminderContext): Promise<void>;
20
+ private resolveReminderRecipients;
21
+ private buildBaseMessage;
22
+ private buildTargetedMessage;
23
+ private withTeamNames;
13
24
  }
14
25
  export declare const tournamentNotificationService: TournamentNotificationService;
15
26
  //# sourceMappingURL=notification.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notification.service.d.ts","sourceRoot":"/","sources":["modules/tournament/notification/notification.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAG9C,OAAO,EAGH,yBAAyB,EACzB,qCAAqC,EACxC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,mBAAmB,EAAC,MAAM,2CAA2C,CAAC;AAG9E,OAAO,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAEzD,qBACa,6BAA6B;IACtC,OAAO,CAAC,kBAAkB,CAAqB;;IAM/C,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,eAAe;IAIV,YAAY,CAAC,YAAY,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB7D,WAAW,CAAC,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBhG,oBAAoB,CAC7B,IAAI,EAAE,yBAAyB,EAC/B,MAAM,EAAE,qCAAqC,EAC7C,SAAS,EAAE,GAAG,CAAC,yBAAyB,EAAE,mBAAmB,CAAC,EAC9D,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EACrC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAClC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAChC,cAAc,EAAE,IAAI,EACpB,aAAa,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;CAqBnB;AAED,eAAO,MAAM,6BAA6B,+BAA+C,CAAC"}
1
+ {"version":3,"file":"notification.service.d.ts","sourceRoot":"/","sources":["modules/tournament/notification/notification.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAI9C,OAAO,EAGH,yBAAyB,EACzB,qCAAqC,EACxC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,mBAAmB,EAAE,eAAe,EAAC,MAAM,2CAA2C,CAAC;AAG/F,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAEzD,MAAM,WAAW,wBAAwB;IACrC,IAAI,EAAE,yBAAyB,CAAC;IAChC,MAAM,EAAE,qCAAqC,CAAC;IAC9C,SAAS,EAAE,GAAG,CAAC,yBAAyB,EAAE,mBAAmB,CAAC,CAAC;IAC/D,UAAU,EAAE,kBAAkB,CAAC;IAC/B,KAAK,EAAE,eAAe,CAAC;CAC1B;AAED,qBACa,6BAA6B;IACtC,OAAO,CAAC,kBAAkB,CAAqB;;IAM/C,OAAO,CAAC,WAAW;IAMN,YAAY,CAAC,YAAY,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB7D,WAAW,CAAC,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA6DhG,yBAAyB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpF,OAAO,CAAC,yBAAyB;IAcjC,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,aAAa;CAKxB;AAED,eAAO,MAAM,6BAA6B,+BAA+C,CAAC"}
@@ -44,6 +44,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.tournamentNotificationService = exports.TournamentNotificationService = void 0;
46
46
  const typedi_1 = __importStar(require("typedi"));
47
+ const core_1 = require("@lyxa.ai/core");
47
48
  const event_1 = require("@lyxa.ai/core/dist/libraries/event");
48
49
  const notification_1 = require("@lyxa.ai/core/dist/libraries/notification");
49
50
  const enum_1 = require("@lyxa.ai/core/dist/utilities/enum");
@@ -59,9 +60,6 @@ let TournamentNotificationService = class TournamentNotificationService {
59
60
  throw new Error(`No notification template for tag: ${tag}`);
60
61
  return t;
61
62
  }
62
- hasNotPredicted(participant, matchId) {
63
- return !participant.predictions.some(p => p.matchId.toString() === matchId.toString());
64
- }
65
63
  async notifyGlobal(notification) {
66
64
  await (0, event_1.publishEvent)(new notification_1.NotificationGlobalEvent({
67
65
  title: notification.title,
@@ -77,37 +75,97 @@ let TournamentNotificationService = class TournamentNotificationService {
77
75
  }));
78
76
  }
79
77
  async notifyUsers(notification, userIds) {
80
- await Promise.all(userIds.map(recipientId => (0, event_1.publishEvent)(new notification_1.NotificationEvent({
81
- title: notification.title,
82
- body: notification.body,
83
- data: {
84
- saveNotification: true,
85
- accountType: enum_1.NotificationAccountType.USER,
86
- recipientId,
87
- isGlobal: false,
88
- isClickable: true,
89
- clickType: enum_1.ClickType.CLICKABLE_TOURNAMENT,
90
- ...notification,
91
- },
92
- }))));
78
+ const logger = (0, core_1.getLibraries)().getLoggerKit();
79
+ try {
80
+ await Promise.all(userIds.map(recipientId => (0, event_1.publishEvent)(new notification_1.NotificationEvent({
81
+ title: notification.title,
82
+ body: notification.body,
83
+ data: {
84
+ saveNotification: true,
85
+ accountType: enum_1.NotificationAccountType.USER,
86
+ recipientId,
87
+ isGlobal: false,
88
+ isClickable: true,
89
+ clickType: enum_1.ClickType.CLICKABLE_TOURNAMENT,
90
+ ...notification,
91
+ },
92
+ }))));
93
+ logger.info('Tournament user notifications dispatched', {
94
+ tag: 'notification.queue.tournament',
95
+ data: {
96
+ payload: {
97
+ saveNotification: true,
98
+ accountType: enum_1.NotificationAccountType.USER,
99
+ userIds,
100
+ isGlobal: false,
101
+ isClickable: true,
102
+ clickType: enum_1.ClickType.CLICKABLE_TOURNAMENT,
103
+ ...notification,
104
+ },
105
+ tournamentId: notification.tournamentId,
106
+ type: notification.tournamentNotificationType,
107
+ recipientCount: userIds.length,
108
+ },
109
+ });
110
+ }
111
+ catch (error) {
112
+ logger.error('Failed to dispatch tournament user notifications', error, {
113
+ tag: 'notification.queue.tournament',
114
+ data: {
115
+ tournamentId: notification.tournamentId,
116
+ type: notification.tournamentNotificationType,
117
+ recipientCount: userIds.length,
118
+ payload: {
119
+ saveNotification: true,
120
+ accountType: enum_1.NotificationAccountType.USER,
121
+ userIds,
122
+ isGlobal: false,
123
+ isClickable: true,
124
+ clickType: enum_1.ClickType.CLICKABLE_TOURNAMENT,
125
+ ...notification,
126
+ },
127
+ },
128
+ });
129
+ }
93
130
  }
94
- async dispatchScheduledJob(type, target, templates, tournamentId, teamIds, matchId, matchStartTime, deepLinkRoute) {
95
- const { title, body } = this.getTemplate(templates, type);
96
- if (target === enum_1.TournamentScheduledNotificationTarget.GLOBAL) {
97
- await this.notifyGlobal({ title, body, tournamentNotificationType: type, tournamentId: tournamentId.toString(), deepLinkRoute });
131
+ async dispatchScheduledReminder(ctx) {
132
+ if (ctx.target === enum_1.TournamentScheduledNotificationTarget.GLOBAL) {
133
+ await this.notifyGlobal(this.buildBaseMessage(ctx));
134
+ return;
98
135
  }
99
- else {
100
- const unpredicted = await this.participantService.getUnpredicted(tournamentId, teamIds, matchId);
101
- await this.notifyUsers({
102
- title,
103
- body,
104
- tournamentNotificationType: type,
105
- predictionExpiryTime: matchStartTime,
106
- matchId: matchId.toString(),
107
- tournamentId: tournamentId.toString(),
108
- deepLinkRoute,
109
- }, unpredicted.map(p => p.userId));
136
+ const recipients = await this.resolveReminderRecipients(ctx);
137
+ if (!recipients.length)
138
+ return;
139
+ await this.notifyUsers(this.buildTargetedMessage(ctx), recipients.map(p => p.userId));
140
+ }
141
+ resolveReminderRecipients(ctx) {
142
+ const { tournament, match, type } = ctx;
143
+ if (type === enum_1.TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR) {
144
+ return this.participantService.getPredicted(tournament._id, match._id);
110
145
  }
146
+ return this.participantService.getUnpredicted(tournament._id, [match.team1.teamId, match.team2.teamId], match._id);
147
+ }
148
+ buildBaseMessage(ctx) {
149
+ const { title, body } = this.getTemplate(ctx.templates, ctx.type);
150
+ return {
151
+ title: this.withTeamNames(title, ctx.match),
152
+ body: this.withTeamNames(body, ctx.match),
153
+ tournamentNotificationType: ctx.type,
154
+ tournamentId: ctx.tournament._id.toString(),
155
+ deepLinkRoute: ctx.tournament.deepLinkRoute,
156
+ };
157
+ }
158
+ buildTargetedMessage(ctx) {
159
+ return {
160
+ ...this.buildBaseMessage(ctx),
161
+ matchId: ctx.match._id.toString(),
162
+ predictionExpiryTime: ctx.match.startingDate,
163
+ };
164
+ }
165
+ withTeamNames(text, match) {
166
+ return text
167
+ .replace(/{team1}/g, match.team1.name)
168
+ .replace(/{team2}/g, match.team2.name);
111
169
  }
112
170
  };
113
171
  exports.TournamentNotificationService = TournamentNotificationService;
@@ -1 +1 @@
1
- {"version":3,"file":"notification.service.js","sourceRoot":"/","sources":["modules/tournament/notification/notification.service.ts"],"names":[],"mappings":"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","sourcesContent":["import Container, {Service} from 'typedi';\nimport {mongoose} from '@typegoose/typegoose';\nimport {publishEvent} from '@lyxa.ai/core/dist/libraries/event';\nimport {NotificationEvent, NotificationGlobalEvent} from '@lyxa.ai/core/dist/libraries/notification';\nimport {\n ClickType,\n NotificationAccountType,\n TournamentNotificationTag,\n TournamentScheduledNotificationTarget\n} from '@lyxa.ai/core/dist/utilities/enum';\nimport {NotificationMessage} from '@lyxa.ai/core/dist/libraries/mongo/models';\nimport {ParticipantService} from '../participant/participant.service';\nimport {TournamentParticipantOutputDTO} from '../participant/participant.validation';\nimport {NotificationObject} from './notification.config';\n\n@Service()\nexport class TournamentNotificationService {\n private participantService: ParticipantService;\n\n constructor() {\n this.participantService = Container.get(ParticipantService);\n }\n\n private getTemplate(templates: Map<string, NotificationMessage>, tag: string): NotificationMessage {\n const t = templates.get(tag);\n if (!t) throw new Error(`No notification template for tag: ${tag}`);\n return t;\n }\n\n private hasNotPredicted(participant: TournamentParticipantOutputDTO, matchId: mongoose.Types.ObjectId): boolean {\n return !participant.predictions.some(p => p.matchId.toString() === matchId.toString());\n }\n\n public async notifyGlobal(notification: NotificationObject): Promise<void> {\n await publishEvent(\n new NotificationGlobalEvent({\n title: notification.title,\n body: notification.body,\n data: {\n saveNotification: true,\n accountType: NotificationAccountType.USER,\n isGlobal: true,\n isClickable: true,\n clickType: ClickType.CLICKABLE_TOURNAMENT,\n ...notification,\n },\n })\n );\n }\n\n public async notifyUsers(notification: NotificationObject, userIds: mongoose.Types.ObjectId[]): Promise<void> {\n await Promise.all(\n userIds.map(recipientId =>\n publishEvent(\n new NotificationEvent({\n title: notification.title,\n body: notification.body,\n data: {\n saveNotification: true,\n accountType: NotificationAccountType.USER,\n recipientId,\n isGlobal: false,\n isClickable: true,\n clickType: ClickType.CLICKABLE_TOURNAMENT,\n ...notification,\n },\n })\n )\n )\n );\n }\n\n public async dispatchScheduledJob(\n type: TournamentNotificationTag,\n target: TournamentScheduledNotificationTarget,\n templates: Map<TournamentNotificationTag, NotificationMessage>,\n tournamentId: mongoose.Types.ObjectId,\n teamIds: mongoose.Types.ObjectId[],\n matchId: mongoose.Types.ObjectId,\n matchStartTime: Date,\n deepLinkRoute?: string\n ): Promise<void> {\n const {title, body} = this.getTemplate(templates, type);\n\n if (target === TournamentScheduledNotificationTarget.GLOBAL) {\n await this.notifyGlobal({title, body, tournamentNotificationType: type, tournamentId: tournamentId.toString(), deepLinkRoute});\n } else {\n const unpredicted = await this.participantService.getUnpredicted(tournamentId, teamIds, matchId);\n await this.notifyUsers(\n {\n title,\n body,\n tournamentNotificationType: type,\n predictionExpiryTime: matchStartTime,\n matchId: matchId.toString(),\n tournamentId: tournamentId.toString(),\n deepLinkRoute,\n },\n unpredicted.map(p => p.userId)\n );\n }\n }\n}\n\nexport const tournamentNotificationService = Container.get(TournamentNotificationService);\n"]}
1
+ {"version":3,"file":"notification.service.js","sourceRoot":"/","sources":["modules/tournament/notification/notification.service.ts"],"names":[],"mappings":"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","sourcesContent":["import Container, {Service} from 'typedi';\nimport {mongoose} from '@typegoose/typegoose';\nimport {getLibraries} from '@lyxa.ai/core';\nimport {publishEvent} from '@lyxa.ai/core/dist/libraries/event';\nimport {NotificationEvent, NotificationGlobalEvent} from '@lyxa.ai/core/dist/libraries/notification';\nimport {\n ClickType,\n NotificationAccountType,\n TournamentNotificationTag,\n TournamentScheduledNotificationTarget\n} from '@lyxa.ai/core/dist/utilities/enum';\nimport {NotificationMessage, TournamentMatch} from '@lyxa.ai/core/dist/libraries/mongo/models';\nimport {ParticipantService} from '../participant/participant.service';\nimport {TournamentParticipantOutputDTO} from '../participant/participant.validation';\nimport {TournamentDocument} from '../config/config.types';\nimport {NotificationObject} from './notification.config';\n\nexport interface ScheduledReminderContext {\n type: TournamentNotificationTag;\n target: TournamentScheduledNotificationTarget;\n templates: Map<TournamentNotificationTag, NotificationMessage>;\n tournament: TournamentDocument;\n match: TournamentMatch;\n}\n\n@Service()\nexport class TournamentNotificationService {\n private participantService: ParticipantService;\n\n constructor() {\n this.participantService = Container.get(ParticipantService);\n }\n\n private getTemplate(templates: Map<string, NotificationMessage>, tag: string): NotificationMessage {\n const t = templates.get(tag);\n if (!t) throw new Error(`No notification template for tag: ${tag}`);\n return t;\n }\n\n public async notifyGlobal(notification: NotificationObject): Promise<void> {\n await publishEvent(\n new NotificationGlobalEvent({\n title: notification.title,\n body: notification.body,\n data: {\n saveNotification: true,\n accountType: NotificationAccountType.USER,\n isGlobal: true,\n isClickable: true,\n clickType: ClickType.CLICKABLE_TOURNAMENT,\n ...notification,\n },\n })\n );\n }\n\n public async notifyUsers(notification: NotificationObject, userIds: mongoose.Types.ObjectId[]): Promise<void> {\n const logger = getLibraries().getLoggerKit();\n try {\n await Promise.all(\n userIds.map(recipientId =>\n publishEvent(\n new NotificationEvent({\n title: notification.title,\n body: notification.body,\n data: {\n saveNotification: true,\n accountType: NotificationAccountType.USER,\n recipientId,\n isGlobal: false,\n isClickable: true,\n clickType: ClickType.CLICKABLE_TOURNAMENT,\n ...notification,\n },\n })\n )\n )\n );\n\n logger.info('Tournament user notifications dispatched', {\n tag: 'notification.queue.tournament',\n data: {\n payload: {\n saveNotification: true,\n accountType: NotificationAccountType.USER,\n userIds,\n isGlobal: false,\n isClickable: true,\n clickType: ClickType.CLICKABLE_TOURNAMENT,\n ...notification,\n },\n tournamentId: notification.tournamentId,\n type: notification.tournamentNotificationType,\n recipientCount: userIds.length,\n },\n });\n } catch (error) {\n logger.error('Failed to dispatch tournament user notifications', error, {\n tag: 'notification.queue.tournament',\n data: {\n tournamentId: notification.tournamentId,\n type: notification.tournamentNotificationType,\n recipientCount: userIds.length,\n payload: {\n saveNotification: true,\n accountType: NotificationAccountType.USER,\n userIds,\n isGlobal: false,\n isClickable: true,\n clickType: ClickType.CLICKABLE_TOURNAMENT,\n ...notification,\n },\n },\n });\n }\n }\n\n public async dispatchScheduledReminder(ctx: ScheduledReminderContext): Promise<void> {\n if (ctx.target === TournamentScheduledNotificationTarget.GLOBAL) {\n await this.notifyGlobal(this.buildBaseMessage(ctx));\n return;\n }\n\n const recipients = await this.resolveReminderRecipients(ctx);\n if (!recipients.length) return;\n\n await this.notifyUsers(this.buildTargetedMessage(ctx), recipients.map(p => p.userId));\n }\n\n private resolveReminderRecipients(ctx: ScheduledReminderContext): Promise<TournamentParticipantOutputDTO[]> {\n const {tournament, match, type} = ctx;\n\n if (type === TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR) {\n return this.participantService.getPredicted(tournament._id, match._id);\n }\n\n return this.participantService.getUnpredicted(\n tournament._id,\n [match.team1.teamId, match.team2.teamId],\n match._id\n );\n }\n\n private buildBaseMessage(ctx: ScheduledReminderContext): NotificationObject {\n const {title, body} = this.getTemplate(ctx.templates, ctx.type);\n return {\n title: this.withTeamNames(title, ctx.match),\n body: this.withTeamNames(body, ctx.match),\n tournamentNotificationType: ctx.type,\n tournamentId: ctx.tournament._id.toString(),\n deepLinkRoute: ctx.tournament.deepLinkRoute,\n };\n }\n\n private buildTargetedMessage(ctx: ScheduledReminderContext): NotificationObject {\n return {\n ...this.buildBaseMessage(ctx),\n matchId: ctx.match._id.toString(),\n predictionExpiryTime: ctx.match.startingDate,\n };\n }\n\n private withTeamNames(text: string, match: TournamentMatch): string {\n return text\n .replace(/{team1}/g, match.team1.name)\n .replace(/{team2}/g, match.team2.name);\n }\n}\n\nexport const tournamentNotificationService = Container.get(TournamentNotificationService);\n"]}
@@ -11,6 +11,7 @@ export declare class TournamentParticipantRepository {
11
11
  updateToJoined(userId: mongoose.Types.ObjectId, tournamentId: mongoose.Types.ObjectId, teamId: mongoose.Types.ObjectId): Promise<TournamentParticipantOutputDTO | null>;
12
12
  createIfAbsent(userId: mongoose.Types.ObjectId, tournamentId: mongoose.Types.ObjectId, status: string): Promise<void>;
13
13
  findByTournamentWithoutPrediction(tournamentId: mongoose.Types.ObjectId, teamIds: mongoose.Types.ObjectId[], matchId: mongoose.Types.ObjectId): Promise<TournamentParticipantOutputDTO[]>;
14
+ findByTournamentWithPrediction(tournamentId: mongoose.Types.ObjectId, matchId: mongoose.Types.ObjectId): Promise<TournamentParticipantOutputDTO[]>;
14
15
  }
15
16
  export declare const tournamentParticipantRepository: TournamentParticipantRepository;
16
17
  //# sourceMappingURL=participant.repository.d.ts.map