@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.
- package/dist/admin/admin.d.ts.map +1 -1
- package/dist/admin/admin.js +4 -5
- package/dist/admin/admin.js.map +1 -1
- package/dist/content/content.d.ts +0 -5
- package/dist/content/content.d.ts.map +1 -1
- package/dist/content/content.js +118 -122
- package/dist/content/content.js.map +1 -1
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -1
- package/dist/content/index.js.map +1 -1
- package/dist/contest/contest.d.ts +2 -308
- package/dist/contest/contest.d.ts.map +1 -1
- package/dist/contest/contest.js +87 -878
- package/dist/contest/contest.js.map +1 -1
- package/dist/contest/entries.d.ts +41 -0
- package/dist/contest/entries.d.ts.map +1 -0
- package/dist/contest/entries.js +285 -0
- package/dist/contest/entries.js.map +1 -0
- package/dist/contest/export.d.ts +20 -0
- package/dist/contest/export.d.ts.map +1 -0
- package/dist/contest/export.js +131 -0
- package/dist/contest/export.js.map +1 -0
- package/dist/contest/index.d.ts +11 -2
- package/dist/contest/index.d.ts.map +1 -1
- package/dist/contest/index.js +8 -1
- package/dist/contest/index.js.map +1 -1
- package/dist/contest/judges.js +9 -8
- package/dist/contest/judges.js.map +1 -1
- package/dist/contest/judging.d.ts +38 -0
- package/dist/contest/judging.d.ts.map +1 -0
- package/dist/contest/judging.js +274 -0
- package/dist/contest/judging.js.map +1 -0
- package/dist/contest/read.d.ts +44 -0
- package/dist/contest/read.d.ts.map +1 -0
- package/dist/contest/read.js +164 -0
- package/dist/contest/read.js.map +1 -0
- package/dist/contest/stages.d.ts +28 -0
- package/dist/contest/stages.d.ts.map +1 -0
- package/dist/contest/stages.js +52 -0
- package/dist/contest/stages.js.map +1 -0
- package/dist/contest/submissions.d.ts +90 -0
- package/dist/contest/submissions.d.ts.map +1 -0
- package/dist/contest/submissions.js +275 -0
- package/dist/contest/submissions.js.map +1 -0
- package/dist/contest/types.d.ts +197 -0
- package/dist/contest/types.d.ts.map +1 -0
- package/dist/contest/types.js +2 -0
- package/dist/contest/types.js.map +1 -0
- package/dist/contest/validation.d.ts +32 -0
- package/dist/contest/validation.d.ts.map +1 -0
- package/dist/contest/validation.js +132 -0
- package/dist/contest/validation.js.map +1 -0
- package/dist/docs/docs.d.ts +9 -3
- package/dist/docs/docs.d.ts.map +1 -1
- package/dist/docs/docs.js +16 -6
- package/dist/docs/docs.js.map +1 -1
- package/dist/events/events.d.ts.map +1 -1
- package/dist/events/events.js +12 -6
- package/dist/events/events.js.map +1 -1
- package/dist/federation/activityDedup.d.ts +14 -0
- package/dist/federation/activityDedup.d.ts.map +1 -0
- package/dist/federation/activityDedup.js +34 -0
- package/dist/federation/activityDedup.js.map +1 -0
- package/dist/federation/assertPublicHost.d.ts +14 -0
- package/dist/federation/assertPublicHost.d.ts.map +1 -0
- package/dist/federation/assertPublicHost.js +62 -0
- package/dist/federation/assertPublicHost.js.map +1 -0
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +37 -51
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +11 -7
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/hubMirroring.d.ts.map +1 -1
- package/dist/federation/hubMirroring.js +85 -66
- package/dist/federation/hubMirroring.js.map +1 -1
- package/dist/federation/inboxHandlers.d.ts.map +1 -1
- package/dist/federation/inboxHandlers.js +84 -73
- package/dist/federation/inboxHandlers.js.map +1 -1
- package/dist/federation/inboxParsing.d.ts +28 -0
- package/dist/federation/inboxParsing.d.ts.map +1 -0
- package/dist/federation/inboxParsing.js +71 -0
- package/dist/federation/inboxParsing.js.map +1 -0
- package/dist/federation/index.d.ts +2 -0
- package/dist/federation/index.d.ts.map +1 -1
- package/dist/federation/index.js +2 -0
- package/dist/federation/index.js.map +1 -1
- package/dist/federation/mastodonLogin.d.ts.map +1 -1
- package/dist/federation/mastodonLogin.js +19 -0
- package/dist/federation/mastodonLogin.js.map +1 -1
- package/dist/federation/outboxQueries.js +1 -1
- package/dist/federation/outboxQueries.js.map +1 -1
- package/dist/federation/timeline.d.ts +11 -0
- package/dist/federation/timeline.d.ts.map +1 -1
- package/dist/federation/timeline.js +101 -69
- package/dist/federation/timeline.js.map +1 -1
- package/dist/hub/hub.d.ts.map +1 -1
- package/dist/hub/hub.js +41 -3
- package/dist/hub/hub.js.map +1 -1
- package/dist/hub/index.d.ts +1 -1
- package/dist/hub/index.d.ts.map +1 -1
- package/dist/hub/index.js +1 -1
- package/dist/hub/index.js.map +1 -1
- package/dist/hub/members.d.ts +19 -0
- package/dist/hub/members.d.ts.map +1 -1
- package/dist/hub/members.js +158 -13
- package/dist/hub/members.js.map +1 -1
- package/dist/hub/moderation.d.ts +1 -1
- package/dist/hub/moderation.d.ts.map +1 -1
- package/dist/hub/moderation.js +25 -11
- package/dist/hub/moderation.js.map +1 -1
- package/dist/hub/posts.d.ts.map +1 -1
- package/dist/hub/posts.js +18 -10
- package/dist/hub/posts.js.map +1 -1
- package/dist/hub/resources.js +2 -2
- package/dist/hub/resources.js.map +1 -1
- package/dist/identity/mastodonFactory.d.ts.map +1 -1
- package/dist/identity/mastodonFactory.js +7 -0
- package/dist/identity/mastodonFactory.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/learning/learning.d.ts.map +1 -1
- package/dist/learning/learning.js +42 -22
- package/dist/learning/learning.js.map +1 -1
- package/dist/messaging/messaging.d.ts.map +1 -1
- package/dist/messaging/messaging.js +3 -3
- package/dist/messaging/messaging.js.map +1 -1
- package/dist/notification/notification.d.ts.map +1 -1
- package/dist/notification/notification.js +4 -2
- package/dist/notification/notification.js.map +1 -1
- package/dist/product/product.d.ts.map +1 -1
- package/dist/product/product.js +75 -37
- package/dist/product/product.js.map +1 -1
- package/dist/profile/index.d.ts +2 -1
- package/dist/profile/index.d.ts.map +1 -1
- package/dist/profile/index.js +1 -1
- package/dist/profile/index.js.map +1 -1
- package/dist/profile/profile.d.ts +24 -2
- package/dist/profile/profile.d.ts.map +1 -1
- package/dist/profile/profile.js +34 -8
- package/dist/profile/profile.js.map +1 -1
- package/dist/query.d.ts +18 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +24 -6
- package/dist/query.js.map +1 -1
- package/dist/rbac/seed.d.ts +1 -1
- package/dist/rbac/seed.d.ts.map +1 -1
- package/dist/rbac/seed.js +1 -0
- package/dist/rbac/seed.js.map +1 -1
- package/dist/search/contentSearch.d.ts +1 -1
- package/dist/search/contentSearch.d.ts.map +1 -1
- package/dist/search/contentSearch.js +22 -12
- package/dist/search/contentSearch.js.map +1 -1
- package/dist/social/social.d.ts +4 -2
- package/dist/social/social.d.ts.map +1 -1
- package/dist/social/social.js +25 -8
- package/dist/social/social.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/video/video.d.ts +3 -0
- package/dist/video/video.d.ts.map +1 -1
- package/dist/video/video.js +17 -13
- package/dist/video/video.js.map +1 -1
- package/dist/voting/voting.d.ts.map +1 -1
- package/dist/voting/voting.js +39 -1
- package/dist/voting/voting.js.map +1 -1
- 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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"}
|