@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.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/access/helpers/index.d.ts +9 -0
  4. package/dist/access/helpers/index.d.ts.map +1 -0
  5. package/dist/access/helpers/index.js +25 -0
  6. package/dist/access/index.d.ts +3 -0
  7. package/dist/access/index.d.ts.map +1 -0
  8. package/dist/access/index.js +2 -0
  9. package/dist/access/roles/index.d.ts +15 -0
  10. package/dist/access/roles/index.d.ts.map +1 -0
  11. package/dist/access/roles/index.js +55 -0
  12. package/dist/admin/ActorField.d.ts +9 -0
  13. package/dist/admin/ActorField.d.ts.map +1 -0
  14. package/dist/admin/ActorField.js +19 -0
  15. package/dist/admin/MetricChangeField.d.ts +9 -0
  16. package/dist/admin/MetricChangeField.d.ts.map +1 -0
  17. package/dist/admin/MetricChangeField.js +21 -0
  18. package/dist/admin/MetricField.d.ts +9 -0
  19. package/dist/admin/MetricField.d.ts.map +1 -0
  20. package/dist/admin/MetricField.js +23 -0
  21. package/dist/admin/useEventTypeFlags.d.ts +7 -0
  22. package/dist/admin/useEventTypeFlags.d.ts.map +1 -0
  23. package/dist/admin/useEventTypeFlags.js +68 -0
  24. package/dist/catalog.d.ts +21 -0
  25. package/dist/catalog.d.ts.map +1 -0
  26. package/dist/catalog.js +9 -0
  27. package/dist/collections/AchievementRequests/access/index.d.ts +3 -0
  28. package/dist/collections/AchievementRequests/access/index.d.ts.map +1 -0
  29. package/dist/collections/AchievementRequests/access/index.js +8 -0
  30. package/dist/collections/AchievementRequests/hooks/index.d.ts +3 -0
  31. package/dist/collections/AchievementRequests/hooks/index.d.ts.map +1 -0
  32. package/dist/collections/AchievementRequests/hooks/index.js +5 -0
  33. package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts +12 -0
  34. package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts.map +1 -0
  35. package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.js +105 -0
  36. package/dist/collections/AchievementRequests/index.d.ts +3 -0
  37. package/dist/collections/AchievementRequests/index.d.ts.map +1 -0
  38. package/dist/collections/AchievementRequests/index.js +39 -0
  39. package/dist/collections/Achievements/access/index.d.ts +3 -0
  40. package/dist/collections/Achievements/access/index.d.ts.map +1 -0
  41. package/dist/collections/Achievements/access/index.js +2 -0
  42. package/dist/collections/Achievements/hooks/index.d.ts +3 -0
  43. package/dist/collections/Achievements/hooks/index.d.ts.map +1 -0
  44. package/dist/collections/Achievements/hooks/index.js +4 -0
  45. package/dist/collections/Achievements/hooks/validateEligibilityRules.d.ts +3 -0
  46. package/dist/collections/Achievements/hooks/validateEligibilityRules.d.ts.map +1 -0
  47. package/dist/collections/Achievements/hooks/validateEligibilityRules.js +8 -0
  48. package/dist/collections/Achievements/index.d.ts +3 -0
  49. package/dist/collections/Achievements/index.d.ts.map +1 -0
  50. package/dist/collections/Achievements/index.js +62 -0
  51. package/dist/collections/EventTypes/access/index.d.ts +12 -0
  52. package/dist/collections/EventTypes/access/index.d.ts.map +1 -0
  53. package/dist/collections/EventTypes/access/index.js +2 -0
  54. package/dist/collections/EventTypes/hooks/index.d.ts +3 -0
  55. package/dist/collections/EventTypes/hooks/index.d.ts.map +1 -0
  56. package/dist/collections/EventTypes/hooks/index.js +6 -0
  57. package/dist/collections/EventTypes/index.d.ts +3 -0
  58. package/dist/collections/EventTypes/index.d.ts.map +1 -0
  59. package/dist/collections/EventTypes/index.js +67 -0
  60. package/dist/collections/Grants/access/index.d.ts +3 -0
  61. package/dist/collections/Grants/access/index.d.ts.map +1 -0
  62. package/dist/collections/Grants/access/index.js +8 -0
  63. package/dist/collections/Grants/hooks/ensureUniqueGrant.d.ts +4 -0
  64. package/dist/collections/Grants/hooks/ensureUniqueGrant.d.ts.map +1 -0
  65. package/dist/collections/Grants/hooks/ensureUniqueGrant.js +34 -0
  66. package/dist/collections/Grants/hooks/index.d.ts +3 -0
  67. package/dist/collections/Grants/hooks/index.d.ts.map +1 -0
  68. package/dist/collections/Grants/hooks/index.js +7 -0
  69. package/dist/collections/Grants/hooks/setGrantTitle.d.ts +4 -0
  70. package/dist/collections/Grants/hooks/setGrantTitle.d.ts.map +1 -0
  71. package/dist/collections/Grants/hooks/setGrantTitle.js +47 -0
  72. package/dist/collections/Grants/index.d.ts +3 -0
  73. package/dist/collections/Grants/index.d.ts.map +1 -0
  74. package/dist/collections/Grants/index.js +62 -0
  75. package/dist/collections/Logs/access/index.d.ts +3 -0
  76. package/dist/collections/Logs/access/index.d.ts.map +1 -0
  77. package/dist/collections/Logs/access/index.js +8 -0
  78. package/dist/collections/Logs/fields/validators.d.ts +5 -0
  79. package/dist/collections/Logs/fields/validators.d.ts.map +1 -0
  80. package/dist/collections/Logs/fields/validators.js +44 -0
  81. package/dist/collections/Logs/hooks/ensureTypeRequirements.d.ts +3 -0
  82. package/dist/collections/Logs/hooks/ensureTypeRequirements.d.ts.map +1 -0
  83. package/dist/collections/Logs/hooks/ensureTypeRequirements.js +35 -0
  84. package/dist/collections/Logs/hooks/index.d.ts +3 -0
  85. package/dist/collections/Logs/hooks/index.d.ts.map +1 -0
  86. package/dist/collections/Logs/hooks/index.js +4 -0
  87. package/dist/collections/Logs/index.d.ts +4 -0
  88. package/dist/collections/Logs/index.d.ts.map +1 -0
  89. package/dist/collections/Logs/index.js +113 -0
  90. package/dist/collections/Metrics/access/index.d.ts +12 -0
  91. package/dist/collections/Metrics/access/index.d.ts.map +1 -0
  92. package/dist/collections/Metrics/access/index.js +2 -0
  93. package/dist/collections/Metrics/hooks/index.d.ts +3 -0
  94. package/dist/collections/Metrics/hooks/index.d.ts.map +1 -0
  95. package/dist/collections/Metrics/hooks/index.js +6 -0
  96. package/dist/collections/Metrics/index.d.ts +3 -0
  97. package/dist/collections/Metrics/index.d.ts.map +1 -0
  98. package/dist/collections/Metrics/index.js +35 -0
  99. package/dist/collections/Tiers/access/index.d.ts +3 -0
  100. package/dist/collections/Tiers/access/index.d.ts.map +1 -0
  101. package/dist/collections/Tiers/access/index.js +2 -0
  102. package/dist/collections/Tiers/hooks/index.d.ts +3 -0
  103. package/dist/collections/Tiers/hooks/index.d.ts.map +1 -0
  104. package/dist/collections/Tiers/hooks/index.js +4 -0
  105. package/dist/collections/Tiers/hooks/validateUnlockRules.d.ts +3 -0
  106. package/dist/collections/Tiers/hooks/validateUnlockRules.d.ts.map +1 -0
  107. package/dist/collections/Tiers/hooks/validateUnlockRules.js +7 -0
  108. package/dist/collections/Tiers/index.d.ts +3 -0
  109. package/dist/collections/Tiers/index.d.ts.map +1 -0
  110. package/dist/collections/Tiers/index.js +81 -0
  111. package/dist/collections/access/definitions.d.ts +5 -0
  112. package/dist/collections/access/definitions.d.ts.map +1 -0
  113. package/dist/collections/access/definitions.js +11 -0
  114. package/dist/collections/applyOverrides.d.ts +9 -0
  115. package/dist/collections/applyOverrides.d.ts.map +1 -0
  116. package/dist/collections/applyOverrides.js +34 -0
  117. package/dist/collections/fields/catalogFilter.d.ts +8 -0
  118. package/dist/collections/fields/catalogFilter.d.ts.map +1 -0
  119. package/dist/collections/fields/catalogFilter.js +10 -0
  120. package/dist/collections/fields/index.d.ts +6 -0
  121. package/dist/collections/fields/index.d.ts.map +1 -0
  122. package/dist/collections/fields/index.js +5 -0
  123. package/dist/collections/fields/localizedText.d.ts +10 -0
  124. package/dist/collections/fields/localizedText.d.ts.map +1 -0
  125. package/dist/collections/fields/localizedText.js +20 -0
  126. package/dist/collections/fields/ruleFields.d.ts +8 -0
  127. package/dist/collections/fields/ruleFields.d.ts.map +1 -0
  128. package/dist/collections/fields/ruleFields.js +123 -0
  129. package/dist/collections/fields/scopeField.d.ts +7 -0
  130. package/dist/collections/fields/scopeField.d.ts.map +1 -0
  131. package/dist/collections/fields/scopeField.js +22 -0
  132. package/dist/collections/fields/userField.d.ts +4 -0
  133. package/dist/collections/fields/userField.d.ts.map +1 -0
  134. package/dist/collections/fields/userField.js +12 -0
  135. package/dist/collections/fields/usersAchievementsGroup.d.ts +8 -0
  136. package/dist/collections/fields/usersAchievementsGroup.d.ts.map +1 -0
  137. package/dist/collections/fields/usersAchievementsGroup.js +36 -0
  138. package/dist/collections/helpers.d.ts +16 -0
  139. package/dist/collections/helpers.d.ts.map +1 -0
  140. package/dist/collections/helpers.js +31 -0
  141. package/dist/collections/hooks/eachRuleLeaf.d.ts +3 -0
  142. package/dist/collections/hooks/eachRuleLeaf.d.ts.map +1 -0
  143. package/dist/collections/hooks/eachRuleLeaf.js +20 -0
  144. package/dist/collections/hooks/index.d.ts +4 -0
  145. package/dist/collections/hooks/index.d.ts.map +1 -0
  146. package/dist/collections/hooks/index.js +3 -0
  147. package/dist/collections/hooks/lockSystemCatalogFields.d.ts +7 -0
  148. package/dist/collections/hooks/lockSystemCatalogFields.d.ts.map +1 -0
  149. package/dist/collections/hooks/lockSystemCatalogFields.js +15 -0
  150. package/dist/collections/hooks/preventCatalogDelete.d.ts +5 -0
  151. package/dist/collections/hooks/preventCatalogDelete.d.ts.map +1 -0
  152. package/dist/collections/hooks/preventCatalogDelete.js +89 -0
  153. package/dist/collections/index.d.ts +4 -0
  154. package/dist/collections/index.d.ts.map +1 -0
  155. package/dist/collections/index.js +23 -0
  156. package/dist/collections/slugs.d.ts +22 -0
  157. package/dist/collections/slugs.d.ts.map +1 -0
  158. package/dist/collections/slugs.js +51 -0
  159. package/dist/defaults.d.ts +21 -0
  160. package/dist/defaults.d.ts.map +1 -0
  161. package/dist/defaults.js +31 -0
  162. package/dist/enabled.d.ts +4 -0
  163. package/dist/enabled.d.ts.map +1 -0
  164. package/dist/enabled.js +9 -0
  165. package/dist/endpoints/index.d.ts +4 -0
  166. package/dist/endpoints/index.d.ts.map +1 -0
  167. package/dist/endpoints/index.js +6 -0
  168. package/dist/endpoints/me.d.ts +7 -0
  169. package/dist/endpoints/me.d.ts.map +1 -0
  170. package/dist/endpoints/me.js +50 -0
  171. package/dist/exports/client.d.ts +4 -0
  172. package/dist/exports/client.d.ts.map +1 -0
  173. package/dist/exports/client.js +3 -0
  174. package/dist/extensions/registry.d.ts +3 -0
  175. package/dist/extensions/registry.d.ts.map +1 -0
  176. package/dist/extensions/registry.js +12 -0
  177. package/dist/index.d.ts +13 -0
  178. package/dist/index.d.ts.map +1 -0
  179. package/dist/index.js +8 -0
  180. package/dist/options-store.d.ts +7 -0
  181. package/dist/options-store.d.ts.map +1 -0
  182. package/dist/options-store.js +15 -0
  183. package/dist/plugin.d.ts +8 -0
  184. package/dist/plugin.d.ts.map +1 -0
  185. package/dist/plugin.js +31 -0
  186. package/dist/seed/index.d.ts +63 -0
  187. package/dist/seed/index.d.ts.map +1 -0
  188. package/dist/seed/index.js +375 -0
  189. package/dist/services/achievement/buildCatalogProgressGroups.d.ts +37 -0
  190. package/dist/services/achievement/buildCatalogProgressGroups.d.ts.map +1 -0
  191. package/dist/services/achievement/buildCatalogProgressGroups.js +70 -0
  192. package/dist/services/achievement/builtInRules.d.ts +3 -0
  193. package/dist/services/achievement/builtInRules.d.ts.map +1 -0
  194. package/dist/services/achievement/builtInRules.js +280 -0
  195. package/dist/services/achievement/evaluateRules.d.ts +48 -0
  196. package/dist/services/achievement/evaluateRules.d.ts.map +1 -0
  197. package/dist/services/achievement/evaluateRules.js +206 -0
  198. package/dist/services/achievement/eventTypeRequiresActor.d.ts +24 -0
  199. package/dist/services/achievement/eventTypeRequiresActor.d.ts.map +1 -0
  200. package/dist/services/achievement/eventTypeRequiresActor.js +46 -0
  201. package/dist/services/achievement/getUserProgress.d.ts +53 -0
  202. package/dist/services/achievement/getUserProgress.d.ts.map +1 -0
  203. package/dist/services/achievement/getUserProgress.js +163 -0
  204. package/dist/services/achievement/index.d.ts +14 -0
  205. package/dist/services/achievement/index.d.ts.map +1 -0
  206. package/dist/services/achievement/index.js +9 -0
  207. package/dist/services/achievement/recordEvent.d.ts +41 -0
  208. package/dist/services/achievement/recordEvent.d.ts.map +1 -0
  209. package/dist/services/achievement/recordEvent.js +126 -0
  210. package/dist/services/achievement/relationId.d.ts +3 -0
  211. package/dist/services/achievement/relationId.d.ts.map +1 -0
  212. package/dist/services/achievement/relationId.js +15 -0
  213. package/dist/services/achievement/resolveCatalog.d.ts +14 -0
  214. package/dist/services/achievement/resolveCatalog.d.ts.map +1 -0
  215. package/dist/services/achievement/resolveCatalog.js +34 -0
  216. package/dist/services/achievement/resolveCurrentTier.d.ts +21 -0
  217. package/dist/services/achievement/resolveCurrentTier.d.ts.map +1 -0
  218. package/dist/services/achievement/resolveCurrentTier.js +51 -0
  219. package/dist/services/achievement/resolveTierProgress.d.ts +35 -0
  220. package/dist/services/achievement/resolveTierProgress.d.ts.map +1 -0
  221. package/dist/services/achievement/resolveTierProgress.js +99 -0
  222. package/dist/services/achievement/reviewAchievementRequest.d.ts +11 -0
  223. package/dist/services/achievement/reviewAchievementRequest.d.ts.map +1 -0
  224. package/dist/services/achievement/reviewAchievementRequest.js +37 -0
  225. package/dist/services/achievement/submitAchievementRequest.d.ts +21 -0
  226. package/dist/services/achievement/submitAchievementRequest.d.ts.map +1 -0
  227. package/dist/services/achievement/submitAchievementRequest.js +126 -0
  228. package/dist/services/achievement/validateRules.d.ts +11 -0
  229. package/dist/services/achievement/validateRules.d.ts.map +1 -0
  230. package/dist/services/achievement/validateRules.js +74 -0
  231. package/dist/services/achievement/where.d.ts +9 -0
  232. package/dist/services/achievement/where.d.ts.map +1 -0
  233. package/dist/services/achievement/where.js +23 -0
  234. package/dist/types.d.ts +117 -0
  235. package/dist/types.d.ts.map +1 -0
  236. package/dist/types.js +1 -0
  237. package/package.json +85 -0
@@ -0,0 +1,21 @@
1
+ export declare const ENGINE_EVENT_TYPES: readonly [{
2
+ readonly slug: "achievement.granted";
3
+ readonly name: "Achievement granted";
4
+ }, {
5
+ readonly slug: "metric.delta";
6
+ readonly name: "Metric delta";
7
+ readonly requiresMetric: true;
8
+ }, {
9
+ readonly slug: "tier.changed";
10
+ readonly name: "Tier changed";
11
+ }];
12
+ export declare const ENGINE_METRICS: readonly [{
13
+ readonly slug: "points";
14
+ readonly name: "Points";
15
+ }];
16
+ export type EngineEventTypeSlug = (typeof ENGINE_EVENT_TYPES)[number]['slug'];
17
+ export type EngineMetricSlug = (typeof ENGINE_METRICS)[number]['slug'];
18
+ export declare const DEFAULT_ACHIEVEMENT_METRIC_SLUG: EngineMetricSlug;
19
+ /** @deprecated Use {@link DEFAULT_ACHIEVEMENT_METRIC_SLUG}. */
20
+ export declare const DEFAULT_ACHIEVEMENT_METRIC: "points";
21
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;;;;;;;;;;EAIrB,CAAA;AAEV,eAAO,MAAM,cAAc;;;EAAgD,CAAA;AAE3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtE,eAAO,MAAM,+BAA+B,EAAE,gBAA2B,CAAA;AAEzE,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,UAAkC,CAAA"}
@@ -0,0 +1,9 @@
1
+ export const ENGINE_EVENT_TYPES = [
2
+ { slug: 'achievement.granted', name: 'Achievement granted' },
3
+ { slug: 'metric.delta', name: 'Metric delta', requiresMetric: true },
4
+ { slug: 'tier.changed', name: 'Tier changed' },
5
+ ];
6
+ export const ENGINE_METRICS = [{ slug: 'points', name: 'Points' }];
7
+ export const DEFAULT_ACHIEVEMENT_METRIC_SLUG = 'points';
8
+ /** @deprecated Use {@link DEFAULT_ACHIEVEMENT_METRIC_SLUG}. */
9
+ export const DEFAULT_ACHIEVEMENT_METRIC = DEFAULT_ACHIEVEMENT_METRIC_SLUG;
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const access: NonNullable<CollectionConfig['access']>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/AchievementRequests/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAK/C,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAK1D,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { isAuthenticated, requireOne } from '../../../access/index';
2
+ import { isReviewer, isSelfUser } from '../../../access/roles/index';
3
+ export const access = {
4
+ read: requireOne(isReviewer('achievementRequests'), isSelfUser('user')),
5
+ create: isAuthenticated,
6
+ update: requireOne(isReviewer('achievementRequests')),
7
+ delete: requireOne(isReviewer('achievementRequests')),
8
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const hooks: CollectionConfig['hooks'];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/AchievementRequests/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,eAAO,MAAM,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAG3C,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { grantOnApprovedRequest, prepareAchievementRequest } from './prepareAchievementRequest';
2
+ export const hooks = {
3
+ beforeValidate: [prepareAchievementRequest],
4
+ afterChange: [grantOnApprovedRequest],
5
+ };
@@ -0,0 +1,12 @@
1
+ import type { CollectionAfterChangeHook, CollectionBeforeValidateHook } from 'payload';
2
+ /**
3
+ * Create: bind the requesting user, enforce eligibility,
4
+ * auto-approve when the definition does not require review.
5
+ * Update: status transitions handled in afterChange.
6
+ */
7
+ export declare const prepareAchievementRequest: CollectionBeforeValidateHook;
8
+ /**
9
+ * When a request becomes approved, grant the achievement (create or pending→approved).
10
+ */
11
+ export declare const grantOnApprovedRequest: CollectionAfterChangeHook;
12
+ //# sourceMappingURL=prepareAchievementRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareAchievementRequest.d.ts","sourceRoot":"","sources":["../../../../src/collections/AchievementRequests/hooks/prepareAchievementRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,4BAA4B,EAE7B,MAAM,SAAS,CAAA;AA0ChB;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,4BAqDvC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,yBAsCpC,CAAA"}
@@ -0,0 +1,105 @@
1
+ import { APIError } from 'payload';
2
+ import { collectionOf } from '../../../collections/helpers';
3
+ import { evaluateRules, ruleGroupHasProgressRequirements } from '../../../services/achievement/evaluateRules';
4
+ import { grantAchievement } from '../../../services/achievement/submitAchievementRequest';
5
+ import { relationId } from '../../../services/achievement/relationId';
6
+ function scopeIdOf(data) {
7
+ const scope = data?.scope;
8
+ if (!scope)
9
+ return null;
10
+ return relationId(scope);
11
+ }
12
+ async function resolveAchievement(args) {
13
+ const achievementId = relationId(args.data.achievement);
14
+ if (!achievementId) {
15
+ throw new APIError('achievement required', 400);
16
+ }
17
+ const doc = (await args.req.payload.findByID({
18
+ collection: collectionOf('achievements'),
19
+ id: achievementId,
20
+ depth: 0,
21
+ overrideAccess: true,
22
+ req: args.req,
23
+ }));
24
+ if (!doc)
25
+ throw new APIError(`Unknown achievement: ${achievementId}`, 400);
26
+ return doc;
27
+ }
28
+ /**
29
+ * Create: bind the requesting user, enforce eligibility,
30
+ * auto-approve when the definition does not require review.
31
+ * Update: status transitions handled in afterChange.
32
+ */
33
+ export const prepareAchievementRequest = async ({ data, operation, req, originalDoc, }) => {
34
+ if (!data)
35
+ return data;
36
+ if (operation === 'create') {
37
+ const userId = req.user?.id;
38
+ if (typeof userId !== 'string')
39
+ throw new APIError('Unauthorized', 401);
40
+ data.user = userId;
41
+ const scopeId = scopeIdOf(data);
42
+ const achievement = await resolveAchievement({
43
+ req,
44
+ data: data,
45
+ });
46
+ data.achievement = achievement.id;
47
+ if (ruleGroupHasProgressRequirements(achievement.completionRules)) {
48
+ throw new APIError('This achievement is granted automatically when its completion rules pass.', 400);
49
+ }
50
+ const eligible = await evaluateRules({
51
+ payload: req.payload,
52
+ req,
53
+ rules: achievement.eligibilityRules,
54
+ userId: userId,
55
+ scopeId,
56
+ });
57
+ if (!eligible) {
58
+ throw new APIError('Eligibility rules not met for this achievement.', 400);
59
+ }
60
+ data.status = achievement.requiresReview === false ? 'approved' : 'pending';
61
+ return data;
62
+ }
63
+ if (operation === 'update') {
64
+ // Cannot reassign user or achievement on an existing request.
65
+ if (originalDoc) {
66
+ data.user = originalDoc.user;
67
+ data.achievement = originalDoc.achievement;
68
+ }
69
+ }
70
+ return data;
71
+ };
72
+ /**
73
+ * When a request becomes approved, grant the achievement (create or pending→approved).
74
+ */
75
+ export const grantOnApprovedRequest = async ({ doc, operation, previousDoc, req, }) => {
76
+ if (req.context?.achievementGranting)
77
+ return doc;
78
+ const status = doc.status;
79
+ if (status !== 'approved')
80
+ return doc;
81
+ const previousStatus = operation === 'update' ? previousDoc?.status : undefined;
82
+ if (previousStatus === 'approved')
83
+ return doc;
84
+ const achievementId = relationId(doc.achievement);
85
+ const userId = relationId(doc.user);
86
+ if (!achievementId || !userId) {
87
+ throw new APIError('Approved request missing achievement or user', 400);
88
+ }
89
+ const scopeId = scopeIdOf(doc);
90
+ const note = doc.note;
91
+ req.context.achievementGranting = true;
92
+ try {
93
+ await grantAchievement({
94
+ req,
95
+ userId,
96
+ achievementId,
97
+ scopeId,
98
+ note,
99
+ });
100
+ }
101
+ finally {
102
+ req.context.achievementGranting = false;
103
+ }
104
+ return doc;
105
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare function buildAchievementRequestsCollection(): CollectionConfig;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/AchievementRequests/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;AAOtD,wBAAgB,kCAAkC,IAAI,gBAAgB,CAmCrE"}
@@ -0,0 +1,39 @@
1
+ import { collectionAdmin, collectionOf, optionalFields, slugOf } from '../helpers';
2
+ import { scopeField, userField } from '../fields';
3
+ import { access } from './access';
4
+ import { hooks } from './hooks';
5
+ export function buildAchievementRequestsCollection() {
6
+ return {
7
+ slug: slugOf('achievementRequests'),
8
+ access: { ...access },
9
+ hooks,
10
+ admin: collectionAdmin({
11
+ useAsTitle: 'id',
12
+ defaultColumns: ['user', 'achievement', 'status', 'createdAt'],
13
+ description: 'When someone asks to earn an achievement. We check eligibility, then either grant it right away or wait for a review.',
14
+ }),
15
+ fields: [
16
+ ...optionalFields(scopeField()),
17
+ userField(),
18
+ {
19
+ name: 'achievement',
20
+ type: 'relationship',
21
+ relationTo: collectionOf('achievements'),
22
+ required: true,
23
+ index: true,
24
+ },
25
+ {
26
+ name: 'status',
27
+ type: 'select',
28
+ required: true,
29
+ defaultValue: 'pending',
30
+ options: [
31
+ { label: 'Pending', value: 'pending' },
32
+ { label: 'Approved', value: 'approved' },
33
+ { label: 'Rejected', value: 'rejected' },
34
+ ],
35
+ },
36
+ { name: 'note', type: 'textarea', admin: { description: 'Optional note from the requester or reviewer.' } },
37
+ ],
38
+ };
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const access: NonNullable<CollectionConfig['access']>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/Achievements/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAoC,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { definitionAccess } from '../../access/definitions';
2
+ export const access = definitionAccess('achievements');
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const hooks: CollectionConfig['hooks'];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/Achievements/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,eAAO,MAAM,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAE3C,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { validateAchievementRules } from './validateEligibilityRules';
2
+ export const hooks = {
3
+ beforeValidate: [validateAchievementRules],
4
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionBeforeValidateHook } from 'payload';
2
+ export declare const validateAchievementRules: CollectionBeforeValidateHook;
3
+ //# sourceMappingURL=validateEligibilityRules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateEligibilityRules.d.ts","sourceRoot":"","sources":["../../../../src/collections/Achievements/hooks/validateEligibilityRules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;AAI3D,eAAO,MAAM,wBAAwB,EAAE,4BAWtC,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { assertRuleGroupValid } from '../../../services/achievement/validateRules';
2
+ export const validateAchievementRules = ({ data }) => {
3
+ if (!data)
4
+ return data;
5
+ assertRuleGroupValid(data.eligibilityRules, 'Eligibility rules');
6
+ assertRuleGroupValid(data.completionRules, 'Completion rules');
7
+ return data;
8
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare function buildAchievementsCollection(): CollectionConfig;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Achievements/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAO/C,wBAAgB,2BAA2B,IAAI,gBAAgB,CA0D9D"}
@@ -0,0 +1,62 @@
1
+ import { buildRuleTreeFields, localizedDescriptionField, localizedNameField, scopeField } from '../fields';
2
+ import { collectionAdmin, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers';
3
+ import { access } from './access';
4
+ import { hooks } from './hooks';
5
+ export function buildAchievementsCollection() {
6
+ return {
7
+ slug: slugOf('achievements'),
8
+ labels: {
9
+ singular: 'Achievement Definition',
10
+ plural: 'Achievement Definitions',
11
+ },
12
+ access: { ...access },
13
+ hooks,
14
+ admin: collectionAdmin({
15
+ useAsTitle: 'name',
16
+ defaultColumns: columnsWithOptionalScope(['name', 'slug', 'requiresReview', 'scope']),
17
+ description: 'What someone can earn. Set who’s eligible and whether a person on your team needs to approve it.',
18
+ }),
19
+ fields: [
20
+ ...optionalFields(scopeField()),
21
+ {
22
+ type: 'row',
23
+ fields: [
24
+ localizedNameField({ width: '50%' }),
25
+ {
26
+ name: 'slug',
27
+ type: 'text',
28
+ required: true,
29
+ unique: true,
30
+ index: true,
31
+ admin: { width: '50%' },
32
+ },
33
+ ],
34
+ },
35
+ localizedDescriptionField(),
36
+ {
37
+ name: 'requiresReview',
38
+ type: 'checkbox',
39
+ defaultValue: true,
40
+ label: 'Requires review',
41
+ },
42
+ {
43
+ name: 'eligibilityRules',
44
+ type: 'group',
45
+ label: 'Eligibility rules',
46
+ admin: {
47
+ description: 'Who may request this achievement. Composed parents that auto-grant should leave this empty.',
48
+ },
49
+ fields: buildRuleTreeFields(),
50
+ },
51
+ {
52
+ name: 'completionRules',
53
+ type: 'group',
54
+ label: 'Completion rules',
55
+ admin: {
56
+ description: 'What this achievement is made of. When every rule passes, it is granted automatically (do not require review on composed parents). Nested achievements appear in this order in your app UI.',
57
+ },
58
+ fields: buildRuleTreeFields(),
59
+ },
60
+ ],
61
+ };
62
+ }
@@ -0,0 +1,12 @@
1
+ export declare const access: {
2
+ admin?: ({ req }: {
3
+ req: import("payload").PayloadRequest;
4
+ }) => boolean | Promise<boolean>;
5
+ create?: import("payload").Access;
6
+ delete?: import("payload").Access;
7
+ read?: import("payload").Access;
8
+ readVersions?: import("payload").Access;
9
+ unlock?: import("payload").Access;
10
+ update?: import("payload").Access;
11
+ };
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/EventTypes/access/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;;;;;;;CAAiC,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { definitionAccess } from '../../access/definitions';
2
+ export const access = definitionAccess('eventTypes');
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const hooks: CollectionConfig['hooks'];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/EventTypes/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAK/C,eAAO,MAAM,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAG3C,CAAA"}
@@ -0,0 +1,6 @@
1
+ import { lockSystemCatalogFields } from '../../hooks/lockSystemCatalogFields';
2
+ import { preventCatalogDelete } from '../../hooks/preventCatalogDelete';
3
+ export const hooks = {
4
+ beforeChange: [lockSystemCatalogFields],
5
+ beforeDelete: [preventCatalogDelete({ kind: 'eventType' })],
6
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare function buildEventTypesCollection(): CollectionConfig;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/EventTypes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;AAOtD,wBAAgB,yBAAyB,IAAI,gBAAgB,CAgE5D"}
@@ -0,0 +1,67 @@
1
+ import { collectionAdmin, collectionOf, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers';
2
+ import { localizedNameField, scopeField } from '../fields';
3
+ import { access } from './access';
4
+ import { hooks } from './hooks';
5
+ export function buildEventTypesCollection() {
6
+ return {
7
+ slug: slugOf('eventTypes'),
8
+ access: { ...access },
9
+ hooks,
10
+ admin: collectionAdmin({
11
+ useAsTitle: 'name',
12
+ defaultColumns: columnsWithOptionalScope([
13
+ 'name',
14
+ 'slug',
15
+ 'requiresActor',
16
+ 'requiresMetric',
17
+ 'system',
18
+ 'scope',
19
+ ]),
20
+ description: 'Names for things that can happen and show up in the achievement logs.',
21
+ }),
22
+ fields: [
23
+ ...optionalFields(scopeField()),
24
+ {
25
+ type: 'row',
26
+ fields: [
27
+ localizedNameField({ width: '50%' }),
28
+ { name: 'slug', type: 'text', required: true, unique: true, index: true, admin: { width: '50%' } },
29
+ ],
30
+ },
31
+ {
32
+ name: 'requiresActor',
33
+ type: 'checkbox',
34
+ defaultValue: false,
35
+ label: 'Requires an actor',
36
+ admin: {
37
+ description: 'Turn this on when someone else caused the entry (for example a peer nomination). We’ll ask for that person when it’s logged.',
38
+ },
39
+ },
40
+ {
41
+ name: 'requiresMetric',
42
+ type: 'checkbox',
43
+ defaultValue: false,
44
+ label: 'Adjusts a metric',
45
+ admin: {
46
+ description: 'Turn this on when the entry changes a score (like points). We’ll ask which metric and by how much.',
47
+ },
48
+ },
49
+ {
50
+ name: 'system',
51
+ type: 'checkbox',
52
+ defaultValue: false,
53
+ admin: {
54
+ readOnly: true,
55
+ description: 'Built-in type. It can’t be deleted.',
56
+ },
57
+ },
58
+ {
59
+ name: 'logs',
60
+ type: 'join',
61
+ collection: collectionOf('logs'),
62
+ on: 'type',
63
+ admin: { allowCreate: false, defaultColumns: ['user', 'createdAt'] },
64
+ },
65
+ ],
66
+ };
67
+ }
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const access: NonNullable<CollectionConfig['access']>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/Grants/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAK/C,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAK1D,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { requireOne } from '../../../access/index';
2
+ import { isReviewer, isSelfUser } from '../../../access/roles/index';
3
+ export const access = {
4
+ read: requireOne(isReviewer('grants'), isSelfUser('user')),
5
+ create: requireOne(isReviewer('grants')),
6
+ update: requireOne(isReviewer('grants')),
7
+ delete: requireOne(isReviewer('grants')),
8
+ };
@@ -0,0 +1,4 @@
1
+ import type { CollectionBeforeValidateHook } from 'payload';
2
+ /** Reject a second grant of the same achievement for the same user (+ scope). */
3
+ export declare const ensureUniqueGrant: CollectionBeforeValidateHook;
4
+ //# sourceMappingURL=ensureUniqueGrant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureUniqueGrant.d.ts","sourceRoot":"","sources":["../../../../src/collections/Grants/hooks/ensureUniqueGrant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAS,MAAM,SAAS,CAAA;AAMlE,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,EAAE,4BAoC/B,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { APIError } from 'payload';
2
+ import { collectionOf } from '../../helpers';
3
+ import { relationId } from '../../../services/achievement/relationId';
4
+ /** Reject a second grant of the same achievement for the same user (+ scope). */
5
+ export const ensureUniqueGrant = async ({ data, originalDoc, operation, req, }) => {
6
+ if (!req?.payload || !data)
7
+ return data;
8
+ const userId = relationId(data.user ?? originalDoc?.user);
9
+ const achievementId = relationId(data.achievement ?? originalDoc?.achievement);
10
+ const scopeId = relationId(data.scope ?? originalDoc?.scope);
11
+ if (!userId || !achievementId)
12
+ return data;
13
+ const clauses = [
14
+ { user: { equals: userId } },
15
+ { achievement: { equals: achievementId } },
16
+ ];
17
+ if (scopeId)
18
+ clauses.push({ scope: { equals: scopeId } });
19
+ else
20
+ clauses.push({ scope: { exists: false } });
21
+ const existing = await req.payload.find({
22
+ collection: collectionOf('grants'),
23
+ depth: 0,
24
+ limit: 1,
25
+ overrideAccess: true,
26
+ req,
27
+ where: { and: clauses },
28
+ });
29
+ const other = existing.docs[0];
30
+ if (other && (operation === 'create' || relationId(other.id) !== relationId(originalDoc?.id))) {
31
+ throw new APIError('This achievement is already granted to that user for this scope.', 400);
32
+ }
33
+ return data;
34
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const hooks: CollectionConfig['hooks'];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/Grants/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAK/C,eAAO,MAAM,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAI3C,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { ensureUniqueGrant } from './ensureUniqueGrant';
2
+ import { fillGrantTitle, setGrantTitle } from './setGrantTitle';
3
+ export const hooks = {
4
+ beforeValidate: [ensureUniqueGrant],
5
+ beforeChange: [setGrantTitle],
6
+ afterRead: [fillGrantTitle],
7
+ };
@@ -0,0 +1,4 @@
1
+ import type { CollectionAfterReadHook, CollectionBeforeChangeHook } from 'payload';
2
+ export declare const setGrantTitle: CollectionBeforeChangeHook;
3
+ export declare const fillGrantTitle: CollectionAfterReadHook;
4
+ //# sourceMappingURL=setGrantTitle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setGrantTitle.d.ts","sourceRoot":"","sources":["../../../../src/collections/Grants/hooks/setGrantTitle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAG3B,MAAM,SAAS,CAAA;AAkChB,eAAO,MAAM,aAAa,EAAE,0BAU3B,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,uBAU5B,CAAA"}
@@ -0,0 +1,47 @@
1
+ import { relationId } from '../../../services/achievement/relationId';
2
+ import { collectionOf } from '../../helpers';
3
+ async function achievementName(args) {
4
+ const id = relationId(args.value);
5
+ if (!id)
6
+ return 'Grant';
7
+ if (args.value && typeof args.value === 'object') {
8
+ const row = args.value;
9
+ if (typeof row.name === 'string' && row.name)
10
+ return row.name;
11
+ }
12
+ try {
13
+ const doc = await args.payload.findByID({
14
+ collection: collectionOf('achievements'),
15
+ id,
16
+ depth: 0,
17
+ overrideAccess: true,
18
+ req: args.req,
19
+ ...(args.req?.locale ? { locale: args.req.locale } : {}),
20
+ });
21
+ const name = doc?.name;
22
+ return typeof name === 'string' && name ? name : id;
23
+ }
24
+ catch {
25
+ return id;
26
+ }
27
+ }
28
+ export const setGrantTitle = async ({ data, originalDoc, req }) => {
29
+ if (!req?.payload || !data)
30
+ return data;
31
+ data.title = await achievementName({
32
+ payload: req.payload,
33
+ req,
34
+ value: data.achievement ?? originalDoc?.achievement,
35
+ });
36
+ return data;
37
+ };
38
+ export const fillGrantTitle = async ({ doc, req }) => {
39
+ if (!req?.payload || !doc)
40
+ return doc;
41
+ doc.title = await achievementName({
42
+ payload: req.payload,
43
+ req,
44
+ value: doc.achievement,
45
+ });
46
+ return doc;
47
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare function buildGrantsCollection(): CollectionConfig;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;AAOtD,wBAAgB,qBAAqB,IAAI,gBAAgB,CAyDxD"}