@commonpub/server 2.88.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 +9 -0
- package/dist/admin/admin.d.ts.map +1 -1
- package/dist/admin/admin.js +50 -13
- 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 +7 -300
- package/dist/contest/contest.d.ts.map +1 -1
- package/dist/contest/contest.js +99 -882
- 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 +12 -3
- package/dist/contest/index.d.ts.map +1 -1
- package/dist/contest/index.js +9 -2
- 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/stakeholders.d.ts +32 -7
- package/dist/contest/stakeholders.d.ts.map +1 -1
- package/dist/contest/stakeholders.js +55 -14
- package/dist/contest/stakeholders.js.map +1 -1
- 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 +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- 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/admin.d.ts +43 -0
- package/dist/rbac/admin.d.ts.map +1 -0
- package/dist/rbac/admin.js +172 -0
- package/dist/rbac/admin.js.map +1 -0
- package/dist/rbac/index.d.ts +4 -0
- package/dist/rbac/index.d.ts.map +1 -1
- package/dist/rbac/index.js +2 -0
- package/dist/rbac/index.js.map +1 -1
- package/dist/rbac/seed.d.ts +30 -0
- package/dist/rbac/seed.d.ts.map +1 -0
- package/dist/rbac/seed.js +75 -0
- package/dist/rbac/seed.js.map +1 -0
- 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 +9 -7
|
@@ -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":"AAAA,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGjF,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAS7C,gFAAgF;AAChF,iFAAiF;AAEjF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAM,EACN,UAA0B,EAAE,EAC5B,MAAgD;IAEhD,MAAM,UAAU,GAAG,EAAE,CAAC;IAEtB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CACb,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CACpC,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,8EAA8E;IAC9E,8EAA8E;IAC9E,kFAAkF;IAClF,sFAAsF;IACtF,IAAI,MAAM,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QACrD,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;YACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACvD,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,QAAQ,CAAC,EAAE,EACX,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,mBAAmB,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAChI,CACF,CAAC;YACF,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,QAAQ,CAAC,EAAE,EACX,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAC9G,CACF,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;YACjB,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAA,GAAG,QAAQ,CAAC,cAAc,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5F,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC;QAEvE,6EAA6E;QAC7E,qFAAqF;QACrF,UAAU,CAAC,IAAI,CACb,MAAM,EAAE,MAAM;YACZ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,CAAE;YAC5E,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CACjC,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAEvD,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtC,EAAE;aACC,MAAM,EAAE;aACR,IAAI,CAAC,QAAQ,CAAC;aACd,KAAK,CAAC,KAAK,CAAC;aACZ,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;aACpD,KAAK,CAAC,KAAK,CAAC;aACZ,MAAM,CAAC,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC;KAC/B,CAAC,CAAC;IAEH,MAAM,KAAK,GAAsB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAClD,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,CAAC,CAAC,CAAC;IAEJ,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC;AAID,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAAC,GAAe;IAC7C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QACxC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QACxC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,uBAAuB,EAAE,GAAG,CAAC,uBAAuB;QACpD,iBAAiB,EAAG,GAAG,CAAC,iBAAsC,IAAI,IAAI;QACtE,WAAW,EAAG,GAAG,CAAC,WAAgC,IAAI,IAAI;QAC1D,YAAY,EAAG,GAAG,CAAC,YAAiC,IAAI,IAAI;QAC5D,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;QACxB,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI;QAC1C,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI;QAC1B,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,IAAI;QAC5C,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QACxC,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,sBAAsB,EAAE,GAAG,CAAC,sBAAsB;QAClD,oBAAoB,EAAE,GAAG,CAAC,oBAAoB,IAAI,IAAI;QACtD,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,IAAI,IAAI;QAChD,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI;QAC1C,WAAW,EAAE,GAAG,CAAC,WAAW;KAC7B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAM,EACN,OAA6H,EAC7H,IAAyC;IAEzC,6EAA6E;IAC7E,8EAA8E;IAC9E,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1E,IAAI,MAAM,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QACrE,IAAI,MAAM,cAAc,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/D,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1E,IAAI,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtF,IAAI,MAAM,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,MAAM,cAAc,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,EAAM,EACN,IAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,EAAE;SACR,IAAI,CAAC,QAAQ,CAAC;SACd,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;SAC9B,KAAK,CAAC,CAAC,CAAC,CAAC;IAEZ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAAoC,EACpC,MAAqB,EACrB,UAAmB;IAEnB,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC3C,OAAO,MAAM,KAAK,WAAW,CAAC;AAChC,CAAC"}
|
|
@@ -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"}
|
|
@@ -1,27 +1,52 @@
|
|
|
1
|
+
import type { StakeholderRole } from '@commonpub/schema';
|
|
1
2
|
import type { DB } from '../types.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Per-contest collaborators, distinguished by `role`:
|
|
5
|
+
* - 'reviewer' — view-only: can see a contest (even private / pre-publish)
|
|
6
|
+
* without admin-panel access or being a judge. Never appears in the judge
|
|
7
|
+
* list and cannot score. (Original stakeholder semantics.)
|
|
8
|
+
* - 'editor' — full edit rights to THIS contest only (no system-wide access).
|
|
9
|
+
* Recognized by `isContestEditor`, which the edit/advance/transition routes
|
|
10
|
+
* fold into their canManage decision.
|
|
6
11
|
*/
|
|
7
12
|
export interface ContestStakeholderItem {
|
|
8
13
|
id: string;
|
|
9
14
|
contestId: string;
|
|
10
15
|
userId: string;
|
|
16
|
+
role: StakeholderRole;
|
|
11
17
|
invitedAt: Date;
|
|
12
18
|
userName: string;
|
|
13
19
|
userUsername: string;
|
|
14
20
|
userAvatar: string | null;
|
|
15
21
|
}
|
|
16
22
|
export declare function listContestStakeholders(db: DB, contestId: string): Promise<ContestStakeholderItem[]>;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Grant (or update) a user's per-contest collaborator role. Upserts: if the
|
|
25
|
+
* user is already a stakeholder with a different role, their role is updated
|
|
26
|
+
* (so a reviewer can be promoted to editor) — `updated:true` is returned.
|
|
27
|
+
* Adding/promoting is gated to the contest owner / `contest.manage` at the route
|
|
28
|
+
* (an editor cannot mint more editors).
|
|
29
|
+
*/
|
|
30
|
+
export declare function addContestStakeholder(db: DB, contestId: string, userId: string, opts?: {
|
|
31
|
+
role?: StakeholderRole;
|
|
32
|
+
contestSlug?: string;
|
|
33
|
+
contestTitle?: string;
|
|
34
|
+
invitedBy?: string;
|
|
21
35
|
}): Promise<{
|
|
22
36
|
added: boolean;
|
|
37
|
+
updated?: boolean;
|
|
23
38
|
error?: string;
|
|
24
39
|
}>;
|
|
25
40
|
export declare function removeContestStakeholder(db: DB, contestId: string, userId: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* True if the user is a stakeholder of ANY role (reviewer or editor) — i.e. may
|
|
43
|
+
* VIEW a private/draft contest. Role-agnostic on purpose.
|
|
44
|
+
*/
|
|
26
45
|
export declare function isContestStakeholder(db: DB, contestId: string, userId: string): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* True if the user is an `editor` stakeholder of this contest — i.e. holds full
|
|
48
|
+
* edit rights to it (no system-wide access). Folded into the canManage decision
|
|
49
|
+
* on the edit/advance/transition routes alongside owner + `contest.manage`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isContestEditor(db: DB, contestId: string, userId: string): Promise<boolean>;
|
|
27
52
|
//# sourceMappingURL=stakeholders.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stakeholders.d.ts","sourceRoot":"","sources":["../../src/contest/stakeholders.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAGtC
|
|
1
|
+
{"version":3,"file":"stakeholders.d.ts","sourceRoot":"","sources":["../../src/contest/stakeholders.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAGtC;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAoBnC;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BhE;AAoBD,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAOlB"}
|
|
@@ -14,13 +14,22 @@ export async function listContestStakeholders(db, contestId) {
|
|
|
14
14
|
id: sh.id,
|
|
15
15
|
contestId: sh.contestId,
|
|
16
16
|
userId: sh.userId,
|
|
17
|
+
role: sh.role ?? 'reviewer',
|
|
17
18
|
invitedAt: sh.invitedAt,
|
|
18
19
|
userName: user.displayName ?? user.username,
|
|
19
20
|
userUsername: user.username,
|
|
20
21
|
userAvatar: user.avatarUrl,
|
|
21
22
|
}));
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Grant (or update) a user's per-contest collaborator role. Upserts: if the
|
|
26
|
+
* user is already a stakeholder with a different role, their role is updated
|
|
27
|
+
* (so a reviewer can be promoted to editor) — `updated:true` is returned.
|
|
28
|
+
* Adding/promoting is gated to the contest owner / `contest.manage` at the route
|
|
29
|
+
* (an editor cannot mint more editors).
|
|
30
|
+
*/
|
|
31
|
+
export async function addContestStakeholder(db, contestId, userId, opts) {
|
|
32
|
+
const role = opts?.role ?? 'reviewer';
|
|
24
33
|
const [contest] = await db.select({ id: contests.id }).from(contests).where(eq(contests.id, contestId)).limit(1);
|
|
25
34
|
if (!contest)
|
|
26
35
|
return { added: false, error: 'Contest not found' };
|
|
@@ -28,25 +37,40 @@ export async function addContestStakeholder(db, contestId, userId, context) {
|
|
|
28
37
|
if (!user)
|
|
29
38
|
return { added: false, error: 'User not found' };
|
|
30
39
|
const [existing] = await db
|
|
31
|
-
.select({ id: contestStakeholders.id })
|
|
40
|
+
.select({ id: contestStakeholders.id, role: contestStakeholders.role })
|
|
32
41
|
.from(contestStakeholders)
|
|
33
42
|
.where(and(eq(contestStakeholders.contestId, contestId), eq(contestStakeholders.userId, userId)))
|
|
34
43
|
.limit(1);
|
|
35
|
-
if (existing)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
type: 'contest',
|
|
42
|
-
title: 'Contest Access',
|
|
43
|
-
message: `You've been granted review access to "${context.contestTitle}"`,
|
|
44
|
-
link: `/contests/${context.contestSlug}`,
|
|
45
|
-
actorId: context.invitedBy,
|
|
46
|
-
}).catch(() => { });
|
|
44
|
+
if (existing) {
|
|
45
|
+
if (existing.role === role)
|
|
46
|
+
return { added: false, error: 'User already has that role' };
|
|
47
|
+
await db.update(contestStakeholders).set({ role }).where(eq(contestStakeholders.id, existing.id));
|
|
48
|
+
notifyStakeholder(db, userId, role, opts);
|
|
49
|
+
return { added: true, updated: true };
|
|
47
50
|
}
|
|
51
|
+
// Conflict-safe insert: a concurrent double-submit can pass the `existing`
|
|
52
|
+
// check above and race the unique (contestId,userId) constraint — upsert the
|
|
53
|
+
// role instead of 500ing on the violation.
|
|
54
|
+
await db
|
|
55
|
+
.insert(contestStakeholders)
|
|
56
|
+
.values({ contestId, userId, role })
|
|
57
|
+
.onConflictDoUpdate({ target: [contestStakeholders.contestId, contestStakeholders.userId], set: { role } });
|
|
58
|
+
notifyStakeholder(db, userId, role, opts);
|
|
48
59
|
return { added: true };
|
|
49
60
|
}
|
|
61
|
+
function notifyStakeholder(db, userId, role, opts) {
|
|
62
|
+
if (!opts?.contestSlug || !opts.contestTitle || !opts.invitedBy)
|
|
63
|
+
return;
|
|
64
|
+
const access = role === 'editor' ? 'edit access' : 'review access';
|
|
65
|
+
createNotification(db, {
|
|
66
|
+
userId,
|
|
67
|
+
type: 'contest',
|
|
68
|
+
title: 'Contest Access',
|
|
69
|
+
message: `You've been granted ${access} to "${opts.contestTitle}"`,
|
|
70
|
+
link: `/contests/${opts.contestSlug}`,
|
|
71
|
+
actorId: opts.invitedBy,
|
|
72
|
+
}).catch(() => { });
|
|
73
|
+
}
|
|
50
74
|
export async function removeContestStakeholder(db, contestId, userId) {
|
|
51
75
|
const [existing] = await db
|
|
52
76
|
.select({ id: contestStakeholders.id })
|
|
@@ -58,6 +82,10 @@ export async function removeContestStakeholder(db, contestId, userId) {
|
|
|
58
82
|
await db.delete(contestStakeholders).where(eq(contestStakeholders.id, existing.id));
|
|
59
83
|
return true;
|
|
60
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* True if the user is a stakeholder of ANY role (reviewer or editor) — i.e. may
|
|
87
|
+
* VIEW a private/draft contest. Role-agnostic on purpose.
|
|
88
|
+
*/
|
|
61
89
|
export async function isContestStakeholder(db, contestId, userId) {
|
|
62
90
|
const [row] = await db
|
|
63
91
|
.select({ id: contestStakeholders.id })
|
|
@@ -66,4 +94,17 @@ export async function isContestStakeholder(db, contestId, userId) {
|
|
|
66
94
|
.limit(1);
|
|
67
95
|
return !!row;
|
|
68
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* True if the user is an `editor` stakeholder of this contest — i.e. holds full
|
|
99
|
+
* edit rights to it (no system-wide access). Folded into the canManage decision
|
|
100
|
+
* on the edit/advance/transition routes alongside owner + `contest.manage`.
|
|
101
|
+
*/
|
|
102
|
+
export async function isContestEditor(db, contestId, userId) {
|
|
103
|
+
const [row] = await db
|
|
104
|
+
.select({ role: contestStakeholders.role })
|
|
105
|
+
.from(contestStakeholders)
|
|
106
|
+
.where(and(eq(contestStakeholders.contestId, contestId), eq(contestStakeholders.userId, userId)))
|
|
107
|
+
.limit(1);
|
|
108
|
+
return row?.role === 'editor';
|
|
109
|
+
}
|
|
69
110
|
//# sourceMappingURL=stakeholders.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stakeholders.js","sourceRoot":"","sources":["../../src/contest/stakeholders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"stakeholders.js","sourceRoot":"","sources":["../../src/contest/stakeholders.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|