Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drizzle/0025_add_claimed_at.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "project" ADD COLUMN "claimedAt" timestamp;
33 changes: 23 additions & 10 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import express from 'express';
import { handler } from './build/handler.js';
// import { CronJob } from 'cron';
import { CronJob } from 'cron';

// new CronJob(
// '* * * * * *', // cronTime
// function () {
// console.log('You will see this message every second');
// }, // onTick
// null, // onComplete
// true, // start
// 'Europe/London' // timeZone
// );
// Run daily at midnight UTC to unclaim expired print claims (older than 7 days)
new CronJob(
'0 0 * * *', // Every day at midnight
async function () {
try {
const baseUrl = process.env.PUBLIC_BASE_URL || `http://localhost:${process.env.PORT ?? 3000}`;
const response = await fetch(`${baseUrl}/api/cron/unclaim-expired`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.APP_SECRET_KEY}`
}
});
const result = await response.json();
console.log(`[Cron] Unclaimed ${result.unclaimedCount} expired print claims`);
} catch (error) {
console.error('[Cron] Failed to unclaim expired prints:', error);
}
},
null,
true,
'UTC'
);

const app = express();

Expand Down
1 change: 1 addition & 0 deletions src/lib/server/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const project = pgTable('project', {

status: projectStatusEnum().notNull().default('building'),
printedBy: integer().references(() => user.id),
claimedAt: timestamp(), // When the project was claimed for printing

submittedToAirtable: boolean().default(false),

Expand Down
64 changes: 64 additions & 0 deletions src/routes/api/cron/unclaim-expired/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { json, error } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import { db } from '$lib/server/db/index.js';
import { project, legionReview } from '$lib/server/db/schema.js';
import { eq, and, lt, isNotNull } from 'drizzle-orm';

const CLAIM_EXPIRY_DAYS = 7;

export async function POST({ request }) {
const authHeader = request.headers.get('Authorization');
const expectedToken = `Bearer ${env.APP_SECRET_KEY}`;

if (!authHeader || authHeader !== expectedToken) {
throw error(401, { message: 'Unauthorized' });
}

const expiryDate = new Date();
expiryDate.setDate(expiryDate.getDate() - CLAIM_EXPIRY_DAYS);

// Find all projects that are in 'printing' status with claimedAt older than 7 days
const expiredClaims = await db
.select({
id: project.id,
name: project.name,
printedBy: project.printedBy,
claimedAt: project.claimedAt
})
.from(project)
.where(
and(
eq(project.status, 'printing'),
eq(project.deleted, false),
isNotNull(project.claimedAt),
lt(project.claimedAt, expiryDate)
)
);

// Unclaim each expired project
for (const expiredProject of expiredClaims) {
if (expiredProject.printedBy) {
await db.insert(legionReview).values({
projectId: expiredProject.id,
userId: expiredProject.printedBy,
action: 'unmark_for_printing',
notes: 'Auto-unclaimed after 7 days without printing'
});
}

await db
.update(project)
.set({
status: 't1_approved',
printedBy: null,
claimedAt: null
})
.where(eq(project.id, expiredProject.id));
}

return json({
success: true,
unclaimedCount: expiredClaims.length,
unclaimed: expiredClaims.map((p) => ({ id: p.id, name: p.name }))
});
}
16 changes: 14 additions & 2 deletions src/routes/dashboard/admin/print/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { db } from '$lib/server/db/index.js';
import { project, user, devlog } from '$lib/server/db/schema.js';
import { error } from '@sveltejs/kit';
import { eq, and, sql, ne, inArray } from 'drizzle-orm';
import { alias } from 'drizzle-orm/pg-core';
import type { Actions } from './$types';
import { getCurrentlyPrinting } from './utils.server';

Expand Down Expand Up @@ -83,6 +84,8 @@ async function getProjects(
projectFilter: number[],
userFilter: number[]
) {
const printer = alias(user, 'printer');

return await db
.select({
project: {
Expand All @@ -91,18 +94,24 @@ async function getProjects(
description: project.description,
url: project.url,
createdAt: project.createdAt,
status: project.status
status: project.status,
claimedAt: project.claimedAt
},
user: {
id: user.id,
name: user.name
},
printer: {
id: printer.id,
name: printer.name
},
timeSpent: sql<number>`COALESCE(SUM(${devlog.timeSpent}), 0)`,
devlogCount: sql<number>`COALESCE(COUNT(${devlog.id}), 0)`
})
.from(project)
.leftJoin(devlog, and(eq(project.id, devlog.projectId), eq(devlog.deleted, false)))
.leftJoin(user, eq(user.id, project.userId))
.leftJoin(printer, eq(printer.id, project.printedBy))
.where(
and(
eq(project.deleted, false),
Expand All @@ -118,7 +127,10 @@ async function getProjects(
project.url,
project.createdAt,
project.status,
project.claimedAt,
user.id,
user.name
user.name,
printer.id,
printer.name
);
}
10 changes: 10 additions & 0 deletions src/routes/dashboard/admin/print/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@
</p>
<p class="text-sm">{projectStatuses[project.project.status]}</p>
</div>
{#if project.project.status === 'printing' && project.printer?.name}
<p class="mt-1 text-sm text-primary-400">
Claimed by <span class="font-medium">{project.printer.name}</span>
{#if project.project.claimedAt}
<abbr title={project.project.claimedAt.toUTCString()}>
{relativeDate(project.project.claimedAt)}
</abbr>
{/if}
</p>
{/if}
</div>
{/each}
</div>
Expand Down
40 changes: 15 additions & 25 deletions src/routes/dashboard/admin/print/[id]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { eq, and, asc, sql } from 'drizzle-orm';
import type { Actions } from './$types';
import { sendSlackDM } from '$lib/server/slack.js';
import { getReviewHistory } from '../../getReviewHistory.server';
import { getCurrentlyPrinting } from '../utils.server';

export async function load({ locals, params }) {
if (!locals.user) {
Expand Down Expand Up @@ -80,13 +79,10 @@ export async function load({ locals, params }) {
.where(and(eq(devlog.projectId, queriedProject.project.id), eq(devlog.deleted, false)))
.orderBy(asc(devlog.createdAt));

const currentlyPrinting = await getCurrentlyPrinting(locals.user);

return {
project: queriedProject,
devlogs,
reviews: await getReviewHistory(id),
currentlyPrinting
reviews: await getReviewHistory(id)
};
}

Expand All @@ -99,16 +95,8 @@ export const actions = {
throw error(403, { message: 'oi get out' });
}

const currentlyPrinting = await getCurrentlyPrinting(locals.user);

const id: number = parseInt(params.id);

if (currentlyPrinting && currentlyPrinting.id !== id) {
return error(400, {
message: 'you are already printing something else right now'
});
}

const [queriedProject] = await db
.select({
id: project.id,
Expand Down Expand Up @@ -136,7 +124,8 @@ export const actions = {
.update(project)
.set({
status: 'printing',
printedBy: locals.user.id
printedBy: locals.user.id,
claimedAt: new Date()
})
.where(eq(project.id, id));

Expand Down Expand Up @@ -181,7 +170,8 @@ export const actions = {
.update(project)
.set({
status: 't1_approved',
printedBy: null
printedBy: null,
claimedAt: null
})
.where(eq(project.id, id));

Expand All @@ -196,20 +186,13 @@ export const actions = {
throw error(403, { message: 'oi get out' });
}

const currentlyPrinting = await getCurrentlyPrinting(locals.user);

const id: number = parseInt(params.id);

if (!currentlyPrinting || currentlyPrinting.id !== id) {
return error(400, {
message: "you can only print a project if you've marked it as you're printing it"
});
}

const [queriedProject] = await db
.select({
id: project.id,
status: project.status
status: project.status,
printedBy: project.printedBy
})
.from(project)
.where(and(eq(project.id, id), eq(project.deleted, false)))
Expand All @@ -223,6 +206,12 @@ export const actions = {
return error(403, { message: 'project is not marked as currently printing' });
}

if (queriedProject.printedBy !== locals.user.id) {
return error(400, {
message: "you can only print a project if you've marked it as you're printing it"
});
}

const data = await request.formData();
const filamentUsed = data.get('filament');
const notes = data.get('notes')?.toString();
Expand Down Expand Up @@ -252,7 +241,8 @@ export const actions = {
await db
.update(project)
.set({
status: 'printed'
status: 'printed',
claimedAt: null
})
.where(eq(project.id, id));

Expand Down
4 changes: 2 additions & 2 deletions src/routes/dashboard/admin/print/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@

<h2 class="mt-2 text-2xl font-bold">Printering area</h2>

{#if (data.project.project.status === 't1_approved' && !data.currentlyPrinting) || (data.project.project.status === 'printing' && data.project.project.printedBy === data.user.id)}
{#if data.project.project.status === 't1_approved' || (data.project.project.status === 'printing' && data.project.project.printedBy === data.user.id)}
<div class="themed-box flex flex-col gap-3 p-3">
{#if data.project.project.status === 't1_approved' && !data.currentlyPrinting}
{#if data.project.project.status === 't1_approved'}
<form
method="POST"
action="?/markForPrint"
Expand Down