@commonpub/server 2.89.0 → 2.90.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 (170) hide show
  1. package/dist/admin/admin.d.ts.map +1 -1
  2. package/dist/admin/admin.js +4 -5
  3. package/dist/admin/admin.js.map +1 -1
  4. package/dist/content/content.d.ts +0 -5
  5. package/dist/content/content.d.ts.map +1 -1
  6. package/dist/content/content.js +118 -122
  7. package/dist/content/content.js.map +1 -1
  8. package/dist/content/index.d.ts +1 -1
  9. package/dist/content/index.d.ts.map +1 -1
  10. package/dist/content/index.js +1 -1
  11. package/dist/content/index.js.map +1 -1
  12. package/dist/contest/contest.d.ts +2 -308
  13. package/dist/contest/contest.d.ts.map +1 -1
  14. package/dist/contest/contest.js +87 -878
  15. package/dist/contest/contest.js.map +1 -1
  16. package/dist/contest/entries.d.ts +41 -0
  17. package/dist/contest/entries.d.ts.map +1 -0
  18. package/dist/contest/entries.js +285 -0
  19. package/dist/contest/entries.js.map +1 -0
  20. package/dist/contest/export.d.ts +20 -0
  21. package/dist/contest/export.d.ts.map +1 -0
  22. package/dist/contest/export.js +131 -0
  23. package/dist/contest/export.js.map +1 -0
  24. package/dist/contest/index.d.ts +11 -2
  25. package/dist/contest/index.d.ts.map +1 -1
  26. package/dist/contest/index.js +8 -1
  27. package/dist/contest/index.js.map +1 -1
  28. package/dist/contest/judges.js +9 -8
  29. package/dist/contest/judges.js.map +1 -1
  30. package/dist/contest/judging.d.ts +38 -0
  31. package/dist/contest/judging.d.ts.map +1 -0
  32. package/dist/contest/judging.js +274 -0
  33. package/dist/contest/judging.js.map +1 -0
  34. package/dist/contest/read.d.ts +44 -0
  35. package/dist/contest/read.d.ts.map +1 -0
  36. package/dist/contest/read.js +164 -0
  37. package/dist/contest/read.js.map +1 -0
  38. package/dist/contest/stages.d.ts +28 -0
  39. package/dist/contest/stages.d.ts.map +1 -0
  40. package/dist/contest/stages.js +52 -0
  41. package/dist/contest/stages.js.map +1 -0
  42. package/dist/contest/submissions.d.ts +90 -0
  43. package/dist/contest/submissions.d.ts.map +1 -0
  44. package/dist/contest/submissions.js +275 -0
  45. package/dist/contest/submissions.js.map +1 -0
  46. package/dist/contest/types.d.ts +197 -0
  47. package/dist/contest/types.d.ts.map +1 -0
  48. package/dist/contest/types.js +2 -0
  49. package/dist/contest/types.js.map +1 -0
  50. package/dist/contest/validation.d.ts +32 -0
  51. package/dist/contest/validation.d.ts.map +1 -0
  52. package/dist/contest/validation.js +132 -0
  53. package/dist/contest/validation.js.map +1 -0
  54. package/dist/docs/docs.d.ts +9 -3
  55. package/dist/docs/docs.d.ts.map +1 -1
  56. package/dist/docs/docs.js +16 -6
  57. package/dist/docs/docs.js.map +1 -1
  58. package/dist/events/events.d.ts.map +1 -1
  59. package/dist/events/events.js +12 -6
  60. package/dist/events/events.js.map +1 -1
  61. package/dist/federation/activityDedup.d.ts +14 -0
  62. package/dist/federation/activityDedup.d.ts.map +1 -0
  63. package/dist/federation/activityDedup.js +34 -0
  64. package/dist/federation/activityDedup.js.map +1 -0
  65. package/dist/federation/assertPublicHost.d.ts +14 -0
  66. package/dist/federation/assertPublicHost.d.ts.map +1 -0
  67. package/dist/federation/assertPublicHost.js +62 -0
  68. package/dist/federation/assertPublicHost.js.map +1 -0
  69. package/dist/federation/delivery.d.ts.map +1 -1
  70. package/dist/federation/delivery.js +37 -51
  71. package/dist/federation/delivery.js.map +1 -1
  72. package/dist/federation/federation.d.ts.map +1 -1
  73. package/dist/federation/federation.js +11 -7
  74. package/dist/federation/federation.js.map +1 -1
  75. package/dist/federation/hubMirroring.d.ts.map +1 -1
  76. package/dist/federation/hubMirroring.js +85 -66
  77. package/dist/federation/hubMirroring.js.map +1 -1
  78. package/dist/federation/inboxHandlers.d.ts.map +1 -1
  79. package/dist/federation/inboxHandlers.js +84 -73
  80. package/dist/federation/inboxHandlers.js.map +1 -1
  81. package/dist/federation/inboxParsing.d.ts +28 -0
  82. package/dist/federation/inboxParsing.d.ts.map +1 -0
  83. package/dist/federation/inboxParsing.js +71 -0
  84. package/dist/federation/inboxParsing.js.map +1 -0
  85. package/dist/federation/index.d.ts +2 -0
  86. package/dist/federation/index.d.ts.map +1 -1
  87. package/dist/federation/index.js +2 -0
  88. package/dist/federation/index.js.map +1 -1
  89. package/dist/federation/mastodonLogin.d.ts.map +1 -1
  90. package/dist/federation/mastodonLogin.js +19 -0
  91. package/dist/federation/mastodonLogin.js.map +1 -1
  92. package/dist/federation/outboxQueries.js +1 -1
  93. package/dist/federation/outboxQueries.js.map +1 -1
  94. package/dist/federation/timeline.d.ts +11 -0
  95. package/dist/federation/timeline.d.ts.map +1 -1
  96. package/dist/federation/timeline.js +101 -69
  97. package/dist/federation/timeline.js.map +1 -1
  98. package/dist/hub/hub.d.ts.map +1 -1
  99. package/dist/hub/hub.js +41 -3
  100. package/dist/hub/hub.js.map +1 -1
  101. package/dist/hub/index.d.ts +1 -1
  102. package/dist/hub/index.d.ts.map +1 -1
  103. package/dist/hub/index.js +1 -1
  104. package/dist/hub/index.js.map +1 -1
  105. package/dist/hub/members.d.ts +19 -0
  106. package/dist/hub/members.d.ts.map +1 -1
  107. package/dist/hub/members.js +158 -13
  108. package/dist/hub/members.js.map +1 -1
  109. package/dist/hub/moderation.d.ts +1 -1
  110. package/dist/hub/moderation.d.ts.map +1 -1
  111. package/dist/hub/moderation.js +25 -11
  112. package/dist/hub/moderation.js.map +1 -1
  113. package/dist/hub/posts.d.ts.map +1 -1
  114. package/dist/hub/posts.js +18 -10
  115. package/dist/hub/posts.js.map +1 -1
  116. package/dist/hub/resources.js +2 -2
  117. package/dist/hub/resources.js.map +1 -1
  118. package/dist/identity/mastodonFactory.d.ts.map +1 -1
  119. package/dist/identity/mastodonFactory.js +7 -0
  120. package/dist/identity/mastodonFactory.js.map +1 -1
  121. package/dist/index.d.ts +7 -7
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +5 -5
  124. package/dist/index.js.map +1 -1
  125. package/dist/learning/learning.d.ts.map +1 -1
  126. package/dist/learning/learning.js +42 -22
  127. package/dist/learning/learning.js.map +1 -1
  128. package/dist/messaging/messaging.d.ts.map +1 -1
  129. package/dist/messaging/messaging.js +3 -3
  130. package/dist/messaging/messaging.js.map +1 -1
  131. package/dist/notification/notification.d.ts.map +1 -1
  132. package/dist/notification/notification.js +4 -2
  133. package/dist/notification/notification.js.map +1 -1
  134. package/dist/product/product.d.ts.map +1 -1
  135. package/dist/product/product.js +75 -37
  136. package/dist/product/product.js.map +1 -1
  137. package/dist/profile/index.d.ts +2 -1
  138. package/dist/profile/index.d.ts.map +1 -1
  139. package/dist/profile/index.js +1 -1
  140. package/dist/profile/index.js.map +1 -1
  141. package/dist/profile/profile.d.ts +24 -2
  142. package/dist/profile/profile.d.ts.map +1 -1
  143. package/dist/profile/profile.js +34 -8
  144. package/dist/profile/profile.js.map +1 -1
  145. package/dist/query.d.ts +18 -2
  146. package/dist/query.d.ts.map +1 -1
  147. package/dist/query.js +24 -6
  148. package/dist/query.js.map +1 -1
  149. package/dist/rbac/seed.d.ts +1 -1
  150. package/dist/rbac/seed.d.ts.map +1 -1
  151. package/dist/rbac/seed.js +1 -0
  152. package/dist/rbac/seed.js.map +1 -1
  153. package/dist/search/contentSearch.d.ts +1 -1
  154. package/dist/search/contentSearch.d.ts.map +1 -1
  155. package/dist/search/contentSearch.js +22 -12
  156. package/dist/search/contentSearch.js.map +1 -1
  157. package/dist/social/social.d.ts +4 -2
  158. package/dist/social/social.d.ts.map +1 -1
  159. package/dist/social/social.js +25 -8
  160. package/dist/social/social.js.map +1 -1
  161. package/dist/types.d.ts +3 -0
  162. package/dist/types.d.ts.map +1 -1
  163. package/dist/video/video.d.ts +3 -0
  164. package/dist/video/video.d.ts.map +1 -1
  165. package/dist/video/video.js +17 -13
  166. package/dist/video/video.js.map +1 -1
  167. package/dist/voting/voting.d.ts.map +1 -1
  168. package/dist/voting/voting.js +39 -1
  169. package/dist/voting/voting.js.map +1 -1
  170. package/package.json +10 -8
@@ -0,0 +1,164 @@
1
+ import { eq, ne, and, or, desc, sql, inArray } from 'drizzle-orm';
2
+ import { contests, contestJudges, contestStakeholders } from '@commonpub/schema';
3
+ import { normalizePagination, countRows } from '../query.js';
4
+ import { isContestStakeholder } from './stakeholders.js';
5
+ import { isContestJudge } from './judges.js';
6
+ // Contest read path: listing (visibility-filtered), detail mapping, per-contest
7
+ // view authorization, and the score-reveal decision. Writers live in contest.ts.
8
+ export async function listContests(db, filters = {}, viewer) {
9
+ const conditions = [];
10
+ if (filters.status) {
11
+ conditions.push(eq(contests.status, filters.status));
12
+ }
13
+ // Visibility: admins see everything. Everyone else sees `public` contests,
14
+ // plus — when signed in — the ones they have a relationship to so they're not
15
+ // hidden in the listing: their own, ones they review (stakeholder), ones they
16
+ // judge, and private ones whose `visibleToRoles` includes their role. (`unlisted`
17
+ // stays link-only; mirrors canViewContest so the listing matches per-contest access.)
18
+ if (viewer?.role !== 'admin') {
19
+ const visConds = [eq(contests.visibility, 'public')];
20
+ if (viewer?.userId) {
21
+ visConds.push(eq(contests.createdById, viewer.userId));
22
+ visConds.push(inArray(contests.id, db.select({ id: contestStakeholders.contestId }).from(contestStakeholders).where(eq(contestStakeholders.userId, viewer.userId))));
23
+ visConds.push(inArray(contests.id, db.select({ id: contestJudges.contestId }).from(contestJudges).where(eq(contestJudges.userId, viewer.userId))));
24
+ }
25
+ if (viewer?.role) {
26
+ visConds.push(sql `${contests.visibleToRoles} @> ${JSON.stringify([viewer.role])}::jsonb`);
27
+ }
28
+ conditions.push(visConds.length > 1 ? or(...visConds) : visConds[0]);
29
+ // Drafts never appear in listings except to their own owner (admins, handled
30
+ // above, see everything). Orthogonal to visibility — a public draft is still hidden.
31
+ conditions.push(viewer?.userId
32
+ ? or(ne(contests.status, 'draft'), eq(contests.createdById, viewer.userId))
33
+ : ne(contests.status, 'draft'));
34
+ }
35
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
36
+ const { limit, offset } = normalizePagination(filters);
37
+ const [rows, total] = await Promise.all([
38
+ db
39
+ .select()
40
+ .from(contests)
41
+ .where(where)
42
+ .orderBy(desc(contests.startDate), desc(contests.id))
43
+ .limit(limit)
44
+ .offset(offset),
45
+ countRows(db, contests, where),
46
+ ]);
47
+ const items = rows.map((row) => ({
48
+ id: row.id,
49
+ title: row.title,
50
+ slug: row.slug,
51
+ subheading: row.subheading,
52
+ description: row.description,
53
+ bannerUrl: row.bannerUrl,
54
+ coverImageUrl: row.coverImageUrl,
55
+ status: row.status,
56
+ startDate: row.startDate,
57
+ endDate: row.endDate,
58
+ entryCount: row.entryCount,
59
+ createdAt: row.createdAt,
60
+ }));
61
+ return { items, total };
62
+ }
63
+ /** Map a contest row to the API detail shape. Shared by the CRUD writers. */
64
+ export function toContestDetail(row) {
65
+ return {
66
+ id: row.id,
67
+ title: row.title,
68
+ slug: row.slug,
69
+ description: row.description,
70
+ bannerUrl: row.bannerUrl,
71
+ coverImageUrl: row.coverImageUrl,
72
+ status: row.status,
73
+ startDate: row.startDate,
74
+ endDate: row.endDate,
75
+ entryCount: row.entryCount,
76
+ createdAt: row.createdAt,
77
+ subheading: row.subheading,
78
+ rules: row.rules,
79
+ prizesDescription: row.prizesDescription,
80
+ descriptionFormat: row.descriptionFormat,
81
+ rulesFormat: row.rulesFormat,
82
+ prizesDescriptionFormat: row.prizesDescriptionFormat,
83
+ descriptionBlocks: row.descriptionBlocks ?? null,
84
+ rulesBlocks: row.rulesBlocks ?? null,
85
+ prizesBlocks: row.prizesBlocks ?? null,
86
+ showPrizes: row.showPrizes,
87
+ stages: row.stages ?? [],
88
+ currentStageId: row.currentStageId ?? null,
89
+ prizes: row.prizes ?? null,
90
+ judgingCriteria: row.judgingCriteria ?? null,
91
+ judgingVisibility: row.judgingVisibility,
92
+ judgingEndDate: row.judgingEndDate,
93
+ communityVotingEnabled: row.communityVotingEnabled,
94
+ eligibleContentTypes: row.eligibleContentTypes ?? null,
95
+ maxEntriesPerUser: row.maxEntriesPerUser ?? null,
96
+ visibility: row.visibility,
97
+ visibleToRoles: row.visibleToRoles ?? null,
98
+ createdById: row.createdById,
99
+ };
100
+ }
101
+ /**
102
+ * Whether `user` may view this contest. `public`/`unlisted` are viewable by
103
+ * anyone (unlisted is simply hidden from listings). `private` is restricted to
104
+ * the owner, admins, stakeholders, panel judges, and users whose role is in
105
+ * `visibleToRoles`.
106
+ */
107
+ export async function canViewContest(db, contest, user) {
108
+ // Drafts are owner-only regardless of the visibility setting — an unlaunched
109
+ // contest must never be world-readable, even when its visibility is `public`.
110
+ if (contest.status === 'draft') {
111
+ if (!user)
112
+ return false;
113
+ if (user.id === contest.createdById || user.role === 'admin')
114
+ return true;
115
+ if (await isContestStakeholder(db, contest.id, user.id))
116
+ return true;
117
+ if (await isContestJudge(db, contest.id, user.id))
118
+ return true;
119
+ return false;
120
+ }
121
+ if (contest.visibility !== 'private')
122
+ return true;
123
+ if (!user)
124
+ return false;
125
+ if (user.id === contest.createdById || user.role === 'admin')
126
+ return true;
127
+ if (contest.visibleToRoles && contest.visibleToRoles.includes(user.role))
128
+ return true;
129
+ if (await isContestStakeholder(db, contest.id, user.id))
130
+ return true;
131
+ if (await isContestJudge(db, contest.id, user.id))
132
+ return true;
133
+ return false;
134
+ }
135
+ export async function getContestBySlug(db, slug) {
136
+ const rows = await db
137
+ .select()
138
+ .from(contests)
139
+ .where(eq(contests.slug, slug))
140
+ .limit(1);
141
+ if (rows.length === 0)
142
+ return null;
143
+ return toContestDetail(rows[0]);
144
+ }
145
+ /**
146
+ * Decide whether a viewer may see aggregate entry scores, honouring the
147
+ * contest's `judgingVisibility` setting. Pure + exhaustively testable.
148
+ *
149
+ * - Privileged viewers (owner / admin / panel judge) always see scores.
150
+ * - `public` → scores visible to everyone (during judging and after).
151
+ * - `judges-only`→ scores hidden from the public until the contest completes.
152
+ * - `private` → aggregate scores never exposed to the public (ranks may
153
+ * still be shown so winners can be announced).
154
+ */
155
+ export function shouldRevealScores(visibility, status, privileged) {
156
+ if (privileged)
157
+ return true;
158
+ if (visibility === 'public')
159
+ return true;
160
+ if (visibility === 'private')
161
+ return false;
162
+ return status === 'completed';
163
+ }
164
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/contest/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { ContestStage } from '@commonpub/schema';
2
+ import type { StageSource } from './types.js';
3
+ /**
4
+ * The classic Submissions → Judging → Results timeline, synthesized from the
5
+ * status + date columns for contests that haven't defined explicit stages.
6
+ * Stable ids let `currentStageId` reference them even for legacy contests.
7
+ */
8
+ export declare function synthesizeStages(c: StageSource): ContestStage[];
9
+ /**
10
+ * The contest's stage timeline: its explicit `stages` if any are defined,
11
+ * otherwise the synthesized classic flow. The standard flow is the zero-config
12
+ * default — a contest with no `stages` renders identically to pre-B1.
13
+ */
14
+ export declare function normalizeStages(c: StageSource): ContestStage[];
15
+ /**
16
+ * The stage that is currently "now": the one `currentStageId` points at (if it
17
+ * resolves), else derived from the coarse `status`. Null while draft/cancelled
18
+ * (nothing is running). `status` remains the behavioural source of truth for
19
+ * gating; this is for DISPLAY (hero pill, sidebar highlight, countdown label).
20
+ */
21
+ export declare function currentStage(c: StageSource): ContestStage | null;
22
+ /** True when an entry was culled at some review stage (Phase B2 cohort gate). */
23
+ export declare function isEliminated(entry: {
24
+ stageState?: Array<{
25
+ status: string;
26
+ }> | null;
27
+ }): boolean;
28
+ //# sourceMappingURL=stages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stages.d.ts","sourceRoot":"","sources":["../../src/contest/stages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAO9C;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,CAM/D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,IAAI,CAiBhE;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAE9F"}
@@ -0,0 +1,52 @@
1
+ // --- Phase B1: stage timeline helpers (pure — operate on a contest-like object) ---
2
+ const toIso = (d) => d ? new Date(d).toISOString() : undefined;
3
+ /**
4
+ * The classic Submissions → Judging → Results timeline, synthesized from the
5
+ * status + date columns for contests that haven't defined explicit stages.
6
+ * Stable ids let `currentStageId` reference them even for legacy contests.
7
+ */
8
+ export function synthesizeStages(c) {
9
+ return [
10
+ { id: 'core-submission', name: 'Submissions', kind: 'submission', core: true, startsAt: toIso(c.startDate), endsAt: toIso(c.endDate) },
11
+ { id: 'core-review', name: 'Judging', kind: 'review', core: true, endsAt: toIso(c.judgingEndDate) ?? toIso(c.endDate) },
12
+ { id: 'core-results', name: 'Results', kind: 'results', core: true },
13
+ ];
14
+ }
15
+ /**
16
+ * The contest's stage timeline: its explicit `stages` if any are defined,
17
+ * otherwise the synthesized classic flow. The standard flow is the zero-config
18
+ * default — a contest with no `stages` renders identically to pre-B1.
19
+ */
20
+ export function normalizeStages(c) {
21
+ return c.stages && c.stages.length > 0 ? c.stages : synthesizeStages(c);
22
+ }
23
+ /**
24
+ * The stage that is currently "now": the one `currentStageId` points at (if it
25
+ * resolves), else derived from the coarse `status`. Null while draft/cancelled
26
+ * (nothing is running). `status` remains the behavioural source of truth for
27
+ * gating; this is for DISPLAY (hero pill, sidebar highlight, countdown label).
28
+ */
29
+ export function currentStage(c) {
30
+ const stages = normalizeStages(c);
31
+ if (c.currentStageId) {
32
+ const found = stages.find((s) => s.id === c.currentStageId);
33
+ if (found)
34
+ return found;
35
+ }
36
+ switch (c.status) {
37
+ case 'draft':
38
+ case 'cancelled':
39
+ return null;
40
+ case 'completed':
41
+ return stages.find((s) => s.kind === 'results') ?? stages[stages.length - 1] ?? null;
42
+ case 'judging':
43
+ return stages.find((s) => s.kind === 'review') ?? null;
44
+ default: // upcoming | active | paused
45
+ return stages.find((s) => s.kind === 'submission') ?? stages[0] ?? null;
46
+ }
47
+ }
48
+ /** True when an entry was culled at some review stage (Phase B2 cohort gate). */
49
+ export function isEliminated(entry) {
50
+ return !!entry.stageState?.some((s) => s.status === 'eliminated');
51
+ }
52
+ //# sourceMappingURL=stages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stages.js","sourceRoot":"","sources":["../../src/contest/stages.ts"],"names":[],"mappings":"AAGA,qFAAqF;AAErF,MAAM,KAAK,GAAG,CAAC,CAAmC,EAAsB,EAAE,CACxE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAE5C;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAc;IAC7C,OAAO;QACL,EAAE,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE;QACtI,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE;QACvH,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;KACrE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,CAAc;IAC5C,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,CAAc;IACzC,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,cAAc,CAAC,CAAC;QAC5D,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,WAAW;YACd,OAAO,IAAI,CAAC;QACd,KAAK,WAAW;YACd,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;QACvF,KAAK,SAAS;YACZ,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC;QACzD,SAAS,6BAA6B;YACpC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC5E,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,KAAwD;IACnF,OAAO,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC;AACpE,CAAC"}
@@ -0,0 +1,90 @@
1
+ import type { ContestStageSubmission } from '@commonpub/schema';
2
+ import type { DB } from '../types.js';
3
+ import type { AgreementAcceptanceInput, ContestTx } from './types.js';
4
+ /**
5
+ * Persist the PII + agreement halves of a partitioned submission within an open
6
+ * transaction. PII is upserted (one row per entry, merged with any prior PII so
7
+ * a later stage's PII doesn't wipe an earlier stage's); agreements are appended
8
+ * as immutable acceptance rows (terms hash + snapshot). No-op when both empty.
9
+ */
10
+ export declare function recordPrivateAndAgreements(tx: ContestTx, args: {
11
+ contestId: string;
12
+ entryId: string;
13
+ userId: string;
14
+ stageId: string;
15
+ pii: Record<string, string>;
16
+ agreements: AgreementAcceptanceInput[];
17
+ ip?: string | null;
18
+ }): Promise<void>;
19
+ /**
20
+ * Submit (or update) an entrant's per-stage artifact: the filled template
21
+ * values for one `submission` stage, snapshotted onto the entry's
22
+ * `stageSubmissions`. Owner-only. The stage must be the contest's CURRENT
23
+ * stage while the contest is `active` (status stays the gating truth — the
24
+ * organizer maps a later submission round back to `active` when advancing).
25
+ * Re-submitting while the stage is open replaces that stage's artifact.
26
+ * Cohort gate: an entry culled at a prior review stage can no longer submit.
27
+ */
28
+ export declare function submitStageArtifact(db: DB, entryId: string, stageId: string, fields: Record<string, string>, userId: string, ip?: string | null): Promise<{
29
+ submitted: boolean;
30
+ stageSubmissions?: ContestStageSubmission[];
31
+ error?: string;
32
+ }>;
33
+ export interface SubmitProposalArgs {
34
+ contestId: string;
35
+ stageId: string;
36
+ /** Raw entrant form values (template-key → string). */
37
+ fields: Record<string, string>;
38
+ userId: string;
39
+ /** Best-effort client IP, recorded with any agreement acceptances. */
40
+ ip?: string | null;
41
+ }
42
+ export type SubmitProposalResult = {
43
+ ok: true;
44
+ entryId: string;
45
+ projectSlug: string;
46
+ contentId: string;
47
+ contentType: string;
48
+ } | {
49
+ ok: false;
50
+ error: string;
51
+ };
52
+ /**
53
+ * Form-first proposal submission (Phase 4). For a `submission` stage in
54
+ * `proposal` mode: validate the form, create a DRAFT placeholder project, link a
55
+ * contest entry to it (relaxing the published-only gate that `submitContestEntry`
56
+ * enforces), snapshot the artifact, record agreement acceptances, store PII in
57
+ * the private table, and bump `entryCount`. The placeholder + the entry +
58
+ * PII/agreements are written atomically: the placeholder is created first, then
59
+ * the entry tx; if the entry tx fails the placeholder is removed (compensating
60
+ * delete) so a failed submit never leaves an orphan draft.
61
+ *
62
+ * Flag (`features.contestProposals`) is enforced at the route; this fn enforces
63
+ * the structural gate that the target stage is current + proposal-mode.
64
+ */
65
+ export declare function submitContestProposal(db: DB, args: SubmitProposalArgs): Promise<SubmitProposalResult>;
66
+ export interface EntryPrivateData {
67
+ contestId: string;
68
+ entryId: string;
69
+ userId: string;
70
+ fields: Record<string, string>;
71
+ updatedAt: Date;
72
+ agreements: Array<{
73
+ fieldKey: string;
74
+ stageId: string;
75
+ termsHash: string;
76
+ termsSnapshot: string;
77
+ acceptedAt: Date;
78
+ /** Consent-audit IP captured at acceptance. Surfaced so the subject can see
79
+ * the data held about them (transparency) and organizers have the audit trail. */
80
+ ip: string | null;
81
+ }>;
82
+ }
83
+ /**
84
+ * Read an entry's PII + agreement acceptances. NEVER reachable through the
85
+ * normal entries endpoints — the calling route gates this on the `contest.pii`
86
+ * permission OR the requester being the entrant. Returns null when the entry has
87
+ * no stored PII and no agreements.
88
+ */
89
+ export declare function getEntryPrivateData(db: DB, entryId: string): Promise<EntryPrivateData | null>;
90
+ //# sourceMappingURL=submissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissions.d.ts","sourceRoot":"","sources":["../../src/contest/submissions.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAe,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAKtC,OAAO,KAAK,EAAe,wBAAwB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAInF;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,SAAS,EACb,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,UAAU,EAAE,wBAAwB,EAAE,CAAC;IACvC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB,GACA,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GACjB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkF9F;AAOD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAI5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC1F;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiG3G;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,KAAK,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,IAAI,CAAC;QACjB;2FACmF;QACnF,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;KACnB,CAAC,CAAC;CACJ;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA6BnG"}
@@ -0,0 +1,275 @@
1
+ import { eq, and, sql } from 'drizzle-orm';
2
+ import { contests, contestEntries, contestAgreementAcceptances, contestEntryPrivateFields, } from '@commonpub/schema';
3
+ import { countRows } from '../query.js';
4
+ import { createContent, deleteContent } from '../content/content.js';
5
+ import { normalizeStages, currentStage, isEliminated } from './stages.js';
6
+ import { validateSubmissionFields, hashTerms } from './validation.js';
7
+ // DB-backed submission writers. Pure validation/partition lives in validation.ts.
8
+ /**
9
+ * Persist the PII + agreement halves of a partitioned submission within an open
10
+ * transaction. PII is upserted (one row per entry, merged with any prior PII so
11
+ * a later stage's PII doesn't wipe an earlier stage's); agreements are appended
12
+ * as immutable acceptance rows (terms hash + snapshot). No-op when both empty.
13
+ */
14
+ export async function recordPrivateAndAgreements(tx, args) {
15
+ const { contestId, entryId, userId, stageId, pii, agreements, ip } = args;
16
+ if (Object.keys(pii).length > 0) {
17
+ const [existing] = await tx
18
+ .select({ fields: contestEntryPrivateFields.fields })
19
+ .from(contestEntryPrivateFields)
20
+ .where(eq(contestEntryPrivateFields.entryId, entryId))
21
+ .for('update');
22
+ const merged = { ...(existing?.fields ?? {}), ...pii };
23
+ await tx
24
+ .insert(contestEntryPrivateFields)
25
+ .values({ contestId, entryId, userId, fields: merged })
26
+ .onConflictDoUpdate({
27
+ target: contestEntryPrivateFields.entryId,
28
+ set: { fields: merged, updatedAt: new Date() },
29
+ });
30
+ }
31
+ if (agreements.length > 0) {
32
+ await tx.insert(contestAgreementAcceptances).values(agreements.map((a) => ({
33
+ contestId,
34
+ entryId,
35
+ userId,
36
+ stageId,
37
+ fieldKey: a.fieldKey,
38
+ termsHash: hashTerms(a.terms),
39
+ termsSnapshot: a.terms,
40
+ ip: ip ?? null,
41
+ })));
42
+ }
43
+ }
44
+ /**
45
+ * Submit (or update) an entrant's per-stage artifact: the filled template
46
+ * values for one `submission` stage, snapshotted onto the entry's
47
+ * `stageSubmissions`. Owner-only. The stage must be the contest's CURRENT
48
+ * stage while the contest is `active` (status stays the gating truth — the
49
+ * organizer maps a later submission round back to `active` when advancing).
50
+ * Re-submitting while the stage is open replaces that stage's artifact.
51
+ * Cohort gate: an entry culled at a prior review stage can no longer submit.
52
+ */
53
+ export async function submitStageArtifact(db, entryId, stageId, fields, userId, ip) {
54
+ const fail = (error) => ({ submitted: false, error });
55
+ const existing = await db
56
+ .select({
57
+ contestId: contestEntries.contestId,
58
+ entrantId: contestEntries.userId,
59
+ stageState: contestEntries.stageState,
60
+ contestStatus: contests.status,
61
+ stages: contests.stages,
62
+ currentStageId: contests.currentStageId,
63
+ startDate: contests.startDate,
64
+ endDate: contests.endDate,
65
+ judgingEndDate: contests.judgingEndDate,
66
+ })
67
+ .from(contestEntries)
68
+ .innerJoin(contests, eq(contestEntries.contestId, contests.id))
69
+ .where(eq(contestEntries.id, entryId))
70
+ .limit(1);
71
+ if (existing.length === 0)
72
+ return fail('Entry not found');
73
+ const row = existing[0];
74
+ if (row.entrantId !== userId)
75
+ return fail('Not the entry owner');
76
+ if (row.contestStatus !== 'active') {
77
+ return fail('Stage submissions are only open while the contest is active');
78
+ }
79
+ const source = {
80
+ status: row.contestStatus,
81
+ startDate: row.startDate,
82
+ endDate: row.endDate,
83
+ judgingEndDate: row.judgingEndDate,
84
+ stages: row.stages,
85
+ currentStageId: row.currentStageId,
86
+ };
87
+ const stages = normalizeStages(source);
88
+ const stage = stages.find((s) => s.id === stageId);
89
+ if (!stage)
90
+ return fail('Unknown stage');
91
+ if (stage.kind !== 'submission')
92
+ return fail('This stage does not accept submissions');
93
+ const template = stage.submissionTemplate ?? [];
94
+ if (template.length === 0)
95
+ return fail('This stage has no submission template');
96
+ const current = currentStage(source);
97
+ if (current?.id !== stageId)
98
+ return fail('This stage is not currently open');
99
+ // Cohort gate: once a review cut culled the field, eliminated entries are
100
+ // out of every later round (mirrors judgeContestEntry's gate).
101
+ if (isEliminated({ stageState: row.stageState })) {
102
+ return fail('This entry was not advanced and can no longer submit');
103
+ }
104
+ const validated = validateSubmissionFields(template, fields);
105
+ if (!validated.ok)
106
+ return fail(validated.error);
107
+ const { artifact, pii, agreements } = validated.result;
108
+ // Atomic read-modify-write: lock the entry row so two concurrent saves of
109
+ // the same artifact can't clobber each other (same pattern as judgeScores).
110
+ // PII + agreements (if the template has any) are persisted in the SAME tx so
111
+ // they can never land in the public stageSubmissions jsonb.
112
+ return db.transaction(async (tx) => {
113
+ const [locked] = await tx
114
+ .select({ stageSubmissions: contestEntries.stageSubmissions })
115
+ .from(contestEntries)
116
+ .where(eq(contestEntries.id, entryId))
117
+ .for('update');
118
+ const submissions = (locked?.stageSubmissions ?? []);
119
+ const record = { stageId, fields: artifact, submittedAt: new Date().toISOString() };
120
+ const idx = submissions.findIndex((s) => s.stageId === stageId);
121
+ if (idx >= 0)
122
+ submissions[idx] = record;
123
+ else
124
+ submissions.push(record);
125
+ await tx
126
+ .update(contestEntries)
127
+ .set({ stageSubmissions: submissions })
128
+ .where(eq(contestEntries.id, entryId));
129
+ await recordPrivateAndAgreements(tx, { contestId: row.contestId, entryId, userId, stageId, pii, agreements, ip });
130
+ return { submitted: true, stageSubmissions: submissions };
131
+ });
132
+ }
133
+ // --- Form-first proposal submissions (Phase 4) ---
134
+ /** Content types a placeholder proposal project may be created as. */
135
+ const PLACEHOLDER_TYPES = ['project', 'blog', 'explainer'];
136
+ /**
137
+ * Form-first proposal submission (Phase 4). For a `submission` stage in
138
+ * `proposal` mode: validate the form, create a DRAFT placeholder project, link a
139
+ * contest entry to it (relaxing the published-only gate that `submitContestEntry`
140
+ * enforces), snapshot the artifact, record agreement acceptances, store PII in
141
+ * the private table, and bump `entryCount`. The placeholder + the entry +
142
+ * PII/agreements are written atomically: the placeholder is created first, then
143
+ * the entry tx; if the entry tx fails the placeholder is removed (compensating
144
+ * delete) so a failed submit never leaves an orphan draft.
145
+ *
146
+ * Flag (`features.contestProposals`) is enforced at the route; this fn enforces
147
+ * the structural gate that the target stage is current + proposal-mode.
148
+ */
149
+ export async function submitContestProposal(db, args) {
150
+ const { contestId, stageId, fields, userId, ip } = args;
151
+ const fail = (error) => ({ ok: false, error });
152
+ const [contest] = await db
153
+ .select({
154
+ id: contests.id,
155
+ title: contests.title,
156
+ status: contests.status,
157
+ stages: contests.stages,
158
+ currentStageId: contests.currentStageId,
159
+ startDate: contests.startDate,
160
+ endDate: contests.endDate,
161
+ judgingEndDate: contests.judgingEndDate,
162
+ maxEntriesPerUser: contests.maxEntriesPerUser,
163
+ eligibleContentTypes: contests.eligibleContentTypes,
164
+ })
165
+ .from(contests)
166
+ .where(eq(contests.id, contestId))
167
+ .limit(1);
168
+ if (!contest)
169
+ return fail('Contest not found');
170
+ if (contest.status !== 'active')
171
+ return fail('Proposals are only open while the contest is active');
172
+ const source = {
173
+ status: contest.status,
174
+ startDate: contest.startDate,
175
+ endDate: contest.endDate,
176
+ judgingEndDate: contest.judgingEndDate,
177
+ stages: contest.stages,
178
+ currentStageId: contest.currentStageId,
179
+ };
180
+ const stage = normalizeStages(source).find((s) => s.id === stageId);
181
+ if (!stage)
182
+ return fail('Unknown stage');
183
+ if (stage.kind !== 'submission')
184
+ return fail('This stage does not accept submissions');
185
+ if (stage.submissionMode !== 'proposal')
186
+ return fail('This stage is not accepting proposals');
187
+ if (currentStage(source)?.id !== stageId)
188
+ return fail('This stage is not currently open');
189
+ const template = stage.submissionTemplate ?? [];
190
+ const validated = validateSubmissionFields(template, fields);
191
+ if (!validated.ok)
192
+ return fail(validated.error);
193
+ const { artifact, pii, agreements } = validated.result;
194
+ // Per-user entry cap (mirrors submitContestEntry).
195
+ if (contest.maxEntriesPerUser != null) {
196
+ const existing = await countRows(db, contestEntries, and(eq(contestEntries.contestId, contestId), eq(contestEntries.userId, userId)));
197
+ if (existing >= contest.maxEntriesPerUser)
198
+ return fail('You have reached the entry limit for this contest');
199
+ }
200
+ // Pick the placeholder content type: the contest's first eligible type when
201
+ // it's one we can create, else a project.
202
+ const eligible = (contest.eligibleContentTypes ?? []).find((t) => PLACEHOLDER_TYPES.includes(t));
203
+ const placeholderType = (eligible ?? 'project');
204
+ const title = artifact.title?.trim() || `${contest.title} proposal`;
205
+ // 1) Create the DRAFT placeholder (its own writes). The entrant develops it in
206
+ // the editor; the proposal text lives in the stage artifact below.
207
+ const placeholder = await createContent(db, userId, { type: placeholderType, title });
208
+ // 2) Link the entry + persist artifact/PII/agreements/count atomically. On any
209
+ // failure, remove the placeholder so a failed submit leaves nothing behind.
210
+ try {
211
+ const entryId = await db.transaction(async (tx) => {
212
+ const [inserted] = await tx
213
+ .insert(contestEntries)
214
+ .values({
215
+ contestId,
216
+ contentId: placeholder.id,
217
+ userId,
218
+ stageSubmissions: [{ stageId, fields: artifact, submittedAt: new Date().toISOString() }],
219
+ })
220
+ .onConflictDoNothing()
221
+ .returning({ id: contestEntries.id });
222
+ if (!inserted)
223
+ throw new Error('entry-conflict');
224
+ await tx
225
+ .update(contests)
226
+ .set({ entryCount: sql `${contests.entryCount} + 1` })
227
+ .where(eq(contests.id, contestId));
228
+ await recordPrivateAndAgreements(tx, { contestId, entryId: inserted.id, userId, stageId, pii, agreements, ip });
229
+ return inserted.id;
230
+ });
231
+ return { ok: true, entryId, projectSlug: placeholder.slug, contentId: placeholder.id, contentType: placeholder.type };
232
+ }
233
+ catch (err) {
234
+ await deleteContent(db, placeholder.id, userId).catch(() => { });
235
+ if (err instanceof Error && err.message === 'entry-conflict') {
236
+ return fail('You already have an entry for this contest');
237
+ }
238
+ throw err;
239
+ }
240
+ }
241
+ /**
242
+ * Read an entry's PII + agreement acceptances. NEVER reachable through the
243
+ * normal entries endpoints — the calling route gates this on the `contest.pii`
244
+ * permission OR the requester being the entrant. Returns null when the entry has
245
+ * no stored PII and no agreements.
246
+ */
247
+ export async function getEntryPrivateData(db, entryId) {
248
+ const [priv] = await db
249
+ .select()
250
+ .from(contestEntryPrivateFields)
251
+ .where(eq(contestEntryPrivateFields.entryId, entryId))
252
+ .limit(1);
253
+ const agreements = await db
254
+ .select({
255
+ fieldKey: contestAgreementAcceptances.fieldKey,
256
+ stageId: contestAgreementAcceptances.stageId,
257
+ termsHash: contestAgreementAcceptances.termsHash,
258
+ termsSnapshot: contestAgreementAcceptances.termsSnapshot,
259
+ acceptedAt: contestAgreementAcceptances.acceptedAt,
260
+ ip: contestAgreementAcceptances.ip,
261
+ })
262
+ .from(contestAgreementAcceptances)
263
+ .where(eq(contestAgreementAcceptances.entryId, entryId));
264
+ if (!priv && agreements.length === 0)
265
+ return null;
266
+ return {
267
+ contestId: priv?.contestId ?? '',
268
+ entryId,
269
+ userId: priv?.userId ?? '',
270
+ fields: (priv?.fields ?? {}),
271
+ updatedAt: priv?.updatedAt ?? new Date(0),
272
+ agreements,
273
+ };
274
+ }
275
+ //# sourceMappingURL=submissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissions.js","sourceRoot":"","sources":["../../src/contest/submissions.ts"],"names":[],"mappings":"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"}