@dappermountain/payload-plugin-achievements 0.2.9 → 0.2.11
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 +9 -5
- package/dist/collections/Tiers/hooks/afterRequiresReviewDisabled.d.ts +3 -2
- package/dist/collections/Tiers/hooks/afterRequiresReviewDisabled.d.ts.map +1 -1
- package/dist/collections/Tiers/hooks/afterRequiresReviewDisabled.js +3 -2
- package/dist/extensions/elapsedSinceOptions.d.ts +7 -5
- package/dist/extensions/elapsedSinceOptions.d.ts.map +1 -1
- package/dist/extensions/elapsedSinceOptions.js +8 -5
- package/dist/extensions/metricAnchors.d.ts +13 -0
- package/dist/extensions/metricAnchors.d.ts.map +1 -0
- package/dist/extensions/metricAnchors.js +24 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/services/achievement/releaseReviewGate.d.ts +9 -0
- package/dist/services/achievement/releaseReviewGate.d.ts.map +1 -1
- package/dist/services/achievement/releaseReviewGate.js +65 -1
- package/dist/services/achievement/resolveElapsedAnchor.d.ts.map +1 -1
- package/dist/services/achievement/resolveElapsedAnchor.js +2 -1
- package/dist/types.d.ts +12 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
|
|
|
110
110
|
- Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count`, `elapsed-since`
|
|
111
111
|
- Custom leaves → register via `extensions.ruleTypes`
|
|
112
112
|
|
|
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**. **Next-tier fill** uses fractional progress on the same trees (`resolveTierProgress` / `evaluateRuleProgress`):
|
|
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`):
|
|
114
114
|
|
|
115
115
|
| Combinator / leaf | Progress behavior |
|
|
116
116
|
| --- | --- |
|
|
@@ -186,7 +186,7 @@ achievementPlugin({
|
|
|
186
186
|
| `canReview` | _(denied)_ | **Important.** Without this, users cannot manage catalogs or approve requests. Receives `(user, scopeId)` where `scopeId` may be `null`. |
|
|
187
187
|
| `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
188
|
| `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
|
|
189
|
+
| `extensions.metricAnchors` | _(none)_ | Named date resolvers for computed elapsed / `elapsed-since`. Prefer `{ label, resolve }` where `label` is a Payload Admin {@link OptionLabel} (string, `{ en, es }`, or `({ t }) => t('custom:…')`). Not CMS localization. |
|
|
190
190
|
|
|
191
191
|
### `collections`
|
|
192
192
|
|
|
@@ -245,9 +245,13 @@ achievementPlugin({
|
|
|
245
245
|
canReview,
|
|
246
246
|
extensions: {
|
|
247
247
|
metricAnchors: {
|
|
248
|
-
'membership-granted':
|
|
249
|
-
//
|
|
250
|
-
|
|
248
|
+
'membership-granted': {
|
|
249
|
+
// Admin UI i18n (Payload label), not a CMS localized field
|
|
250
|
+
label: ({ t }) => t('custom:achievements:anchors:membershipGranted'),
|
|
251
|
+
resolve: async ({ payload, userId, scopeId }) => {
|
|
252
|
+
// return a Date from host data, or null
|
|
253
|
+
return null
|
|
254
|
+
},
|
|
251
255
|
},
|
|
252
256
|
},
|
|
253
257
|
ruleTypes: [
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { CollectionAfterChangeHook } from 'payload';
|
|
2
2
|
/**
|
|
3
|
-
* When Requires review is turned off, approve pending tier requests
|
|
4
|
-
*
|
|
3
|
+
* When Requires review is turned off, approve pending tier requests and sync
|
|
4
|
+
* `tier.changed` for members who already met unlock rules (including those with
|
|
5
|
+
* no pending request — otherwise the UI advances with no audit until Repair).
|
|
5
6
|
*/
|
|
6
7
|
export declare const afterRequiresReviewDisabled: CollectionAfterChangeHook;
|
|
7
8
|
//# sourceMappingURL=afterRequiresReviewDisabled.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"afterRequiresReviewDisabled.d.ts","sourceRoot":"","sources":["../../../../src/collections/Tiers/hooks/afterRequiresReviewDisabled.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAOxD
|
|
1
|
+
{"version":3,"file":"afterRequiresReviewDisabled.d.ts","sourceRoot":"","sources":["../../../../src/collections/Tiers/hooks/afterRequiresReviewDisabled.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAOxD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,yBAmBzC,CAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { didDisableRequiresReview, releasePendingTierReviews, } from '../../../services/achievement/releaseReviewGate.js';
|
|
2
2
|
/**
|
|
3
|
-
* When Requires review is turned off, approve pending tier requests
|
|
4
|
-
*
|
|
3
|
+
* When Requires review is turned off, approve pending tier requests and sync
|
|
4
|
+
* `tier.changed` for members who already met unlock rules (including those with
|
|
5
|
+
* no pending request — otherwise the UI advances with no audit until Repair).
|
|
5
6
|
*/
|
|
6
7
|
export const afterRequiresReviewDisabled = async ({ doc, operation, previousDoc, req, }) => {
|
|
7
8
|
if (operation !== 'update')
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
1
|
+
import type { Option } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Admin select options for elapsed `since`.
|
|
4
|
+
* Built-in labels are English (same as other plugin Admin chrome).
|
|
5
|
+
* Host anchors pass through Payload {@link OptionLabel} (string, locale map, or `({ t }) => t(…)`).
|
|
6
|
+
*/
|
|
7
|
+
export declare function elapsedSinceSelectOptions(): Option[];
|
|
6
8
|
export declare function isBuiltinElapsedSince(since: string): boolean;
|
|
7
9
|
//# sourceMappingURL=elapsedSinceOptions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elapsedSinceOptions.d.ts","sourceRoot":"","sources":["../../src/extensions/elapsedSinceOptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"elapsedSinceOptions.d.ts","sourceRoot":"","sources":["../../src/extensions/elapsedSinceOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AASrC;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,EAAE,CAKpD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5D"}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getMetricAnchorEntries } from './metricAnchors.js';
|
|
2
2
|
const BUILTIN_ELAPSED_SINCE_OPTIONS = [
|
|
3
3
|
{ label: 'User created at', value: 'user-created-at' },
|
|
4
4
|
{ label: 'First matching event log', value: 'first-event' },
|
|
5
5
|
];
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Admin select options for elapsed `since`.
|
|
8
|
+
* Built-in labels are English (same as other plugin Admin chrome).
|
|
9
|
+
* Host anchors pass through Payload {@link OptionLabel} (string, locale map, or `({ t }) => t(…)`).
|
|
10
|
+
*/
|
|
7
11
|
export function elapsedSinceSelectOptions() {
|
|
8
|
-
const custom = Object.keys(getAchievementOptions().extensions?.metricAnchors ?? {});
|
|
9
12
|
return [
|
|
10
|
-
...BUILTIN_ELAPSED_SINCE_OPTIONS
|
|
11
|
-
...
|
|
13
|
+
...BUILTIN_ELAPSED_SINCE_OPTIONS,
|
|
14
|
+
...getMetricAnchorEntries().map(({ key, label }) => ({ label, value: key })),
|
|
12
15
|
];
|
|
13
16
|
}
|
|
14
17
|
export function isBuiltinElapsedSince(since) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OptionLabel } from 'payload';
|
|
2
|
+
import type { AchievementMetricAnchor, AchievementMetricAnchorEntry } from '../types.js';
|
|
3
|
+
export declare function resolveMetricAnchorEntry(entry: AchievementMetricAnchorEntry, key: string): {
|
|
4
|
+
label: OptionLabel;
|
|
5
|
+
resolve: AchievementMetricAnchor;
|
|
6
|
+
};
|
|
7
|
+
export declare function getMetricAnchorEntries(): Array<{
|
|
8
|
+
key: string;
|
|
9
|
+
label: OptionLabel;
|
|
10
|
+
resolve: AchievementMetricAnchor;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function getMetricAnchorResolver(key: string): AchievementMetricAnchor | null;
|
|
13
|
+
//# sourceMappingURL=metricAnchors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricAnchors.d.ts","sourceRoot":"","sources":["../../src/extensions/metricAnchors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,OAAO,KAAK,EACV,uBAAuB,EAEvB,4BAA4B,EAC7B,MAAM,aAAa,CAAA;AAOpB,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,4BAA4B,EACnC,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,uBAAuB,CAAA;CAAE,CAM1D;AAED,wBAAgB,sBAAsB,IAAI,KAAK,CAAC;IAC9C,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,uBAAuB,CAAA;CACjC,CAAC,CAMD;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI,CAInF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getAchievementOptions } from '../options-store.js';
|
|
2
|
+
function isAnchorConfig(entry) {
|
|
3
|
+
return typeof entry === 'object' && entry != null && 'resolve' in entry;
|
|
4
|
+
}
|
|
5
|
+
export function resolveMetricAnchorEntry(entry, key) {
|
|
6
|
+
if (isAnchorConfig(entry)) {
|
|
7
|
+
return { label: entry.label, resolve: entry.resolve };
|
|
8
|
+
}
|
|
9
|
+
// Bare resolver: Admin shows the key until the host supplies a Payload label.
|
|
10
|
+
return { label: key, resolve: entry };
|
|
11
|
+
}
|
|
12
|
+
export function getMetricAnchorEntries() {
|
|
13
|
+
const anchors = getAchievementOptions().extensions?.metricAnchors ?? {};
|
|
14
|
+
return Object.entries(anchors).map(([key, entry]) => {
|
|
15
|
+
const resolved = resolveMetricAnchorEntry(entry, key);
|
|
16
|
+
return { key, ...resolved };
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function getMetricAnchorResolver(key) {
|
|
20
|
+
const entry = getAchievementOptions().extensions?.metricAnchors?.[key];
|
|
21
|
+
if (!entry)
|
|
22
|
+
return null;
|
|
23
|
+
return resolveMetricAnchorEntry(entry, key).resolve;
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { seedAchievementCatalog, seedAchievements } from './seed/index.js';
|
|
|
5
5
|
export type { AchievementSeedAchievement, AchievementSeedCatalogRow, AchievementSeedInput, AchievementSeedLocalizedText, AchievementSeedTier, } from './seed/index.js';
|
|
6
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
7
|
export type { ResolvedTier, RuleGroup, TierLadderProgress, CatalogProgressGroup, CatalogProgressItem, CatalogRef, TransitionLogData, MetricLeaderboardResult, MetricLeaderboardRow, ReconcileProgressionArgs, ReconcileProgressionResult, ReleaseReviewGateResult, } from './services/achievement/index.js';
|
|
8
|
-
export type { BuiltInEventType, BuiltInRuleType, AchievementCollectionKey, AchievementCollectionOverride, AchievementCollectionsOptions, AchievementEndpointsOptions, AchievementMetricAnchor, AchievementPluginOptions, AchievementRuleType, AchievementScopeConfig, AchievementUsersOptions, } from './types.js';
|
|
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';
|
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,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,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,6 +1,8 @@
|
|
|
1
1
|
import type { PayloadRequest } from 'payload';
|
|
2
2
|
export type ReleaseReviewGateResult = {
|
|
3
3
|
requestsApproved: number;
|
|
4
|
+
/** `tier.changed` rows written for users who newly resolve past the released gate. */
|
|
5
|
+
tierLogsWritten?: number;
|
|
4
6
|
};
|
|
5
7
|
/**
|
|
6
8
|
* When a definition stops requiring review, approve each pending request for that
|
|
@@ -10,6 +12,13 @@ export declare function releasePendingAchievementReviews(args: {
|
|
|
10
12
|
req: PayloadRequest;
|
|
11
13
|
achievementId: string;
|
|
12
14
|
}): Promise<ReleaseReviewGateResult>;
|
|
15
|
+
/**
|
|
16
|
+
* When a tier stops requiring review:
|
|
17
|
+
* 1. Approve pending tier requests (their afterChange hooks sync `tier.changed`).
|
|
18
|
+
* 2. Sync `tier.changed` for every (user, scope) that may now resolve to this rung —
|
|
19
|
+
* including members who already met unlock rules but never had a pending request
|
|
20
|
+
* (UI would otherwise show the new rank with no audit log until Repair).
|
|
21
|
+
*/
|
|
13
22
|
export declare function releasePendingTierReviews(args: {
|
|
14
23
|
req: PayloadRequest;
|
|
15
24
|
tierId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseReviewGate.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/releaseReviewGate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"releaseReviewGate.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/releaseReviewGate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAMpD,MAAM,MAAM,uBAAuB,GAAG;IACpC,gBAAgB,EAAE,MAAM,CAAA;IACxB,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAcD;;;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;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA0FnC;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"}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { collectionOf } from '../../collections/helpers.js';
|
|
2
|
+
import { syncTierChangedLog } from './recordEvent.js';
|
|
2
3
|
import { relationId } from './relationId.js';
|
|
4
|
+
function addUserScopePair(pairs, userId, scopeId) {
|
|
5
|
+
if (!userId)
|
|
6
|
+
return;
|
|
7
|
+
const key = `${userId}::${scopeId ?? ''}`;
|
|
8
|
+
if (!pairs.has(key))
|
|
9
|
+
pairs.set(key, { userId, scopeId });
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* When a definition stops requiring review, approve each pending request for that
|
|
5
13
|
* definition so existing grant / tier.changed hooks run for that user + scope only.
|
|
@@ -36,6 +44,13 @@ export async function releasePendingAchievementReviews(args) {
|
|
|
36
44
|
}
|
|
37
45
|
return { requestsApproved };
|
|
38
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* When a tier stops requiring review:
|
|
49
|
+
* 1. Approve pending tier requests (their afterChange hooks sync `tier.changed`).
|
|
50
|
+
* 2. Sync `tier.changed` for every (user, scope) that may now resolve to this rung —
|
|
51
|
+
* including members who already met unlock rules but never had a pending request
|
|
52
|
+
* (UI would otherwise show the new rank with no audit log until Repair).
|
|
53
|
+
*/
|
|
39
54
|
export async function releasePendingTierReviews(args) {
|
|
40
55
|
const pending = await args.req.payload.find({
|
|
41
56
|
collection: collectionOf('tierRequests'),
|
|
@@ -63,7 +78,56 @@ export async function releasePendingTierReviews(args) {
|
|
|
63
78
|
});
|
|
64
79
|
requestsApproved += 1;
|
|
65
80
|
}
|
|
66
|
-
|
|
81
|
+
const tier = (await args.req.payload.findByID({
|
|
82
|
+
collection: collectionOf('tiers'),
|
|
83
|
+
id: args.tierId,
|
|
84
|
+
depth: 0,
|
|
85
|
+
overrideAccess: true,
|
|
86
|
+
req: args.req,
|
|
87
|
+
}));
|
|
88
|
+
const tierScopeId = relationId(tier?.scope);
|
|
89
|
+
const pairs = new Map();
|
|
90
|
+
const allRequests = await args.req.payload.find({
|
|
91
|
+
collection: collectionOf('tierRequests'),
|
|
92
|
+
depth: 0,
|
|
93
|
+
limit: 5000,
|
|
94
|
+
pagination: false,
|
|
95
|
+
overrideAccess: true,
|
|
96
|
+
req: args.req,
|
|
97
|
+
where: { tier: { equals: args.tierId } },
|
|
98
|
+
});
|
|
99
|
+
for (const doc of allRequests.docs) {
|
|
100
|
+
addUserScopePair(pairs, relationId(doc.user), tierScopeId ?? relationId(doc.scope));
|
|
101
|
+
}
|
|
102
|
+
const grantWhere = tierScopeId
|
|
103
|
+
? { scope: { equals: tierScopeId } }
|
|
104
|
+
: {
|
|
105
|
+
or: [{ scope: { exists: false } }, { scope: { equals: null } }],
|
|
106
|
+
};
|
|
107
|
+
const grants = await args.req.payload.find({
|
|
108
|
+
collection: collectionOf('grants'),
|
|
109
|
+
depth: 0,
|
|
110
|
+
limit: 5000,
|
|
111
|
+
pagination: false,
|
|
112
|
+
overrideAccess: true,
|
|
113
|
+
req: args.req,
|
|
114
|
+
where: grantWhere,
|
|
115
|
+
});
|
|
116
|
+
for (const doc of grants.docs) {
|
|
117
|
+
addUserScopePair(pairs, relationId(doc.user), tierScopeId ?? relationId(doc.scope));
|
|
118
|
+
}
|
|
119
|
+
let tierLogsWritten = 0;
|
|
120
|
+
for (const pair of pairs.values()) {
|
|
121
|
+
const log = await syncTierChangedLog({
|
|
122
|
+
payload: args.req.payload,
|
|
123
|
+
req: args.req,
|
|
124
|
+
userId: pair.userId,
|
|
125
|
+
scopeId: pair.scopeId,
|
|
126
|
+
});
|
|
127
|
+
if (log)
|
|
128
|
+
tierLogsWritten += 1;
|
|
129
|
+
}
|
|
130
|
+
return { requestsApproved, tierLogsWritten };
|
|
67
131
|
}
|
|
68
132
|
/** True when `requiresReview` flipped from required → not required. */
|
|
69
133
|
export function didDisableRequiresReview(previousDoc, doc) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveElapsedAnchor.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/resolveElapsedAnchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"resolveElapsedAnchor.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/resolveElapsedAnchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AA+CtD;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CA0DvB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { collectionOf } from '../../collections/helpers.js';
|
|
2
2
|
import { getAchievementOptions } from '../../options-store.js';
|
|
3
|
+
import { getMetricAnchorResolver } from '../../extensions/metricAnchors.js';
|
|
3
4
|
import { parseDate } from './duration.js';
|
|
4
5
|
import { relationId } from './relationId.js';
|
|
5
6
|
import { userScopeIncludingUnscopedWhere } from './where.js';
|
|
@@ -81,7 +82,7 @@ export async function resolveElapsedAnchor(args) {
|
|
|
81
82
|
});
|
|
82
83
|
return parseDate(logs.docs[0]?.createdAt);
|
|
83
84
|
}
|
|
84
|
-
const hostAnchor =
|
|
85
|
+
const hostAnchor = getMetricAnchorResolver(since);
|
|
85
86
|
if (!hostAnchor)
|
|
86
87
|
return null;
|
|
87
88
|
try {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CollectionConfig, Payload, PayloadRequest, TypedUser } from 'payload';
|
|
1
|
+
import type { CollectionConfig, OptionLabel, Payload, PayloadRequest, TypedUser } from 'payload';
|
|
2
2
|
/** Host-registered date anchors for computed elapsed metrics / `elapsed-since` rules. */
|
|
3
3
|
export type AchievementMetricAnchor = (args: {
|
|
4
4
|
payload: Payload;
|
|
@@ -6,6 +6,16 @@ export type AchievementMetricAnchor = (args: {
|
|
|
6
6
|
userId: string;
|
|
7
7
|
scopeId: string | null;
|
|
8
8
|
}) => Date | null | Promise<Date | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Anchor with an Admin label.
|
|
11
|
+
* `label` is a Payload Admin UI string ({@link OptionLabel}): plain string, `{ en, es, … }`,
|
|
12
|
+
* or `({ t }) => t('custom:…')` — not CMS `localization`.
|
|
13
|
+
*/
|
|
14
|
+
export type AchievementMetricAnchorConfig = {
|
|
15
|
+
label: OptionLabel;
|
|
16
|
+
resolve: AchievementMetricAnchor;
|
|
17
|
+
};
|
|
18
|
+
export type AchievementMetricAnchorEntry = AchievementMetricAnchor | AchievementMetricAnchorConfig;
|
|
9
19
|
export type AchievementScopeConfig = {
|
|
10
20
|
collection: string;
|
|
11
21
|
relationField?: string;
|
|
@@ -118,7 +128,7 @@ export type AchievementPluginOptions = {
|
|
|
118
128
|
* Named date anchors for computed `elapsed` metrics and `elapsed-since` rules.
|
|
119
129
|
* Keys appear in Admin `since` selects; missing keys resolve to null at eval time.
|
|
120
130
|
*/
|
|
121
|
-
metricAnchors?: Record<string,
|
|
131
|
+
metricAnchors?: Record<string, AchievementMetricAnchorEntry>;
|
|
122
132
|
};
|
|
123
133
|
/**
|
|
124
134
|
* When true (default), ensure system metrics + event types exist on Payload `onInit`
|
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,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;
|
|
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.2.
|
|
3
|
+
"version": "0.2.11",
|
|
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",
|