From 1c643efed13e2972a300fd68cd3d14b898c836ef Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Sun, 19 Apr 2026 13:16:16 +0000 Subject: [PATCH 1/2] Add end cutoff --- .../projects/[id]/ship/+page.server.ts | 36 ++++++++------- .../dashboard/projects/[id]/ship/+page.svelte | 45 ++++++++++++++++--- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/routes/dashboard/projects/[id]/ship/+page.server.ts b/src/routes/dashboard/projects/[id]/ship/+page.server.ts index c317cf9..724b877 100644 --- a/src/routes/dashboard/projects/[id]/ship/+page.server.ts +++ b/src/routes/dashboard/projects/[id]/ship/+page.server.ts @@ -12,6 +12,7 @@ import { PutObjectCommand } from '@aws-sdk/client-s3'; import { S3 } from '$lib/server/s3'; import { ship } from '$lib/server/db/schema.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; +import { END_DATE } from '$lib/defs'; export async function load({ params, locals }) { const id: number = parseInt(params.id); @@ -109,7 +110,7 @@ export const actions = { }); } - // Double dipping + // Double dipping if (doubleDippingWith !== 'none' && doubleDippingWith !== 'enclosure') { return error(400); } @@ -233,6 +234,7 @@ export const actions = { name: project.name, description: project.description, url: project.url, + status: project.status, timeSpent: sql`COALESCE(SUM(${devlog.timeSpent}), 0)`, devlogCount: sql`COALESCE(COUNT(${devlog.id}), 0)` }) @@ -267,6 +269,23 @@ export const actions = { return error(400, { message: 'project must have a description' }); } + // Get club ID if submitting as club + let clubIdForShip: number | null = null; + if (submitAsClub) { + const [membership] = await db + .select({ clubId: clubMembership.clubId }) + .from(clubMembership) + .where(eq(clubMembership.userId, locals.user.id)) + .limit(1); + if (membership) { + clubIdForShip = membership.clubId; + } + } + + if (END_DATE <= new Date() && queriedProject.status === 'building' && clubIdForShip === null) { + return error(400, { message: "can't submit individual project past end date" }); + } + // Editor file const editorFilePath = `ships/editor-files/${crypto.randomUUID()}${extname(editorFile.name)}`; @@ -299,7 +318,7 @@ export const actions = { uploadedFileUrl: editorFileExists ? editorFilePath : undefined, modelFile: modelPath, - doubleDippingWith + doubleDippingWith }) .where( and( @@ -309,19 +328,6 @@ export const actions = { ) ); - // Get club ID if submitting as club - let clubIdForShip: number | null = null; - if (submitAsClub) { - const [membership] = await db - .select({ clubId: clubMembership.clubId }) - .from(clubMembership) - .where(eq(clubMembership.userId, locals.user.id)) - .limit(1); - if (membership) { - clubIdForShip = membership.clubId; - } - } - await db.insert(ship).values({ userId: locals.user.id, projectId: queriedProject.id, diff --git a/src/routes/dashboard/projects/[id]/ship/+page.svelte b/src/routes/dashboard/projects/[id]/ship/+page.svelte index 8a2b65b..1de6313 100644 --- a/src/routes/dashboard/projects/[id]/ship/+page.svelte +++ b/src/routes/dashboard/projects/[id]/ship/+page.svelte @@ -4,6 +4,7 @@ import Head from '$lib/components/Head.svelte'; import Project from '$lib/components/Project.svelte'; import { calculateCurrencyPayout, calculateMinutes } from '$lib/currency'; + import { END_DATE } from '$lib/defs'; import { MAX_UPLOAD_SIZE } from '../config'; import type { PageProps } from './$types'; import { Ship, SquarePen } from '@lucide/svelte'; @@ -16,7 +17,11 @@ let editorUrl = $state(data.project.editorUrl); let editorUploadFile = $state(null); let modelFile = $state(null); - let submitAsClub = $state(false); + let submitAsClub = $state( + END_DATE <= new Date() && data.project.status === 'building' && data.clubMembership + ? true + : false + ); let hasEditorFile = $derived((editorUrl || editorUploadFile) && !(editorUrl && editorUploadFile)); @@ -33,6 +38,20 @@

Ship project

+ +{#if END_DATE <= new Date() && data.project.status == 'building'} +
+

New non-clubs submissions are now closed!

+

+ Construct has ended for non-clubs projects. The market is still open and you can re-ship + rejected projects, however you can't ship new projects. +

+

You can still ship new projects as normal for clubs.

+
+{/if} +

Submit as

-
{#if data.project.timeSpent >= 60 && data.project.description != '' && data.project.url != ''} -

- Are you sure you want to ship "{data.project.name}"? - You won't be able to edit it or journal again unless it gets rejected. -

+ {#if !submitAsClub && END_DATE <= new Date() && data.project.status === 'building'} +

Non-clubs submissions are now closed!

+ {:else} +

+ Are you sure you want to ship "{data.project.name}"? + You won't be able to edit it or journal again unless it gets + rejected. +

+ {/if} {/if}
@@ -258,7 +288,8 @@ data.project.description == '' || !printablesUrl || !hasEditorFile || - !modelFile} + !modelFile || + (!submitAsClub && END_DATE <= new Date())} > Ship From e0330b72eec63da0cbec6a422a87e7937cf327b1 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Sun, 19 Apr 2026 14:42:10 +0000 Subject: [PATCH 2/2] Prevent rejected projects shipped for clubs to be shipped individually after deadline --- .../projects/[id]/ship/+page.server.ts | 25 +++++++++++++++---- .../dashboard/projects/[id]/ship/+page.svelte | 10 ++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/routes/dashboard/projects/[id]/ship/+page.server.ts b/src/routes/dashboard/projects/[id]/ship/+page.server.ts index 724b877..09a060b 100644 --- a/src/routes/dashboard/projects/[id]/ship/+page.server.ts +++ b/src/routes/dashboard/projects/[id]/ship/+page.server.ts @@ -1,7 +1,7 @@ import { db } from '$lib/server/db/index.js'; -import { devlog, project, clubMembership, club } from '$lib/server/db/schema.js'; +import { devlog, project, clubMembership, club, ship } from '$lib/server/db/schema.js'; import { error, fail, redirect } from '@sveltejs/kit'; -import { eq, and, or, sql } from 'drizzle-orm'; +import { eq, and, or, sql, desc } from 'drizzle-orm'; import type { Actions } from './$types'; import { sendSlackDM } from '$lib/server/slack.js'; import { isValidUrl } from '$lib/utils'; @@ -10,7 +10,6 @@ import { extname } from 'path'; import { env } from '$env/dynamic/private'; import { PutObjectCommand } from '@aws-sdk/client-s3'; import { S3 } from '$lib/server/s3'; -import { ship } from '$lib/server/db/schema.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; import { END_DATE } from '$lib/defs'; @@ -75,7 +74,8 @@ export async function load({ params, locals }) { return { project: queriedProject, - clubMembership: membership.length > 0 ? membership[0] : null + clubMembership: membership.length > 0 ? membership[0] : null, + lastIsClubsShip: await lastIsClubsShip(id) }; } @@ -282,7 +282,7 @@ export const actions = { } } - if (END_DATE <= new Date() && queriedProject.status === 'building' && clubIdForShip === null) { + if (END_DATE <= new Date() && clubIdForShip === null && (queriedProject.status === 'building' || await lastIsClubsShip(id))) { return error(400, { message: "can't submit individual project past end date" }); } @@ -349,3 +349,18 @@ export const actions = { return redirect(303, '/dashboard/projects'); } } satisfies Actions; + +async function lastIsClubsShip(id: number) { + const [latestShip] = await db + .select({ + clubId: ship.clubId + }) + .from(ship) + .where(eq(ship.projectId, id)) + .orderBy(desc(ship.timestamp)) + .limit(1); + + if (latestShip && latestShip.clubId) return true; + + return false; +} diff --git a/src/routes/dashboard/projects/[id]/ship/+page.svelte b/src/routes/dashboard/projects/[id]/ship/+page.svelte index 1de6313..996dee0 100644 --- a/src/routes/dashboard/projects/[id]/ship/+page.svelte +++ b/src/routes/dashboard/projects/[id]/ship/+page.svelte @@ -18,7 +18,7 @@ let editorUploadFile = $state(null); let modelFile = $state(null); let submitAsClub = $state( - END_DATE <= new Date() && data.project.status === 'building' && data.clubMembership + END_DATE <= new Date() && (data.project.status === 'building' || data.lastIsClubsShip) && data.clubMembership ? true : false ); @@ -190,9 +190,9 @@