@dappermountain/payload-plugin-achievements 0.1.0 → 0.2.0
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 +20 -6
- package/dist/admin/ReconcileProgressionButton.d.ts +11 -0
- package/dist/admin/ReconcileProgressionButton.d.ts.map +1 -0
- package/dist/admin/ReconcileProgressionButton.js +41 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -0
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts.map +1 -1
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.js +35 -15
- package/dist/collections/Grants/hooks/afterGrantMutation.d.ts +4 -0
- package/dist/collections/Grants/hooks/afterGrantMutation.d.ts.map +1 -0
- package/dist/collections/Grants/hooks/afterGrantMutation.js +56 -0
- package/dist/collections/Grants/hooks/index.d.ts.map +1 -1
- package/dist/collections/Grants/hooks/index.js +3 -0
- package/dist/collections/Grants/index.d.ts.map +1 -1
- package/dist/collections/Grants/index.js +18 -1
- package/dist/collections/Logs/index.js +1 -1
- package/dist/collections/TierRequests/access/index.d.ts +3 -0
- package/dist/collections/TierRequests/access/index.d.ts.map +1 -0
- package/dist/collections/TierRequests/access/index.js +8 -0
- package/dist/collections/TierRequests/hooks/index.d.ts +3 -0
- package/dist/collections/TierRequests/hooks/index.d.ts.map +1 -0
- package/dist/collections/TierRequests/hooks/index.js +5 -0
- package/dist/collections/TierRequests/hooks/prepareTierRequest.d.ts +8 -0
- package/dist/collections/TierRequests/hooks/prepareTierRequest.d.ts.map +1 -0
- package/dist/collections/TierRequests/hooks/prepareTierRequest.js +67 -0
- package/dist/collections/TierRequests/index.d.ts +3 -0
- package/dist/collections/TierRequests/index.d.ts.map +1 -0
- package/dist/collections/TierRequests/index.js +47 -0
- package/dist/collections/Tiers/index.d.ts.map +1 -1
- package/dist/collections/Tiers/index.js +11 -3
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/collections/index.js +2 -0
- package/dist/collections/slugs.d.ts.map +1 -1
- package/dist/collections/slugs.js +1 -0
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +12 -3
- package/dist/endpoints/index.d.ts.map +1 -1
- package/dist/endpoints/index.js +9 -3
- package/dist/endpoints/reconcile.d.ts +8 -0
- package/dist/endpoints/reconcile.d.ts.map +1 -0
- package/dist/endpoints/reconcile.js +70 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.d.ts.map +1 -1
- package/dist/seed/index.js +1 -0
- package/dist/services/achievement/contextFlags.d.ts +5 -0
- package/dist/services/achievement/contextFlags.d.ts.map +1 -0
- package/dist/services/achievement/contextFlags.js +4 -0
- package/dist/services/achievement/grantSideEffects.d.ts +54 -0
- package/dist/services/achievement/grantSideEffects.d.ts.map +1 -0
- package/dist/services/achievement/grantSideEffects.js +269 -0
- package/dist/services/achievement/index.d.ts +4 -0
- package/dist/services/achievement/index.d.ts.map +1 -1
- package/dist/services/achievement/index.js +3 -0
- package/dist/services/achievement/reconcile.d.ts +24 -0
- package/dist/services/achievement/reconcile.d.ts.map +1 -0
- package/dist/services/achievement/reconcile.js +170 -0
- package/dist/services/achievement/recordEvent.d.ts.map +1 -1
- package/dist/services/achievement/recordEvent.js +8 -0
- package/dist/services/achievement/resolveCurrentTier.d.ts +1 -1
- package/dist/services/achievement/resolveCurrentTier.d.ts.map +1 -1
- package/dist/services/achievement/resolveCurrentTier.js +12 -2
- package/dist/services/achievement/submitAchievementRequest.d.ts +4 -2
- package/dist/services/achievement/submitAchievementRequest.d.ts.map +1 -1
- package/dist/services/achievement/submitAchievementRequest.js +8 -101
- package/dist/services/achievement/tierApproval.d.ts +8 -0
- package/dist/services/achievement/tierApproval.d.ts.map +1 -0
- package/dist/services/achievement/tierApproval.js +21 -0
- package/dist/services/achievement/tierProgression.d.ts +22 -0
- package/dist/services/achievement/tierProgression.d.ts.map +1 -0
- package/dist/services/achievement/tierProgression.js +100 -0
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/defaults.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AchievementPluginOptions } from './types';
|
|
|
2
2
|
import { resolveCollectionSlugs } from './collections/slugs';
|
|
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_RECONCILE_ENDPOINT_PATH = "/achievements/reconcile";
|
|
5
6
|
export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users' | 'endpoints'> & {
|
|
6
7
|
enabled: boolean;
|
|
7
8
|
usersCollectionSlug: string;
|
|
@@ -14,8 +15,10 @@ export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users'
|
|
|
14
15
|
adminGroup: string | false;
|
|
15
16
|
endpoints: {
|
|
16
17
|
me: string | false;
|
|
18
|
+
reconcile: string | false;
|
|
17
19
|
};
|
|
18
20
|
};
|
|
19
21
|
export declare function resolveMeEndpointPath(me?: string | false): string | false;
|
|
22
|
+
export declare function resolveReconcileEndpointPath(path?: string | false): string | false;
|
|
20
23
|
export declare function resolveOptions(options?: AchievementPluginOptions): ResolvedAchievementOptions;
|
|
21
24
|
//# 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,SAAS,CAAA;AACvD,OAAO,EAEL,sBAAsB,EACvB,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,wBAAwB,UAAU,CAAA;AAC/C,eAAO,MAAM,wBAAwB,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,EAEL,sBAAsB,EACvB,MAAM,qBAAqB,CAAA;AAE5B,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;CAC7D,CAAA;AAQD,wBAAgB,qBAAqB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIzE;AAED,wBAAgB,4BAA4B,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIlF;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,wBAA6B,GAAG,0BAA0B,CAejG"}
|
package/dist/defaults.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { resolveAdminGroup, resolveCollectionSlugs, } from './collections/slugs';
|
|
2
2
|
export const DEFAULT_USERS_COLLECTION = 'users';
|
|
3
3
|
export const DEFAULT_ME_ENDPOINT_PATH = '/achievements/me';
|
|
4
|
-
|
|
4
|
+
export const DEFAULT_RECONCILE_ENDPOINT_PATH = '/achievements/reconcile';
|
|
5
|
+
function normalizeEndpointPath(path, fallback) {
|
|
5
6
|
const trimmed = path.trim();
|
|
6
7
|
if (!trimmed)
|
|
7
|
-
return
|
|
8
|
+
return fallback;
|
|
8
9
|
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
9
10
|
}
|
|
10
11
|
export function resolveMeEndpointPath(me) {
|
|
@@ -12,7 +13,14 @@ export function resolveMeEndpointPath(me) {
|
|
|
12
13
|
return false;
|
|
13
14
|
if (me == null)
|
|
14
15
|
return DEFAULT_ME_ENDPOINT_PATH;
|
|
15
|
-
return normalizeEndpointPath(me);
|
|
16
|
+
return normalizeEndpointPath(me, DEFAULT_ME_ENDPOINT_PATH);
|
|
17
|
+
}
|
|
18
|
+
export function resolveReconcileEndpointPath(path) {
|
|
19
|
+
if (path === false)
|
|
20
|
+
return false;
|
|
21
|
+
if (path == null)
|
|
22
|
+
return DEFAULT_RECONCILE_ENDPOINT_PATH;
|
|
23
|
+
return normalizeEndpointPath(path, DEFAULT_RECONCILE_ENDPOINT_PATH);
|
|
16
24
|
}
|
|
17
25
|
export function resolveOptions(options = {}) {
|
|
18
26
|
return {
|
|
@@ -26,6 +34,7 @@ export function resolveOptions(options = {}) {
|
|
|
26
34
|
adminGroup: resolveAdminGroup(options),
|
|
27
35
|
endpoints: {
|
|
28
36
|
me: resolveMeEndpointPath(options.endpoints?.me),
|
|
37
|
+
reconcile: resolveReconcileEndpointPath(options.endpoints?.reconcile),
|
|
29
38
|
},
|
|
30
39
|
};
|
|
31
40
|
}
|
|
@@ -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,aAAa,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,aAAa,CAAA;AAI7D,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CASzF"}
|
package/dist/endpoints/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { buildMeEndpoint } from './me';
|
|
2
|
+
import { buildReconcileEndpoint } from './reconcile';
|
|
2
3
|
export function buildAchievementEndpoints(options) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const endpoints = [];
|
|
5
|
+
if (options.endpoints.me !== false) {
|
|
6
|
+
endpoints.push(buildMeEndpoint(options.endpoints.me));
|
|
7
|
+
}
|
|
8
|
+
if (options.endpoints.reconcile !== false) {
|
|
9
|
+
endpoints.push(buildReconcileEndpoint(options.endpoints.reconcile));
|
|
10
|
+
}
|
|
11
|
+
return endpoints;
|
|
6
12
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* `POST` repair progression from existing grants.
|
|
4
|
+
* Body (optional): `{ userId?, scopeId?, limit? }`.
|
|
5
|
+
* Requires host `canReview`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildReconcileEndpoint(path: string): Endpoint;
|
|
8
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/endpoints/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AAsBvD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAsD7D"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getUserId } from '../access/helpers/index';
|
|
2
|
+
import { getAchievementOptions } from '../options-store';
|
|
3
|
+
import { reconcileProgression, reconcileUserProgression } from '../services/achievement/reconcile';
|
|
4
|
+
function requestUrl(req) {
|
|
5
|
+
return req.url ? new URL(req.url, 'http://local') : null;
|
|
6
|
+
}
|
|
7
|
+
async function readBody(req) {
|
|
8
|
+
try {
|
|
9
|
+
if (typeof req.json === 'function') {
|
|
10
|
+
const body = await req.json();
|
|
11
|
+
if (body && typeof body === 'object')
|
|
12
|
+
return body;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// empty body
|
|
17
|
+
}
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `POST` repair progression from existing grants.
|
|
22
|
+
* Body (optional): `{ userId?, scopeId?, limit? }`.
|
|
23
|
+
* Requires host `canReview`.
|
|
24
|
+
*/
|
|
25
|
+
export function buildReconcileEndpoint(path) {
|
|
26
|
+
return {
|
|
27
|
+
path,
|
|
28
|
+
method: 'post',
|
|
29
|
+
handler: async (req) => {
|
|
30
|
+
const actorId = getUserId(req);
|
|
31
|
+
if (!actorId || !req.user) {
|
|
32
|
+
return Response.json({ errors: [{ message: 'Unauthorized' }] }, { status: 401 });
|
|
33
|
+
}
|
|
34
|
+
const { canReview } = getAchievementOptions();
|
|
35
|
+
if (!canReview) {
|
|
36
|
+
return Response.json({ errors: [{ message: 'Forbidden' }] }, { status: 403 });
|
|
37
|
+
}
|
|
38
|
+
const body = await readBody(req);
|
|
39
|
+
const url = requestUrl(req);
|
|
40
|
+
const scopeIdRaw = (typeof body.scopeId === 'string' && body.scopeId) ||
|
|
41
|
+
url?.searchParams.get('scopeId') ||
|
|
42
|
+
url?.searchParams.get('scope') ||
|
|
43
|
+
null;
|
|
44
|
+
const scopeId = scopeIdRaw && scopeIdRaw.length > 0 ? scopeIdRaw : null;
|
|
45
|
+
if (!(await canReview(req.user, scopeId))) {
|
|
46
|
+
return Response.json({ errors: [{ message: 'Forbidden' }] }, { status: 403 });
|
|
47
|
+
}
|
|
48
|
+
const userId = typeof body.userId === 'string' && body.userId.length > 0 ? body.userId : null;
|
|
49
|
+
const limit = typeof body.limit === 'number' && Number.isFinite(body.limit)
|
|
50
|
+
? Math.floor(body.limit)
|
|
51
|
+
: undefined;
|
|
52
|
+
if (userId) {
|
|
53
|
+
const result = await reconcileUserProgression({
|
|
54
|
+
payload: req.payload,
|
|
55
|
+
req,
|
|
56
|
+
userId,
|
|
57
|
+
scopeId,
|
|
58
|
+
});
|
|
59
|
+
return Response.json({ ok: true, usersScanned: 1, ...result });
|
|
60
|
+
}
|
|
61
|
+
const result = await reconcileProgression({
|
|
62
|
+
payload: req.payload,
|
|
63
|
+
req,
|
|
64
|
+
scopeId,
|
|
65
|
+
limit,
|
|
66
|
+
});
|
|
67
|
+
return Response.json({ ok: true, ...result });
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
package/dist/exports/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { ActorField } from '../admin/ActorField';
|
|
2
2
|
export { MetricChangeField } from '../admin/MetricChangeField';
|
|
3
3
|
export { MetricField } from '../admin/MetricField';
|
|
4
|
+
export { ReconcileProgressionButton } from '../admin/ReconcileProgressionButton';
|
|
4
5
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAA"}
|
package/dist/exports/client.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ export type { EngineEventTypeSlug, EngineMetricSlug } from './catalog';
|
|
|
3
3
|
export { achievementPlugin } from './plugin';
|
|
4
4
|
export { seedAchievementCatalog, seedAchievements } from './seed/index';
|
|
5
5
|
export type { AchievementSeedAchievement, AchievementSeedCatalogRow, AchievementSeedInput, AchievementSeedLocalizedText, AchievementSeedTier, } from './seed/index';
|
|
6
|
-
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, getUserProgress, grantAchievement, recordEvent, recordLog, recordMetricChange, resolveCurrentTier, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, validateRuleGroup, validateRuleNode, } from './services/achievement/index';
|
|
7
|
-
export type { ResolvedTier, RuleGroup, TierLadderProgress, CatalogProgressGroup, CatalogProgressItem } from './services/achievement/index';
|
|
6
|
+
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, getUserProgress, grantAchievement, recordEvent, recordLog, recordMetricChange, reconcileProgression, reconcileUserProgression, resolveCurrentTier, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, validateRuleGroup, validateRuleNode, } from './services/achievement/index';
|
|
7
|
+
export type { ResolvedTier, RuleGroup, TierLadderProgress, CatalogProgressGroup, CatalogProgressItem, ReconcileProgressionResult, } from './services/achievement/index';
|
|
8
8
|
export type { BuiltInEventType, BuiltInRuleType, AchievementCollectionKey, AchievementCollectionOverride, AchievementCollectionsOptions, AchievementEndpointsOptions, AchievementPluginOptions, AchievementRuleType, AchievementScopeConfig, AchievementUsersOptions, } from './types';
|
|
9
9
|
export { DEFAULT_COLLECTION_BASE_SLUGS, DEFAULT_COLLECTION_PREFIX, DEFAULT_ADMIN_GROUP } from './collections/slugs';
|
|
10
|
-
export { DEFAULT_ME_ENDPOINT_PATH } from './defaults';
|
|
10
|
+
export { DEFAULT_ME_ENDPOINT_PATH, DEFAULT_RECONCILE_ENDPOINT_PATH } from './defaults';
|
|
11
11
|
export { collectionOf, slugOf } from './collections/helpers';
|
|
12
12
|
export { isAchievementPluginEnabled } from './enabled';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
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,WAAW,CAAA;AAClB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACvE,YAAY,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,8BAA8B,CAAA;AACrC,YAAY,
|
|
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,WAAW,CAAA;AAClB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACvE,YAAY,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,wBAAwB,EACxB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACnH,OAAO,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,YAAY,CAAA;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { DEFAULT_ACHIEVEMENT_METRIC, DEFAULT_ACHIEVEMENT_METRIC_SLUG, ENGINE_EVENT_TYPES, ENGINE_METRICS, } from './catalog';
|
|
2
2
|
export { achievementPlugin } from './plugin';
|
|
3
3
|
export { seedAchievementCatalog, seedAchievements } from './seed/index';
|
|
4
|
-
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, getUserProgress, grantAchievement, recordEvent, recordLog, recordMetricChange, resolveCurrentTier, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, validateRuleGroup, validateRuleNode, } from './services/achievement/index';
|
|
4
|
+
export { evaluateRules, evaluateRuleProgress, collectAchievementCompleteRefs, buildCatalogProgressGroups, getUserProgress, grantAchievement, recordEvent, recordLog, recordMetricChange, reconcileProgression, reconcileUserProgression, resolveCurrentTier, resolveTierProgress, reviewAchievementRequest, submitAchievementRequest, syncTierChangedLog, syncTierProgression, syncCompositeProgression, validateRuleGroup, validateRuleNode, } from './services/achievement/index';
|
|
5
5
|
export { DEFAULT_COLLECTION_BASE_SLUGS, DEFAULT_COLLECTION_PREFIX, DEFAULT_ADMIN_GROUP } from './collections/slugs';
|
|
6
|
-
export { DEFAULT_ME_ENDPOINT_PATH } from './defaults';
|
|
6
|
+
export { DEFAULT_ME_ENDPOINT_PATH, DEFAULT_RECONCILE_ENDPOINT_PATH } from './defaults';
|
|
7
7
|
export { collectionOf, slugOf } from './collections/helpers';
|
|
8
8
|
export { isAchievementPluginEnabled } from './enabled';
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type AchievementSeedTier = {
|
|
|
14
14
|
/** Optional media upload id when the host configured `mediaCollection`. */
|
|
15
15
|
image?: string;
|
|
16
16
|
scope?: string;
|
|
17
|
+
/** When true, unlock rules are not enough — an approved tier request is required. */
|
|
18
|
+
requiresReview?: boolean;
|
|
17
19
|
/** Bare rule array (AND) or `{ combinator, rules }`. Seed slugs resolved to relationships. */
|
|
18
20
|
unlockRules?: Record<string, unknown>[] | {
|
|
19
21
|
combinator?: string;
|
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;AAMtC,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,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;AAmMD,2FAA2F;AAC3F,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5F;AAiCD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seed/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAMtC,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;AAmMD,2FAA2F;AAC3F,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5F;AAiCD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmLnG"}
|
package/dist/seed/index.js
CHANGED
|
@@ -221,6 +221,7 @@ export async function seedAchievements(payload, input) {
|
|
|
221
221
|
slug: tier.slug,
|
|
222
222
|
rank: tier.rank,
|
|
223
223
|
scope: tier.scope,
|
|
224
|
+
requiresReview: tier.requiresReview ?? false,
|
|
224
225
|
...(tier.icon ? { icon: tier.icon } : {}),
|
|
225
226
|
...(tier.image ? { image: tier.image } : {}),
|
|
226
227
|
unlockRules: { combinator: 'and', rules: [] },
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Shared req.context keys for grant / request side-effect guards. */
|
|
2
|
+
export declare const CTX_SKIP_GRANT_SIDE_EFFECTS = "achievementSkipGrantSideEffects";
|
|
3
|
+
export declare const CTX_CASCADING_REVOKE = "achievementCascadingRevoke";
|
|
4
|
+
export declare const CTX_SYSTEM_REQUEST = "achievementSystemRequest";
|
|
5
|
+
//# sourceMappingURL=contextFlags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextFlags.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/contextFlags.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,oCAAoC,CAAA;AAC5E,eAAO,MAAM,oBAAoB,+BAA+B,CAAA;AAChE,eAAO,MAAM,kBAAkB,6BAA6B,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Shared req.context keys for grant / request side-effect guards. */
|
|
2
|
+
export const CTX_SKIP_GRANT_SIDE_EFFECTS = 'achievementSkipGrantSideEffects';
|
|
3
|
+
export const CTX_CASCADING_REVOKE = 'achievementCascadingRevoke';
|
|
4
|
+
export const CTX_SYSTEM_REQUEST = 'achievementSystemRequest';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
export { CTX_CASCADING_REVOKE, CTX_SKIP_GRANT_SIDE_EFFECTS, CTX_SYSTEM_REQUEST } from './contextFlags';
|
|
3
|
+
/** Create a grant row only — side effects run in Grants afterChange. */
|
|
4
|
+
export declare function createGrantRow(args: {
|
|
5
|
+
req: PayloadRequest;
|
|
6
|
+
userId: string;
|
|
7
|
+
achievementId: string;
|
|
8
|
+
scopeId?: string | null;
|
|
9
|
+
note?: string | null;
|
|
10
|
+
completedAt?: string;
|
|
11
|
+
}): Promise<Record<string, unknown>>;
|
|
12
|
+
/** Ensure a pending achievement request exists (system / reconcile path). */
|
|
13
|
+
export declare function ensureAchievementRequest(args: {
|
|
14
|
+
req: PayloadRequest;
|
|
15
|
+
userId: string;
|
|
16
|
+
achievementId: string;
|
|
17
|
+
scopeId?: string | null;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
doc: Record<string, unknown>;
|
|
20
|
+
created: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Auto-grant or open a request for composed achievements whose completion rules pass.
|
|
24
|
+
*/
|
|
25
|
+
export declare function syncCompositeProgression(args: {
|
|
26
|
+
req: PayloadRequest;
|
|
27
|
+
userId: string;
|
|
28
|
+
scopeId: string | null;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
granted: number;
|
|
31
|
+
requested: number;
|
|
32
|
+
}>;
|
|
33
|
+
/** Delete grants for composed achievements that no longer pass completion rules. */
|
|
34
|
+
export declare function revokeIncompleteComposites(args: {
|
|
35
|
+
req: PayloadRequest;
|
|
36
|
+
userId: string;
|
|
37
|
+
scopeId: string | null;
|
|
38
|
+
}): Promise<number>;
|
|
39
|
+
export declare function runAfterGrantCreated(args: {
|
|
40
|
+
req: PayloadRequest;
|
|
41
|
+
userId: string;
|
|
42
|
+
achievementId: string;
|
|
43
|
+
scopeId: string | null;
|
|
44
|
+
achievementSlug?: string;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
export declare function runAfterGrantDeleted(args: {
|
|
47
|
+
req: PayloadRequest;
|
|
48
|
+
userId: string;
|
|
49
|
+
achievementId: string;
|
|
50
|
+
scopeId: string | null;
|
|
51
|
+
achievementSlug?: string;
|
|
52
|
+
cascading?: boolean;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
//# sourceMappingURL=grantSideEffects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grantSideEffects.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/grantSideEffects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAUpD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAStG,wEAAwE;AACxE,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+BnC;AAED,6EAA6E;AAC7E,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuC9D;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsElD;AAED,oFAAoF;AACpF,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChB"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { collectionOf } from '../../collections/helpers';
|
|
2
|
+
import { CTX_CASCADING_REVOKE, CTX_SYSTEM_REQUEST } from './contextFlags';
|
|
3
|
+
import { evaluateRules, ruleGroupHasProgressRequirements } from './evaluateRules';
|
|
4
|
+
import { recordLog, syncTierChangedLog } from './recordEvent';
|
|
5
|
+
import { relationId } from './relationId';
|
|
6
|
+
import { syncTierProgression } from './tierProgression';
|
|
7
|
+
import { userScopeWhere } from './where';
|
|
8
|
+
export { CTX_CASCADING_REVOKE, CTX_SKIP_GRANT_SIDE_EFFECTS, CTX_SYSTEM_REQUEST } from './contextFlags';
|
|
9
|
+
/** Create a grant row only — side effects run in Grants afterChange. */
|
|
10
|
+
export async function createGrantRow(args) {
|
|
11
|
+
const scopeId = args.scopeId ?? null;
|
|
12
|
+
const clauses = [
|
|
13
|
+
{ user: { equals: args.userId } },
|
|
14
|
+
{ achievement: { equals: args.achievementId } },
|
|
15
|
+
];
|
|
16
|
+
if (scopeId)
|
|
17
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
18
|
+
else
|
|
19
|
+
clauses.push({ scope: { exists: false } });
|
|
20
|
+
const existing = await args.req.payload.find({
|
|
21
|
+
collection: collectionOf('grants'),
|
|
22
|
+
depth: 0,
|
|
23
|
+
limit: 1,
|
|
24
|
+
overrideAccess: true,
|
|
25
|
+
req: args.req,
|
|
26
|
+
where: { and: clauses },
|
|
27
|
+
});
|
|
28
|
+
if (existing.docs[0])
|
|
29
|
+
return existing.docs[0];
|
|
30
|
+
return (await args.req.payload.create({
|
|
31
|
+
collection: collectionOf('grants'),
|
|
32
|
+
data: {
|
|
33
|
+
user: args.userId,
|
|
34
|
+
scope: scopeId ?? undefined,
|
|
35
|
+
achievement: args.achievementId,
|
|
36
|
+
completedAt: args.completedAt ?? new Date().toISOString(),
|
|
37
|
+
...(args.note ? { note: args.note } : {}),
|
|
38
|
+
},
|
|
39
|
+
overrideAccess: true,
|
|
40
|
+
req: args.req,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/** Ensure a pending achievement request exists (system / reconcile path). */
|
|
44
|
+
export async function ensureAchievementRequest(args) {
|
|
45
|
+
const scopeId = args.scopeId ?? null;
|
|
46
|
+
const clauses = [
|
|
47
|
+
{ user: { equals: args.userId } },
|
|
48
|
+
{ achievement: { equals: args.achievementId } },
|
|
49
|
+
{ status: { in: ['pending', 'approved'] } },
|
|
50
|
+
];
|
|
51
|
+
if (scopeId)
|
|
52
|
+
clauses.push({ scope: { equals: scopeId } });
|
|
53
|
+
else
|
|
54
|
+
clauses.push({ scope: { exists: false } });
|
|
55
|
+
const existing = await args.req.payload.find({
|
|
56
|
+
collection: collectionOf('achievementRequests'),
|
|
57
|
+
depth: 0,
|
|
58
|
+
limit: 1,
|
|
59
|
+
overrideAccess: true,
|
|
60
|
+
req: args.req,
|
|
61
|
+
where: { and: clauses },
|
|
62
|
+
});
|
|
63
|
+
if (existing.docs[0]) {
|
|
64
|
+
return { doc: existing.docs[0], created: false };
|
|
65
|
+
}
|
|
66
|
+
args.req.context[CTX_SYSTEM_REQUEST] = true;
|
|
67
|
+
try {
|
|
68
|
+
const doc = (await args.req.payload.create({
|
|
69
|
+
collection: collectionOf('achievementRequests'),
|
|
70
|
+
data: {
|
|
71
|
+
user: args.userId,
|
|
72
|
+
scope: scopeId ?? undefined,
|
|
73
|
+
achievement: args.achievementId,
|
|
74
|
+
status: 'pending',
|
|
75
|
+
},
|
|
76
|
+
overrideAccess: true,
|
|
77
|
+
req: args.req,
|
|
78
|
+
}));
|
|
79
|
+
return { doc, created: true };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
delete args.req.context[CTX_SYSTEM_REQUEST];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Auto-grant or open a request for composed achievements whose completion rules pass.
|
|
87
|
+
*/
|
|
88
|
+
export async function syncCompositeProgression(args) {
|
|
89
|
+
const where = args.scopeId
|
|
90
|
+
? { or: [{ scope: { equals: args.scopeId } }, { scope: { exists: false } }] }
|
|
91
|
+
: undefined;
|
|
92
|
+
const catalog = await args.req.payload.find({
|
|
93
|
+
collection: collectionOf('achievements'),
|
|
94
|
+
depth: 0,
|
|
95
|
+
limit: 500,
|
|
96
|
+
pagination: false,
|
|
97
|
+
overrideAccess: true,
|
|
98
|
+
req: args.req,
|
|
99
|
+
...(where ? { where } : {}),
|
|
100
|
+
});
|
|
101
|
+
let granted = 0;
|
|
102
|
+
let requested = 0;
|
|
103
|
+
for (const doc of catalog.docs) {
|
|
104
|
+
const def = doc;
|
|
105
|
+
if (!ruleGroupHasProgressRequirements(def.completionRules))
|
|
106
|
+
continue;
|
|
107
|
+
const complete = await evaluateRules({
|
|
108
|
+
payload: args.req.payload,
|
|
109
|
+
req: args.req,
|
|
110
|
+
rules: def.completionRules,
|
|
111
|
+
userId: args.userId,
|
|
112
|
+
scopeId: args.scopeId,
|
|
113
|
+
});
|
|
114
|
+
if (!complete)
|
|
115
|
+
continue;
|
|
116
|
+
if (def.requiresReview !== false) {
|
|
117
|
+
const { created } = await ensureAchievementRequest({
|
|
118
|
+
req: args.req,
|
|
119
|
+
userId: args.userId,
|
|
120
|
+
achievementId: String(def.id),
|
|
121
|
+
scopeId: args.scopeId,
|
|
122
|
+
});
|
|
123
|
+
if (created)
|
|
124
|
+
requested += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const before = await args.req.payload.find({
|
|
128
|
+
collection: collectionOf('grants'),
|
|
129
|
+
depth: 0,
|
|
130
|
+
limit: 1,
|
|
131
|
+
overrideAccess: true,
|
|
132
|
+
req: args.req,
|
|
133
|
+
where: {
|
|
134
|
+
and: [
|
|
135
|
+
{ user: { equals: args.userId } },
|
|
136
|
+
{ achievement: { equals: String(def.id) } },
|
|
137
|
+
...(args.scopeId
|
|
138
|
+
? [{ scope: { equals: args.scopeId } }]
|
|
139
|
+
: [{ scope: { exists: false } }]),
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
if (before.docs[0])
|
|
144
|
+
continue;
|
|
145
|
+
await createGrantRow({
|
|
146
|
+
req: args.req,
|
|
147
|
+
userId: args.userId,
|
|
148
|
+
achievementId: String(def.id),
|
|
149
|
+
scopeId: args.scopeId,
|
|
150
|
+
});
|
|
151
|
+
granted += 1;
|
|
152
|
+
}
|
|
153
|
+
return { granted, requested };
|
|
154
|
+
}
|
|
155
|
+
/** Delete grants for composed achievements that no longer pass completion rules. */
|
|
156
|
+
export async function revokeIncompleteComposites(args) {
|
|
157
|
+
const grants = await args.req.payload.find({
|
|
158
|
+
collection: collectionOf('grants'),
|
|
159
|
+
depth: 0,
|
|
160
|
+
limit: 500,
|
|
161
|
+
pagination: false,
|
|
162
|
+
overrideAccess: true,
|
|
163
|
+
req: args.req,
|
|
164
|
+
where: userScopeWhere(args.userId, args.scopeId),
|
|
165
|
+
});
|
|
166
|
+
let revoked = 0;
|
|
167
|
+
for (const grant of grants.docs) {
|
|
168
|
+
const achievementId = relationId(grant.achievement);
|
|
169
|
+
if (!achievementId)
|
|
170
|
+
continue;
|
|
171
|
+
const def = (await args.req.payload.findByID({
|
|
172
|
+
collection: collectionOf('achievements'),
|
|
173
|
+
id: achievementId,
|
|
174
|
+
depth: 0,
|
|
175
|
+
overrideAccess: true,
|
|
176
|
+
req: args.req,
|
|
177
|
+
}));
|
|
178
|
+
if (!def || !ruleGroupHasProgressRequirements(def.completionRules))
|
|
179
|
+
continue;
|
|
180
|
+
const complete = await evaluateRules({
|
|
181
|
+
payload: args.req.payload,
|
|
182
|
+
req: args.req,
|
|
183
|
+
rules: def.completionRules,
|
|
184
|
+
userId: args.userId,
|
|
185
|
+
scopeId: args.scopeId,
|
|
186
|
+
});
|
|
187
|
+
if (complete)
|
|
188
|
+
continue;
|
|
189
|
+
args.req.context[CTX_CASCADING_REVOKE] = true;
|
|
190
|
+
try {
|
|
191
|
+
await args.req.payload.delete({
|
|
192
|
+
collection: collectionOf('grants'),
|
|
193
|
+
id: String(grant.id),
|
|
194
|
+
overrideAccess: true,
|
|
195
|
+
req: args.req,
|
|
196
|
+
});
|
|
197
|
+
revoked += 1;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
delete args.req.context[CTX_CASCADING_REVOKE];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return revoked;
|
|
204
|
+
}
|
|
205
|
+
export async function runAfterGrantCreated(args) {
|
|
206
|
+
await recordLog({
|
|
207
|
+
payload: args.req.payload,
|
|
208
|
+
req: args.req,
|
|
209
|
+
userId: args.userId,
|
|
210
|
+
scopeId: args.scopeId,
|
|
211
|
+
type: 'achievement.granted',
|
|
212
|
+
data: {
|
|
213
|
+
achievement: args.achievementId,
|
|
214
|
+
...(args.achievementSlug ? { achievementSlug: args.achievementSlug } : {}),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
await syncCompositeProgression({
|
|
218
|
+
req: args.req,
|
|
219
|
+
userId: args.userId,
|
|
220
|
+
scopeId: args.scopeId,
|
|
221
|
+
});
|
|
222
|
+
await syncTierProgression({
|
|
223
|
+
req: args.req,
|
|
224
|
+
userId: args.userId,
|
|
225
|
+
scopeId: args.scopeId,
|
|
226
|
+
});
|
|
227
|
+
await syncTierChangedLog({
|
|
228
|
+
payload: args.req.payload,
|
|
229
|
+
req: args.req,
|
|
230
|
+
userId: args.userId,
|
|
231
|
+
scopeId: args.scopeId,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export async function runAfterGrantDeleted(args) {
|
|
235
|
+
await recordLog({
|
|
236
|
+
payload: args.req.payload,
|
|
237
|
+
req: args.req,
|
|
238
|
+
userId: args.userId,
|
|
239
|
+
scopeId: args.scopeId,
|
|
240
|
+
type: 'achievement.revoked',
|
|
241
|
+
data: {
|
|
242
|
+
achievement: args.achievementId,
|
|
243
|
+
...(args.achievementSlug ? { achievementSlug: args.achievementSlug } : {}),
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
if (args.cascading)
|
|
247
|
+
return;
|
|
248
|
+
await revokeIncompleteComposites({
|
|
249
|
+
req: args.req,
|
|
250
|
+
userId: args.userId,
|
|
251
|
+
scopeId: args.scopeId,
|
|
252
|
+
});
|
|
253
|
+
await syncCompositeProgression({
|
|
254
|
+
req: args.req,
|
|
255
|
+
userId: args.userId,
|
|
256
|
+
scopeId: args.scopeId,
|
|
257
|
+
});
|
|
258
|
+
await syncTierProgression({
|
|
259
|
+
req: args.req,
|
|
260
|
+
userId: args.userId,
|
|
261
|
+
scopeId: args.scopeId,
|
|
262
|
+
});
|
|
263
|
+
await syncTierChangedLog({
|
|
264
|
+
payload: args.req.payload,
|
|
265
|
+
req: args.req,
|
|
266
|
+
userId: args.userId,
|
|
267
|
+
scopeId: args.scopeId,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
@@ -11,4 +11,8 @@ export type { TierLadderProgress } from './resolveTierProgress';
|
|
|
11
11
|
export { grantAchievement, submitAchievementRequest } from './submitAchievementRequest';
|
|
12
12
|
export { reviewAchievementRequest } from './reviewAchievementRequest';
|
|
13
13
|
export { validateRuleGroup, validateRuleNode } from './validateRules';
|
|
14
|
+
export { syncCompositeProgression } from './grantSideEffects';
|
|
15
|
+
export { syncTierProgression } from './tierProgression';
|
|
16
|
+
export { reconcileProgression, reconcileUserProgression } from './reconcile';
|
|
17
|
+
export type { ReconcileProgressionResult } from './reconcile';
|
|
14
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAA;AACzH,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAA;AACzH,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAC5E,YAAY,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -7,3 +7,6 @@ export { resolveTierProgress } from './resolveTierProgress';
|
|
|
7
7
|
export { grantAchievement, submitAchievementRequest } from './submitAchievementRequest';
|
|
8
8
|
export { reviewAchievementRequest } from './reviewAchievementRequest';
|
|
9
9
|
export { validateRuleGroup, validateRuleNode } from './validateRules';
|
|
10
|
+
export { syncCompositeProgression } from './grantSideEffects';
|
|
11
|
+
export { syncTierProgression } from './tierProgression';
|
|
12
|
+
export { reconcileProgression, reconcileUserProgression } from './reconcile';
|