diff --git a/src/routes/dashboard/projects/[id]/ship/+page.server.ts b/src/routes/dashboard/projects/[id]/ship/+page.server.ts index c317cf9..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,8 +10,8 @@ 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'; export async function load({ params, locals }) { const id: number = parseInt(params.id); @@ -74,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) }; } @@ -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() && clubIdForShip === null && (queriedProject.status === 'building' || await lastIsClubsShip(id))) { + 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, @@ -343,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 8a2b65b..996dee0 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.lastIsClubsShip) && 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