@dappermountain/payload-plugin-achievements 0.2.6 → 0.2.7
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 +34 -8
- package/dist/catalog.d.ts +1 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -1
- package/dist/cli/reconcile.d.ts.map +1 -1
- package/dist/cli/reconcile.js +1 -0
- package/dist/collections/MetricBalances/access/index.d.ts +4 -0
- package/dist/collections/MetricBalances/access/index.d.ts.map +1 -0
- package/dist/collections/MetricBalances/access/index.js +9 -0
- package/dist/collections/MetricBalances/hooks/index.d.ts +3 -0
- package/dist/collections/MetricBalances/hooks/index.d.ts.map +1 -0
- package/dist/collections/MetricBalances/hooks/index.js +1 -0
- package/dist/collections/MetricBalances/index.d.ts +3 -0
- package/dist/collections/MetricBalances/index.d.ts.map +1 -0
- package/dist/collections/MetricBalances/index.js +48 -0
- package/dist/collections/Metrics/index.d.ts.map +1 -1
- package/dist/collections/Metrics/index.js +64 -3
- package/dist/collections/fields/ruleFields.d.ts.map +1 -1
- package/dist/collections/fields/ruleFields.js +45 -2
- package/dist/collections/hooks/preventCatalogDelete.d.ts.map +1 -1
- package/dist/collections/hooks/preventCatalogDelete.js +10 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/collections/index.js +2 -0
- package/dist/collections/slugs.d.ts.map +1 -1
- package/dist/collections/slugs.js +1 -0
- package/dist/defaults.d.ts +4 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +10 -0
- package/dist/endpoints/index.d.ts.map +1 -1
- package/dist/endpoints/index.js +4 -0
- package/dist/endpoints/leaderboard.d.ts +6 -0
- package/dist/endpoints/leaderboard.d.ts.map +1 -0
- package/dist/endpoints/leaderboard.js +59 -0
- package/dist/extensions/elapsedSinceOptions.d.ts +7 -0
- package/dist/extensions/elapsedSinceOptions.d.ts.map +1 -0
- package/dist/extensions/elapsedSinceOptions.js +16 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +9 -0
- package/dist/seed/index.d.ts +10 -0
- package/dist/seed/index.d.ts.map +1 -1
- package/dist/seed/index.js +14 -2
- package/dist/services/achievement/builtInRules.d.ts.map +1 -1
- package/dist/services/achievement/builtInRules.js +59 -21
- package/dist/services/achievement/duration.d.ts +6 -0
- package/dist/services/achievement/duration.d.ts.map +1 -0
- package/dist/services/achievement/duration.js +43 -0
- package/dist/services/achievement/getMetricLeaderboard.d.ts +30 -0
- package/dist/services/achievement/getMetricLeaderboard.d.ts.map +1 -0
- package/dist/services/achievement/getMetricLeaderboard.js +62 -0
- 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 +4 -0
- package/dist/services/achievement/metricBalances.d.ts +42 -0
- package/dist/services/achievement/metricBalances.d.ts.map +1 -0
- package/dist/services/achievement/metricBalances.js +137 -0
- package/dist/services/achievement/reconcile.d.ts +1 -0
- package/dist/services/achievement/reconcile.d.ts.map +1 -1
- package/dist/services/achievement/reconcile.js +10 -0
- package/dist/services/achievement/recordEvent.d.ts.map +1 -1
- package/dist/services/achievement/recordEvent.js +22 -1
- package/dist/services/achievement/resolveElapsedAnchor.d.ts +15 -0
- package/dist/services/achievement/resolveElapsedAnchor.d.ts.map +1 -0
- package/dist/services/achievement/resolveElapsedAnchor.js +98 -0
- package/dist/services/achievement/resolveMetricValue.d.ts +32 -0
- package/dist/services/achievement/resolveMetricValue.d.ts.map +1 -0
- package/dist/services/achievement/resolveMetricValue.js +73 -0
- package/dist/services/achievement/validateRules.d.ts.map +1 -1
- package/dist/services/achievement/validateRules.js +19 -0
- package/dist/types.d.ts +26 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getUserId } from '../access/helpers/index.js';
|
|
2
|
+
import { getMetricLeaderboard } from '../services/achievement/getMetricLeaderboard.js';
|
|
3
|
+
function requestUrl(req) {
|
|
4
|
+
return req.url ? new URL(req.url, 'http://local') : null;
|
|
5
|
+
}
|
|
6
|
+
function scopeFromRequest(req) {
|
|
7
|
+
const url = requestUrl(req);
|
|
8
|
+
const raw = url?.searchParams.get('scope') ?? url?.searchParams.get('scopeId');
|
|
9
|
+
return raw && raw.length > 0 ? raw : null;
|
|
10
|
+
}
|
|
11
|
+
function positiveInt(raw, fallback) {
|
|
12
|
+
if (raw == null || raw === '')
|
|
13
|
+
return fallback;
|
|
14
|
+
const n = Number(raw);
|
|
15
|
+
if (!Number.isFinite(n) || n < 1)
|
|
16
|
+
return fallback;
|
|
17
|
+
return Math.floor(n);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `GET` ranked stored-metric balances. Query: `metric` (required), optional `scope` / `limit` / `page`.
|
|
21
|
+
*/
|
|
22
|
+
export function buildLeaderboardEndpoint(path) {
|
|
23
|
+
return {
|
|
24
|
+
path,
|
|
25
|
+
method: 'get',
|
|
26
|
+
handler: async (req) => {
|
|
27
|
+
const userId = getUserId(req);
|
|
28
|
+
if (!userId) {
|
|
29
|
+
return Response.json({ errors: [{ message: 'Unauthorized' }] }, { status: 401 });
|
|
30
|
+
}
|
|
31
|
+
const params = requestUrl(req)?.searchParams;
|
|
32
|
+
const metric = params?.get('metric')?.trim();
|
|
33
|
+
if (!metric) {
|
|
34
|
+
return Response.json({ errors: [{ message: 'Query param `metric` is required.' }] }, { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const body = await getMetricLeaderboard({
|
|
38
|
+
payload: req.payload,
|
|
39
|
+
req,
|
|
40
|
+
metric,
|
|
41
|
+
scopeId: scopeFromRequest(req),
|
|
42
|
+
limit: positiveInt(params?.get('limit') ?? null, 20),
|
|
43
|
+
page: positiveInt(params?.get('page') ?? null, 1),
|
|
44
|
+
});
|
|
45
|
+
return Response.json(body);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const status = typeof error === 'object' &&
|
|
49
|
+
error &&
|
|
50
|
+
'status' in error &&
|
|
51
|
+
typeof error.status === 'number'
|
|
52
|
+
? error.status
|
|
53
|
+
: 500;
|
|
54
|
+
const message = error instanceof Error ? error.message : 'Leaderboard failed.';
|
|
55
|
+
return Response.json({ errors: [{ message }] }, { status });
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Admin select options for elapsed `since` (built-ins + host `metricAnchors` keys). */
|
|
2
|
+
export declare function elapsedSinceSelectOptions(): Array<{
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function isBuiltinElapsedSince(since: string): boolean;
|
|
7
|
+
//# sourceMappingURL=elapsedSinceOptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elapsedSinceOptions.d.ts","sourceRoot":"","sources":["../../src/extensions/elapsedSinceOptions.ts"],"names":[],"mappings":"AAOA,wFAAwF;AACxF,wBAAgB,yBAAyB,IAAI,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAMnF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getAchievementOptions } from '../options-store.js';
|
|
2
|
+
const BUILTIN_ELAPSED_SINCE_OPTIONS = [
|
|
3
|
+
{ label: 'User created at', value: 'user-created-at' },
|
|
4
|
+
{ label: 'First matching event log', value: 'first-event' },
|
|
5
|
+
];
|
|
6
|
+
/** Admin select options for elapsed `since` (built-ins + host `metricAnchors` keys). */
|
|
7
|
+
export function elapsedSinceSelectOptions() {
|
|
8
|
+
const custom = Object.keys(getAchievementOptions().extensions?.metricAnchors ?? {});
|
|
9
|
+
return [
|
|
10
|
+
...BUILTIN_ELAPSED_SINCE_OPTIONS.map((option) => ({ ...option })),
|
|
11
|
+
...custom.map((key) => ({ label: key, value: key })),
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
export function isBuiltinElapsedSince(since) {
|
|
15
|
+
return since === 'user-created-at' || since === 'first-event';
|
|
16
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ 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, grantAchievement, recordEvent, recordLog, recordMetricChange, reconcileProgression, reconcileUserProgression, releasePendingAchievementReviews, releasePendingTierReviews, resolveCurrentTier, 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, ReconcileProgressionArgs, ReconcileProgressionResult, ReleaseReviewGateResult, } from './services/achievement/index.js';
|
|
8
|
-
export type { BuiltInEventType, BuiltInRuleType, AchievementCollectionKey, AchievementCollectionOverride, AchievementCollectionsOptions, AchievementEndpointsOptions, AchievementPluginOptions, AchievementRuleType, AchievementScopeConfig, AchievementUsersOptions, } from './types.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';
|
|
8
|
+
export type { BuiltInEventType, BuiltInRuleType, AchievementCollectionKey, AchievementCollectionOverride, AchievementCollectionsOptions, AchievementEndpointsOptions, AchievementMetricAnchor, 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
|
-
export { DEFAULT_ME_ENDPOINT_PATH, DEFAULT_RECONCILE_ENDPOINT_PATH } from './defaults.js';
|
|
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
12
|
export { isAchievementPluginEnabled } from './enabled.js';
|
|
13
13
|
export { descriptionToPlaintext } from './fields/descriptionPlaintext.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,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,EACzB,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,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,wBAAwB,EACxB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,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,
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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, grantAchievement, recordEvent, recordLog, recordMetricChange, reconcileProgression, reconcileUserProgression, releasePendingAchievementReviews, releasePendingTierReviews, resolveCurrentTier, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, catalogRef, transitionLogData, transitionToId, validateRuleGroup, validateRuleNode, } from './services/achievement/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';
|
|
5
5
|
export { DEFAULT_COLLECTION_BASE_SLUGS, DEFAULT_COLLECTION_PREFIX, DEFAULT_ADMIN_GROUP } from './collections/slugs.js';
|
|
6
|
-
export { DEFAULT_ME_ENDPOINT_PATH, DEFAULT_RECONCILE_ENDPOINT_PATH } from './defaults.js';
|
|
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
8
|
export { isAchievementPluginEnabled } from './enabled.js';
|
|
9
9
|
export { descriptionToPlaintext } from './fields/descriptionPlaintext.js';
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,SAAS,CAAA;AAO7C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC3B,UAAS,wBAA6B,KAAG,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,SAAS,CAAA;AAO7C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC3B,UAAS,wBAA6B,KAAG,MAmCzC,CAAA"}
|
package/dist/plugin.js
CHANGED
|
@@ -23,9 +23,18 @@ export const achievementPlugin = (options = {}) => (config) => {
|
|
|
23
23
|
fields: [...(collection.fields ?? []), usersAchievementsGroup(resolved)],
|
|
24
24
|
};
|
|
25
25
|
});
|
|
26
|
+
const incomingOnInit = config.onInit;
|
|
26
27
|
return {
|
|
27
28
|
...config,
|
|
28
29
|
collections: [...collections, ...achievementCollections],
|
|
29
30
|
endpoints: [...(config.endpoints ?? []), ...buildAchievementEndpoints(resolved)],
|
|
31
|
+
onInit: async (payload) => {
|
|
32
|
+
if (incomingOnInit)
|
|
33
|
+
await incomingOnInit(payload);
|
|
34
|
+
if (resolved.seedSystemCatalog) {
|
|
35
|
+
const { seedAchievementCatalog } = await import('./seed/index.js');
|
|
36
|
+
await seedAchievementCatalog(payload);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
30
39
|
};
|
|
31
40
|
};
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -43,6 +43,16 @@ export type AchievementSeedCatalogRow = {
|
|
|
43
43
|
slug: string;
|
|
44
44
|
scope?: string;
|
|
45
45
|
system?: boolean;
|
|
46
|
+
/** Metrics — `stored` (default) or `computed`. */
|
|
47
|
+
kind?: 'stored' | 'computed';
|
|
48
|
+
/** Computed metrics — v1 only `elapsed`. */
|
|
49
|
+
compute?: 'elapsed';
|
|
50
|
+
/** Computed elapsed — unit of the exposed number. */
|
|
51
|
+
unit?: 'days' | 'hours' | 'minutes';
|
|
52
|
+
/** Computed elapsed — built-in or host `metricAnchors` key. */
|
|
53
|
+
since?: string;
|
|
54
|
+
/** Computed elapsed with `since: first-event` — event type slug. */
|
|
55
|
+
eventTypeSlug?: string;
|
|
46
56
|
/** Event types only — when true, logging the event requires an actor. */
|
|
47
57
|
requiresActor?: boolean;
|
|
48
58
|
/** Event types only — when true, logging the event requires metric + change. */
|
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,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,CAAC,EAAE,yBAAyB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,yBAAyB,EAAE,CAAA;IACrC,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAC7B,YAAY,CAAC,EAAE,0BAA0B,EAAE,CAAA;IAC3C,gEAAgE;IAChE,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAA;CAClC,CAAA;
|
|
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
|
@@ -53,6 +53,18 @@ async function findIdBySlug(payload, key, slug) {
|
|
|
53
53
|
async function upsertCatalog(payload, key, row) {
|
|
54
54
|
const existing = await findIdBySlug(payload, key, row.slug);
|
|
55
55
|
const nameParts = localizedParts(row.name);
|
|
56
|
+
let metricComputed = {};
|
|
57
|
+
if (key === 'metrics') {
|
|
58
|
+
metricComputed = { kind: row.kind ?? 'stored' };
|
|
59
|
+
if (row.kind === 'computed') {
|
|
60
|
+
metricComputed.compute = row.compute ?? 'elapsed';
|
|
61
|
+
metricComputed.unit = row.unit ?? 'days';
|
|
62
|
+
metricComputed.since = row.since ?? 'user-created-at';
|
|
63
|
+
if (row.eventTypeSlug) {
|
|
64
|
+
metricComputed.eventType = await findIdBySlug(payload, 'eventTypes', row.eventTypeSlug);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
56
68
|
const data = {
|
|
57
69
|
name: nameParts.en ?? row.slug,
|
|
58
70
|
slug: row.slug,
|
|
@@ -63,7 +75,7 @@ async function upsertCatalog(payload, key, row) {
|
|
|
63
75
|
requiresActor: row.requiresActor ?? false,
|
|
64
76
|
requiresMetric: row.requiresMetric ?? false,
|
|
65
77
|
}
|
|
66
|
-
:
|
|
78
|
+
: metricComputed),
|
|
67
79
|
};
|
|
68
80
|
let id = existing;
|
|
69
81
|
if (existing) {
|
|
@@ -119,7 +131,7 @@ async function resolveRuleNode(payload, rule) {
|
|
|
119
131
|
delete next.achievementSlug;
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
|
-
if (next.type === 'event-count') {
|
|
134
|
+
if (next.type === 'event-count' || next.type === 'elapsed-since') {
|
|
123
135
|
const slug = typeof next.eventTypeSlug === 'string'
|
|
124
136
|
? next.eventTypeSlug
|
|
125
137
|
: typeof next.eventType === 'string'
|
|
@@ -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;AAuJlF,eAAO,MAAM,gBAAgB,EAAE,mBAAmB,EA+LjD,CAAA"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { collectionOf } from '../../collections/helpers.js';
|
|
2
|
+
import { durationMs } from './duration.js';
|
|
2
3
|
import { relationId } from './relationId.js';
|
|
3
|
-
import {
|
|
4
|
+
import { resolveElapsedAnchor } from './resolveElapsedAnchor.js';
|
|
5
|
+
import { resolveMetricValue } from './resolveMetricValue.js';
|
|
6
|
+
import { userScopeIncludingUnscopedWhere } from './where.js';
|
|
4
7
|
function clamp01(value) {
|
|
5
8
|
if (!Number.isFinite(value))
|
|
6
9
|
return 0;
|
|
@@ -22,22 +25,6 @@ async function resolveMetricId(args) {
|
|
|
22
25
|
});
|
|
23
26
|
return relationId(found.docs[0]);
|
|
24
27
|
}
|
|
25
|
-
async function metricValue(args) {
|
|
26
|
-
const logs = await args.payload.find({
|
|
27
|
-
collection: collectionOf('logs'),
|
|
28
|
-
depth: 0,
|
|
29
|
-
limit: 1000,
|
|
30
|
-
pagination: false,
|
|
31
|
-
overrideAccess: true,
|
|
32
|
-
where: {
|
|
33
|
-
and: [userScopeWhere(args.userId, args.scopeId), { metric: { equals: args.metricId } }],
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
return logs.docs.reduce((sum, doc) => {
|
|
37
|
-
const change = doc.change;
|
|
38
|
-
return sum + (typeof change === 'number' && Number.isFinite(change) ? change : 0);
|
|
39
|
-
}, 0);
|
|
40
|
-
}
|
|
41
28
|
async function resolveEventTypeId(args) {
|
|
42
29
|
let eventTypeId = relationId(args.rule.eventType);
|
|
43
30
|
if (eventTypeId) {
|
|
@@ -225,22 +212,34 @@ export const builtInRuleTypes = [
|
|
|
225
212
|
},
|
|
226
213
|
{
|
|
227
214
|
type: 'metric-minimum',
|
|
228
|
-
async evaluate({ payload, rule, userId, scopeId }) {
|
|
215
|
+
async evaluate({ payload, req, rule, userId, scopeId }) {
|
|
229
216
|
const resolvedId = await resolveMetricId({ payload, rule });
|
|
230
217
|
if (!resolvedId)
|
|
231
218
|
return false;
|
|
232
219
|
const minimum = Number(rule.minimum ?? 0);
|
|
233
|
-
const value = await
|
|
220
|
+
const value = await resolveMetricValue({
|
|
221
|
+
payload,
|
|
222
|
+
req,
|
|
223
|
+
metric: resolvedId,
|
|
224
|
+
userId,
|
|
225
|
+
scopeId,
|
|
226
|
+
});
|
|
234
227
|
return value >= minimum;
|
|
235
228
|
},
|
|
236
|
-
async progress({ payload, rule, userId, scopeId }) {
|
|
229
|
+
async progress({ payload, req, rule, userId, scopeId }) {
|
|
237
230
|
const resolvedId = await resolveMetricId({ payload, rule });
|
|
238
231
|
if (!resolvedId)
|
|
239
232
|
return 0;
|
|
240
233
|
const minimum = Number(rule.minimum ?? 0);
|
|
241
234
|
if (!(minimum > 0))
|
|
242
235
|
return 1;
|
|
243
|
-
const value = await
|
|
236
|
+
const value = await resolveMetricValue({
|
|
237
|
+
payload,
|
|
238
|
+
req,
|
|
239
|
+
metric: resolvedId,
|
|
240
|
+
userId,
|
|
241
|
+
scopeId,
|
|
242
|
+
});
|
|
244
243
|
return clamp01(value / minimum);
|
|
245
244
|
},
|
|
246
245
|
},
|
|
@@ -277,4 +276,43 @@ export const builtInRuleTypes = [
|
|
|
277
276
|
return clamp01(result.totalDocs / needed);
|
|
278
277
|
},
|
|
279
278
|
},
|
|
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
|
+
},
|
|
280
318
|
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function durationMs(amount: number, unit: unknown): number | null;
|
|
2
|
+
export declare function unitMs(unit: unknown): number | null;
|
|
3
|
+
export declare function parseDate(value: unknown): Date | null;
|
|
4
|
+
/** Whole units elapsed from anchor to `now` (floor). */
|
|
5
|
+
export declare function elapsedUnits(anchor: Date, unit: unknown, now?: Date): number | null;
|
|
6
|
+
//# sourceMappingURL=duration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/duration.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAevE;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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const MS_PER_MINUTE = 60_000;
|
|
2
|
+
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
3
|
+
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
4
|
+
export function durationMs(amount, unit) {
|
|
5
|
+
if (!(typeof amount === 'number') || !Number.isFinite(amount) || amount < 0)
|
|
6
|
+
return null;
|
|
7
|
+
switch (unit) {
|
|
8
|
+
case 'minutes':
|
|
9
|
+
return amount * MS_PER_MINUTE;
|
|
10
|
+
case 'hours':
|
|
11
|
+
return amount * MS_PER_HOUR;
|
|
12
|
+
case 'days':
|
|
13
|
+
case undefined:
|
|
14
|
+
case null:
|
|
15
|
+
case '':
|
|
16
|
+
return amount * MS_PER_DAY;
|
|
17
|
+
default:
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function unitMs(unit) {
|
|
22
|
+
return durationMs(1, unit ?? 'days');
|
|
23
|
+
}
|
|
24
|
+
export function parseDate(value) {
|
|
25
|
+
if (value instanceof Date && !Number.isNaN(value.getTime()))
|
|
26
|
+
return value;
|
|
27
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
28
|
+
const date = new Date(value);
|
|
29
|
+
if (!Number.isNaN(date.getTime()))
|
|
30
|
+
return date;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** Whole units elapsed from anchor to `now` (floor). */
|
|
35
|
+
export function elapsedUnits(anchor, unit, now = new Date()) {
|
|
36
|
+
const ms = unitMs(unit);
|
|
37
|
+
if (ms == null || ms <= 0)
|
|
38
|
+
return null;
|
|
39
|
+
const delta = now.getTime() - anchor.getTime();
|
|
40
|
+
if (!Number.isFinite(delta) || delta < 0)
|
|
41
|
+
return 0;
|
|
42
|
+
return Math.floor(delta / ms);
|
|
43
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
export type MetricLeaderboardRow = {
|
|
3
|
+
user: string;
|
|
4
|
+
value: number;
|
|
5
|
+
rank: number;
|
|
6
|
+
};
|
|
7
|
+
export type MetricLeaderboardResult = {
|
|
8
|
+
docs: MetricLeaderboardRow[];
|
|
9
|
+
totalDocs: number;
|
|
10
|
+
page: number;
|
|
11
|
+
limit: number;
|
|
12
|
+
totalPages: number;
|
|
13
|
+
metric: {
|
|
14
|
+
id: string;
|
|
15
|
+
slug?: string;
|
|
16
|
+
};
|
|
17
|
+
scopeId: string | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Ranked stored-metric balances for a scope (or unscoped board when `scopeId` is null).
|
|
21
|
+
*/
|
|
22
|
+
export declare function getMetricLeaderboard(args: {
|
|
23
|
+
payload: Payload;
|
|
24
|
+
req?: PayloadRequest;
|
|
25
|
+
metric: string;
|
|
26
|
+
scopeId?: string | null;
|
|
27
|
+
limit?: number;
|
|
28
|
+
page?: number;
|
|
29
|
+
}): Promise<MetricLeaderboardResult>;
|
|
30
|
+
//# sourceMappingURL=getMetricLeaderboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getMetricLeaderboard.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/getMetricLeaderboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAQ7D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,oBAAoB,EAAE,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,CAAA;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAyDnC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { APIError } from 'payload';
|
|
2
|
+
import { collectionOf } from '../../collections/helpers.js';
|
|
3
|
+
import { getAchievementOptions } from '../../options-store.js';
|
|
4
|
+
import { loadMetricDoc } from './resolveMetricValue.js';
|
|
5
|
+
import { relationId } from './relationId.js';
|
|
6
|
+
/**
|
|
7
|
+
* Ranked stored-metric balances for a scope (or unscoped board when `scopeId` is null).
|
|
8
|
+
*/
|
|
9
|
+
export async function getMetricLeaderboard(args) {
|
|
10
|
+
const metric = await loadMetricDoc({
|
|
11
|
+
payload: args.payload,
|
|
12
|
+
req: args.req,
|
|
13
|
+
metric: args.metric,
|
|
14
|
+
});
|
|
15
|
+
if (!metric) {
|
|
16
|
+
throw new APIError('Metric not found.', 404);
|
|
17
|
+
}
|
|
18
|
+
if ((metric.kind ?? 'stored') === 'computed') {
|
|
19
|
+
throw new APIError('Leaderboards are only available for stored metrics.', 400);
|
|
20
|
+
}
|
|
21
|
+
const limit = Math.min(Math.max(args.limit ?? 20, 1), 100);
|
|
22
|
+
const page = Math.max(args.page ?? 1, 1);
|
|
23
|
+
const scopeId = args.scopeId ?? null;
|
|
24
|
+
const scopeConfigured = Boolean(getAchievementOptions().scope?.collection);
|
|
25
|
+
const clauses = [{ metric: { equals: metric.id } }];
|
|
26
|
+
if (scopeId) {
|
|
27
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
28
|
+
}
|
|
29
|
+
else if (scopeConfigured) {
|
|
30
|
+
clauses.push({
|
|
31
|
+
or: [{ scope: { exists: false } }, { scope: { equals: null } }],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const result = await args.payload.find({
|
|
35
|
+
collection: collectionOf('metricBalances'),
|
|
36
|
+
depth: 0,
|
|
37
|
+
limit,
|
|
38
|
+
page,
|
|
39
|
+
overrideAccess: true,
|
|
40
|
+
req: args.req,
|
|
41
|
+
sort: '-value,id',
|
|
42
|
+
where: { and: clauses },
|
|
43
|
+
});
|
|
44
|
+
const offset = (page - 1) * limit;
|
|
45
|
+
const docs = result.docs.map((doc, index) => {
|
|
46
|
+
const row = doc;
|
|
47
|
+
return {
|
|
48
|
+
user: relationId(row.user) ?? String(row.user ?? ''),
|
|
49
|
+
value: typeof row.value === 'number' && Number.isFinite(row.value) ? row.value : 0,
|
|
50
|
+
rank: offset + index + 1,
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
docs,
|
|
55
|
+
totalDocs: result.totalDocs,
|
|
56
|
+
page: result.page ?? page,
|
|
57
|
+
limit,
|
|
58
|
+
totalPages: result.totalPages ?? Math.max(1, Math.ceil(result.totalDocs / limit)),
|
|
59
|
+
metric: { id: metric.id, slug: metric.slug },
|
|
60
|
+
scopeId,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -3,6 +3,11 @@ export type { RuleGroup, AchievementCompleteRef } from './evaluateRules.js';
|
|
|
3
3
|
export { buildCatalogProgressGroups } from './buildCatalogProgressGroups.js';
|
|
4
4
|
export type { CatalogProgressGroup, CatalogProgressItem } from './buildCatalogProgressGroups.js';
|
|
5
5
|
export { getUserProgress } from './getUserProgress.js';
|
|
6
|
+
export { getMetricLeaderboard } from './getMetricLeaderboard.js';
|
|
7
|
+
export type { MetricLeaderboardResult, MetricLeaderboardRow } from './getMetricLeaderboard.js';
|
|
8
|
+
export { applyMetricBalanceDelta, findMetricBalance, metricBalanceKey, rebuildMetricBalance, rebuildMetricBalancesForUser, } from './metricBalances.js';
|
|
9
|
+
export { resolveMetricValue, loadMetricDoc } from './resolveMetricValue.js';
|
|
10
|
+
export { resolveElapsedAnchor } from './resolveElapsedAnchor.js';
|
|
6
11
|
export { recordEvent, recordLog, recordMetricChange, syncTierChangedLog } from './recordEvent.js';
|
|
7
12
|
export { resolveCurrentTier } from './resolveCurrentTier.js';
|
|
8
13
|
export type { ResolvedTier } from './resolveCurrentTier.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,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,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,6 +1,10 @@
|
|
|
1
1
|
export { evaluateRules, evaluateRuleProgress, normalizeRuleGroup, collectAchievementCompleteRefs } from './evaluateRules.js';
|
|
2
2
|
export { buildCatalogProgressGroups } from './buildCatalogProgressGroups.js';
|
|
3
3
|
export { getUserProgress } from './getUserProgress.js';
|
|
4
|
+
export { getMetricLeaderboard } from './getMetricLeaderboard.js';
|
|
5
|
+
export { applyMetricBalanceDelta, findMetricBalance, metricBalanceKey, rebuildMetricBalance, rebuildMetricBalancesForUser, } from './metricBalances.js';
|
|
6
|
+
export { resolveMetricValue, loadMetricDoc } from './resolveMetricValue.js';
|
|
7
|
+
export { resolveElapsedAnchor } from './resolveElapsedAnchor.js';
|
|
4
8
|
export { recordEvent, recordLog, recordMetricChange, syncTierChangedLog } from './recordEvent.js';
|
|
5
9
|
export { resolveCurrentTier } from './resolveCurrentTier.js';
|
|
6
10
|
export { resolveTierProgress } from './resolveTierProgress.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
export declare function metricBalanceKey(userId: string, scopeId: string | null, metricId: string): string;
|
|
3
|
+
export declare function findMetricBalance(args: {
|
|
4
|
+
payload: Payload;
|
|
5
|
+
req?: PayloadRequest;
|
|
6
|
+
userId: string;
|
|
7
|
+
scopeId: string | null;
|
|
8
|
+
metricId: string;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
id: string;
|
|
11
|
+
value: number;
|
|
12
|
+
} | null>;
|
|
13
|
+
export declare function applyMetricBalanceDelta(args: {
|
|
14
|
+
payload: Payload;
|
|
15
|
+
req?: PayloadRequest;
|
|
16
|
+
userId: string;
|
|
17
|
+
scopeId: string | null;
|
|
18
|
+
metricId: string;
|
|
19
|
+
change: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
id: string;
|
|
22
|
+
value: number;
|
|
23
|
+
}>;
|
|
24
|
+
/** Sum metric.delta log changes and upsert the balance projection. */
|
|
25
|
+
export declare function rebuildMetricBalance(args: {
|
|
26
|
+
payload: Payload;
|
|
27
|
+
req?: PayloadRequest;
|
|
28
|
+
userId: string;
|
|
29
|
+
scopeId?: string | null;
|
|
30
|
+
metricId: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
id: string;
|
|
33
|
+
value: number;
|
|
34
|
+
}>;
|
|
35
|
+
/** Rebuild balances for every distinct metric seen in this user’s metric.delta logs. */
|
|
36
|
+
export declare function rebuildMetricBalancesForUser(args: {
|
|
37
|
+
payload: Payload;
|
|
38
|
+
req?: PayloadRequest;
|
|
39
|
+
userId: string;
|
|
40
|
+
scopeId?: string | null;
|
|
41
|
+
}): Promise<number>;
|
|
42
|
+
//# sourceMappingURL=metricBalances.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricBalances.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/metricBalances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAMtD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEjG;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAehD;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAgCzC;AAED,sEAAsE;AACtE,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoDzC;AAED,wFAAwF;AACxF,wBAAsB,4BAA4B,CAAC,IAAI,EAAE;IACvD,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,MAAM,CAAC,CAgClB"}
|