@dappermountain/payload-plugin-achievements 0.2.15 → 0.3.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 +25 -10
- package/dist/collections/Metrics/index.d.ts.map +1 -1
- package/dist/collections/Metrics/index.js +5 -3
- package/dist/collections/fields/ruleFields.d.ts.map +1 -1
- package/dist/collections/fields/ruleFields.js +2 -48
- package/dist/collections/hooks/eachRuleLeaf.d.ts +5 -0
- package/dist/collections/hooks/eachRuleLeaf.d.ts.map +1 -1
- package/dist/collections/hooks/eachRuleLeaf.js +7 -1
- package/dist/collections/hooks/preventCatalogDelete.d.ts.map +1 -1
- package/dist/collections/hooks/preventCatalogDelete.js +1 -2
- package/dist/endpoints/me.d.ts +2 -1
- package/dist/endpoints/me.d.ts.map +1 -1
- package/dist/endpoints/me.js +7 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.d.ts.map +1 -1
- package/dist/seed/index.js +1 -1
- package/dist/services/achievement/buildUnlockRequirementLeaves.d.ts +48 -0
- package/dist/services/achievement/buildUnlockRequirementLeaves.d.ts.map +1 -0
- package/dist/services/achievement/buildUnlockRequirementLeaves.js +192 -0
- package/dist/services/achievement/builtInRules.d.ts.map +1 -1
- package/dist/services/achievement/builtInRules.js +0 -41
- package/dist/services/achievement/duration.d.ts +1 -0
- package/dist/services/achievement/duration.d.ts.map +1 -1
- package/dist/services/achievement/duration.js +8 -1
- package/dist/services/achievement/getUserProgress.d.ts +7 -0
- package/dist/services/achievement/getUserProgress.d.ts.map +1 -1
- package/dist/services/achievement/getUserProgress.js +11 -1
- package/dist/services/achievement/index.d.ts +5 -0
- package/dist/services/achievement/index.d.ts.map +1 -1
- package/dist/services/achievement/index.js +2 -0
- package/dist/services/achievement/loadUserProgressReviews.d.ts +23 -0
- package/dist/services/achievement/loadUserProgressReviews.d.ts.map +1 -0
- package/dist/services/achievement/loadUserProgressReviews.js +79 -0
- package/dist/services/achievement/validateRules.d.ts.map +1 -1
- package/dist/services/achievement/validateRules.js +0 -22
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
|
|
|
107
107
|
|
|
108
108
|
- Top-level empty list → everyone passes
|
|
109
109
|
- Nested groups → AND (“all of these”) or OR (“any of these”)
|
|
110
|
-
- Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count
|
|
110
|
+
- Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count`
|
|
111
111
|
- Custom leaves → register via `extensions.ruleTypes`
|
|
112
112
|
|
|
113
113
|
**Current tier** is computed by walking tiers in `rank` order and evaluating each tier’s unlock rules (`resolveCurrentTier`). Tiers with **`requiresReview`** only become current after an **approved tier request**. Turning **`requiresReview` off** approves pending tier requests and writes missing `tier.changed` logs for members who already meet unlock rules (including those who never had a pending request). **Next-tier fill** uses fractional progress on the same trees (`resolveTierProgress` / `evaluateRuleProgress`):
|
|
@@ -117,14 +117,18 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
|
|
|
117
117
|
| AND | Equal average of *requirement* children |
|
|
118
118
|
| OR | Best child (max) |
|
|
119
119
|
| `tier-at-least` | Gate — skipped in AND averages |
|
|
120
|
-
| `event-count` / `metric-minimum`
|
|
120
|
+
| `event-count` / `metric-minimum` | Fraction of the target |
|
|
121
121
|
| `achievement-complete` | `1` if granted; otherwise rolls up that achievement’s completion (or eligibility) rules |
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
**Computed elapsed metrics** — `kind: computed` + `compute: elapsed` + `unit` (`seconds` | `minutes` | `hours` | `days` | `years`) + `since` (built-in `user-created-at` / `first-event`, or a host `metricAnchors` key). Gate tenure with `metric-minimum` against that metric. Years use a fixed 365-day length (not calendar years). System seed only creates stored `points` — product computed metrics (e.g. `days`) are host-seeded after registering anchors.
|
|
124
124
|
|
|
125
|
-
**Stored
|
|
125
|
+
**Stored metrics** — totals live in `achievement-metric-balances`, updated by `recordMetricChange` (log + balance dual-write). Logs remain the audit/rebuild source; reconcile backfills balances.
|
|
126
126
|
|
|
127
|
-
**Leaderboards** — `getMetricLeaderboard` / `GET /api/achievements/leaderboard?metric=points` ranks stored balances (authenticated). Disable with `endpoints.leaderboard: false`.
|
|
127
|
+
**Leaderboards** — `getMetricLeaderboard` / `GET /api/achievements/leaderboard?metric=points` ranks **stored** metric balances (authenticated, optional `scope`). Computed/elapsed metrics (e.g. tenure days) have no balance rows and are rejected — resolve a single user with `resolveMetricValue`, or materialize snapshots if you need a ranked computed board. Disable with `endpoints.leaderboard: false`. Hosts compose member directories (names, current tier, earned counts, who may appear) themselves; the plugin returns `{ user, value, rank }` only.
|
|
128
|
+
|
|
129
|
+
**Unlock checklist** — `buildUnlockRequirementLeaves` walks a rule tree (including Payload leaves that carry empty `rules: []`), evaluates met/progress per leaf, and returns catalog subjects (`relations`, `target`, `unit`, `since`). It does **not** build UI copy — hosts format labels. `achievement-complete` is omitted by default so those stay on catalog groups. `eachRuleLeaf` is also exported if you need the walker alone.
|
|
130
|
+
|
|
131
|
+
**Review snapshot** — `getUserProgress({ include: { reviews: true } })` or `loadUserProgressReviews` adds pending achievement keys and open/rejected tier requests so hosts do not re-query the engine for badge state.
|
|
128
132
|
|
|
129
133
|
**System catalog** — by default the plugin runs `seedAchievementCatalog` on Payload `onInit` (opt out with `seedSystemCatalog: false`). Hosts still seed product catalog + locales via `seedAchievements`.
|
|
130
134
|
|
|
@@ -186,7 +190,7 @@ achievementPlugin({
|
|
|
186
190
|
| `canReview` | _(denied)_ | **Important.** Without this, users cannot manage catalogs or approve requests. Receives `(user, scopeId)` where `scopeId` may be `null`. |
|
|
187
191
|
| `scope` | _(none)_ | Multi-tenant hook-up. `collection` is the relation target (e.g. `'tenants'`). `relationField` defaults to `'scope'`. Adds an optional scope field across plugin collections. |
|
|
188
192
|
| `mediaCollection` | _(none)_ | When set (e.g. `'media'`), tiers get an optional upload field for ladder badge images. Icon string keys still work without this. |
|
|
189
|
-
| `extensions.metricAnchors` | _(none)_ | Named date resolvers for computed elapsed
|
|
193
|
+
| `extensions.metricAnchors` | _(none)_ | Named date resolvers for computed elapsed metrics. Prefer `{ label, resolve }` where `label` is a Payload Admin {@link OptionLabel} (string, `{ en, es }`, or `({ t }) => t('custom:…')`). Not CMS localization. |
|
|
190
194
|
|
|
191
195
|
### `collections`
|
|
192
196
|
|
|
@@ -230,11 +234,11 @@ achievementPlugin({
|
|
|
230
234
|
|
|
231
235
|
| Option | Default | Notes |
|
|
232
236
|
| --- | --- | --- |
|
|
233
|
-
| `me` | `'/achievements/me'` | Registers `GET /api/achievements/me` for the signed-in user’s grants snapshot (+ derived tiers and lean requests). Pass `false` to skip. Having review access does **not** widen this endpoint to other users. |
|
|
234
|
-
| `leaderboard` | `'/achievements/leaderboard'` | Registers `GET /api/achievements/leaderboard?metric=<slug>` for authenticated users (stored metrics only). Pass `false` to skip. |
|
|
237
|
+
| `me` | `'/achievements/me'` | Registers `GET /api/achievements/me` for the signed-in user’s grants snapshot (+ derived tiers and lean requests). Pass `false` to skip. Having review access does **not** widen this endpoint to other users. Optional `?reviews=1` adds `reviews`. |
|
|
238
|
+
| `leaderboard` | `'/achievements/leaderboard'` | Registers `GET /api/achievements/leaderboard?metric=<slug>` for authenticated users (**stored** metrics only). Pass `false` to skip. |
|
|
235
239
|
| `reconcile` | `'/achievements/reconcile'` | Registers `POST /api/achievements/reconcile` for reviewers. Pass `false` to skip. |
|
|
236
240
|
|
|
237
|
-
Query params (`me`): `scope`, `limit`, `page`, `requestsLimit`. Leaderboard: `metric` (required), `scope`, `limit`, `page`.
|
|
241
|
+
Query params (`me`): `scope`, `limit`, `page`, `requestsLimit`, `reviews`. Leaderboard: `metric` (required), `scope`, `limit`, `page`.
|
|
238
242
|
|
|
239
243
|
### `extensions.ruleTypes` / `metricAnchors`
|
|
240
244
|
|
|
@@ -285,14 +289,25 @@ import {
|
|
|
285
289
|
resolveCurrentTier,
|
|
286
290
|
resolveTierProgress,
|
|
287
291
|
getUserProgress,
|
|
292
|
+
loadUserProgressReviews,
|
|
293
|
+
buildUnlockRequirementLeaves,
|
|
288
294
|
submitAchievementRequest,
|
|
289
295
|
reviewAchievementRequest,
|
|
290
296
|
} from '@dappermountain/payload-plugin-achievements'
|
|
291
297
|
|
|
292
298
|
await recordLog({ payload, userId, scopeId, type: 'host.custom-kind', actorId })
|
|
293
299
|
await recordMetricChange({ payload, userId, scopeId, metric: 'points', change: 10 })
|
|
300
|
+
const points = await resolveMetricValue({ payload, userId, scopeId, metric: 'points' })
|
|
294
301
|
const board = await getMetricLeaderboard({ payload, metric: 'points', scopeId, limit: 20 })
|
|
295
302
|
const tier = await resolveCurrentTier({ payload, userId, scopeId })
|
|
303
|
+
const progress = await getUserProgress({ payload, userId, scopeId, include: { reviews: true } })
|
|
304
|
+
const unlockLeaves = await buildUnlockRequirementLeaves({
|
|
305
|
+
payload,
|
|
306
|
+
userId,
|
|
307
|
+
scopeId,
|
|
308
|
+
rules: nextTier.unlockRules,
|
|
309
|
+
ladderRank: tier?.rank ?? null,
|
|
310
|
+
})
|
|
296
311
|
|
|
297
312
|
// Repair users whose grants predate side-effect hooks (idempotent)
|
|
298
313
|
await reconcileProgression({ payload })
|
|
@@ -334,7 +349,7 @@ Turning **Requires review** off on a definition or tier **approves pending reque
|
|
|
334
349
|
|
|
335
350
|
| Goal | Request |
|
|
336
351
|
| --- | --- |
|
|
337
|
-
| Current user snapshot | `GET /api/achievements/me?scope=<scopeId>&limit=10&page=1` |
|
|
352
|
+
| Current user snapshot | `GET /api/achievements/me?scope=<scopeId>&limit=10&page=1` (`&reviews=1` for pending/rejected review keys) |
|
|
338
353
|
| Stored metric leaderboard | `GET /api/achievements/leaderboard?metric=points&scope=<scopeId>&limit=20&page=1` |
|
|
339
354
|
| Repair progression | `POST /api/achievements/reconcile` (reviewer; optional `{ userId, scopeId, achievementId\|achievementSlug, tierId\|tierSlug, limit }`) |
|
|
340
355
|
| List grants | `GET /api/achievement-grants?where[user][equals]=<userId>` |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAS/C,wBAAgB,sBAAsB,IAAI,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAS/C,wBAAgB,sBAAsB,IAAI,gBAAgB,CAkGzD"}
|
|
@@ -54,13 +54,15 @@ export function buildMetricsCollection() {
|
|
|
54
54
|
defaultValue: 'days',
|
|
55
55
|
enumName: 'ach_metric_unit',
|
|
56
56
|
options: [
|
|
57
|
-
{ label: '
|
|
58
|
-
{ label: 'Hours', value: 'hours' },
|
|
57
|
+
{ label: 'Seconds', value: 'seconds' },
|
|
59
58
|
{ label: 'Minutes', value: 'minutes' },
|
|
59
|
+
{ label: 'Hours', value: 'hours' },
|
|
60
|
+
{ label: 'Days', value: 'days' },
|
|
61
|
+
{ label: 'Years', value: 'years' },
|
|
60
62
|
],
|
|
61
63
|
admin: {
|
|
62
64
|
condition: (_, sibling) => sibling?.kind === 'computed' && sibling?.compute === 'elapsed',
|
|
63
|
-
description: 'Unit of the exposed number (
|
|
65
|
+
description: 'Unit of the exposed number (whole units since the anchor). Years use a fixed 365-day length.',
|
|
64
66
|
},
|
|
65
67
|
},
|
|
66
68
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ruleFields.d.ts","sourceRoot":"","sources":["../../../src/collections/fields/ruleFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ruleFields.d.ts","sourceRoot":"","sources":["../../../src/collections/fields/ruleFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAqGpC;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,EAAE,CAqB7C;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,IAAI,KAAK,EAAE,CAE5C"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { catalogFilterOptions } from './catalogFilter.js';
|
|
2
2
|
import { collectionOf } from '../helpers.js';
|
|
3
|
-
import { elapsedSinceSelectOptions } from '../../extensions/elapsedSinceOptions.js';
|
|
4
3
|
function combinatorField(visibleWhenGroup) {
|
|
5
4
|
return {
|
|
6
5
|
name: 'combinator',
|
|
@@ -26,7 +25,6 @@ function ruleTypeSelect(includeGroup) {
|
|
|
26
25
|
{ label: 'Achievement complete', value: 'achievement-complete' },
|
|
27
26
|
{ label: 'Metric minimum', value: 'metric-minimum' },
|
|
28
27
|
{ label: 'Event count', value: 'event-count' },
|
|
29
|
-
{ label: 'Elapsed since', value: 'elapsed-since' },
|
|
30
28
|
...(includeGroup ? [{ label: 'Group', value: 'group' }] : []),
|
|
31
29
|
],
|
|
32
30
|
};
|
|
@@ -77,9 +75,8 @@ function ruleParamFields() {
|
|
|
77
75
|
relationTo: collectionOf('eventTypes'),
|
|
78
76
|
filterOptions: catalogFilterOptions,
|
|
79
77
|
admin: {
|
|
80
|
-
condition: (_, sibling) => sibling?.type === 'event-count'
|
|
81
|
-
|
|
82
|
-
description: 'Event type to count, or (for elapsed since) the type whose first log starts the clock.',
|
|
78
|
+
condition: (_, sibling) => sibling?.type === 'event-count',
|
|
79
|
+
description: 'Event type to count.',
|
|
83
80
|
},
|
|
84
81
|
},
|
|
85
82
|
{
|
|
@@ -90,49 +87,6 @@ function ruleParamFields() {
|
|
|
90
87
|
description: 'How many times it needs to have happened.',
|
|
91
88
|
},
|
|
92
89
|
},
|
|
93
|
-
{
|
|
94
|
-
name: 'since',
|
|
95
|
-
type: 'select',
|
|
96
|
-
// Nested under eligibility/completion/unlock → rules → rules; keep PG enum ≤63 chars.
|
|
97
|
-
enumName: 'ach_elapsed_since',
|
|
98
|
-
defaultValue: 'user-created-at',
|
|
99
|
-
options: elapsedSinceSelectOptions(),
|
|
100
|
-
admin: {
|
|
101
|
-
condition: (_, sibling) => sibling?.type === 'elapsed-since',
|
|
102
|
-
description: 'Which timestamp starts the clock.',
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
type: 'row',
|
|
107
|
-
admin: {
|
|
108
|
-
condition: (_, sibling) => sibling?.type === 'elapsed-since',
|
|
109
|
-
},
|
|
110
|
-
fields: [
|
|
111
|
-
{
|
|
112
|
-
name: 'amount',
|
|
113
|
-
type: 'number',
|
|
114
|
-
admin: {
|
|
115
|
-
width: '50%',
|
|
116
|
-
description: 'How much time must pass.',
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
name: 'unit',
|
|
121
|
-
type: 'select',
|
|
122
|
-
enumName: 'ach_elapsed_unit',
|
|
123
|
-
defaultValue: 'days',
|
|
124
|
-
options: [
|
|
125
|
-
{ label: 'Days', value: 'days' },
|
|
126
|
-
{ label: 'Hours', value: 'hours' },
|
|
127
|
-
{ label: 'Minutes', value: 'minutes' },
|
|
128
|
-
],
|
|
129
|
-
admin: {
|
|
130
|
-
width: '50%',
|
|
131
|
-
description: 'Unit for the amount.',
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
90
|
];
|
|
137
91
|
}
|
|
138
92
|
function buildRuleLeafFields() {
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export type LeafVisitor = (rule: Record<string, unknown>) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Visit every leaf in a rule tree.
|
|
4
|
+
* Payload array rows often include empty `rules: []` and a default `combinator` even on
|
|
5
|
+
* leaves — those must still be visited (do not treat them as groups).
|
|
6
|
+
*/
|
|
2
7
|
export declare function eachRuleLeaf(node: unknown, visit: LeafVisitor): void;
|
|
3
8
|
//# sourceMappingURL=eachRuleLeaf.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eachRuleLeaf.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/eachRuleLeaf.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;AAEjE,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"eachRuleLeaf.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/eachRuleLeaf.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;AAEjE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAiBpE"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visit every leaf in a rule tree.
|
|
3
|
+
* Payload array rows often include empty `rules: []` and a default `combinator` even on
|
|
4
|
+
* leaves — those must still be visited (do not treat them as groups).
|
|
5
|
+
*/
|
|
1
6
|
export function eachRuleLeaf(node, visit) {
|
|
2
7
|
if (Array.isArray(node)) {
|
|
3
8
|
for (const item of node)
|
|
@@ -11,7 +16,8 @@ export function eachRuleLeaf(node, visit) {
|
|
|
11
16
|
eachRuleLeaf(rec.rules, visit);
|
|
12
17
|
return;
|
|
13
18
|
}
|
|
14
|
-
|
|
19
|
+
// Top-level `{ combinator, rules }` with no leaf type
|
|
20
|
+
if (!rec.type && Array.isArray(rec.rules)) {
|
|
15
21
|
eachRuleLeaf(rec.rules, visit);
|
|
16
22
|
return;
|
|
17
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preventCatalogDelete.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/preventCatalogDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AA6CzD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAA;CAC7B,GAAG,0BAA0B,
|
|
1
|
+
{"version":3,"file":"preventCatalogDelete.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/preventCatalogDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AA6CzD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAA;CAC7B,GAAG,0BAA0B,CA8D7B"}
|
|
@@ -61,8 +61,7 @@ export function preventCatalogDelete(args) {
|
|
|
61
61
|
parts.push(`${logs.totalDocs} log row(s)`);
|
|
62
62
|
const rules = await countMatchingLeaves({
|
|
63
63
|
req,
|
|
64
|
-
match: (rule) =>
|
|
65
|
-
relationId(rule.eventType) === id,
|
|
64
|
+
match: (rule) => rule.type === 'event-count' && relationId(rule.eventType) === id,
|
|
66
65
|
});
|
|
67
66
|
if (rules > 0)
|
|
68
67
|
parts.push(`${rules} rule(s)`);
|
package/dist/endpoints/me.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload';
|
|
2
2
|
/**
|
|
3
3
|
* `GET` current user’s snapshot. Body is Payload `find`-shaped for **grants**,
|
|
4
|
-
* plus ladder-derived `tiers` and lean `requests`. Optional `?scope=` / `limit` / `page
|
|
4
|
+
* plus ladder-derived `tiers` and lean `requests`. Optional `?scope=` / `limit` / `page`
|
|
5
|
+
* / `reviews=1` (pending achievement keys + open/rejected tier requests).
|
|
5
6
|
*/
|
|
6
7
|
export declare function buildMeEndpoint(path: string): Endpoint;
|
|
7
8
|
//# sourceMappingURL=me.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../src/endpoints/me.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../src/endpoints/me.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AAoCvD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAsBtD"}
|
package/dist/endpoints/me.js
CHANGED
|
@@ -24,9 +24,14 @@ function pagingFromRequest(req) {
|
|
|
24
24
|
requestsLimit: positiveInt(params?.get('requestsLimit') ?? null, 20),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
function reviewsFromRequest(req) {
|
|
28
|
+
const raw = requestUrl(req)?.searchParams.get('reviews');
|
|
29
|
+
return raw === '1' || raw === 'true';
|
|
30
|
+
}
|
|
27
31
|
/**
|
|
28
32
|
* `GET` current user’s snapshot. Body is Payload `find`-shaped for **grants**,
|
|
29
|
-
* plus ladder-derived `tiers` and lean `requests`. Optional `?scope=` / `limit` / `page
|
|
33
|
+
* plus ladder-derived `tiers` and lean `requests`. Optional `?scope=` / `limit` / `page`
|
|
34
|
+
* / `reviews=1` (pending achievement keys + open/rejected tier requests).
|
|
30
35
|
*/
|
|
31
36
|
export function buildMeEndpoint(path) {
|
|
32
37
|
return {
|
|
@@ -43,6 +48,7 @@ export function buildMeEndpoint(path) {
|
|
|
43
48
|
userId,
|
|
44
49
|
scopeId: scopeFromRequest(req),
|
|
45
50
|
paging: pagingFromRequest(req),
|
|
51
|
+
include: reviewsFromRequest(req) ? { reviews: true } : undefined,
|
|
46
52
|
});
|
|
47
53
|
return Response.json(body);
|
|
48
54
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,13 @@ export type { EngineEventTypeSlug, EngineMetricSlug } from './catalog.js';
|
|
|
3
3
|
export { achievementPlugin } from './plugin.js';
|
|
4
4
|
export { seedAchievementCatalog, seedAchievements } from './seed/index.js';
|
|
5
5
|
export type { AchievementSeedAchievement, AchievementSeedCatalogRow, AchievementSeedInput, AchievementSeedLocalizedText, AchievementSeedTier, } from './seed/index.js';
|
|
6
|
-
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, getUserProgress, getMetricLeaderboard, grantAchievement, loadMetricDoc, recordEvent, recordLog, recordMetricChange, rebuildMetricBalance, rebuildMetricBalancesForUser, reconcileProgression, reconcileUserProgression, releasePendingAchievementReviews, releasePendingTierReviews, resolveCurrentTier, resolveElapsedAnchor, resolveMetricValue, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, catalogRef, transitionLogData, transitionToId, validateRuleGroup, validateRuleNode, } from './services/achievement/index.js';
|
|
7
|
-
export type { ResolvedTier, RuleGroup, TierLadderProgress, CatalogProgressGroup, CatalogProgressItem, CatalogRef, TransitionLogData, MetricLeaderboardResult, MetricLeaderboardRow, ReconcileProgressionArgs, ReconcileProgressionResult, ReleaseReviewGateResult, } from './services/achievement/index.js';
|
|
6
|
+
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, buildUnlockRequirementLeaves, getUserProgress, loadUserProgressReviews, getMetricLeaderboard, grantAchievement, loadMetricDoc, recordEvent, recordLog, recordMetricChange, rebuildMetricBalance, rebuildMetricBalancesForUser, reconcileProgression, reconcileUserProgression, releasePendingAchievementReviews, releasePendingTierReviews, resolveCurrentTier, resolveElapsedAnchor, resolveMetricValue, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, catalogRef, transitionLogData, transitionToId, validateRuleGroup, validateRuleNode, } from './services/achievement/index.js';
|
|
7
|
+
export type { ResolvedTier, RuleGroup, TierLadderProgress, CatalogProgressGroup, CatalogProgressItem, UnlockRequirementLeaf, UnlockRequirementRelation, GetUserProgressInclude, UserProgressMeResponse, UserProgressReviews, UserProgressTierRequest, CatalogRef, TransitionLogData, MetricLeaderboardResult, MetricLeaderboardRow, ReconcileProgressionArgs, ReconcileProgressionResult, ReleaseReviewGateResult, } from './services/achievement/index.js';
|
|
8
8
|
export type { BuiltInEventType, BuiltInRuleType, AchievementCollectionKey, AchievementCollectionOverride, AchievementCollectionsOptions, AchievementEndpointsOptions, AchievementMetricAnchor, AchievementMetricAnchorConfig, AchievementMetricAnchorEntry, AchievementPluginOptions, AchievementRuleType, AchievementScopeConfig, AchievementUsersOptions, } from './types.js';
|
|
9
9
|
export { DEFAULT_COLLECTION_BASE_SLUGS, DEFAULT_COLLECTION_PREFIX, DEFAULT_ADMIN_GROUP } from './collections/slugs.js';
|
|
10
10
|
export { DEFAULT_ME_ENDPOINT_PATH, DEFAULT_LEADERBOARD_ENDPOINT_PATH, DEFAULT_RECONCILE_ENDPOINT_PATH, } from './defaults.js';
|
|
11
11
|
export { collectionOf, slugOf } from './collections/helpers.js';
|
|
12
|
+
export { eachRuleLeaf } from './collections/hooks/eachRuleLeaf.js';
|
|
12
13
|
export { isAchievementPluginEnabled } from './enabled.js';
|
|
13
14
|
export { descriptionToPlaintext } from './fields/descriptionPlaintext.js';
|
|
14
15
|
export { plainTextToLexical } from './fields/plainTextToLexical.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,+BAA+B,EAC/B,kBAAkB,EAClB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACV,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,UAAU,EACV,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,wBAAwB,EACxB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,EAC5B,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AACtH,OAAO,EACL,wBAAwB,EACxB,iCAAiC,EACjC,+BAA+B,GAChC,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,+BAA+B,EAC/B,kBAAkB,EAClB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,EAC5B,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACV,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,UAAU,EACV,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,wBAAwB,EACxB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,EAC5B,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AACtH,OAAO,EACL,wBAAwB,EACxB,iCAAiC,EACjC,+BAA+B,GAChC,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { DEFAULT_ACHIEVEMENT_METRIC, DEFAULT_ACHIEVEMENT_METRIC_SLUG, ENGINE_EVENT_TYPES, ENGINE_METRICS, } from './catalog.js';
|
|
2
2
|
export { achievementPlugin } from './plugin.js';
|
|
3
3
|
export { seedAchievementCatalog, seedAchievements } from './seed/index.js';
|
|
4
|
-
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, getUserProgress, getMetricLeaderboard, grantAchievement, loadMetricDoc, recordEvent, recordLog, recordMetricChange, rebuildMetricBalance, rebuildMetricBalancesForUser, reconcileProgression, reconcileUserProgression, releasePendingAchievementReviews, releasePendingTierReviews, resolveCurrentTier, resolveElapsedAnchor, resolveMetricValue, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, catalogRef, transitionLogData, transitionToId, validateRuleGroup, validateRuleNode, } from './services/achievement/index.js';
|
|
4
|
+
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, buildUnlockRequirementLeaves, getUserProgress, loadUserProgressReviews, getMetricLeaderboard, grantAchievement, loadMetricDoc, recordEvent, recordLog, recordMetricChange, rebuildMetricBalance, rebuildMetricBalancesForUser, reconcileProgression, reconcileUserProgression, releasePendingAchievementReviews, releasePendingTierReviews, resolveCurrentTier, resolveElapsedAnchor, resolveMetricValue, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, catalogRef, transitionLogData, transitionToId, validateRuleGroup, validateRuleNode, } from './services/achievement/index.js';
|
|
5
5
|
export { DEFAULT_COLLECTION_BASE_SLUGS, DEFAULT_COLLECTION_PREFIX, DEFAULT_ADMIN_GROUP } from './collections/slugs.js';
|
|
6
6
|
export { DEFAULT_ME_ENDPOINT_PATH, DEFAULT_LEADERBOARD_ENDPOINT_PATH, DEFAULT_RECONCILE_ENDPOINT_PATH, } from './defaults.js';
|
|
7
7
|
export { collectionOf, slugOf } from './collections/helpers.js';
|
|
8
|
+
export { eachRuleLeaf } from './collections/hooks/eachRuleLeaf.js';
|
|
8
9
|
export { isAchievementPluginEnabled } from './enabled.js';
|
|
9
10
|
export { descriptionToPlaintext } from './fields/descriptionPlaintext.js';
|
|
10
11
|
export { plainTextToLexical } from './fields/plainTextToLexical.js';
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type AchievementSeedCatalogRow = {
|
|
|
48
48
|
/** Computed metrics — v1 only `elapsed`. */
|
|
49
49
|
compute?: 'elapsed';
|
|
50
50
|
/** Computed elapsed — unit of the exposed number. */
|
|
51
|
-
unit?: '
|
|
51
|
+
unit?: 'seconds' | 'minutes' | 'hours' | 'days' | 'years';
|
|
52
52
|
/** Computed elapsed — built-in or host `metricAnchors` key. */
|
|
53
53
|
since?: string;
|
|
54
54
|
/** Computed elapsed with `since: first-event` — event type slug. */
|
package/dist/seed/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seed/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAOtC,mGAAmG;AACnG,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AAExG,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,4BAA4B,CAAA;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,4BAA4B,CAAA;IAC1C,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qFAAqF;IACrF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;KAAE,CAAA;CACrG,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,4BAA4B,CAAA;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,4BAA4B,CAAA;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;KAAE,CAAA;IACzG,qGAAqG;IACrG,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;KAAE,CAAA;CACzG,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,4BAA4B,CAAA;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kDAAkD;IAClD,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC5B,4CAA4C;IAC5C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,qDAAqD;IACrD,IAAI,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seed/index.ts"],"names":[],"mappings":"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"}
|
package/dist/seed/index.js
CHANGED
|
@@ -136,7 +136,7 @@ async function resolveRuleNode(payload, rule) {
|
|
|
136
136
|
delete next.achievementSlug;
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
if (next.type === 'event-count'
|
|
139
|
+
if (next.type === 'event-count') {
|
|
140
140
|
const slug = typeof next.eventTypeSlug === 'string'
|
|
141
141
|
? next.eventTypeSlug
|
|
142
142
|
: typeof next.eventType === 'string'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
export declare const UNLOCK_REQUIREMENT_RELATION_FIELDS: {
|
|
3
|
+
readonly tier: "tiers";
|
|
4
|
+
readonly achievement: "achievements";
|
|
5
|
+
readonly metric: "metrics";
|
|
6
|
+
readonly eventType: "eventTypes";
|
|
7
|
+
};
|
|
8
|
+
export type UnlockRequirementCollectionKey = (typeof UNLOCK_REQUIREMENT_RELATION_FIELDS)[keyof typeof UNLOCK_REQUIREMENT_RELATION_FIELDS];
|
|
9
|
+
export type UnlockRequirementRelation = {
|
|
10
|
+
field: keyof typeof UNLOCK_REQUIREMENT_RELATION_FIELDS;
|
|
11
|
+
collectionKey: UnlockRequirementCollectionKey;
|
|
12
|
+
id?: string;
|
|
13
|
+
slug?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Structured unlock/eligibility leaf — catalog names only, no host UI copy.
|
|
18
|
+
* Hosts format `label` / `detail` from these fields.
|
|
19
|
+
*/
|
|
20
|
+
export type UnlockRequirementLeaf = {
|
|
21
|
+
id: string;
|
|
22
|
+
type: string;
|
|
23
|
+
met: boolean;
|
|
24
|
+
/** 0..1 from {@link evaluateRuleProgress}. */
|
|
25
|
+
progress: number;
|
|
26
|
+
relations: UnlockRequirementRelation[];
|
|
27
|
+
target?: number;
|
|
28
|
+
unit?: string;
|
|
29
|
+
since?: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Walk every leaf in a rule tree, evaluate met/progress, and return catalog-backed
|
|
33
|
+
* subjects. Payload rows often carry empty `rules: []` + default combinator — those
|
|
34
|
+
* are still leaves ({@link eachRuleLeaf}).
|
|
35
|
+
*
|
|
36
|
+
* `achievement-complete` is omitted by default so hosts can keep those on catalog groups.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildUnlockRequirementLeaves(args: {
|
|
39
|
+
payload: Payload;
|
|
40
|
+
req?: PayloadRequest;
|
|
41
|
+
userId: string;
|
|
42
|
+
scopeId: string | null;
|
|
43
|
+
rules: unknown;
|
|
44
|
+
ladderRank?: number | null;
|
|
45
|
+
locale?: string;
|
|
46
|
+
includeAchievementComplete?: boolean;
|
|
47
|
+
}): Promise<UnlockRequirementLeaf[]>;
|
|
48
|
+
//# sourceMappingURL=buildUnlockRequirementLeaves.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildUnlockRequirementLeaves.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/buildUnlockRequirementLeaves.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAOtD,eAAO,MAAM,kCAAkC;;;;;CAKyC,CAAA;AAExF,MAAM,MAAM,8BAA8B,GACxC,CAAC,OAAO,kCAAkC,CAAC,CAAC,MAAM,OAAO,kCAAkC,CAAC,CAAA;AAE9F,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,OAAO,kCAAkC,CAAA;IACtD,aAAa,EAAE,8BAA8B,CAAA;IAC7C,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,OAAO,CAAA;IACZ,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,yBAAyB,EAAE,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAqHD;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE;IACvD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0BAA0B,CAAC,EAAE,OAAO,CAAA;CACrC,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CA8EnC"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { eachRuleLeaf } from '../../collections/hooks/eachRuleLeaf.js';
|
|
2
|
+
import { collectionOf } from '../../collections/helpers.js';
|
|
3
|
+
import { evaluateRuleProgress, evaluateRules } from './evaluateRules.js';
|
|
4
|
+
import { relationId } from './relationId.js';
|
|
5
|
+
export const UNLOCK_REQUIREMENT_RELATION_FIELDS = {
|
|
6
|
+
tier: 'tiers',
|
|
7
|
+
achievement: 'achievements',
|
|
8
|
+
metric: 'metrics',
|
|
9
|
+
eventType: 'eventTypes',
|
|
10
|
+
};
|
|
11
|
+
function relationName(value) {
|
|
12
|
+
if (value && typeof value === 'object' && 'name' in value) {
|
|
13
|
+
const name = value.name;
|
|
14
|
+
if (typeof name === 'string' && name)
|
|
15
|
+
return name;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function relationSlug(value) {
|
|
20
|
+
if (value && typeof value === 'object' && 'slug' in value) {
|
|
21
|
+
const slug = value.slug;
|
|
22
|
+
if (typeof slug === 'string' && slug)
|
|
23
|
+
return slug;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function slugField(rule, relationKey) {
|
|
28
|
+
const raw = rule[`${relationKey}Slug`];
|
|
29
|
+
return typeof raw === 'string' && raw ? raw : null;
|
|
30
|
+
}
|
|
31
|
+
function numericTarget(rule) {
|
|
32
|
+
for (const key of ['amount', 'minimum', 'count']) {
|
|
33
|
+
const value = rule[key];
|
|
34
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0)
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
async function resolveCatalogName(args) {
|
|
40
|
+
const named = relationName(args.value);
|
|
41
|
+
const slug = args.slug || relationSlug(args.value);
|
|
42
|
+
const id = relationId(args.value);
|
|
43
|
+
if (named) {
|
|
44
|
+
return {
|
|
45
|
+
...(id ? { id } : {}),
|
|
46
|
+
...(slug ? { slug } : {}),
|
|
47
|
+
name: named,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (slug) {
|
|
51
|
+
const cached = args.cache.get(`slug:${args.collectionKey}:${slug}`);
|
|
52
|
+
if (cached)
|
|
53
|
+
return { ...(id ? { id } : {}), slug, name: cached };
|
|
54
|
+
}
|
|
55
|
+
if (id) {
|
|
56
|
+
const cached = args.cache.get(`id:${args.collectionKey}:${id}`);
|
|
57
|
+
if (cached) {
|
|
58
|
+
return { id, ...(slug ? { slug } : {}), name: cached };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const doc = (await args.payload.findByID({
|
|
62
|
+
collection: collectionOf(args.collectionKey),
|
|
63
|
+
id,
|
|
64
|
+
depth: 0,
|
|
65
|
+
overrideAccess: true,
|
|
66
|
+
req: args.req,
|
|
67
|
+
...(args.locale ? { locale: args.locale } : {}),
|
|
68
|
+
}));
|
|
69
|
+
const name = (typeof doc?.name === 'string' && doc.name) ||
|
|
70
|
+
(typeof doc?.slug === 'string' && doc.slug) ||
|
|
71
|
+
null;
|
|
72
|
+
if (name) {
|
|
73
|
+
args.cache.set(`id:${args.collectionKey}:${id}`, name);
|
|
74
|
+
if (doc?.slug)
|
|
75
|
+
args.cache.set(`slug:${args.collectionKey}:${doc.slug}`, name);
|
|
76
|
+
return { id, ...(doc?.slug ? { slug: doc.slug } : slug ? { slug } : {}), name };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* fall through */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (slug && !id) {
|
|
84
|
+
try {
|
|
85
|
+
const found = await args.payload.find({
|
|
86
|
+
collection: collectionOf(args.collectionKey),
|
|
87
|
+
depth: 0,
|
|
88
|
+
limit: 1,
|
|
89
|
+
overrideAccess: true,
|
|
90
|
+
req: args.req,
|
|
91
|
+
where: { slug: { equals: slug } },
|
|
92
|
+
...(args.locale ? { locale: args.locale } : {}),
|
|
93
|
+
});
|
|
94
|
+
const doc = found.docs[0];
|
|
95
|
+
const resolvedId = relationId(doc);
|
|
96
|
+
const name = (typeof doc?.name === 'string' && doc.name) ||
|
|
97
|
+
(typeof doc?.slug === 'string' && doc.slug) ||
|
|
98
|
+
slug;
|
|
99
|
+
if (resolvedId)
|
|
100
|
+
args.cache.set(`id:${args.collectionKey}:${resolvedId}`, name);
|
|
101
|
+
args.cache.set(`slug:${args.collectionKey}:${slug}`, name);
|
|
102
|
+
return { ...(resolvedId ? { id: resolvedId } : {}), slug, name };
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* fall through */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...(id ? { id } : {}),
|
|
110
|
+
...(slug ? { slug } : {}),
|
|
111
|
+
...(slug || id ? { name: slug || id || undefined } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Walk every leaf in a rule tree, evaluate met/progress, and return catalog-backed
|
|
116
|
+
* subjects. Payload rows often carry empty `rules: []` + default combinator — those
|
|
117
|
+
* are still leaves ({@link eachRuleLeaf}).
|
|
118
|
+
*
|
|
119
|
+
* `achievement-complete` is omitted by default so hosts can keep those on catalog groups.
|
|
120
|
+
*/
|
|
121
|
+
export async function buildUnlockRequirementLeaves(args) {
|
|
122
|
+
const req = args.req ?? { payload: args.payload };
|
|
123
|
+
const cache = new Map();
|
|
124
|
+
const leaves = [];
|
|
125
|
+
eachRuleLeaf(args.rules, (rule) => {
|
|
126
|
+
const type = String(rule.type ?? '');
|
|
127
|
+
if (!type)
|
|
128
|
+
return;
|
|
129
|
+
if (type === 'achievement-complete' && !args.includeAchievementComplete)
|
|
130
|
+
return;
|
|
131
|
+
leaves.push(rule);
|
|
132
|
+
});
|
|
133
|
+
const out = [];
|
|
134
|
+
for (let index = 0; index < leaves.length; index += 1) {
|
|
135
|
+
const rule = leaves[index];
|
|
136
|
+
const type = String(rule.type);
|
|
137
|
+
const leafRules = [rule];
|
|
138
|
+
const met = await evaluateRules({
|
|
139
|
+
payload: args.payload,
|
|
140
|
+
req,
|
|
141
|
+
rules: leafRules,
|
|
142
|
+
userId: args.userId,
|
|
143
|
+
scopeId: args.scopeId,
|
|
144
|
+
ladderRank: args.ladderRank,
|
|
145
|
+
});
|
|
146
|
+
const progress = await evaluateRuleProgress({
|
|
147
|
+
payload: args.payload,
|
|
148
|
+
req,
|
|
149
|
+
rules: leafRules,
|
|
150
|
+
userId: args.userId,
|
|
151
|
+
scopeId: args.scopeId,
|
|
152
|
+
ladderRank: args.ladderRank,
|
|
153
|
+
});
|
|
154
|
+
const relations = [];
|
|
155
|
+
for (const [field, collectionKey] of Object.entries(UNLOCK_REQUIREMENT_RELATION_FIELDS)) {
|
|
156
|
+
const key = field;
|
|
157
|
+
if (rule[key] == null && !slugField(rule, key))
|
|
158
|
+
continue;
|
|
159
|
+
const resolved = await resolveCatalogName({
|
|
160
|
+
payload: args.payload,
|
|
161
|
+
req,
|
|
162
|
+
locale: args.locale,
|
|
163
|
+
collectionKey,
|
|
164
|
+
value: rule[key],
|
|
165
|
+
slug: slugField(rule, key),
|
|
166
|
+
cache,
|
|
167
|
+
});
|
|
168
|
+
if (!resolved.id && !resolved.slug && !resolved.name)
|
|
169
|
+
continue;
|
|
170
|
+
relations.push({ field: key, collectionKey, ...resolved });
|
|
171
|
+
}
|
|
172
|
+
const target = numericTarget(rule);
|
|
173
|
+
const unit = typeof rule.unit === 'string' && rule.unit ? rule.unit : undefined;
|
|
174
|
+
// `since` / `unit` only matter when a duration/count target is set (defaults pollute other leaves).
|
|
175
|
+
const since = target != null && typeof rule.since === 'string' && rule.since ? rule.since : undefined;
|
|
176
|
+
const subjectKey = relations
|
|
177
|
+
.map((row) => row.name || row.slug || row.id)
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.join(':') || 'leaf';
|
|
180
|
+
out.push({
|
|
181
|
+
id: `${type}:${index}:${subjectKey}`,
|
|
182
|
+
type,
|
|
183
|
+
met,
|
|
184
|
+
progress,
|
|
185
|
+
relations,
|
|
186
|
+
...(target != null ? { target } : {}),
|
|
187
|
+
...(unit ? { unit } : {}),
|
|
188
|
+
...(since ? { since } : {}),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtInRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/builtInRules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"builtInRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/builtInRules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAqJlF,eAAO,MAAM,gBAAgB,EAAE,mBAAmB,EA6JjD,CAAA"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { collectionOf } from '../../collections/helpers.js';
|
|
2
|
-
import { durationMs } from './duration.js';
|
|
3
2
|
import { relationId } from './relationId.js';
|
|
4
|
-
import { resolveElapsedAnchor } from './resolveElapsedAnchor.js';
|
|
5
3
|
import { resolveMetricValue } from './resolveMetricValue.js';
|
|
6
4
|
import { userScopeIncludingUnscopedWhere } from './where.js';
|
|
7
5
|
function clamp01(value) {
|
|
@@ -276,43 +274,4 @@ export const builtInRuleTypes = [
|
|
|
276
274
|
return clamp01(result.totalDocs / needed);
|
|
277
275
|
},
|
|
278
276
|
},
|
|
279
|
-
{
|
|
280
|
-
type: 'elapsed-since',
|
|
281
|
-
async evaluate({ payload, req, rule, userId, scopeId }) {
|
|
282
|
-
const needed = durationMs(Number(rule.amount ?? NaN), rule.unit);
|
|
283
|
-
if (needed == null)
|
|
284
|
-
return false;
|
|
285
|
-
const anchor = await resolveElapsedAnchor({
|
|
286
|
-
payload,
|
|
287
|
-
req,
|
|
288
|
-
since: rule.since,
|
|
289
|
-
eventType: rule.eventType,
|
|
290
|
-
eventTypeSlug: rule.eventTypeSlug,
|
|
291
|
-
userId,
|
|
292
|
-
scopeId,
|
|
293
|
-
});
|
|
294
|
-
if (!anchor)
|
|
295
|
-
return false;
|
|
296
|
-
return Date.now() - anchor.getTime() >= needed;
|
|
297
|
-
},
|
|
298
|
-
async progress({ payload, req, rule, userId, scopeId }) {
|
|
299
|
-
const needed = durationMs(Number(rule.amount ?? NaN), rule.unit);
|
|
300
|
-
if (needed == null)
|
|
301
|
-
return 0;
|
|
302
|
-
if (!(needed > 0))
|
|
303
|
-
return 1;
|
|
304
|
-
const anchor = await resolveElapsedAnchor({
|
|
305
|
-
payload,
|
|
306
|
-
req,
|
|
307
|
-
since: rule.since,
|
|
308
|
-
eventType: rule.eventType,
|
|
309
|
-
eventTypeSlug: rule.eventTypeSlug,
|
|
310
|
-
userId,
|
|
311
|
-
scopeId,
|
|
312
|
-
});
|
|
313
|
-
if (!anchor)
|
|
314
|
-
return 0;
|
|
315
|
-
return clamp01((Date.now() - anchor.getTime()) / needed);
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
277
|
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type ElapsedUnit = 'seconds' | 'minutes' | 'hours' | 'days' | 'years';
|
|
1
2
|
export declare function durationMs(amount: number, unit: unknown): number | null;
|
|
2
3
|
export declare function unitMs(unit: unknown): number | null;
|
|
3
4
|
export declare function parseDate(value: unknown): Date | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/duration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/duration.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;AAE5E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmBvE;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAOrD;AAED,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAM/F"}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
const
|
|
1
|
+
const MS_PER_SECOND = 1_000;
|
|
2
|
+
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
2
3
|
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
3
4
|
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
5
|
+
/** Fixed-length year for elapsed floors (not calendar years). */
|
|
6
|
+
const MS_PER_YEAR = 365 * MS_PER_DAY;
|
|
4
7
|
export function durationMs(amount, unit) {
|
|
5
8
|
if (!(typeof amount === 'number') || !Number.isFinite(amount) || amount < 0)
|
|
6
9
|
return null;
|
|
7
10
|
switch (unit) {
|
|
11
|
+
case 'seconds':
|
|
12
|
+
return amount * MS_PER_SECOND;
|
|
8
13
|
case 'minutes':
|
|
9
14
|
return amount * MS_PER_MINUTE;
|
|
10
15
|
case 'hours':
|
|
11
16
|
return amount * MS_PER_HOUR;
|
|
17
|
+
case 'years':
|
|
18
|
+
return amount * MS_PER_YEAR;
|
|
12
19
|
case 'days':
|
|
13
20
|
case undefined:
|
|
14
21
|
case null:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PaginatedDocs, Payload, PayloadRequest } from 'payload';
|
|
2
|
+
import { type UserProgressReviews } from './loadUserProgressReviews.js';
|
|
2
3
|
/** Catalog slice — `description` matches the collection (Lexical JSON, or legacy string). */
|
|
3
4
|
type LeanCatalog = {
|
|
4
5
|
id: string;
|
|
@@ -32,10 +33,15 @@ declare function leanRequest(doc: Record<string, unknown>): {
|
|
|
32
33
|
updatedAt: {} | null;
|
|
33
34
|
createdAt: {} | null;
|
|
34
35
|
};
|
|
36
|
+
export type GetUserProgressInclude = {
|
|
37
|
+
/** Pending achievement keys + open/rejected tier requests. */
|
|
38
|
+
reviews?: boolean;
|
|
39
|
+
};
|
|
35
40
|
export type UserProgressMeResponse = PaginatedDocs<ReturnType<typeof leanGrant>> & {
|
|
36
41
|
/** Ladder-derived current tier(s). */
|
|
37
42
|
tiers: LeanTier[];
|
|
38
43
|
requests: ReturnType<typeof leanRequest>[];
|
|
44
|
+
reviews?: UserProgressReviews;
|
|
39
45
|
};
|
|
40
46
|
/**
|
|
41
47
|
* Current-user snapshot. Top-level Payload `find` shape for **grants**;
|
|
@@ -49,6 +55,7 @@ export declare function getUserProgress(args: {
|
|
|
49
55
|
paging?: GetUserProgressPaging;
|
|
50
56
|
/** Content locale for localized achievement / tier names (falls back to `req.locale`). */
|
|
51
57
|
locale?: string;
|
|
58
|
+
include?: GetUserProgressInclude;
|
|
52
59
|
}): Promise<UserProgressMeResponse>;
|
|
53
60
|
export {};
|
|
54
61
|
//# sourceMappingURL=getUserProgress.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getUserProgress.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/getUserProgress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"getUserProgress.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/getUserProgress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAGrE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAKhG,6FAA6F;AAC7F,KAAK,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AACtF,KAAK,QAAQ,GAAG,WAAW,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAExE,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAmBD,iBAAS,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;EAU9C;AAED,iBAAS,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;EAShD;AAoBD,MAAM,MAAM,sBAAsB,GAAG;IACnC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,aAAa,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,GAAG;IACjF,sCAAsC;IACtC,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,QAAQ,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,EAAE,CAAA;IAC1C,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAC9B,CAAA;AAkED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,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,qBAAqB,CAAA;IAC9B,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA0DlC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { collectionOf } from '../../collections/helpers.js';
|
|
2
|
+
import { loadUserProgressReviews } from './loadUserProgressReviews.js';
|
|
2
3
|
import { relationId } from './relationId.js';
|
|
3
4
|
import { resolveCurrentTier } from './resolveCurrentTier.js';
|
|
4
5
|
import { userScopeWhere } from './where.js';
|
|
@@ -125,7 +126,7 @@ export async function getUserProgress(args) {
|
|
|
125
126
|
const page = args.paging?.page ?? 1;
|
|
126
127
|
const requestsLimit = args.paging?.requestsLimit ?? 20;
|
|
127
128
|
const locale = args.locale ?? (args.req?.locale || undefined);
|
|
128
|
-
const [grants, requests, tiers] = await Promise.all([
|
|
129
|
+
const [grants, requests, tiers, reviews] = await Promise.all([
|
|
129
130
|
args.payload.find({
|
|
130
131
|
collection: collectionOf('grants'),
|
|
131
132
|
depth: 1,
|
|
@@ -155,10 +156,19 @@ export async function getUserProgress(args) {
|
|
|
155
156
|
scopeId,
|
|
156
157
|
locale,
|
|
157
158
|
}),
|
|
159
|
+
args.include?.reviews
|
|
160
|
+
? loadUserProgressReviews({
|
|
161
|
+
payload: args.payload,
|
|
162
|
+
req: args.req,
|
|
163
|
+
userId: args.userId,
|
|
164
|
+
scopeId,
|
|
165
|
+
})
|
|
166
|
+
: Promise.resolve(undefined),
|
|
158
167
|
]);
|
|
159
168
|
return {
|
|
160
169
|
...mapPaginatedDocs(grants, (doc) => leanGrant(doc)),
|
|
161
170
|
tiers,
|
|
162
171
|
requests: requests.docs.map((doc) => leanRequest(doc)),
|
|
172
|
+
...(reviews ? { reviews } : {}),
|
|
163
173
|
};
|
|
164
174
|
}
|
|
@@ -2,7 +2,12 @@ export { evaluateRules, evaluateRuleProgress, normalizeRuleGroup, collectAchieve
|
|
|
2
2
|
export type { RuleGroup, AchievementCompleteRef } from './evaluateRules.js';
|
|
3
3
|
export { buildCatalogProgressGroups } from './buildCatalogProgressGroups.js';
|
|
4
4
|
export type { CatalogProgressGroup, CatalogProgressItem } from './buildCatalogProgressGroups.js';
|
|
5
|
+
export { buildUnlockRequirementLeaves } from './buildUnlockRequirementLeaves.js';
|
|
6
|
+
export type { UnlockRequirementCollectionKey, UnlockRequirementLeaf, UnlockRequirementRelation, } from './buildUnlockRequirementLeaves.js';
|
|
5
7
|
export { getUserProgress } from './getUserProgress.js';
|
|
8
|
+
export type { GetUserProgressInclude, GetUserProgressPaging, UserProgressMeResponse } from './getUserProgress.js';
|
|
9
|
+
export { loadUserProgressReviews } from './loadUserProgressReviews.js';
|
|
10
|
+
export type { UserProgressReviews, UserProgressTierRequest } from './loadUserProgressReviews.js';
|
|
6
11
|
export { getMetricLeaderboard } from './getMetricLeaderboard.js';
|
|
7
12
|
export type { MetricLeaderboardResult, MetricLeaderboardRow } from './getMetricLeaderboard.js';
|
|
8
13
|
export { applyMetricBalanceDelta, findMetricBalance, metricBalanceKey, rebuildMetricBalance, rebuildMetricBalancesForUser, } from './metricBalances.js';
|
|
@@ -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,oBAAoB,CAAA;AAC5H,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAC9F,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC/E,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAC1F,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,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,oBAAoB,CAAA;AAC5H,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AAChG,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAA;AAChF,YAAY,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AACjH,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAChG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAC9F,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC/E,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAC1F,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { evaluateRules, evaluateRuleProgress, normalizeRuleGroup, collectAchievementCompleteRefs } from './evaluateRules.js';
|
|
2
2
|
export { buildCatalogProgressGroups } from './buildCatalogProgressGroups.js';
|
|
3
|
+
export { buildUnlockRequirementLeaves } from './buildUnlockRequirementLeaves.js';
|
|
3
4
|
export { getUserProgress } from './getUserProgress.js';
|
|
5
|
+
export { loadUserProgressReviews } from './loadUserProgressReviews.js';
|
|
4
6
|
export { getMetricLeaderboard } from './getMetricLeaderboard.js';
|
|
5
7
|
export { applyMetricBalanceDelta, findMetricBalance, metricBalanceKey, rebuildMetricBalance, rebuildMetricBalancesForUser, } from './metricBalances.js';
|
|
6
8
|
export { resolveMetricValue, loadMetricDoc } from './resolveMetricValue.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
export type UserProgressTierRequest = {
|
|
3
|
+
id: string;
|
|
4
|
+
status: 'pending' | 'rejected';
|
|
5
|
+
tierId: string | null;
|
|
6
|
+
};
|
|
7
|
+
export type UserProgressReviews = {
|
|
8
|
+
/** Achievement definition ids and slugs with a pending request. */
|
|
9
|
+
pendingAchievementKeys: string[];
|
|
10
|
+
/** Open or rejected tier requests (host decides which badge wins). */
|
|
11
|
+
tierRequests: UserProgressTierRequest[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Review-queue snapshot for member UI (pending achievement keys + open/rejected tier requests).
|
|
15
|
+
* Does not decide ready/rejected presentation — hosts apply ladder context themselves.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadUserProgressReviews(args: {
|
|
18
|
+
payload: Payload;
|
|
19
|
+
req?: PayloadRequest;
|
|
20
|
+
userId: string;
|
|
21
|
+
scopeId?: string | null;
|
|
22
|
+
}): Promise<UserProgressReviews>;
|
|
23
|
+
//# sourceMappingURL=loadUserProgressReviews.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadUserProgressReviews.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/loadUserProgressReviews.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAQ7D,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,SAAS,GAAG,UAAU,CAAA;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,mEAAmE;IACnE,sBAAsB,EAAE,MAAM,EAAE,CAAA;IAChC,sEAAsE;IACtE,YAAY,EAAE,uBAAuB,EAAE,CAAA;CACxC,CAAA;AAyBD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,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,mBAAmB,CAAC,CA+C/B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { collectionOf } from '../../collections/helpers.js';
|
|
2
|
+
import { relationId } from './relationId.js';
|
|
3
|
+
const PENDING_ACHIEVEMENT_REQUESTS_LIMIT = 200;
|
|
4
|
+
const TIER_REQUESTS_LIMIT = 50;
|
|
5
|
+
function relationSlug(value) {
|
|
6
|
+
if (value && typeof value === 'object' && 'slug' in value) {
|
|
7
|
+
const slug = value.slug;
|
|
8
|
+
if (typeof slug === 'string' && slug)
|
|
9
|
+
return slug;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function pendingAchievementWhere(userId, scopeId) {
|
|
14
|
+
const clauses = [{ user: { equals: userId } }, { status: { equals: 'pending' } }];
|
|
15
|
+
if (scopeId)
|
|
16
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
17
|
+
return { and: clauses };
|
|
18
|
+
}
|
|
19
|
+
function openOrRejectedTierRequestWhere(userId, scopeId) {
|
|
20
|
+
const clauses = [
|
|
21
|
+
{ user: { equals: userId } },
|
|
22
|
+
{ status: { in: ['pending', 'rejected'] } },
|
|
23
|
+
];
|
|
24
|
+
if (scopeId)
|
|
25
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
26
|
+
return { and: clauses };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Review-queue snapshot for member UI (pending achievement keys + open/rejected tier requests).
|
|
30
|
+
* Does not decide ready/rejected presentation — hosts apply ladder context themselves.
|
|
31
|
+
*/
|
|
32
|
+
export async function loadUserProgressReviews(args) {
|
|
33
|
+
const scopeId = args.scopeId ?? null;
|
|
34
|
+
const [pendingAchievementRequests, tierRequests] = await Promise.all([
|
|
35
|
+
args.payload.find({
|
|
36
|
+
collection: collectionOf('achievementRequests'),
|
|
37
|
+
depth: 1,
|
|
38
|
+
limit: PENDING_ACHIEVEMENT_REQUESTS_LIMIT,
|
|
39
|
+
pagination: false,
|
|
40
|
+
overrideAccess: true,
|
|
41
|
+
req: args.req,
|
|
42
|
+
where: pendingAchievementWhere(args.userId, scopeId),
|
|
43
|
+
}),
|
|
44
|
+
scopeId
|
|
45
|
+
? args.payload.find({
|
|
46
|
+
collection: collectionOf('tierRequests'),
|
|
47
|
+
depth: 0,
|
|
48
|
+
limit: TIER_REQUESTS_LIMIT,
|
|
49
|
+
pagination: false,
|
|
50
|
+
overrideAccess: true,
|
|
51
|
+
req: args.req,
|
|
52
|
+
where: openOrRejectedTierRequestWhere(args.userId, scopeId),
|
|
53
|
+
})
|
|
54
|
+
: Promise.resolve({ docs: [] }),
|
|
55
|
+
]);
|
|
56
|
+
const pendingAchievementKeys = new Set();
|
|
57
|
+
for (const doc of pendingAchievementRequests.docs) {
|
|
58
|
+
const id = relationId(doc.achievement);
|
|
59
|
+
const slug = relationSlug(doc.achievement);
|
|
60
|
+
if (id)
|
|
61
|
+
pendingAchievementKeys.add(id);
|
|
62
|
+
if (slug)
|
|
63
|
+
pendingAchievementKeys.add(slug);
|
|
64
|
+
}
|
|
65
|
+
const reviews = [];
|
|
66
|
+
for (const doc of tierRequests.docs) {
|
|
67
|
+
const status = doc.status === 'rejected' ? 'rejected' : doc.status === 'pending' ? 'pending' : null;
|
|
68
|
+
if (!status)
|
|
69
|
+
continue;
|
|
70
|
+
const id = typeof doc.id === 'string' && doc.id ? doc.id : relationId(doc.id);
|
|
71
|
+
if (!id)
|
|
72
|
+
continue;
|
|
73
|
+
reviews.push({ id, status, tierId: relationId(doc.tier) });
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
pendingAchievementKeys: [...pendingAchievementKeys],
|
|
77
|
+
tierRequests: reviews,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/validateRules.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validateRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/validateRules.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAuD7E;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9D,MAAM,GAAG,IAAI,CAOf;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,EAC/D,KAAK,EAAE,MAAM,GACZ,IAAI,CAKN"}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { APIError } from 'payload';
|
|
2
|
-
import { isBuiltinElapsedSince } from '../../extensions/elapsedSinceOptions.js';
|
|
3
|
-
import { getMetricAnchorResolver } from '../../extensions/metricAnchors.js';
|
|
4
2
|
import { relationId } from './relationId.js';
|
|
5
3
|
import { normalizeRuleGroup } from './evaluateRules.js';
|
|
6
4
|
/**
|
|
@@ -51,26 +49,6 @@ export function validateRuleNode(rule) {
|
|
|
51
49
|
}
|
|
52
50
|
return null;
|
|
53
51
|
}
|
|
54
|
-
if (type === 'elapsed-since') {
|
|
55
|
-
const since = String(rule.since ?? 'user-created-at');
|
|
56
|
-
if (since === 'first-event') {
|
|
57
|
-
if (!relationId(rule.eventType) && typeof rule.eventTypeSlug !== 'string') {
|
|
58
|
-
return 'Elapsed since (first event) needs an event type.';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (!isBuiltinElapsedSince(since) && !getMetricAnchorResolver(since)) {
|
|
62
|
-
// Host anchors (e.g. membership-granted) are registered via extensions.metricAnchors.
|
|
63
|
-
return 'Elapsed since needs a valid since source.';
|
|
64
|
-
}
|
|
65
|
-
if (typeof rule.amount !== 'number' || Number.isNaN(rule.amount) || rule.amount < 0) {
|
|
66
|
-
return 'Elapsed since needs a non-negative amount.';
|
|
67
|
-
}
|
|
68
|
-
const unit = rule.unit ?? 'days';
|
|
69
|
-
if (unit !== 'days' && unit !== 'hours' && unit !== 'minutes') {
|
|
70
|
-
return 'Elapsed since unit must be days, hours, or minutes.';
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
52
|
if (!type) {
|
|
75
53
|
return 'Each rule needs a type.';
|
|
76
54
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CollectionConfig, OptionLabel, Payload, PayloadRequest, TypedUser } from 'payload';
|
|
2
|
-
/** Host-registered date anchors for computed elapsed metrics
|
|
2
|
+
/** Host-registered date anchors for computed elapsed metrics. */
|
|
3
3
|
export type AchievementMetricAnchor = (args: {
|
|
4
4
|
payload: Payload;
|
|
5
5
|
req?: PayloadRequest;
|
|
@@ -125,8 +125,8 @@ export type AchievementPluginOptions = {
|
|
|
125
125
|
/** Custom rule evaluators merged with built-ins. */
|
|
126
126
|
ruleTypes?: AchievementRuleType[];
|
|
127
127
|
/**
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
* Named date anchors for computed `elapsed` metrics.
|
|
129
|
+
* Keys appear in Admin `since` selects; missing keys resolve to null at eval time.
|
|
130
130
|
*/
|
|
131
131
|
metricAnchors?: Record<string, AchievementMetricAnchorEntry>;
|
|
132
132
|
};
|
|
@@ -150,7 +150,7 @@ export type AchievementPluginOptions = {
|
|
|
150
150
|
users?: AchievementUsersOptions;
|
|
151
151
|
usersCollectionSlug?: string;
|
|
152
152
|
};
|
|
153
|
-
export type BuiltInRuleType = 'achievement-complete' | '
|
|
153
|
+
export type BuiltInRuleType = 'achievement-complete' | 'event-count' | 'metric-minimum' | 'tier-at-least';
|
|
154
154
|
/** Engine log-type catalog slugs (seeded as system catalog rows). */
|
|
155
155
|
export type BuiltInEventType = 'achievement.granted' | 'achievement.revoked' | 'metric.delta' | 'tier.changed';
|
|
156
156
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEhG,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dappermountain/payload-plugin-achievements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A robust, Admin-configured achievements engine for Payload: composable unlock rules, derived rank progress, request/review grants, metrics, and scoped multi-tenant support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|