@classytic/loyalty 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/config-aNigy4ec.d.mts +90 -0
- package/dist/constants-C75GvizO.mjs +49 -0
- package/dist/domain/enums/index.d.mts +2 -0
- package/dist/domain/enums/index.mjs +2 -0
- package/dist/domain/index.d.mts +90 -0
- package/dist/domain/index.mjs +4 -0
- package/dist/domain-errors-D7q-2LYD.mjs +120 -0
- package/dist/event-emitter.port-CLYPoMni.d.mts +8 -0
- package/dist/event-types-xQBtYBbX.mjs +24 -0
- package/dist/events/index.d.mts +2 -0
- package/dist/events/index.mjs +3 -0
- package/dist/events-CprEWlN7.mjs +25 -0
- package/dist/index-B6ZstxSP.d.mts +2357 -0
- package/dist/index-BcoGeDco.d.mts +127 -0
- package/dist/index-CaH4GhSF.d.mts +19 -0
- package/dist/index-DqzTjeEB.d.mts +148 -0
- package/dist/index-O7TL4R2P.d.mts +86 -0
- package/dist/index-_pg4J11t.d.mts +191 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +95 -0
- package/dist/models/index.d.mts +2 -0
- package/dist/models/index.mjs +2 -0
- package/dist/models-CrZPmVxt.mjs +417 -0
- package/dist/notification.port-BzUsH0It.d.mts +13 -0
- package/dist/notifications/index.d.mts +21 -0
- package/dist/notifications/index.mjs +19 -0
- package/dist/repositories/index.d.mts +2 -0
- package/dist/repositories/index.mjs +2 -0
- package/dist/repositories-qMX7Xh_y.mjs +440 -0
- package/dist/services/index.d.mts +2 -0
- package/dist/services/index.mjs +2 -0
- package/dist/services-CoTqC4E6.mjs +1098 -0
- package/dist/tier-definition-Djf72UPa.d.mts +120 -0
- package/dist/tier-definition.port-BzrHYBRo.d.mts +143 -0
- package/dist/types/index.d.mts +3 -0
- package/dist/types/index.mjs +1 -0
- package/package.json +111 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { n as DEFAULT_TENANT_FIELD, r as DEFAULT_TENANT_TYPE } from "./constants-C75GvizO.mjs";
|
|
2
|
+
import { h as ValidationError } from "./domain-errors-D7q-2LYD.mjs";
|
|
3
|
+
import { t as InProcessEventBus } from "./events-CprEWlN7.mjs";
|
|
4
|
+
import { t as createModels } from "./models-CrZPmVxt.mjs";
|
|
5
|
+
import { t as createRepositories } from "./repositories-qMX7Xh_y.mjs";
|
|
6
|
+
import { t as createServices } from "./services-CoTqC4E6.mjs";
|
|
7
|
+
import { t as LoyaltyEvents } from "./event-types-xQBtYBbX.mjs";
|
|
8
|
+
//#region src/index.ts
|
|
9
|
+
function resolveConfig(config) {
|
|
10
|
+
let tenant;
|
|
11
|
+
if (config.tenant === false) tenant = {
|
|
12
|
+
enabled: false,
|
|
13
|
+
field: "",
|
|
14
|
+
type: "ObjectId",
|
|
15
|
+
contextKey: ""
|
|
16
|
+
};
|
|
17
|
+
else if (config.tenant === void 0) tenant = {
|
|
18
|
+
enabled: true,
|
|
19
|
+
field: DEFAULT_TENANT_FIELD,
|
|
20
|
+
type: DEFAULT_TENANT_TYPE,
|
|
21
|
+
contextKey: DEFAULT_TENANT_FIELD
|
|
22
|
+
};
|
|
23
|
+
else tenant = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
field: config.tenant.field ?? "organizationId",
|
|
26
|
+
type: config.tenant.type ?? "ObjectId",
|
|
27
|
+
contextKey: config.tenant.contextKey ?? config.tenant.field ?? "organizationId",
|
|
28
|
+
...config.tenant.ref ? { ref: config.tenant.ref } : {}
|
|
29
|
+
};
|
|
30
|
+
const resolved = {
|
|
31
|
+
programId: config.program?.id ?? "default",
|
|
32
|
+
conversionRate: config.program?.conversionRate ?? 10,
|
|
33
|
+
redemption: {
|
|
34
|
+
minPoints: config.redemption?.minPoints ?? 0,
|
|
35
|
+
minOrderAmount: config.redemption?.minOrderAmount ?? 0,
|
|
36
|
+
maxRedeemPercent: config.redemption?.maxRedeemPercent ?? 50,
|
|
37
|
+
reservationTtlMinutes: config.redemption?.reservationTtlMinutes ?? 30
|
|
38
|
+
},
|
|
39
|
+
referral: {
|
|
40
|
+
referrerRewardPoints: config.referral?.referrerRewardPoints ?? 100,
|
|
41
|
+
refereeRewardPoints: config.referral?.refereeRewardPoints ?? 50,
|
|
42
|
+
maxPerPeriod: config.referral?.maxPerPeriod ?? 10,
|
|
43
|
+
periodDays: config.referral?.periodDays ?? 30,
|
|
44
|
+
requireApproval: config.referral?.requireApproval ?? false,
|
|
45
|
+
codeLength: config.referral?.codeLength ?? 8
|
|
46
|
+
},
|
|
47
|
+
expiry: {
|
|
48
|
+
enabled: config.expiry?.enabled ?? false,
|
|
49
|
+
...config.expiry?.defaultDays !== void 0 ? { defaultDays: config.expiry.defaultDays } : {}
|
|
50
|
+
},
|
|
51
|
+
tenant
|
|
52
|
+
};
|
|
53
|
+
if (resolved.conversionRate <= 0) throw new ValidationError("conversionRate must be positive");
|
|
54
|
+
if (resolved.referral.codeLength < 4) throw new ValidationError("referral codeLength must be >= 4");
|
|
55
|
+
if (resolved.redemption.maxRedeemPercent < 0 || resolved.redemption.maxRedeemPercent > 100) throw new ValidationError("maxRedeemPercent must be between 0 and 100");
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
var MongoUnitOfWork = class {
|
|
59
|
+
constructor(connection) {
|
|
60
|
+
this.connection = connection;
|
|
61
|
+
}
|
|
62
|
+
async withTransaction(cb) {
|
|
63
|
+
const session = await this.connection.startSession();
|
|
64
|
+
try {
|
|
65
|
+
let result;
|
|
66
|
+
await session.withTransaction(async () => {
|
|
67
|
+
result = await cb(session);
|
|
68
|
+
});
|
|
69
|
+
return result;
|
|
70
|
+
} finally {
|
|
71
|
+
await session.endSession();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
function createLoyaltyEngine(config) {
|
|
76
|
+
const resolvedConfig = resolveConfig(config);
|
|
77
|
+
const models = createModels(config.mongoose, resolvedConfig.tenant, config.indexes);
|
|
78
|
+
const repositories = createRepositories(models, config.plugins, resolvedConfig.tenant.field);
|
|
79
|
+
const unitOfWork = new MongoUnitOfWork(config.mongoose);
|
|
80
|
+
const events = config.events?.adapter ?? new InProcessEventBus();
|
|
81
|
+
return {
|
|
82
|
+
models,
|
|
83
|
+
repositories,
|
|
84
|
+
services: createServices({
|
|
85
|
+
repositories,
|
|
86
|
+
unitOfWork,
|
|
87
|
+
events,
|
|
88
|
+
notifications: config.notifications ?? null,
|
|
89
|
+
config: resolvedConfig
|
|
90
|
+
}),
|
|
91
|
+
events
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { LoyaltyEvents, createLoyaltyEngine, resolveConfig };
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { a as MEMBER_STATUSES, c as RULE_STATUSES, l as RULE_TYPES, o as REDEMPTION_STATUSES, s as REFERRAL_STATUSES, u as TRANSACTION_TYPES } from "./constants-C75GvizO.mjs";
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
//#region src/models/create-model.ts
|
|
4
|
+
/**
|
|
5
|
+
* Injects the tenant field into a schema and prepends it to ALL existing indexes.
|
|
6
|
+
*/
|
|
7
|
+
function injectTenantField(schema, tenant) {
|
|
8
|
+
if (!tenant.enabled) return;
|
|
9
|
+
schema.add({ [tenant.field]: {
|
|
10
|
+
type: tenant.type === "ObjectId" ? mongoose.Schema.Types.ObjectId : String,
|
|
11
|
+
required: true,
|
|
12
|
+
index: true,
|
|
13
|
+
...tenant.ref ? { ref: tenant.ref } : {}
|
|
14
|
+
} });
|
|
15
|
+
const existingIndexes = schema._indexes;
|
|
16
|
+
if (existingIndexes && existingIndexes.length > 0) for (const indexEntry of existingIndexes) {
|
|
17
|
+
const fields = indexEntry[0];
|
|
18
|
+
const newFields = { [tenant.field]: 1 };
|
|
19
|
+
for (const [key, val] of Object.entries(fields)) newFields[key] = val;
|
|
20
|
+
indexEntry[0] = newFields;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adds user-provided indexes, prepending the tenant field if tenant is enabled.
|
|
25
|
+
*/
|
|
26
|
+
function applyUserIndexes(schema, indexes, tenant) {
|
|
27
|
+
for (const def of indexes) {
|
|
28
|
+
const fields = tenant.enabled ? {
|
|
29
|
+
[tenant.field]: 1,
|
|
30
|
+
...def.fields
|
|
31
|
+
} : { ...def.fields };
|
|
32
|
+
schema.index(fields, def.options ?? {});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/models/schemas/earning-rule.schema.ts
|
|
37
|
+
function createEarningRuleSchema() {
|
|
38
|
+
const schema = new mongoose.Schema({
|
|
39
|
+
programId: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: "default"
|
|
42
|
+
},
|
|
43
|
+
name: {
|
|
44
|
+
type: String,
|
|
45
|
+
required: true
|
|
46
|
+
},
|
|
47
|
+
description: { type: String },
|
|
48
|
+
type: {
|
|
49
|
+
type: String,
|
|
50
|
+
enum: RULE_TYPES,
|
|
51
|
+
required: true
|
|
52
|
+
},
|
|
53
|
+
status: {
|
|
54
|
+
type: String,
|
|
55
|
+
enum: RULE_STATUSES,
|
|
56
|
+
default: "active"
|
|
57
|
+
},
|
|
58
|
+
priority: {
|
|
59
|
+
type: Number,
|
|
60
|
+
default: 0
|
|
61
|
+
},
|
|
62
|
+
conditions: {
|
|
63
|
+
minOrderAmount: { type: Number },
|
|
64
|
+
categories: [{ type: String }],
|
|
65
|
+
tiers: [{ type: String }],
|
|
66
|
+
actions: [{ type: String }],
|
|
67
|
+
dayOfWeek: [{ type: Number }],
|
|
68
|
+
dateRange: {
|
|
69
|
+
start: { type: Date },
|
|
70
|
+
end: { type: Date }
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
reward: {
|
|
74
|
+
pointsPerAmount: { type: Number },
|
|
75
|
+
amountPerPoint: { type: Number },
|
|
76
|
+
fixedPoints: { type: Number },
|
|
77
|
+
multiplier: { type: Number },
|
|
78
|
+
roundingMode: {
|
|
79
|
+
type: String,
|
|
80
|
+
enum: [
|
|
81
|
+
"floor",
|
|
82
|
+
"round",
|
|
83
|
+
"ceil"
|
|
84
|
+
],
|
|
85
|
+
default: "floor"
|
|
86
|
+
},
|
|
87
|
+
maxPointsPerTransaction: { type: Number }
|
|
88
|
+
},
|
|
89
|
+
startsAt: { type: Date },
|
|
90
|
+
endsAt: { type: Date },
|
|
91
|
+
metadata: { type: mongoose.Schema.Types.Mixed }
|
|
92
|
+
}, { timestamps: true });
|
|
93
|
+
schema.index({
|
|
94
|
+
status: 1,
|
|
95
|
+
type: 1,
|
|
96
|
+
priority: 1
|
|
97
|
+
});
|
|
98
|
+
return schema;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/models/schemas/member.schema.ts
|
|
102
|
+
function createMemberSchema() {
|
|
103
|
+
const schema = new mongoose.Schema({
|
|
104
|
+
programId: {
|
|
105
|
+
type: String,
|
|
106
|
+
default: "default"
|
|
107
|
+
},
|
|
108
|
+
externalId: {
|
|
109
|
+
type: String,
|
|
110
|
+
required: true
|
|
111
|
+
},
|
|
112
|
+
externalType: {
|
|
113
|
+
type: String,
|
|
114
|
+
required: true
|
|
115
|
+
},
|
|
116
|
+
status: {
|
|
117
|
+
type: String,
|
|
118
|
+
enum: MEMBER_STATUSES,
|
|
119
|
+
default: "active"
|
|
120
|
+
},
|
|
121
|
+
cardId: {
|
|
122
|
+
type: String,
|
|
123
|
+
unique: true,
|
|
124
|
+
sparse: true
|
|
125
|
+
},
|
|
126
|
+
referralCode: {
|
|
127
|
+
type: String,
|
|
128
|
+
unique: true,
|
|
129
|
+
sparse: true
|
|
130
|
+
},
|
|
131
|
+
tier: {
|
|
132
|
+
type: String,
|
|
133
|
+
default: null
|
|
134
|
+
},
|
|
135
|
+
tierOverride: { type: String },
|
|
136
|
+
tierOverrideReason: { type: String },
|
|
137
|
+
tierQualifiedAt: { type: Date },
|
|
138
|
+
tierExpiresAt: { type: Date },
|
|
139
|
+
balance: {
|
|
140
|
+
current: {
|
|
141
|
+
type: Number,
|
|
142
|
+
default: 0
|
|
143
|
+
},
|
|
144
|
+
lifetime: {
|
|
145
|
+
type: Number,
|
|
146
|
+
default: 0
|
|
147
|
+
},
|
|
148
|
+
redeemed: {
|
|
149
|
+
type: Number,
|
|
150
|
+
default: 0
|
|
151
|
+
},
|
|
152
|
+
expired: {
|
|
153
|
+
type: Number,
|
|
154
|
+
default: 0
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
enrolledAt: {
|
|
158
|
+
type: Date,
|
|
159
|
+
default: Date.now
|
|
160
|
+
},
|
|
161
|
+
deactivatedAt: { type: Date },
|
|
162
|
+
metadata: { type: mongoose.Schema.Types.Mixed }
|
|
163
|
+
}, { timestamps: true });
|
|
164
|
+
schema.index({
|
|
165
|
+
externalId: 1,
|
|
166
|
+
externalType: 1
|
|
167
|
+
}, { unique: true });
|
|
168
|
+
return schema;
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/models/schemas/point-transaction.schema.ts
|
|
172
|
+
function createPointTransactionSchema() {
|
|
173
|
+
const schema = new mongoose.Schema({
|
|
174
|
+
memberId: {
|
|
175
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
176
|
+
required: true,
|
|
177
|
+
ref: "LoyaltyMember"
|
|
178
|
+
},
|
|
179
|
+
programId: {
|
|
180
|
+
type: String,
|
|
181
|
+
default: "default"
|
|
182
|
+
},
|
|
183
|
+
type: {
|
|
184
|
+
type: String,
|
|
185
|
+
enum: TRANSACTION_TYPES,
|
|
186
|
+
required: true
|
|
187
|
+
},
|
|
188
|
+
points: {
|
|
189
|
+
type: Number,
|
|
190
|
+
required: true
|
|
191
|
+
},
|
|
192
|
+
balanceAfter: {
|
|
193
|
+
type: Number,
|
|
194
|
+
required: true
|
|
195
|
+
},
|
|
196
|
+
description: {
|
|
197
|
+
type: String,
|
|
198
|
+
required: true
|
|
199
|
+
},
|
|
200
|
+
referenceType: { type: String },
|
|
201
|
+
referenceId: { type: String },
|
|
202
|
+
idempotencyKey: { type: String },
|
|
203
|
+
earningRuleId: { type: mongoose.Schema.Types.ObjectId },
|
|
204
|
+
expiresAt: { type: Date },
|
|
205
|
+
expiredAt: { type: Date },
|
|
206
|
+
metadata: { type: mongoose.Schema.Types.Mixed },
|
|
207
|
+
actorId: {
|
|
208
|
+
type: String,
|
|
209
|
+
required: true
|
|
210
|
+
}
|
|
211
|
+
}, { timestamps: {
|
|
212
|
+
createdAt: true,
|
|
213
|
+
updatedAt: false
|
|
214
|
+
} });
|
|
215
|
+
schema.index({
|
|
216
|
+
memberId: 1,
|
|
217
|
+
createdAt: -1
|
|
218
|
+
});
|
|
219
|
+
schema.index({ idempotencyKey: 1 }, {
|
|
220
|
+
unique: true,
|
|
221
|
+
partialFilterExpression: { idempotencyKey: {
|
|
222
|
+
$exists: true,
|
|
223
|
+
$ne: null
|
|
224
|
+
} }
|
|
225
|
+
});
|
|
226
|
+
return schema;
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/models/schemas/redemption.schema.ts
|
|
230
|
+
function createRedemptionSchema() {
|
|
231
|
+
const schema = new mongoose.Schema({
|
|
232
|
+
memberId: {
|
|
233
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
234
|
+
required: true,
|
|
235
|
+
ref: "LoyaltyMember"
|
|
236
|
+
},
|
|
237
|
+
programId: {
|
|
238
|
+
type: String,
|
|
239
|
+
default: "default"
|
|
240
|
+
},
|
|
241
|
+
pointsReserved: {
|
|
242
|
+
type: Number,
|
|
243
|
+
required: true
|
|
244
|
+
},
|
|
245
|
+
pointsConfirmed: {
|
|
246
|
+
type: Number,
|
|
247
|
+
default: 0
|
|
248
|
+
},
|
|
249
|
+
discountAmount: {
|
|
250
|
+
type: Number,
|
|
251
|
+
required: true
|
|
252
|
+
},
|
|
253
|
+
conversionRate: {
|
|
254
|
+
type: Number,
|
|
255
|
+
required: true
|
|
256
|
+
},
|
|
257
|
+
status: {
|
|
258
|
+
type: String,
|
|
259
|
+
enum: REDEMPTION_STATUSES,
|
|
260
|
+
default: "reserved"
|
|
261
|
+
},
|
|
262
|
+
ownerType: {
|
|
263
|
+
type: String,
|
|
264
|
+
required: true
|
|
265
|
+
},
|
|
266
|
+
ownerId: {
|
|
267
|
+
type: String,
|
|
268
|
+
required: true
|
|
269
|
+
},
|
|
270
|
+
expiresAt: { type: Date },
|
|
271
|
+
confirmedAt: { type: Date },
|
|
272
|
+
releasedAt: { type: Date },
|
|
273
|
+
transactionId: { type: mongoose.Schema.Types.ObjectId }
|
|
274
|
+
}, { timestamps: true });
|
|
275
|
+
schema.index({
|
|
276
|
+
ownerType: 1,
|
|
277
|
+
ownerId: 1
|
|
278
|
+
}, { unique: true });
|
|
279
|
+
return schema;
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/models/schemas/referral.schema.ts
|
|
283
|
+
function createReferralSchema() {
|
|
284
|
+
const schema = new mongoose.Schema({
|
|
285
|
+
programId: {
|
|
286
|
+
type: String,
|
|
287
|
+
default: "default"
|
|
288
|
+
},
|
|
289
|
+
referrerId: {
|
|
290
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
291
|
+
required: true,
|
|
292
|
+
ref: "LoyaltyMember"
|
|
293
|
+
},
|
|
294
|
+
refereeId: {
|
|
295
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
296
|
+
required: true,
|
|
297
|
+
ref: "LoyaltyMember"
|
|
298
|
+
},
|
|
299
|
+
referralCode: {
|
|
300
|
+
type: String,
|
|
301
|
+
required: true
|
|
302
|
+
},
|
|
303
|
+
status: {
|
|
304
|
+
type: String,
|
|
305
|
+
enum: REFERRAL_STATUSES,
|
|
306
|
+
default: "pending"
|
|
307
|
+
},
|
|
308
|
+
referrerRewardPoints: { type: Number },
|
|
309
|
+
refereeRewardPoints: { type: Number },
|
|
310
|
+
referrerTransactionId: { type: mongoose.Schema.Types.ObjectId },
|
|
311
|
+
refereeTransactionId: { type: mongoose.Schema.Types.ObjectId },
|
|
312
|
+
approvedAt: { type: Date },
|
|
313
|
+
rejectedAt: { type: Date },
|
|
314
|
+
rejectionReason: { type: String },
|
|
315
|
+
expiresAt: { type: Date },
|
|
316
|
+
metadata: { type: mongoose.Schema.Types.Mixed }
|
|
317
|
+
}, { timestamps: true });
|
|
318
|
+
schema.index({ refereeId: 1 }, { unique: true });
|
|
319
|
+
schema.index({
|
|
320
|
+
referrerId: 1,
|
|
321
|
+
createdAt: -1
|
|
322
|
+
});
|
|
323
|
+
return schema;
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/models/schemas/tier-definition.schema.ts
|
|
327
|
+
function createTierDefinitionSchema() {
|
|
328
|
+
const schema = new mongoose.Schema({
|
|
329
|
+
programId: {
|
|
330
|
+
type: String,
|
|
331
|
+
default: "default"
|
|
332
|
+
},
|
|
333
|
+
name: {
|
|
334
|
+
type: String,
|
|
335
|
+
required: true
|
|
336
|
+
},
|
|
337
|
+
rank: {
|
|
338
|
+
type: Number,
|
|
339
|
+
required: true
|
|
340
|
+
},
|
|
341
|
+
qualificationCriteria: {
|
|
342
|
+
minLifetimePoints: { type: Number },
|
|
343
|
+
minLifetimeSpend: { type: Number },
|
|
344
|
+
evaluationPeriodDays: { type: Number }
|
|
345
|
+
},
|
|
346
|
+
benefits: {
|
|
347
|
+
pointsMultiplier: { type: Number },
|
|
348
|
+
discountPercent: { type: Number },
|
|
349
|
+
freeShipping: { type: Boolean },
|
|
350
|
+
customBenefits: { type: mongoose.Schema.Types.Mixed }
|
|
351
|
+
},
|
|
352
|
+
downgrade: {
|
|
353
|
+
enabled: {
|
|
354
|
+
type: Boolean,
|
|
355
|
+
default: false
|
|
356
|
+
},
|
|
357
|
+
gracePeriodDays: {
|
|
358
|
+
type: Number,
|
|
359
|
+
default: 30
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
color: { type: String },
|
|
363
|
+
metadata: { type: mongoose.Schema.Types.Mixed }
|
|
364
|
+
}, { timestamps: true });
|
|
365
|
+
schema.index({
|
|
366
|
+
programId: 1,
|
|
367
|
+
rank: 1
|
|
368
|
+
}, { unique: true });
|
|
369
|
+
schema.index({
|
|
370
|
+
programId: 1,
|
|
371
|
+
name: 1
|
|
372
|
+
}, { unique: true });
|
|
373
|
+
return schema;
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/models/index.ts
|
|
377
|
+
function createModels(connection, tenant, indexes) {
|
|
378
|
+
const memberSchema = createMemberSchema();
|
|
379
|
+
const pointTransactionSchema = createPointTransactionSchema();
|
|
380
|
+
const earningRuleSchema = createEarningRuleSchema();
|
|
381
|
+
const tierDefinitionSchema = createTierDefinitionSchema();
|
|
382
|
+
const redemptionSchema = createRedemptionSchema();
|
|
383
|
+
const referralSchema = createReferralSchema();
|
|
384
|
+
if (tenant.enabled) {
|
|
385
|
+
injectTenantField(memberSchema, tenant);
|
|
386
|
+
injectTenantField(pointTransactionSchema, tenant);
|
|
387
|
+
injectTenantField(earningRuleSchema, tenant);
|
|
388
|
+
injectTenantField(tierDefinitionSchema, tenant);
|
|
389
|
+
injectTenantField(redemptionSchema, tenant);
|
|
390
|
+
injectTenantField(referralSchema, tenant);
|
|
391
|
+
}
|
|
392
|
+
if (indexes) {
|
|
393
|
+
const schemaMap = {
|
|
394
|
+
member: memberSchema,
|
|
395
|
+
pointTransaction: pointTransactionSchema,
|
|
396
|
+
earningRule: earningRuleSchema,
|
|
397
|
+
tierDefinition: tierDefinitionSchema,
|
|
398
|
+
redemption: redemptionSchema,
|
|
399
|
+
referral: referralSchema
|
|
400
|
+
};
|
|
401
|
+
for (const [modelName, defs] of Object.entries(indexes)) {
|
|
402
|
+
const schema = schemaMap[modelName];
|
|
403
|
+
if (schema && defs) applyUserIndexes(schema, defs, tenant);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const model = (name, schema) => connection.models[name] || connection.model(name, schema);
|
|
407
|
+
return {
|
|
408
|
+
Member: model("LoyaltyMember", memberSchema),
|
|
409
|
+
PointTransaction: model("LoyaltyPointTransaction", pointTransactionSchema),
|
|
410
|
+
EarningRule: model("LoyaltyEarningRule", earningRuleSchema),
|
|
411
|
+
TierDefinition: model("LoyaltyTierDefinition", tierDefinitionSchema),
|
|
412
|
+
Redemption: model("LoyaltyRedemption", redemptionSchema),
|
|
413
|
+
Referral: model("LoyaltyReferral", referralSchema)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
export { createModels as t };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/domain/ports/notification.port.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Optional notification adapter. Implement to receive loyalty events
|
|
4
|
+
* and dispatch notifications through your own system (email, SMS, push, in-app).
|
|
5
|
+
*
|
|
6
|
+
* The loyalty engine calls this adapter after every state change.
|
|
7
|
+
* The adapter decides what to send, how, and to whom.
|
|
8
|
+
*/
|
|
9
|
+
interface NotificationAdapter {
|
|
10
|
+
notify(event: string, payload: Record<string, unknown>): void | Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { NotificationAdapter as t };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { t as NotificationAdapter } from "../notification.port-BzUsH0It.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/notifications/index.d.ts
|
|
4
|
+
interface NotificationsAdapterConfig {
|
|
5
|
+
service: {
|
|
6
|
+
send: (payload: unknown) => Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
resolveRecipient: (payload: Record<string, unknown>) => Promise<{
|
|
9
|
+
id?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
phone?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
deviceToken?: string;
|
|
14
|
+
} | null>;
|
|
15
|
+
templates?: Record<string, string>;
|
|
16
|
+
channels?: Record<string, string[]>;
|
|
17
|
+
skip?: string[];
|
|
18
|
+
}
|
|
19
|
+
declare function createNotificationsAdapter(config: NotificationsAdapterConfig): NotificationAdapter;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { NotificationsAdapterConfig, createNotificationsAdapter };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/notifications/index.ts
|
|
2
|
+
function createNotificationsAdapter(config) {
|
|
3
|
+
return { async notify(event, payload) {
|
|
4
|
+
if (config.skip?.includes(event)) return;
|
|
5
|
+
const template = config.templates?.[event];
|
|
6
|
+
if (config.templates && !template) return;
|
|
7
|
+
const recipient = await config.resolveRecipient(payload);
|
|
8
|
+
if (!recipient) return;
|
|
9
|
+
await config.service.send({
|
|
10
|
+
event,
|
|
11
|
+
recipient,
|
|
12
|
+
data: payload,
|
|
13
|
+
template,
|
|
14
|
+
channels: config.channels?.[event]
|
|
15
|
+
});
|
|
16
|
+
} };
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { createNotificationsAdapter };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as RedemptionRepository, c as EarningRuleRepository, i as ReferralRepository, n as createRepositories, o as PointTransactionRepository, r as TierDefinitionRepository, s as MemberRepository, t as LoyaltyRepositories } from "../index-BcoGeDco.mjs";
|
|
2
|
+
export { EarningRuleRepository, LoyaltyRepositories, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as PointTransactionRepository, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "../repositories-qMX7Xh_y.mjs";
|
|
2
|
+
export { EarningRuleRepository, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
|