@dappermountain/payload-plugin-achievements 0.1.0 → 0.2.1
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/README.md +36 -6
- package/dist/admin/ReconcileProgressionButton.d.ts +11 -0
- package/dist/admin/ReconcileProgressionButton.d.ts.map +1 -0
- package/dist/admin/ReconcileProgressionButton.js +41 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/reconcile.d.ts +18 -0
- package/dist/cli/reconcile.d.ts.map +1 -0
- package/dist/cli/reconcile.js +90 -0
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts.map +1 -1
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.js +35 -15
- package/dist/collections/Achievements/hooks/afterRequiresReviewDisabled.d.ts +7 -0
- package/dist/collections/Achievements/hooks/afterRequiresReviewDisabled.d.ts.map +1 -0
- package/dist/collections/Achievements/hooks/afterRequiresReviewDisabled.js +15 -0
- package/dist/collections/Achievements/hooks/index.d.ts.map +1 -1
- package/dist/collections/Achievements/hooks/index.js +2 -0
- package/dist/collections/Achievements/index.d.ts.map +1 -1
- package/dist/collections/Achievements/index.js +3 -0
- package/dist/collections/Grants/hooks/afterGrantMutation.d.ts +4 -0
- package/dist/collections/Grants/hooks/afterGrantMutation.d.ts.map +1 -0
- package/dist/collections/Grants/hooks/afterGrantMutation.js +56 -0
- package/dist/collections/Grants/hooks/index.d.ts.map +1 -1
- package/dist/collections/Grants/hooks/index.js +3 -0
- package/dist/collections/Grants/index.d.ts.map +1 -1
- package/dist/collections/Grants/index.js +18 -1
- package/dist/collections/Logs/index.js +1 -1
- package/dist/collections/TierRequests/access/index.d.ts +3 -0
- package/dist/collections/TierRequests/access/index.d.ts.map +1 -0
- package/dist/collections/TierRequests/access/index.js +8 -0
- package/dist/collections/TierRequests/hooks/index.d.ts +3 -0
- package/dist/collections/TierRequests/hooks/index.d.ts.map +1 -0
- package/dist/collections/TierRequests/hooks/index.js +5 -0
- package/dist/collections/TierRequests/hooks/prepareTierRequest.d.ts +8 -0
- package/dist/collections/TierRequests/hooks/prepareTierRequest.d.ts.map +1 -0
- package/dist/collections/TierRequests/hooks/prepareTierRequest.js +67 -0
- package/dist/collections/TierRequests/index.d.ts +3 -0
- package/dist/collections/TierRequests/index.d.ts.map +1 -0
- package/dist/collections/TierRequests/index.js +47 -0
- package/dist/collections/Tiers/hooks/afterRequiresReviewDisabled.d.ts +7 -0
- package/dist/collections/Tiers/hooks/afterRequiresReviewDisabled.d.ts.map +1 -0
- package/dist/collections/Tiers/hooks/afterRequiresReviewDisabled.js +15 -0
- package/dist/collections/Tiers/hooks/index.d.ts.map +1 -1
- package/dist/collections/Tiers/hooks/index.js +2 -0
- package/dist/collections/Tiers/index.d.ts.map +1 -1
- package/dist/collections/Tiers/index.js +11 -3
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/collections/index.js +2 -0
- package/dist/collections/slugs.d.ts.map +1 -1
- package/dist/collections/slugs.js +1 -0
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +12 -3
- package/dist/endpoints/index.d.ts.map +1 -1
- package/dist/endpoints/index.js +9 -3
- package/dist/endpoints/reconcile.d.ts +8 -0
- package/dist/endpoints/reconcile.d.ts.map +1 -0
- package/dist/endpoints/reconcile.js +73 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.d.ts.map +1 -1
- package/dist/seed/index.js +1 -0
- package/dist/services/achievement/contextFlags.d.ts +5 -0
- package/dist/services/achievement/contextFlags.d.ts.map +1 -0
- package/dist/services/achievement/contextFlags.js +4 -0
- package/dist/services/achievement/grantSideEffects.d.ts +54 -0
- package/dist/services/achievement/grantSideEffects.d.ts.map +1 -0
- package/dist/services/achievement/grantSideEffects.js +269 -0
- package/dist/services/achievement/index.d.ts +6 -0
- package/dist/services/achievement/index.d.ts.map +1 -1
- package/dist/services/achievement/index.js +4 -0
- package/dist/services/achievement/reconcile.d.ts +36 -0
- package/dist/services/achievement/reconcile.d.ts.map +1 -0
- package/dist/services/achievement/reconcile.js +292 -0
- package/dist/services/achievement/recordEvent.d.ts.map +1 -1
- package/dist/services/achievement/recordEvent.js +8 -0
- package/dist/services/achievement/releaseReviewGate.d.ts +23 -0
- package/dist/services/achievement/releaseReviewGate.d.ts.map +1 -0
- package/dist/services/achievement/releaseReviewGate.js +71 -0
- package/dist/services/achievement/resolveCurrentTier.d.ts +1 -1
- package/dist/services/achievement/resolveCurrentTier.d.ts.map +1 -1
- package/dist/services/achievement/resolveCurrentTier.js +12 -2
- package/dist/services/achievement/submitAchievementRequest.d.ts +4 -2
- package/dist/services/achievement/submitAchievementRequest.d.ts.map +1 -1
- package/dist/services/achievement/submitAchievementRequest.js +8 -101
- package/dist/services/achievement/tierApproval.d.ts +8 -0
- package/dist/services/achievement/tierApproval.d.ts.map +1 -0
- package/dist/services/achievement/tierApproval.js +21 -0
- package/dist/services/achievement/tierProgression.d.ts +22 -0
- package/dist/services/achievement/tierProgression.d.ts.map +1 -0
- package/dist/services/achievement/tierProgression.js +100 -0
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -6
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { collectionOf } from '../../collections/helpers';
|
|
2
|
+
import { CTX_CASCADING_REVOKE, CTX_SYSTEM_REQUEST } from './contextFlags';
|
|
3
|
+
import { evaluateRules, ruleGroupHasProgressRequirements } from './evaluateRules';
|
|
4
|
+
import { recordLog, syncTierChangedLog } from './recordEvent';
|
|
5
|
+
import { relationId } from './relationId';
|
|
6
|
+
import { syncTierProgression } from './tierProgression';
|
|
7
|
+
import { userScopeWhere } from './where';
|
|
8
|
+
export { CTX_CASCADING_REVOKE, CTX_SKIP_GRANT_SIDE_EFFECTS, CTX_SYSTEM_REQUEST } from './contextFlags';
|
|
9
|
+
/** Create a grant row only — side effects run in Grants afterChange. */
|
|
10
|
+
export async function createGrantRow(args) {
|
|
11
|
+
const scopeId = args.scopeId ?? null;
|
|
12
|
+
const clauses = [
|
|
13
|
+
{ user: { equals: args.userId } },
|
|
14
|
+
{ achievement: { equals: args.achievementId } },
|
|
15
|
+
];
|
|
16
|
+
if (scopeId)
|
|
17
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
18
|
+
else
|
|
19
|
+
clauses.push({ scope: { exists: false } });
|
|
20
|
+
const existing = await args.req.payload.find({
|
|
21
|
+
collection: collectionOf('grants'),
|
|
22
|
+
depth: 0,
|
|
23
|
+
limit: 1,
|
|
24
|
+
overrideAccess: true,
|
|
25
|
+
req: args.req,
|
|
26
|
+
where: { and: clauses },
|
|
27
|
+
});
|
|
28
|
+
if (existing.docs[0])
|
|
29
|
+
return existing.docs[0];
|
|
30
|
+
return (await args.req.payload.create({
|
|
31
|
+
collection: collectionOf('grants'),
|
|
32
|
+
data: {
|
|
33
|
+
user: args.userId,
|
|
34
|
+
scope: scopeId ?? undefined,
|
|
35
|
+
achievement: args.achievementId,
|
|
36
|
+
completedAt: args.completedAt ?? new Date().toISOString(),
|
|
37
|
+
...(args.note ? { note: args.note } : {}),
|
|
38
|
+
},
|
|
39
|
+
overrideAccess: true,
|
|
40
|
+
req: args.req,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/** Ensure a pending achievement request exists (system / reconcile path). */
|
|
44
|
+
export async function ensureAchievementRequest(args) {
|
|
45
|
+
const scopeId = args.scopeId ?? null;
|
|
46
|
+
const clauses = [
|
|
47
|
+
{ user: { equals: args.userId } },
|
|
48
|
+
{ achievement: { equals: args.achievementId } },
|
|
49
|
+
{ status: { in: ['pending', 'approved'] } },
|
|
50
|
+
];
|
|
51
|
+
if (scopeId)
|
|
52
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
53
|
+
else
|
|
54
|
+
clauses.push({ scope: { exists: false } });
|
|
55
|
+
const existing = await args.req.payload.find({
|
|
56
|
+
collection: collectionOf('achievementRequests'),
|
|
57
|
+
depth: 0,
|
|
58
|
+
limit: 1,
|
|
59
|
+
overrideAccess: true,
|
|
60
|
+
req: args.req,
|
|
61
|
+
where: { and: clauses },
|
|
62
|
+
});
|
|
63
|
+
if (existing.docs[0]) {
|
|
64
|
+
return { doc: existing.docs[0], created: false };
|
|
65
|
+
}
|
|
66
|
+
args.req.context[CTX_SYSTEM_REQUEST] = true;
|
|
67
|
+
try {
|
|
68
|
+
const doc = (await args.req.payload.create({
|
|
69
|
+
collection: collectionOf('achievementRequests'),
|
|
70
|
+
data: {
|
|
71
|
+
user: args.userId,
|
|
72
|
+
scope: scopeId ?? undefined,
|
|
73
|
+
achievement: args.achievementId,
|
|
74
|
+
status: 'pending',
|
|
75
|
+
},
|
|
76
|
+
overrideAccess: true,
|
|
77
|
+
req: args.req,
|
|
78
|
+
}));
|
|
79
|
+
return { doc, created: true };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
delete args.req.context[CTX_SYSTEM_REQUEST];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Auto-grant or open a request for composed achievements whose completion rules pass.
|
|
87
|
+
*/
|
|
88
|
+
export async function syncCompositeProgression(args) {
|
|
89
|
+
const where = args.scopeId
|
|
90
|
+
? { or: [{ scope: { equals: args.scopeId } }, { scope: { exists: false } }] }
|
|
91
|
+
: undefined;
|
|
92
|
+
const catalog = await args.req.payload.find({
|
|
93
|
+
collection: collectionOf('achievements'),
|
|
94
|
+
depth: 0,
|
|
95
|
+
limit: 500,
|
|
96
|
+
pagination: false,
|
|
97
|
+
overrideAccess: true,
|
|
98
|
+
req: args.req,
|
|
99
|
+
...(where ? { where } : {}),
|
|
100
|
+
});
|
|
101
|
+
let granted = 0;
|
|
102
|
+
let requested = 0;
|
|
103
|
+
for (const doc of catalog.docs) {
|
|
104
|
+
const def = doc;
|
|
105
|
+
if (!ruleGroupHasProgressRequirements(def.completionRules))
|
|
106
|
+
continue;
|
|
107
|
+
const complete = await evaluateRules({
|
|
108
|
+
payload: args.req.payload,
|
|
109
|
+
req: args.req,
|
|
110
|
+
rules: def.completionRules,
|
|
111
|
+
userId: args.userId,
|
|
112
|
+
scopeId: args.scopeId,
|
|
113
|
+
});
|
|
114
|
+
if (!complete)
|
|
115
|
+
continue;
|
|
116
|
+
if (def.requiresReview !== false) {
|
|
117
|
+
const { created } = await ensureAchievementRequest({
|
|
118
|
+
req: args.req,
|
|
119
|
+
userId: args.userId,
|
|
120
|
+
achievementId: String(def.id),
|
|
121
|
+
scopeId: args.scopeId,
|
|
122
|
+
});
|
|
123
|
+
if (created)
|
|
124
|
+
requested += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const before = await args.req.payload.find({
|
|
128
|
+
collection: collectionOf('grants'),
|
|
129
|
+
depth: 0,
|
|
130
|
+
limit: 1,
|
|
131
|
+
overrideAccess: true,
|
|
132
|
+
req: args.req,
|
|
133
|
+
where: {
|
|
134
|
+
and: [
|
|
135
|
+
{ user: { equals: args.userId } },
|
|
136
|
+
{ achievement: { equals: String(def.id) } },
|
|
137
|
+
...(args.scopeId
|
|
138
|
+
? [{ scope: { equals: args.scopeId } }]
|
|
139
|
+
: [{ scope: { exists: false } }]),
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
if (before.docs[0])
|
|
144
|
+
continue;
|
|
145
|
+
await createGrantRow({
|
|
146
|
+
req: args.req,
|
|
147
|
+
userId: args.userId,
|
|
148
|
+
achievementId: String(def.id),
|
|
149
|
+
scopeId: args.scopeId,
|
|
150
|
+
});
|
|
151
|
+
granted += 1;
|
|
152
|
+
}
|
|
153
|
+
return { granted, requested };
|
|
154
|
+
}
|
|
155
|
+
/** Delete grants for composed achievements that no longer pass completion rules. */
|
|
156
|
+
export async function revokeIncompleteComposites(args) {
|
|
157
|
+
const grants = await args.req.payload.find({
|
|
158
|
+
collection: collectionOf('grants'),
|
|
159
|
+
depth: 0,
|
|
160
|
+
limit: 500,
|
|
161
|
+
pagination: false,
|
|
162
|
+
overrideAccess: true,
|
|
163
|
+
req: args.req,
|
|
164
|
+
where: userScopeWhere(args.userId, args.scopeId),
|
|
165
|
+
});
|
|
166
|
+
let revoked = 0;
|
|
167
|
+
for (const grant of grants.docs) {
|
|
168
|
+
const achievementId = relationId(grant.achievement);
|
|
169
|
+
if (!achievementId)
|
|
170
|
+
continue;
|
|
171
|
+
const def = (await args.req.payload.findByID({
|
|
172
|
+
collection: collectionOf('achievements'),
|
|
173
|
+
id: achievementId,
|
|
174
|
+
depth: 0,
|
|
175
|
+
overrideAccess: true,
|
|
176
|
+
req: args.req,
|
|
177
|
+
}));
|
|
178
|
+
if (!def || !ruleGroupHasProgressRequirements(def.completionRules))
|
|
179
|
+
continue;
|
|
180
|
+
const complete = await evaluateRules({
|
|
181
|
+
payload: args.req.payload,
|
|
182
|
+
req: args.req,
|
|
183
|
+
rules: def.completionRules,
|
|
184
|
+
userId: args.userId,
|
|
185
|
+
scopeId: args.scopeId,
|
|
186
|
+
});
|
|
187
|
+
if (complete)
|
|
188
|
+
continue;
|
|
189
|
+
args.req.context[CTX_CASCADING_REVOKE] = true;
|
|
190
|
+
try {
|
|
191
|
+
await args.req.payload.delete({
|
|
192
|
+
collection: collectionOf('grants'),
|
|
193
|
+
id: String(grant.id),
|
|
194
|
+
overrideAccess: true,
|
|
195
|
+
req: args.req,
|
|
196
|
+
});
|
|
197
|
+
revoked += 1;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
delete args.req.context[CTX_CASCADING_REVOKE];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return revoked;
|
|
204
|
+
}
|
|
205
|
+
export async function runAfterGrantCreated(args) {
|
|
206
|
+
await recordLog({
|
|
207
|
+
payload: args.req.payload,
|
|
208
|
+
req: args.req,
|
|
209
|
+
userId: args.userId,
|
|
210
|
+
scopeId: args.scopeId,
|
|
211
|
+
type: 'achievement.granted',
|
|
212
|
+
data: {
|
|
213
|
+
achievement: args.achievementId,
|
|
214
|
+
...(args.achievementSlug ? { achievementSlug: args.achievementSlug } : {}),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
await syncCompositeProgression({
|
|
218
|
+
req: args.req,
|
|
219
|
+
userId: args.userId,
|
|
220
|
+
scopeId: args.scopeId,
|
|
221
|
+
});
|
|
222
|
+
await syncTierProgression({
|
|
223
|
+
req: args.req,
|
|
224
|
+
userId: args.userId,
|
|
225
|
+
scopeId: args.scopeId,
|
|
226
|
+
});
|
|
227
|
+
await syncTierChangedLog({
|
|
228
|
+
payload: args.req.payload,
|
|
229
|
+
req: args.req,
|
|
230
|
+
userId: args.userId,
|
|
231
|
+
scopeId: args.scopeId,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export async function runAfterGrantDeleted(args) {
|
|
235
|
+
await recordLog({
|
|
236
|
+
payload: args.req.payload,
|
|
237
|
+
req: args.req,
|
|
238
|
+
userId: args.userId,
|
|
239
|
+
scopeId: args.scopeId,
|
|
240
|
+
type: 'achievement.revoked',
|
|
241
|
+
data: {
|
|
242
|
+
achievement: args.achievementId,
|
|
243
|
+
...(args.achievementSlug ? { achievementSlug: args.achievementSlug } : {}),
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
if (args.cascading)
|
|
247
|
+
return;
|
|
248
|
+
await revokeIncompleteComposites({
|
|
249
|
+
req: args.req,
|
|
250
|
+
userId: args.userId,
|
|
251
|
+
scopeId: args.scopeId,
|
|
252
|
+
});
|
|
253
|
+
await syncCompositeProgression({
|
|
254
|
+
req: args.req,
|
|
255
|
+
userId: args.userId,
|
|
256
|
+
scopeId: args.scopeId,
|
|
257
|
+
});
|
|
258
|
+
await syncTierProgression({
|
|
259
|
+
req: args.req,
|
|
260
|
+
userId: args.userId,
|
|
261
|
+
scopeId: args.scopeId,
|
|
262
|
+
});
|
|
263
|
+
await syncTierChangedLog({
|
|
264
|
+
payload: args.req.payload,
|
|
265
|
+
req: args.req,
|
|
266
|
+
userId: args.userId,
|
|
267
|
+
scopeId: args.scopeId,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
@@ -11,4 +11,10 @@ export type { TierLadderProgress } from './resolveTierProgress';
|
|
|
11
11
|
export { grantAchievement, submitAchievementRequest } from './submitAchievementRequest';
|
|
12
12
|
export { reviewAchievementRequest } from './reviewAchievementRequest';
|
|
13
13
|
export { validateRuleGroup, validateRuleNode } from './validateRules';
|
|
14
|
+
export { syncCompositeProgression } from './grantSideEffects';
|
|
15
|
+
export { syncTierProgression } from './tierProgression';
|
|
16
|
+
export { reconcileProgression, reconcileUserProgression } from './reconcile';
|
|
17
|
+
export type { ReconcileProgressionArgs, ReconcileProgressionResult } from './reconcile';
|
|
18
|
+
export { didDisableRequiresReview, releasePendingAchievementReviews, releasePendingTierReviews, } from './releaseReviewGate';
|
|
19
|
+
export type { ReleaseReviewGateResult } from './releaseReviewGate';
|
|
14
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAA;AACzH,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAA;AACzH,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAC5E,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AACvF,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -7,3 +7,7 @@ export { resolveTierProgress } from './resolveTierProgress';
|
|
|
7
7
|
export { grantAchievement, submitAchievementRequest } from './submitAchievementRequest';
|
|
8
8
|
export { reviewAchievementRequest } from './reviewAchievementRequest';
|
|
9
9
|
export { validateRuleGroup, validateRuleNode } from './validateRules';
|
|
10
|
+
export { syncCompositeProgression } from './grantSideEffects';
|
|
11
|
+
export { syncTierProgression } from './tierProgression';
|
|
12
|
+
export { reconcileProgression, reconcileUserProgression } from './reconcile';
|
|
13
|
+
export { didDisableRequiresReview, releasePendingAchievementReviews, releasePendingTierReviews, } from './releaseReviewGate';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
export type ReconcileProgressionResult = {
|
|
3
|
+
usersScanned: number;
|
|
4
|
+
grantedLogsBackfilled: number;
|
|
5
|
+
compositesGranted: number;
|
|
6
|
+
achievementRequestsEnsured: number;
|
|
7
|
+
tierRequestsEnsured: number;
|
|
8
|
+
tierLogsWritten: number;
|
|
9
|
+
};
|
|
10
|
+
export type ReconcileProgressionArgs = {
|
|
11
|
+
payload: Payload;
|
|
12
|
+
req?: PayloadRequest;
|
|
13
|
+
/** Limit to one member. */
|
|
14
|
+
userId?: string | null;
|
|
15
|
+
scopeId?: string | null;
|
|
16
|
+
/** Limit discovery to grants / requests for this achievement (id or slug). */
|
|
17
|
+
achievementId?: string | null;
|
|
18
|
+
achievementSlug?: string | null;
|
|
19
|
+
/** Limit discovery to tier requests for this tier (id or slug). */
|
|
20
|
+
tierId?: string | null;
|
|
21
|
+
tierSlug?: string | null;
|
|
22
|
+
limit?: number;
|
|
23
|
+
};
|
|
24
|
+
/** Repair progression for one user (+ optional scope) from current grants + catalog. */
|
|
25
|
+
export declare function reconcileUserProgression(args: {
|
|
26
|
+
payload: Payload;
|
|
27
|
+
req?: PayloadRequest;
|
|
28
|
+
userId: string;
|
|
29
|
+
scopeId?: string | null;
|
|
30
|
+
}): Promise<Omit<ReconcileProgressionResult, 'usersScanned'>>;
|
|
31
|
+
/**
|
|
32
|
+
* Scan matching (user, scope) pairs and repair progression.
|
|
33
|
+
* Optional filters: `userId`, `scopeId`, `achievementId`/`achievementSlug`, `tierId`/`tierSlug`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function reconcileProgression(args: ReconcileProgressionArgs): Promise<ReconcileProgressionResult>;
|
|
36
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAY7D,MAAM,MAAM,0BAA0B,GAAG;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,0BAA0B,EAAE,MAAM,CAAA;IAClC,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAsFD,wFAAwF;AACxF,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,0BAA0B,EAAE,cAAc,CAAC,CAAC,CAoC5D;AA2LD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CA8CrC"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { collectionOf } from '../../collections/helpers';
|
|
2
|
+
import { syncCompositeProgression, } from './grantSideEffects';
|
|
3
|
+
import { recordLog, syncTierChangedLog } from './recordEvent';
|
|
4
|
+
import { relationId } from './relationId';
|
|
5
|
+
import { resolveCatalogId } from './resolveCatalog';
|
|
6
|
+
import { syncTierProgression } from './tierProgression';
|
|
7
|
+
import { userScopeWhere } from './where';
|
|
8
|
+
async function backfillGrantedLogs(args) {
|
|
9
|
+
const typeId = await resolveCatalogId({
|
|
10
|
+
payload: args.req.payload,
|
|
11
|
+
req: args.req,
|
|
12
|
+
key: 'eventTypes',
|
|
13
|
+
slugOrId: 'achievement.granted',
|
|
14
|
+
});
|
|
15
|
+
const grants = await args.req.payload.find({
|
|
16
|
+
collection: collectionOf('grants'),
|
|
17
|
+
depth: 0,
|
|
18
|
+
limit: 500,
|
|
19
|
+
pagination: false,
|
|
20
|
+
overrideAccess: true,
|
|
21
|
+
req: args.req,
|
|
22
|
+
where: userScopeWhere(args.userId, args.scopeId),
|
|
23
|
+
});
|
|
24
|
+
const logs = await args.req.payload.find({
|
|
25
|
+
collection: collectionOf('logs'),
|
|
26
|
+
depth: 0,
|
|
27
|
+
limit: 500,
|
|
28
|
+
pagination: false,
|
|
29
|
+
overrideAccess: true,
|
|
30
|
+
req: args.req,
|
|
31
|
+
where: {
|
|
32
|
+
and: [userScopeWhere(args.userId, args.scopeId), { type: { equals: typeId } }],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const logged = new Set(logs.docs
|
|
36
|
+
.map((log) => relationId(log.data?.achievement))
|
|
37
|
+
.filter((id) => Boolean(id)));
|
|
38
|
+
let written = 0;
|
|
39
|
+
for (const grant of grants.docs) {
|
|
40
|
+
const achievementId = relationId(grant.achievement);
|
|
41
|
+
if (!achievementId || logged.has(achievementId))
|
|
42
|
+
continue;
|
|
43
|
+
let slug;
|
|
44
|
+
try {
|
|
45
|
+
const def = (await args.req.payload.findByID({
|
|
46
|
+
collection: collectionOf('achievements'),
|
|
47
|
+
id: achievementId,
|
|
48
|
+
depth: 0,
|
|
49
|
+
overrideAccess: true,
|
|
50
|
+
req: args.req,
|
|
51
|
+
select: { slug: true },
|
|
52
|
+
}));
|
|
53
|
+
slug = def?.slug;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
const completedAt = grant.completedAt;
|
|
59
|
+
await recordLog({
|
|
60
|
+
payload: args.req.payload,
|
|
61
|
+
req: args.req,
|
|
62
|
+
userId: args.userId,
|
|
63
|
+
scopeId: args.scopeId,
|
|
64
|
+
type: 'achievement.granted',
|
|
65
|
+
data: {
|
|
66
|
+
achievement: achievementId,
|
|
67
|
+
...(slug ? { achievementSlug: slug } : {}),
|
|
68
|
+
backfilled: true,
|
|
69
|
+
...(completedAt ? { completedAt } : {}),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
logged.add(achievementId);
|
|
73
|
+
written += 1;
|
|
74
|
+
}
|
|
75
|
+
return written;
|
|
76
|
+
}
|
|
77
|
+
/** Repair progression for one user (+ optional scope) from current grants + catalog. */
|
|
78
|
+
export async function reconcileUserProgression(args) {
|
|
79
|
+
const scopeId = args.scopeId ?? null;
|
|
80
|
+
const req = args.req ??
|
|
81
|
+
{
|
|
82
|
+
payload: args.payload,
|
|
83
|
+
context: {},
|
|
84
|
+
};
|
|
85
|
+
if (!req.context)
|
|
86
|
+
req.context = {};
|
|
87
|
+
const grantedLogsBackfilled = await backfillGrantedLogs({ req, userId: args.userId, scopeId });
|
|
88
|
+
const composites = await syncCompositeProgression({
|
|
89
|
+
req,
|
|
90
|
+
userId: args.userId,
|
|
91
|
+
scopeId,
|
|
92
|
+
});
|
|
93
|
+
const tiers = await syncTierProgression({
|
|
94
|
+
req,
|
|
95
|
+
userId: args.userId,
|
|
96
|
+
scopeId,
|
|
97
|
+
});
|
|
98
|
+
const tierLog = await syncTierChangedLog({
|
|
99
|
+
payload: args.payload,
|
|
100
|
+
req,
|
|
101
|
+
userId: args.userId,
|
|
102
|
+
scopeId,
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
grantedLogsBackfilled,
|
|
106
|
+
compositesGranted: composites.granted,
|
|
107
|
+
achievementRequestsEnsured: composites.requested,
|
|
108
|
+
tierRequestsEnsured: tiers.requested,
|
|
109
|
+
tierLogsWritten: tierLog ? 1 : 0,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function scopeKey(scope) {
|
|
113
|
+
const id = relationId(scope);
|
|
114
|
+
return id ?? '';
|
|
115
|
+
}
|
|
116
|
+
function addPair(pairs, userId, scopeId, filterUserId, filterScopeId) {
|
|
117
|
+
if (!userId)
|
|
118
|
+
return;
|
|
119
|
+
if (filterUserId && userId !== filterUserId)
|
|
120
|
+
return;
|
|
121
|
+
if (filterScopeId != null && filterScopeId !== '' && scopeId !== filterScopeId)
|
|
122
|
+
return;
|
|
123
|
+
const key = `${userId}::${scopeKey(scopeId)}`;
|
|
124
|
+
if (!pairs.has(key))
|
|
125
|
+
pairs.set(key, { userId, scopeId });
|
|
126
|
+
}
|
|
127
|
+
async function resolveDefinitionId(args) {
|
|
128
|
+
if (args.id && args.id.length > 0)
|
|
129
|
+
return args.id;
|
|
130
|
+
if (!args.slug || args.slug.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
const found = await args.payload.find({
|
|
133
|
+
collection: collectionOf(args.collection),
|
|
134
|
+
depth: 0,
|
|
135
|
+
limit: 1,
|
|
136
|
+
overrideAccess: true,
|
|
137
|
+
req: args.req,
|
|
138
|
+
where: { slug: { equals: args.slug } },
|
|
139
|
+
});
|
|
140
|
+
return relationId(found.docs[0]?.id) ?? null;
|
|
141
|
+
}
|
|
142
|
+
async function collectPairs(args) {
|
|
143
|
+
const pairs = new Map();
|
|
144
|
+
const filterUserId = args.userId?.trim() || null;
|
|
145
|
+
const filterScopeId = args.scopeId ?? null;
|
|
146
|
+
const achievementId = await resolveDefinitionId({
|
|
147
|
+
payload: args.payload,
|
|
148
|
+
req: args.req,
|
|
149
|
+
collection: 'achievements',
|
|
150
|
+
id: args.achievementId,
|
|
151
|
+
slug: args.achievementSlug,
|
|
152
|
+
});
|
|
153
|
+
const tierId = await resolveDefinitionId({
|
|
154
|
+
payload: args.payload,
|
|
155
|
+
req: args.req,
|
|
156
|
+
collection: 'tiers',
|
|
157
|
+
id: args.tierId,
|
|
158
|
+
slug: args.tierSlug,
|
|
159
|
+
});
|
|
160
|
+
if (filterUserId && !achievementId && !tierId) {
|
|
161
|
+
addPair(pairs, filterUserId, filterScopeId);
|
|
162
|
+
return [...pairs.values()];
|
|
163
|
+
}
|
|
164
|
+
if (achievementId) {
|
|
165
|
+
const grantWhere = {
|
|
166
|
+
and: [
|
|
167
|
+
{ achievement: { equals: achievementId } },
|
|
168
|
+
...(filterUserId ? [{ user: { equals: filterUserId } }] : []),
|
|
169
|
+
...(filterScopeId
|
|
170
|
+
? [{ scope: { equals: filterScopeId } }]
|
|
171
|
+
: []),
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
const grants = await args.payload.find({
|
|
175
|
+
collection: collectionOf('grants'),
|
|
176
|
+
depth: 0,
|
|
177
|
+
limit: args.limit,
|
|
178
|
+
pagination: false,
|
|
179
|
+
overrideAccess: true,
|
|
180
|
+
req: args.req,
|
|
181
|
+
where: grantWhere,
|
|
182
|
+
});
|
|
183
|
+
for (const grant of grants.docs) {
|
|
184
|
+
addPair(pairs, relationId(grant.user), relationId(grant.scope), filterUserId, filterScopeId);
|
|
185
|
+
}
|
|
186
|
+
const requestWhere = {
|
|
187
|
+
and: [
|
|
188
|
+
{ achievement: { equals: achievementId } },
|
|
189
|
+
...(filterUserId ? [{ user: { equals: filterUserId } }] : []),
|
|
190
|
+
...(filterScopeId ? [{ scope: { equals: filterScopeId } }] : []),
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
const requests = await args.payload.find({
|
|
194
|
+
collection: collectionOf('achievementRequests'),
|
|
195
|
+
depth: 0,
|
|
196
|
+
limit: args.limit,
|
|
197
|
+
pagination: false,
|
|
198
|
+
overrideAccess: true,
|
|
199
|
+
req: args.req,
|
|
200
|
+
where: requestWhere,
|
|
201
|
+
});
|
|
202
|
+
for (const request of requests.docs) {
|
|
203
|
+
addPair(pairs, relationId(request.user), relationId(request.scope), filterUserId, filterScopeId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (tierId) {
|
|
207
|
+
const requestWhere = {
|
|
208
|
+
and: [
|
|
209
|
+
{ tier: { equals: tierId } },
|
|
210
|
+
...(filterUserId ? [{ user: { equals: filterUserId } }] : []),
|
|
211
|
+
...(filterScopeId ? [{ scope: { equals: filterScopeId } }] : []),
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
const requests = await args.payload.find({
|
|
215
|
+
collection: collectionOf('tierRequests'),
|
|
216
|
+
depth: 0,
|
|
217
|
+
limit: args.limit,
|
|
218
|
+
pagination: false,
|
|
219
|
+
overrideAccess: true,
|
|
220
|
+
req: args.req,
|
|
221
|
+
where: requestWhere,
|
|
222
|
+
});
|
|
223
|
+
for (const request of requests.docs) {
|
|
224
|
+
addPair(pairs, relationId(request.user), relationId(request.scope), filterUserId, filterScopeId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!achievementId && !tierId) {
|
|
228
|
+
const where = filterScopeId
|
|
229
|
+
? { scope: { equals: filterScopeId } }
|
|
230
|
+
: undefined;
|
|
231
|
+
const grants = await args.payload.find({
|
|
232
|
+
collection: collectionOf('grants'),
|
|
233
|
+
depth: 0,
|
|
234
|
+
limit: args.limit,
|
|
235
|
+
pagination: false,
|
|
236
|
+
overrideAccess: true,
|
|
237
|
+
req: args.req,
|
|
238
|
+
...(where ? { where } : {}),
|
|
239
|
+
});
|
|
240
|
+
for (const grant of grants.docs) {
|
|
241
|
+
addPair(pairs, relationId(grant.user), relationId(grant.scope), filterUserId, filterScopeId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return [...pairs.values()];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Scan matching (user, scope) pairs and repair progression.
|
|
248
|
+
* Optional filters: `userId`, `scopeId`, `achievementId`/`achievementSlug`, `tierId`/`tierSlug`.
|
|
249
|
+
*/
|
|
250
|
+
export async function reconcileProgression(args) {
|
|
251
|
+
const req = args.req ??
|
|
252
|
+
{
|
|
253
|
+
payload: args.payload,
|
|
254
|
+
context: {},
|
|
255
|
+
};
|
|
256
|
+
if (!req.context)
|
|
257
|
+
req.context = {};
|
|
258
|
+
const pairs = await collectPairs({
|
|
259
|
+
payload: args.payload,
|
|
260
|
+
req,
|
|
261
|
+
userId: args.userId,
|
|
262
|
+
scopeId: args.scopeId,
|
|
263
|
+
achievementId: args.achievementId,
|
|
264
|
+
achievementSlug: args.achievementSlug,
|
|
265
|
+
tierId: args.tierId,
|
|
266
|
+
tierSlug: args.tierSlug,
|
|
267
|
+
limit: args.limit ?? 5000,
|
|
268
|
+
});
|
|
269
|
+
const totals = {
|
|
270
|
+
usersScanned: 0,
|
|
271
|
+
grantedLogsBackfilled: 0,
|
|
272
|
+
compositesGranted: 0,
|
|
273
|
+
achievementRequestsEnsured: 0,
|
|
274
|
+
tierRequestsEnsured: 0,
|
|
275
|
+
tierLogsWritten: 0,
|
|
276
|
+
};
|
|
277
|
+
for (const pair of pairs) {
|
|
278
|
+
const result = await reconcileUserProgression({
|
|
279
|
+
payload: args.payload,
|
|
280
|
+
req,
|
|
281
|
+
userId: pair.userId,
|
|
282
|
+
scopeId: pair.scopeId,
|
|
283
|
+
});
|
|
284
|
+
totals.usersScanned += 1;
|
|
285
|
+
totals.grantedLogsBackfilled += result.grantedLogsBackfilled;
|
|
286
|
+
totals.compositesGranted += result.compositesGranted;
|
|
287
|
+
totals.achievementRequestsEnsured += result.achievementRequestsEnsured;
|
|
288
|
+
totals.tierRequestsEnsured += result.tierRequestsEnsured;
|
|
289
|
+
totals.tierLogsWritten += result.tierLogsWritten;
|
|
290
|
+
}
|
|
291
|
+
return totals;
|
|
292
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recordEvent.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/recordEvent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAWtD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACtC,CAAA;AAgBD,6DAA6D;AAC7D,wBAAsB,SAAS,CAAC,IAAI,EAAE,cAAc,wEAuCnD;AAED,4CAA4C;AAC5C,eAAO,MAAM,WAAW,kBAAY,CAAA;AAEpC;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,
|
|
1
|
+
{"version":3,"file":"recordEvent.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/recordEvent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAWtD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACtC,CAAA;AAgBD,6DAA6D;AAC7D,wBAAsB,SAAS,CAAC,IAAI,EAAE,cAAc,wEAuCnD;AAED,4CAA4C;AAC5C,eAAO,MAAM,WAAW,kBAAY,CAAA;AAEpC;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,wEAiCA;AAED,6FAA6F;AAC7F,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,iFAgDA"}
|
|
@@ -71,6 +71,14 @@ export async function recordMetricChange(args) {
|
|
|
71
71
|
change: args.change,
|
|
72
72
|
reason: args.reason,
|
|
73
73
|
});
|
|
74
|
+
if (args.req) {
|
|
75
|
+
const { syncTierProgression } = await import('./tierProgression');
|
|
76
|
+
await syncTierProgression({
|
|
77
|
+
req: args.req,
|
|
78
|
+
userId: args.userId,
|
|
79
|
+
scopeId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
74
82
|
await syncTierChangedLog({
|
|
75
83
|
payload: args.payload,
|
|
76
84
|
req: args.req,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
export type ReleaseReviewGateResult = {
|
|
3
|
+
requestsApproved: number;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* When a definition stops requiring review, approve each pending request for that
|
|
7
|
+
* definition so existing grant / tier.changed hooks run for that user + scope only.
|
|
8
|
+
*/
|
|
9
|
+
export declare function releasePendingAchievementReviews(args: {
|
|
10
|
+
req: PayloadRequest;
|
|
11
|
+
achievementId: string;
|
|
12
|
+
}): Promise<ReleaseReviewGateResult>;
|
|
13
|
+
export declare function releasePendingTierReviews(args: {
|
|
14
|
+
req: PayloadRequest;
|
|
15
|
+
tierId: string;
|
|
16
|
+
}): Promise<ReleaseReviewGateResult>;
|
|
17
|
+
/** True when `requiresReview` flipped from required → not required. */
|
|
18
|
+
export declare function didDisableRequiresReview(previousDoc: {
|
|
19
|
+
requiresReview?: boolean | null;
|
|
20
|
+
} | null | undefined, doc: {
|
|
21
|
+
requiresReview?: boolean | null;
|
|
22
|
+
} | null | undefined): boolean;
|
|
23
|
+
//# sourceMappingURL=releaseReviewGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"releaseReviewGate.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/releaseReviewGate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAK7C,MAAM,MAAM,uBAAuB,GAAG;IACpC,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;;;GAGG;AACH,wBAAsB,gCAAgC,CAAC,IAAI,EAAE;IAC3D,GAAG,EAAE,cAAc,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgCnC;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA6BnC;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,CACtC,WAAW,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACnE,GAAG,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC1D,OAAO,CAET"}
|