@dappermountain/payload-plugin-achievements 0.1.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/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/access/helpers/index.d.ts +9 -0
- package/dist/access/helpers/index.d.ts.map +1 -0
- package/dist/access/helpers/index.js +25 -0
- package/dist/access/index.d.ts +3 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/index.js +2 -0
- package/dist/access/roles/index.d.ts +15 -0
- package/dist/access/roles/index.d.ts.map +1 -0
- package/dist/access/roles/index.js +55 -0
- package/dist/admin/ActorField.d.ts +9 -0
- package/dist/admin/ActorField.d.ts.map +1 -0
- package/dist/admin/ActorField.js +19 -0
- package/dist/admin/MetricChangeField.d.ts +9 -0
- package/dist/admin/MetricChangeField.d.ts.map +1 -0
- package/dist/admin/MetricChangeField.js +21 -0
- package/dist/admin/MetricField.d.ts +9 -0
- package/dist/admin/MetricField.d.ts.map +1 -0
- package/dist/admin/MetricField.js +23 -0
- package/dist/admin/useEventTypeFlags.d.ts +7 -0
- package/dist/admin/useEventTypeFlags.d.ts.map +1 -0
- package/dist/admin/useEventTypeFlags.js +68 -0
- package/dist/catalog.d.ts +21 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +9 -0
- package/dist/collections/AchievementRequests/access/index.d.ts +3 -0
- package/dist/collections/AchievementRequests/access/index.d.ts.map +1 -0
- package/dist/collections/AchievementRequests/access/index.js +8 -0
- package/dist/collections/AchievementRequests/hooks/index.d.ts +3 -0
- package/dist/collections/AchievementRequests/hooks/index.d.ts.map +1 -0
- package/dist/collections/AchievementRequests/hooks/index.js +5 -0
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts +12 -0
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts.map +1 -0
- package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.js +105 -0
- package/dist/collections/AchievementRequests/index.d.ts +3 -0
- package/dist/collections/AchievementRequests/index.d.ts.map +1 -0
- package/dist/collections/AchievementRequests/index.js +39 -0
- package/dist/collections/Achievements/access/index.d.ts +3 -0
- package/dist/collections/Achievements/access/index.d.ts.map +1 -0
- package/dist/collections/Achievements/access/index.js +2 -0
- package/dist/collections/Achievements/hooks/index.d.ts +3 -0
- package/dist/collections/Achievements/hooks/index.d.ts.map +1 -0
- package/dist/collections/Achievements/hooks/index.js +4 -0
- package/dist/collections/Achievements/hooks/validateEligibilityRules.d.ts +3 -0
- package/dist/collections/Achievements/hooks/validateEligibilityRules.d.ts.map +1 -0
- package/dist/collections/Achievements/hooks/validateEligibilityRules.js +8 -0
- package/dist/collections/Achievements/index.d.ts +3 -0
- package/dist/collections/Achievements/index.d.ts.map +1 -0
- package/dist/collections/Achievements/index.js +62 -0
- package/dist/collections/EventTypes/access/index.d.ts +12 -0
- package/dist/collections/EventTypes/access/index.d.ts.map +1 -0
- package/dist/collections/EventTypes/access/index.js +2 -0
- package/dist/collections/EventTypes/hooks/index.d.ts +3 -0
- package/dist/collections/EventTypes/hooks/index.d.ts.map +1 -0
- package/dist/collections/EventTypes/hooks/index.js +6 -0
- package/dist/collections/EventTypes/index.d.ts +3 -0
- package/dist/collections/EventTypes/index.d.ts.map +1 -0
- package/dist/collections/EventTypes/index.js +67 -0
- package/dist/collections/Grants/access/index.d.ts +3 -0
- package/dist/collections/Grants/access/index.d.ts.map +1 -0
- package/dist/collections/Grants/access/index.js +8 -0
- package/dist/collections/Grants/hooks/ensureUniqueGrant.d.ts +4 -0
- package/dist/collections/Grants/hooks/ensureUniqueGrant.d.ts.map +1 -0
- package/dist/collections/Grants/hooks/ensureUniqueGrant.js +34 -0
- package/dist/collections/Grants/hooks/index.d.ts +3 -0
- package/dist/collections/Grants/hooks/index.d.ts.map +1 -0
- package/dist/collections/Grants/hooks/index.js +7 -0
- package/dist/collections/Grants/hooks/setGrantTitle.d.ts +4 -0
- package/dist/collections/Grants/hooks/setGrantTitle.d.ts.map +1 -0
- package/dist/collections/Grants/hooks/setGrantTitle.js +47 -0
- package/dist/collections/Grants/index.d.ts +3 -0
- package/dist/collections/Grants/index.d.ts.map +1 -0
- package/dist/collections/Grants/index.js +62 -0
- package/dist/collections/Logs/access/index.d.ts +3 -0
- package/dist/collections/Logs/access/index.d.ts.map +1 -0
- package/dist/collections/Logs/access/index.js +8 -0
- package/dist/collections/Logs/fields/validators.d.ts +5 -0
- package/dist/collections/Logs/fields/validators.d.ts.map +1 -0
- package/dist/collections/Logs/fields/validators.js +44 -0
- package/dist/collections/Logs/hooks/ensureTypeRequirements.d.ts +3 -0
- package/dist/collections/Logs/hooks/ensureTypeRequirements.d.ts.map +1 -0
- package/dist/collections/Logs/hooks/ensureTypeRequirements.js +35 -0
- package/dist/collections/Logs/hooks/index.d.ts +3 -0
- package/dist/collections/Logs/hooks/index.d.ts.map +1 -0
- package/dist/collections/Logs/hooks/index.js +4 -0
- package/dist/collections/Logs/index.d.ts +4 -0
- package/dist/collections/Logs/index.d.ts.map +1 -0
- package/dist/collections/Logs/index.js +113 -0
- package/dist/collections/Metrics/access/index.d.ts +12 -0
- package/dist/collections/Metrics/access/index.d.ts.map +1 -0
- package/dist/collections/Metrics/access/index.js +2 -0
- package/dist/collections/Metrics/hooks/index.d.ts +3 -0
- package/dist/collections/Metrics/hooks/index.d.ts.map +1 -0
- package/dist/collections/Metrics/hooks/index.js +6 -0
- package/dist/collections/Metrics/index.d.ts +3 -0
- package/dist/collections/Metrics/index.d.ts.map +1 -0
- package/dist/collections/Metrics/index.js +35 -0
- package/dist/collections/Tiers/access/index.d.ts +3 -0
- package/dist/collections/Tiers/access/index.d.ts.map +1 -0
- package/dist/collections/Tiers/access/index.js +2 -0
- package/dist/collections/Tiers/hooks/index.d.ts +3 -0
- package/dist/collections/Tiers/hooks/index.d.ts.map +1 -0
- package/dist/collections/Tiers/hooks/index.js +4 -0
- package/dist/collections/Tiers/hooks/validateUnlockRules.d.ts +3 -0
- package/dist/collections/Tiers/hooks/validateUnlockRules.d.ts.map +1 -0
- package/dist/collections/Tiers/hooks/validateUnlockRules.js +7 -0
- package/dist/collections/Tiers/index.d.ts +3 -0
- package/dist/collections/Tiers/index.d.ts.map +1 -0
- package/dist/collections/Tiers/index.js +81 -0
- package/dist/collections/access/definitions.d.ts +5 -0
- package/dist/collections/access/definitions.d.ts.map +1 -0
- package/dist/collections/access/definitions.js +11 -0
- package/dist/collections/applyOverrides.d.ts +9 -0
- package/dist/collections/applyOverrides.d.ts.map +1 -0
- package/dist/collections/applyOverrides.js +34 -0
- package/dist/collections/fields/catalogFilter.d.ts +8 -0
- package/dist/collections/fields/catalogFilter.d.ts.map +1 -0
- package/dist/collections/fields/catalogFilter.js +10 -0
- package/dist/collections/fields/index.d.ts +6 -0
- package/dist/collections/fields/index.d.ts.map +1 -0
- package/dist/collections/fields/index.js +5 -0
- package/dist/collections/fields/localizedText.d.ts +10 -0
- package/dist/collections/fields/localizedText.d.ts.map +1 -0
- package/dist/collections/fields/localizedText.js +20 -0
- package/dist/collections/fields/ruleFields.d.ts +8 -0
- package/dist/collections/fields/ruleFields.d.ts.map +1 -0
- package/dist/collections/fields/ruleFields.js +123 -0
- package/dist/collections/fields/scopeField.d.ts +7 -0
- package/dist/collections/fields/scopeField.d.ts.map +1 -0
- package/dist/collections/fields/scopeField.js +22 -0
- package/dist/collections/fields/userField.d.ts +4 -0
- package/dist/collections/fields/userField.d.ts.map +1 -0
- package/dist/collections/fields/userField.js +12 -0
- package/dist/collections/fields/usersAchievementsGroup.d.ts +8 -0
- package/dist/collections/fields/usersAchievementsGroup.d.ts.map +1 -0
- package/dist/collections/fields/usersAchievementsGroup.js +36 -0
- package/dist/collections/helpers.d.ts +16 -0
- package/dist/collections/helpers.d.ts.map +1 -0
- package/dist/collections/helpers.js +31 -0
- package/dist/collections/hooks/eachRuleLeaf.d.ts +3 -0
- package/dist/collections/hooks/eachRuleLeaf.d.ts.map +1 -0
- package/dist/collections/hooks/eachRuleLeaf.js +20 -0
- package/dist/collections/hooks/index.d.ts +4 -0
- package/dist/collections/hooks/index.d.ts.map +1 -0
- package/dist/collections/hooks/index.js +3 -0
- package/dist/collections/hooks/lockSystemCatalogFields.d.ts +7 -0
- package/dist/collections/hooks/lockSystemCatalogFields.d.ts.map +1 -0
- package/dist/collections/hooks/lockSystemCatalogFields.js +15 -0
- package/dist/collections/hooks/preventCatalogDelete.d.ts +5 -0
- package/dist/collections/hooks/preventCatalogDelete.d.ts.map +1 -0
- package/dist/collections/hooks/preventCatalogDelete.js +89 -0
- package/dist/collections/index.d.ts +4 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +23 -0
- package/dist/collections/slugs.d.ts +22 -0
- package/dist/collections/slugs.d.ts.map +1 -0
- package/dist/collections/slugs.js +51 -0
- package/dist/defaults.d.ts +21 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +31 -0
- package/dist/enabled.d.ts +4 -0
- package/dist/enabled.d.ts.map +1 -0
- package/dist/enabled.js +9 -0
- package/dist/endpoints/index.d.ts +4 -0
- package/dist/endpoints/index.d.ts.map +1 -0
- package/dist/endpoints/index.js +6 -0
- package/dist/endpoints/me.d.ts +7 -0
- package/dist/endpoints/me.d.ts.map +1 -0
- package/dist/endpoints/me.js +50 -0
- package/dist/exports/client.d.ts +4 -0
- package/dist/exports/client.d.ts.map +1 -0
- package/dist/exports/client.js +3 -0
- package/dist/extensions/registry.d.ts +3 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +12 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/options-store.d.ts +7 -0
- package/dist/options-store.d.ts.map +1 -0
- package/dist/options-store.js +15 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/seed/index.d.ts +63 -0
- package/dist/seed/index.d.ts.map +1 -0
- package/dist/seed/index.js +375 -0
- package/dist/services/achievement/buildCatalogProgressGroups.d.ts +37 -0
- package/dist/services/achievement/buildCatalogProgressGroups.d.ts.map +1 -0
- package/dist/services/achievement/buildCatalogProgressGroups.js +70 -0
- package/dist/services/achievement/builtInRules.d.ts +3 -0
- package/dist/services/achievement/builtInRules.d.ts.map +1 -0
- package/dist/services/achievement/builtInRules.js +280 -0
- package/dist/services/achievement/evaluateRules.d.ts +48 -0
- package/dist/services/achievement/evaluateRules.d.ts.map +1 -0
- package/dist/services/achievement/evaluateRules.js +206 -0
- package/dist/services/achievement/eventTypeRequiresActor.d.ts +24 -0
- package/dist/services/achievement/eventTypeRequiresActor.d.ts.map +1 -0
- package/dist/services/achievement/eventTypeRequiresActor.js +46 -0
- package/dist/services/achievement/getUserProgress.d.ts +53 -0
- package/dist/services/achievement/getUserProgress.d.ts.map +1 -0
- package/dist/services/achievement/getUserProgress.js +163 -0
- package/dist/services/achievement/index.d.ts +14 -0
- package/dist/services/achievement/index.d.ts.map +1 -0
- package/dist/services/achievement/index.js +9 -0
- package/dist/services/achievement/recordEvent.d.ts +41 -0
- package/dist/services/achievement/recordEvent.d.ts.map +1 -0
- package/dist/services/achievement/recordEvent.js +126 -0
- package/dist/services/achievement/relationId.d.ts +3 -0
- package/dist/services/achievement/relationId.d.ts.map +1 -0
- package/dist/services/achievement/relationId.js +15 -0
- package/dist/services/achievement/resolveCatalog.d.ts +14 -0
- package/dist/services/achievement/resolveCatalog.d.ts.map +1 -0
- package/dist/services/achievement/resolveCatalog.js +34 -0
- package/dist/services/achievement/resolveCurrentTier.d.ts +21 -0
- package/dist/services/achievement/resolveCurrentTier.d.ts.map +1 -0
- package/dist/services/achievement/resolveCurrentTier.js +51 -0
- package/dist/services/achievement/resolveTierProgress.d.ts +35 -0
- package/dist/services/achievement/resolveTierProgress.d.ts.map +1 -0
- package/dist/services/achievement/resolveTierProgress.js +99 -0
- package/dist/services/achievement/reviewAchievementRequest.d.ts +11 -0
- package/dist/services/achievement/reviewAchievementRequest.d.ts.map +1 -0
- package/dist/services/achievement/reviewAchievementRequest.js +37 -0
- package/dist/services/achievement/submitAchievementRequest.d.ts +21 -0
- package/dist/services/achievement/submitAchievementRequest.d.ts.map +1 -0
- package/dist/services/achievement/submitAchievementRequest.js +126 -0
- package/dist/services/achievement/validateRules.d.ts +11 -0
- package/dist/services/achievement/validateRules.d.ts.map +1 -0
- package/dist/services/achievement/validateRules.js +74 -0
- package/dist/services/achievement/where.d.ts +9 -0
- package/dist/services/achievement/where.d.ts.map +1 -0
- package/dist/services/achievement/where.js +23 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { ENGINE_EVENT_TYPES, ENGINE_METRICS } from '../catalog';
|
|
2
|
+
import { collectionOf } from '../collections/helpers';
|
|
3
|
+
import { normalizeRuleGroup } from '../services/achievement/evaluateRules';
|
|
4
|
+
function localizedParts(value) {
|
|
5
|
+
if (value == null)
|
|
6
|
+
return {};
|
|
7
|
+
if (typeof value === 'string')
|
|
8
|
+
return { en: value };
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const [locale, text] of Object.entries(value)) {
|
|
11
|
+
if (typeof text === 'string' && text.length > 0)
|
|
12
|
+
out[locale] = text;
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
async function writeLocalizedFields(payload, args) {
|
|
17
|
+
const nameParts = localizedParts(args.fields.name);
|
|
18
|
+
const descriptionParts = localizedParts(args.fields.description);
|
|
19
|
+
const base = args.baseData ?? {};
|
|
20
|
+
const locales = new Set([...Object.keys(nameParts), ...Object.keys(descriptionParts)]);
|
|
21
|
+
// Default locale first so other locales can fall back cleanly.
|
|
22
|
+
const orderedLocales = ['en', ...[...locales].filter((locale) => locale !== 'en')];
|
|
23
|
+
for (const locale of orderedLocales) {
|
|
24
|
+
const name = nameParts[locale];
|
|
25
|
+
const description = descriptionParts[locale];
|
|
26
|
+
if (name == null && description == null && locale !== 'en')
|
|
27
|
+
continue;
|
|
28
|
+
await payload.update({
|
|
29
|
+
collection: args.collection,
|
|
30
|
+
id: args.id,
|
|
31
|
+
data: {
|
|
32
|
+
...(locale === 'en' ? base : {}),
|
|
33
|
+
...(name != null ? { name } : {}),
|
|
34
|
+
...(description != null ? { description } : {}),
|
|
35
|
+
},
|
|
36
|
+
locale: locale,
|
|
37
|
+
overrideAccess: true,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function findIdBySlug(payload, key, slug) {
|
|
42
|
+
const result = await payload.find({
|
|
43
|
+
collection: collectionOf(key),
|
|
44
|
+
depth: 0,
|
|
45
|
+
limit: 1,
|
|
46
|
+
overrideAccess: true,
|
|
47
|
+
where: { slug: { equals: slug } },
|
|
48
|
+
});
|
|
49
|
+
const id = result.docs[0]?.id;
|
|
50
|
+
return typeof id === 'string' ? id : null;
|
|
51
|
+
}
|
|
52
|
+
async function upsertCatalog(payload, key, row) {
|
|
53
|
+
const existing = await findIdBySlug(payload, key, row.slug);
|
|
54
|
+
const nameParts = localizedParts(row.name);
|
|
55
|
+
const data = {
|
|
56
|
+
name: nameParts.en ?? row.slug,
|
|
57
|
+
slug: row.slug,
|
|
58
|
+
scope: row.scope,
|
|
59
|
+
system: row.system ?? false,
|
|
60
|
+
...(key === 'eventTypes'
|
|
61
|
+
? {
|
|
62
|
+
requiresActor: row.requiresActor ?? false,
|
|
63
|
+
requiresMetric: row.requiresMetric ?? false,
|
|
64
|
+
}
|
|
65
|
+
: {}),
|
|
66
|
+
};
|
|
67
|
+
let id = existing;
|
|
68
|
+
if (existing) {
|
|
69
|
+
await payload.update({
|
|
70
|
+
collection: collectionOf(key),
|
|
71
|
+
id: existing,
|
|
72
|
+
data,
|
|
73
|
+
locale: 'en',
|
|
74
|
+
overrideAccess: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const created = await payload.create({
|
|
79
|
+
collection: collectionOf(key),
|
|
80
|
+
data,
|
|
81
|
+
locale: 'en',
|
|
82
|
+
overrideAccess: true,
|
|
83
|
+
});
|
|
84
|
+
id = String(created.id);
|
|
85
|
+
}
|
|
86
|
+
if (id) {
|
|
87
|
+
const extraLocales = Object.entries(nameParts).filter(([locale]) => locale !== 'en');
|
|
88
|
+
for (const [locale, name] of extraLocales) {
|
|
89
|
+
await payload.update({
|
|
90
|
+
collection: collectionOf(key),
|
|
91
|
+
id,
|
|
92
|
+
data: { name },
|
|
93
|
+
locale: locale,
|
|
94
|
+
overrideAccess: true,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function resolveRuleNode(payload, rule) {
|
|
100
|
+
const next = { ...rule };
|
|
101
|
+
if (next.type === 'group' && Array.isArray(next.rules)) {
|
|
102
|
+
next.rules = await Promise.all(next.rules.map((child) => resolveRuleNode(payload, child)));
|
|
103
|
+
return next;
|
|
104
|
+
}
|
|
105
|
+
if (next.type === 'tier-at-least' && !next.tier && typeof next.tierSlug === 'string') {
|
|
106
|
+
const id = await findIdBySlug(payload, 'tiers', next.tierSlug);
|
|
107
|
+
if (id) {
|
|
108
|
+
next.tier = id;
|
|
109
|
+
delete next.tierSlug;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (next.type === 'achievement-complete' &&
|
|
113
|
+
!next.achievement &&
|
|
114
|
+
typeof next.achievementSlug === 'string') {
|
|
115
|
+
const id = await findIdBySlug(payload, 'achievements', next.achievementSlug);
|
|
116
|
+
if (id) {
|
|
117
|
+
next.achievement = id;
|
|
118
|
+
delete next.achievementSlug;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (next.type === 'event-count') {
|
|
122
|
+
const slug = typeof next.eventTypeSlug === 'string'
|
|
123
|
+
? next.eventTypeSlug
|
|
124
|
+
: typeof next.eventType === 'string'
|
|
125
|
+
? next.eventType
|
|
126
|
+
: '';
|
|
127
|
+
if (slug) {
|
|
128
|
+
const id = await findIdBySlug(payload, 'eventTypes', slug);
|
|
129
|
+
if (id) {
|
|
130
|
+
next.eventType = id;
|
|
131
|
+
delete next.eventTypeSlug;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (next.type === 'metric-minimum') {
|
|
136
|
+
const slug = typeof next.metricSlug === 'string' ? next.metricSlug : typeof next.metric === 'string' ? next.metric : '';
|
|
137
|
+
if (slug) {
|
|
138
|
+
const id = await findIdBySlug(payload, 'metrics', slug);
|
|
139
|
+
if (id) {
|
|
140
|
+
next.metric = id;
|
|
141
|
+
delete next.metricSlug;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return next;
|
|
146
|
+
}
|
|
147
|
+
async function resolveRuleGroup(payload, input) {
|
|
148
|
+
const group = normalizeRuleGroup(input);
|
|
149
|
+
return {
|
|
150
|
+
combinator: group.combinator,
|
|
151
|
+
rules: await Promise.all(group.rules.map((rule) => resolveRuleNode(payload, rule))),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Upsert engine event types + metrics (`system: true`). English (default locale) only. */
|
|
155
|
+
export async function seedAchievementCatalog(payload, scope) {
|
|
156
|
+
await renameEngineEventTypeSlug(payload, 'metric.change', 'metric.delta', 'Metric delta');
|
|
157
|
+
for (const row of ENGINE_EVENT_TYPES) {
|
|
158
|
+
await upsertCatalog(payload, 'eventTypes', {
|
|
159
|
+
...row,
|
|
160
|
+
name: row.name,
|
|
161
|
+
scope,
|
|
162
|
+
system: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
for (const row of ENGINE_METRICS) {
|
|
166
|
+
await upsertCatalog(payload, 'metrics', {
|
|
167
|
+
...row,
|
|
168
|
+
name: row.name,
|
|
169
|
+
scope,
|
|
170
|
+
system: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Rename a leftover engine event-type slug in place (preserves relationship ids). */
|
|
175
|
+
async function renameEngineEventTypeSlug(payload, fromSlug, toSlug, name) {
|
|
176
|
+
const fromId = await findIdBySlug(payload, 'eventTypes', fromSlug);
|
|
177
|
+
if (!fromId)
|
|
178
|
+
return;
|
|
179
|
+
if (await findIdBySlug(payload, 'eventTypes', toSlug))
|
|
180
|
+
return;
|
|
181
|
+
const nameParts = localizedParts(name);
|
|
182
|
+
await payload.update({
|
|
183
|
+
collection: collectionOf('eventTypes'),
|
|
184
|
+
id: fromId,
|
|
185
|
+
data: { slug: toSlug, name: nameParts.en ?? toSlug, system: true },
|
|
186
|
+
locale: 'en',
|
|
187
|
+
overrideAccess: true,
|
|
188
|
+
});
|
|
189
|
+
for (const [locale, text] of Object.entries(nameParts)) {
|
|
190
|
+
if (locale === 'en' || !text)
|
|
191
|
+
continue;
|
|
192
|
+
await payload.update({
|
|
193
|
+
collection: collectionOf('eventTypes'),
|
|
194
|
+
id: fromId,
|
|
195
|
+
data: { name: text },
|
|
196
|
+
locale: locale,
|
|
197
|
+
overrideAccess: true,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Idempotent upsert of achievement definitions by slug. Host supplies its own catalog and ladder data.
|
|
203
|
+
*/
|
|
204
|
+
export async function seedAchievements(payload, input) {
|
|
205
|
+
for (const row of input.eventTypes ?? []) {
|
|
206
|
+
await upsertCatalog(payload, 'eventTypes', row);
|
|
207
|
+
}
|
|
208
|
+
for (const row of input.metrics ?? []) {
|
|
209
|
+
await upsertCatalog(payload, 'metrics', row);
|
|
210
|
+
}
|
|
211
|
+
for (const tier of input.tiers ?? []) {
|
|
212
|
+
const existing = await payload.find({
|
|
213
|
+
collection: collectionOf('tiers'),
|
|
214
|
+
depth: 0,
|
|
215
|
+
limit: 1,
|
|
216
|
+
overrideAccess: true,
|
|
217
|
+
where: { slug: { equals: tier.slug } },
|
|
218
|
+
});
|
|
219
|
+
const nameParts = localizedParts(tier.name);
|
|
220
|
+
const baseData = {
|
|
221
|
+
slug: tier.slug,
|
|
222
|
+
rank: tier.rank,
|
|
223
|
+
scope: tier.scope,
|
|
224
|
+
...(tier.icon ? { icon: tier.icon } : {}),
|
|
225
|
+
...(tier.image ? { image: tier.image } : {}),
|
|
226
|
+
unlockRules: { combinator: 'and', rules: [] },
|
|
227
|
+
};
|
|
228
|
+
let id = existing.docs[0]?.id;
|
|
229
|
+
if (id) {
|
|
230
|
+
await writeLocalizedFields(payload, {
|
|
231
|
+
collection: collectionOf('tiers'),
|
|
232
|
+
id: String(id),
|
|
233
|
+
fields: { name: tier.name, description: tier.description },
|
|
234
|
+
baseData,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const created = await payload.create({
|
|
239
|
+
collection: collectionOf('tiers'),
|
|
240
|
+
data: {
|
|
241
|
+
...baseData,
|
|
242
|
+
name: nameParts.en ?? tier.slug,
|
|
243
|
+
...(localizedParts(tier.description).en != null
|
|
244
|
+
? { description: localizedParts(tier.description).en }
|
|
245
|
+
: {}),
|
|
246
|
+
},
|
|
247
|
+
locale: 'en',
|
|
248
|
+
overrideAccess: true,
|
|
249
|
+
});
|
|
250
|
+
id = String(created.id);
|
|
251
|
+
await writeLocalizedFields(payload, {
|
|
252
|
+
collection: collectionOf('tiers'),
|
|
253
|
+
id,
|
|
254
|
+
fields: { name: tier.name, description: tier.description },
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const achievement of input.achievements ?? []) {
|
|
259
|
+
const existing = await payload.find({
|
|
260
|
+
collection: collectionOf('achievements'),
|
|
261
|
+
depth: 0,
|
|
262
|
+
limit: 1,
|
|
263
|
+
overrideAccess: true,
|
|
264
|
+
where: { slug: { equals: achievement.slug } },
|
|
265
|
+
});
|
|
266
|
+
const nameParts = localizedParts(achievement.name);
|
|
267
|
+
const descriptionParts = localizedParts(achievement.description);
|
|
268
|
+
const eligibilityRules = await resolveRuleGroup(payload, achievement.eligibilityRules);
|
|
269
|
+
const completionRules = { combinator: 'and', rules: [] };
|
|
270
|
+
const baseData = {
|
|
271
|
+
slug: achievement.slug,
|
|
272
|
+
scope: achievement.scope,
|
|
273
|
+
requiresReview: achievement.requiresReview ?? true,
|
|
274
|
+
eligibilityRules,
|
|
275
|
+
completionRules,
|
|
276
|
+
};
|
|
277
|
+
const existingId = existing.docs[0]?.id;
|
|
278
|
+
if (existingId) {
|
|
279
|
+
await writeLocalizedFields(payload, {
|
|
280
|
+
collection: collectionOf('achievements'),
|
|
281
|
+
id: String(existingId),
|
|
282
|
+
fields: { name: achievement.name, description: achievement.description },
|
|
283
|
+
baseData,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const created = await payload.create({
|
|
288
|
+
collection: collectionOf('achievements'),
|
|
289
|
+
data: {
|
|
290
|
+
...baseData,
|
|
291
|
+
name: nameParts.en ?? achievement.slug,
|
|
292
|
+
...(descriptionParts.en != null ? { description: descriptionParts.en } : {}),
|
|
293
|
+
},
|
|
294
|
+
locale: 'en',
|
|
295
|
+
overrideAccess: true,
|
|
296
|
+
});
|
|
297
|
+
const id = String(created.id);
|
|
298
|
+
await writeLocalizedFields(payload, {
|
|
299
|
+
collection: collectionOf('achievements'),
|
|
300
|
+
id,
|
|
301
|
+
fields: { name: achievement.name, description: achievement.description },
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const achievement of input.achievements ?? []) {
|
|
306
|
+
if (!achievement.completionRules)
|
|
307
|
+
continue;
|
|
308
|
+
const hasRules = Array.isArray(achievement.completionRules)
|
|
309
|
+
? achievement.completionRules.length > 0
|
|
310
|
+
: Boolean(achievement.completionRules.rules?.length);
|
|
311
|
+
if (!hasRules)
|
|
312
|
+
continue;
|
|
313
|
+
const existing = await payload.find({
|
|
314
|
+
collection: collectionOf('achievements'),
|
|
315
|
+
depth: 0,
|
|
316
|
+
limit: 1,
|
|
317
|
+
overrideAccess: true,
|
|
318
|
+
where: { slug: { equals: achievement.slug } },
|
|
319
|
+
});
|
|
320
|
+
const doc = existing.docs[0];
|
|
321
|
+
if (!doc)
|
|
322
|
+
continue;
|
|
323
|
+
await payload.update({
|
|
324
|
+
collection: collectionOf('achievements'),
|
|
325
|
+
id: doc.id,
|
|
326
|
+
data: {
|
|
327
|
+
completionRules: await resolveRuleGroup(payload, achievement.completionRules),
|
|
328
|
+
},
|
|
329
|
+
overrideAccess: true,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
// Unlock rules can reference achievements — apply after those rows exist.
|
|
333
|
+
for (const tier of input.tiers ?? []) {
|
|
334
|
+
if (!tier.unlockRules)
|
|
335
|
+
continue;
|
|
336
|
+
const hasRules = Array.isArray(tier.unlockRules)
|
|
337
|
+
? tier.unlockRules.length > 0
|
|
338
|
+
: Boolean(tier.unlockRules.rules?.length);
|
|
339
|
+
if (!hasRules)
|
|
340
|
+
continue;
|
|
341
|
+
const existing = await payload.find({
|
|
342
|
+
collection: collectionOf('tiers'),
|
|
343
|
+
depth: 0,
|
|
344
|
+
limit: 1,
|
|
345
|
+
overrideAccess: true,
|
|
346
|
+
where: { slug: { equals: tier.slug } },
|
|
347
|
+
});
|
|
348
|
+
const doc = existing.docs[0];
|
|
349
|
+
if (!doc)
|
|
350
|
+
continue;
|
|
351
|
+
await payload.update({
|
|
352
|
+
collection: collectionOf('tiers'),
|
|
353
|
+
id: doc.id,
|
|
354
|
+
data: {
|
|
355
|
+
unlockRules: await resolveRuleGroup(payload, tier.unlockRules),
|
|
356
|
+
},
|
|
357
|
+
overrideAccess: true,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
for (const slug of input.removeAchievementSlugs ?? []) {
|
|
361
|
+
const id = await findIdBySlug(payload, 'achievements', slug);
|
|
362
|
+
if (!id)
|
|
363
|
+
continue;
|
|
364
|
+
try {
|
|
365
|
+
await payload.delete({
|
|
366
|
+
collection: collectionOf('achievements'),
|
|
367
|
+
id,
|
|
368
|
+
overrideAccess: true,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
payload.logger.warn(`Could not remove obsolete achievement slug "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type CatalogProgressItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
slug?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
completedAt?: string | null;
|
|
7
|
+
earned: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type CatalogProgressGroup = {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
slug?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
items: CatalogProgressItem[];
|
|
15
|
+
};
|
|
16
|
+
type CatalogDoc = {
|
|
17
|
+
id: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
slug?: string;
|
|
20
|
+
description?: string | null;
|
|
21
|
+
completionRules?: unknown;
|
|
22
|
+
};
|
|
23
|
+
type GrantDoc = {
|
|
24
|
+
id: string;
|
|
25
|
+
completedAt?: string | null;
|
|
26
|
+
achievement?: unknown;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Groups catalog achievements by composite parents (`completionRules`).
|
|
30
|
+
* Children follow completion-rule order. Unearned children stay in the list with `earned: false`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildCatalogProgressGroups(args: {
|
|
33
|
+
achievements: CatalogDoc[];
|
|
34
|
+
grants: GrantDoc[];
|
|
35
|
+
}): CatalogProgressGroup[];
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=buildCatalogProgressGroups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildCatalogProgressGroups.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/buildCatalogProgressGroups.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAC7B,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,KAAK,QAAQ,GAAG;IACd,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAWD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,YAAY,EAAE,UAAU,EAAE,CAAA;IAC1B,MAAM,EAAE,QAAQ,EAAE,CAAA;CACnB,GAAG,oBAAoB,EAAE,CA2DzB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { relationId } from './relationId';
|
|
2
|
+
import { collectAchievementCompleteRefs } from './evaluateRules';
|
|
3
|
+
function grantKey(achievement) {
|
|
4
|
+
const id = relationId(achievement);
|
|
5
|
+
const slug = achievement && typeof achievement === 'object' && 'slug' in achievement
|
|
6
|
+
? String(achievement.slug ?? '')
|
|
7
|
+
: '';
|
|
8
|
+
return { id, slug };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Groups catalog achievements by composite parents (`completionRules`).
|
|
12
|
+
* Children follow completion-rule order. Unearned children stay in the list with `earned: false`.
|
|
13
|
+
*/
|
|
14
|
+
export function buildCatalogProgressGroups(args) {
|
|
15
|
+
const grantsById = new Map();
|
|
16
|
+
const grantsBySlug = new Map();
|
|
17
|
+
for (const grant of args.grants) {
|
|
18
|
+
const key = grantKey(grant.achievement);
|
|
19
|
+
if (key.id)
|
|
20
|
+
grantsById.set(key.id, grant);
|
|
21
|
+
if (key.slug)
|
|
22
|
+
grantsBySlug.set(key.slug, grant);
|
|
23
|
+
}
|
|
24
|
+
const byId = new Map(args.achievements.map((doc) => [doc.id, doc]));
|
|
25
|
+
const bySlug = new Map(args.achievements
|
|
26
|
+
.filter((doc) => doc.slug)
|
|
27
|
+
.map((doc) => [String(doc.slug), doc]));
|
|
28
|
+
const toItem = (doc) => {
|
|
29
|
+
const grant = grantsById.get(doc.id) ?? (doc.slug ? grantsBySlug.get(doc.slug) : undefined);
|
|
30
|
+
return {
|
|
31
|
+
id: doc.id,
|
|
32
|
+
earned: Boolean(grant),
|
|
33
|
+
...(typeof doc.name === 'string' && doc.name ? { name: doc.name } : {}),
|
|
34
|
+
...(typeof doc.slug === 'string' && doc.slug ? { slug: doc.slug } : {}),
|
|
35
|
+
...(typeof doc.description === 'string' && doc.description
|
|
36
|
+
? { description: doc.description }
|
|
37
|
+
: {}),
|
|
38
|
+
completedAt: grant?.completedAt ?? null,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const parents = [];
|
|
42
|
+
for (const doc of args.achievements) {
|
|
43
|
+
const refs = collectAchievementCompleteRefs(doc.completionRules);
|
|
44
|
+
if (refs.length === 0)
|
|
45
|
+
continue;
|
|
46
|
+
parents.push(doc);
|
|
47
|
+
}
|
|
48
|
+
const groups = parents.map((parent) => {
|
|
49
|
+
const refs = collectAchievementCompleteRefs(parent.completionRules);
|
|
50
|
+
const items = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const ref of refs) {
|
|
53
|
+
const child = (ref.id && byId.get(ref.id)) || (ref.slug ? bySlug.get(ref.slug) : undefined);
|
|
54
|
+
if (!child || seen.has(child.id))
|
|
55
|
+
continue;
|
|
56
|
+
seen.add(child.id);
|
|
57
|
+
items.push(toItem(child));
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
id: parent.id,
|
|
61
|
+
title: parent.name || parent.slug || parent.id,
|
|
62
|
+
...(parent.slug ? { slug: parent.slug } : {}),
|
|
63
|
+
...(typeof parent.description === 'string' && parent.description
|
|
64
|
+
? { description: parent.description }
|
|
65
|
+
: {}),
|
|
66
|
+
items,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
return groups;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtInRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/builtInRules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,aAAa,CAAA;AA2K/E,eAAO,MAAM,gBAAgB,EAAE,mBAAmB,EAiJjD,CAAA"}
|