@dappermountain/payload-plugin-achievements 0.2.5 → 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/admin/LucideIconField.d.ts +1 -1
- package/dist/admin/LucideIconField.d.ts.map +1 -1
- package/dist/admin/LucideIconField.js +139 -24
- 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
|
@@ -61,7 +61,8 @@ 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) => rule.type === 'event-count'
|
|
64
|
+
match: (rule) => (rule.type === 'event-count' || rule.type === 'elapsed-since') &&
|
|
65
|
+
relationId(rule.eventType) === id,
|
|
65
66
|
});
|
|
66
67
|
if (rules > 0)
|
|
67
68
|
parts.push(`${rules} rule(s)`);
|
|
@@ -75,6 +76,14 @@ export function preventCatalogDelete(args) {
|
|
|
75
76
|
});
|
|
76
77
|
if (logs.totalDocs > 0)
|
|
77
78
|
parts.push(`${logs.totalDocs} log row(s)`);
|
|
79
|
+
const balances = await req.payload.count({
|
|
80
|
+
collection: collectionOf('metricBalances'),
|
|
81
|
+
overrideAccess: true,
|
|
82
|
+
req,
|
|
83
|
+
where: { metric: { equals: id } },
|
|
84
|
+
});
|
|
85
|
+
if (balances.totalDocs > 0)
|
|
86
|
+
parts.push(`${balances.totalDocs} balance row(s)`);
|
|
78
87
|
const rules = await countMatchingLeaves({
|
|
79
88
|
req,
|
|
80
89
|
match: (rule) => rule.type === 'metric-minimum' && relationId(rule.metric) === id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AA2B/C,qEAAqE;AACrE,wBAAgB,2BAA2B,IAAI,gBAAgB,EAAE,CAMhE"}
|
|
@@ -5,11 +5,13 @@ import { buildAchievementsCollection } from './Achievements/index.js';
|
|
|
5
5
|
import { buildEventTypesCollection } from './EventTypes/index.js';
|
|
6
6
|
import { buildGrantsCollection } from './Grants/index.js';
|
|
7
7
|
import { buildLogsCollection } from './Logs/index.js';
|
|
8
|
+
import { buildMetricBalancesCollection } from './MetricBalances/index.js';
|
|
8
9
|
import { buildMetricsCollection } from './Metrics/index.js';
|
|
9
10
|
import { buildTierRequestsCollection } from './TierRequests/index.js';
|
|
10
11
|
import { buildTiersCollection } from './Tiers/index.js';
|
|
11
12
|
const builders = {
|
|
12
13
|
metrics: buildMetricsCollection,
|
|
14
|
+
metricBalances: buildMetricBalancesCollection,
|
|
13
15
|
eventTypes: buildEventTypesCollection,
|
|
14
16
|
tiers: buildTiersCollection,
|
|
15
17
|
achievements: buildAchievementsCollection,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slugs.d.ts","sourceRoot":"","sources":["../../src/collections/slugs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAErF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,gBAAgB,CAAA;AAEtD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"slugs.d.ts","sourceRoot":"","sources":["../../src/collections/slugs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAErF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,gBAAgB,CAAA;AAEtD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAUlF,CAAA;AAED,eAAO,MAAM,mBAAmB,iBAAiB,CAAA;AAEjD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAkB1C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,MAAM,GAAG,KAAK,CAMxF"}
|
package/dist/defaults.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type { AchievementPluginOptions } from './types.js';
|
|
|
2
2
|
import { resolveCollectionSlugs } from './collections/slugs.js';
|
|
3
3
|
export declare const DEFAULT_USERS_COLLECTION = "users";
|
|
4
4
|
export declare const DEFAULT_ME_ENDPOINT_PATH = "/achievements/me";
|
|
5
|
+
export declare const DEFAULT_LEADERBOARD_ENDPOINT_PATH = "/achievements/leaderboard";
|
|
5
6
|
export declare const DEFAULT_RECONCILE_ENDPOINT_PATH = "/achievements/reconcile";
|
|
6
7
|
export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users' | 'endpoints'> & {
|
|
7
8
|
enabled: boolean;
|
|
9
|
+
seedSystemCatalog: boolean;
|
|
8
10
|
usersCollectionSlug: string;
|
|
9
11
|
users: {
|
|
10
12
|
includeJoins: boolean;
|
|
@@ -15,10 +17,12 @@ export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users'
|
|
|
15
17
|
adminGroup: string | false;
|
|
16
18
|
endpoints: {
|
|
17
19
|
me: string | false;
|
|
20
|
+
leaderboard: string | false;
|
|
18
21
|
reconcile: string | false;
|
|
19
22
|
};
|
|
20
23
|
};
|
|
21
24
|
export declare function resolveMeEndpointPath(me?: string | false): string | false;
|
|
25
|
+
export declare function resolveLeaderboardEndpointPath(path?: string | false): string | false;
|
|
22
26
|
export declare function resolveReconcileEndpointPath(path?: string | false): string | false;
|
|
23
27
|
export declare function resolveOptions(options?: AchievementPluginOptions): ResolvedAchievementOptions;
|
|
24
28
|
//# sourceMappingURL=defaults.d.ts.map
|
package/dist/defaults.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAEL,sBAAsB,EACvB,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,wBAAwB,UAAU,CAAA;AAC/C,eAAO,MAAM,wBAAwB,qBAAqB,CAAA;AAC1D,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AAExE,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,wBAAwB,EAAE,OAAO,GAAG,WAAW,CAAC,GAAG;IAC/F,OAAO,EAAE,OAAO,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,CAAA;IAChC,+DAA+D;IAC/D,eAAe,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAA;IAC1D,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,KAAK,CAAA;IAC1B,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAEL,sBAAsB,EACvB,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,wBAAwB,UAAU,CAAA;AAC/C,eAAO,MAAM,wBAAwB,qBAAqB,CAAA;AAC1D,eAAO,MAAM,iCAAiC,8BAA8B,CAAA;AAC5E,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AAExE,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,wBAAwB,EAAE,OAAO,GAAG,WAAW,CAAC,GAAG;IAC/F,OAAO,EAAE,OAAO,CAAA;IAChB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,CAAA;IAChC,+DAA+D;IAC/D,eAAe,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAA;IAC1D,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,KAAK,CAAA;IAC1B,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAA;CAC1F,CAAA;AAQD,wBAAgB,qBAAqB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIzE;AAED,wBAAgB,8BAA8B,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIpF;AAED,wBAAgB,4BAA4B,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIlF;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,wBAA6B,GAAG,0BAA0B,CAiBjG"}
|
package/dist/defaults.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveAdminGroup, resolveCollectionSlugs, } from './collections/slugs.js';
|
|
2
2
|
export const DEFAULT_USERS_COLLECTION = 'users';
|
|
3
3
|
export const DEFAULT_ME_ENDPOINT_PATH = '/achievements/me';
|
|
4
|
+
export const DEFAULT_LEADERBOARD_ENDPOINT_PATH = '/achievements/leaderboard';
|
|
4
5
|
export const DEFAULT_RECONCILE_ENDPOINT_PATH = '/achievements/reconcile';
|
|
5
6
|
function normalizeEndpointPath(path, fallback) {
|
|
6
7
|
const trimmed = path.trim();
|
|
@@ -15,6 +16,13 @@ export function resolveMeEndpointPath(me) {
|
|
|
15
16
|
return DEFAULT_ME_ENDPOINT_PATH;
|
|
16
17
|
return normalizeEndpointPath(me, DEFAULT_ME_ENDPOINT_PATH);
|
|
17
18
|
}
|
|
19
|
+
export function resolveLeaderboardEndpointPath(path) {
|
|
20
|
+
if (path === false)
|
|
21
|
+
return false;
|
|
22
|
+
if (path == null)
|
|
23
|
+
return DEFAULT_LEADERBOARD_ENDPOINT_PATH;
|
|
24
|
+
return normalizeEndpointPath(path, DEFAULT_LEADERBOARD_ENDPOINT_PATH);
|
|
25
|
+
}
|
|
18
26
|
export function resolveReconcileEndpointPath(path) {
|
|
19
27
|
if (path === false)
|
|
20
28
|
return false;
|
|
@@ -26,6 +34,7 @@ export function resolveOptions(options = {}) {
|
|
|
26
34
|
return {
|
|
27
35
|
...options,
|
|
28
36
|
enabled: options.enabled !== false,
|
|
37
|
+
seedSystemCatalog: options.seedSystemCatalog !== false,
|
|
29
38
|
usersCollectionSlug: options.usersCollectionSlug ?? DEFAULT_USERS_COLLECTION,
|
|
30
39
|
users: {
|
|
31
40
|
includeJoins: options.users?.includeJoins !== false,
|
|
@@ -34,6 +43,7 @@ export function resolveOptions(options = {}) {
|
|
|
34
43
|
adminGroup: resolveAdminGroup(options),
|
|
35
44
|
endpoints: {
|
|
36
45
|
me: resolveMeEndpointPath(options.endpoints?.me),
|
|
46
|
+
leaderboard: resolveLeaderboardEndpointPath(options.endpoints?.leaderboard),
|
|
37
47
|
reconcile: resolveReconcileEndpointPath(options.endpoints?.reconcile),
|
|
38
48
|
},
|
|
39
49
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoints/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoints/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAKhE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CAYzF"}
|
package/dist/endpoints/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildLeaderboardEndpoint } from './leaderboard.js';
|
|
1
2
|
import { buildMeEndpoint } from './me.js';
|
|
2
3
|
import { buildReconcileEndpoint } from './reconcile.js';
|
|
3
4
|
export function buildAchievementEndpoints(options) {
|
|
@@ -5,6 +6,9 @@ export function buildAchievementEndpoints(options) {
|
|
|
5
6
|
if (options.endpoints.me !== false) {
|
|
6
7
|
endpoints.push(buildMeEndpoint(options.endpoints.me));
|
|
7
8
|
}
|
|
9
|
+
if (options.endpoints.leaderboard !== false) {
|
|
10
|
+
endpoints.push(buildLeaderboardEndpoint(options.endpoints.leaderboard));
|
|
11
|
+
}
|
|
8
12
|
if (options.endpoints.reconcile !== false) {
|
|
9
13
|
endpoints.push(buildReconcileEndpoint(options.endpoints.reconcile));
|
|
10
14
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* `GET` ranked stored-metric balances. Query: `metric` (required), optional `scope` / `limit` / `page`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildLeaderboardEndpoint(path: string): Endpoint;
|
|
6
|
+
//# sourceMappingURL=leaderboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaderboard.d.ts","sourceRoot":"","sources":["../../src/endpoints/leaderboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AAsBvD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAuC/D"}
|
|
@@ -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"}
|