import { PutObjectCommand, type S3Client } from "@aws-sdk/client-s3"; /*=;* / import type { ClientBase } from "pg"; import type { CemetaryPlot, Assignment, AssignmentState } from "../common/assignments"; /** * Retrieves all assignments for the given user. * * @param dbConn Connection to database. * @param userId ID used to identify user. */ export async function getAssignments(dbConn: ClientBase, userId: number): Promise { const result = await dbConn.query( "SELECT * FROM assignments WHERE gardener_id = $1 ORDER BY date", [userId], ); return result.rows.map( (r) => ({ id: r.id, gardenerId: r.gardener_id, cemetaryPlotId: r.cemetary_plot_id, date: r.date, state: r.state, }) satisfies Assignment, ); } type GetAssignmentResult = | { assignment: Assignment; cemetaryPlot: CemetaryPlot } | { assignment: null; cemetaryPlot: null }; /** * Retrieves a specfic assignment, along with relevant cemetary plot. */ export async function getAssignmentAndCemetaryById( dbConn: ClientBase, assignmentId: number, ): Promise { const queryText = `SELECT a.id, a.gardener_id, a.cemetary_plot_id, a.date, a.state, c.id, c.address, c.owner_id, c.assignment_interval FROM assignments AS a INNER JOIN cemetary_plots AS C ON c.id = a.cemetary_plot_id WHERE c.id = $1`; const result = await dbConn.query({ rowMode: "array", text: queryText }, [assignmentId]); if (result.rowCount == 0) { return { assignment: null, cemetaryPlot: null }; } const assignment: Assignment = { id: result.rows[0][0], gardenerId: result.rows[0][1], cemetaryPlotId: result.rows[0][2], date: result.rows[0][3], state: result.rows[0][4], }; const cemetaryPlot: CemetaryPlot = { id: result.rows[0][5], address: result.rows[0][6], ownerId: result.rows[0][7], //assignmentInterval: result.rows[0][8], }; return { assignment, cemetaryPlot }; } export interface FinishAssignmentArgs { images: { bytes: Uint8Array; name: string }[]; note?: string; assignmentId: number; } // TODO: Error recovery. export async function finishAssignment( dbConn: ClientBase, s3Client: S3Client, { images, note, assignmentId }: FinishAssignmentArgs, ): Promise { // Upload to S3, returning path // FIXME: Should be factored out? const uploadPromises = images.map(async (image) => { const key = generateImageKey(); const cmd = new PutObjectCommand({ Bucket: "images", Key: key, Body: image.bytes, IfNoneMatch: "*", // Error, in case of key collision. }); await s3Client.send(cmd); return { ...image, key }; }); const uploadedImages = await Promise.all(uploadPromises); // TODO: Add beanstalkd job await dbConn.query("BEGIN"); try { // Create 'images' row for each image. // FIXME: Apparently node-pg doesn't have an equivalent to Python's `insert_many`?? for (const image of uploadedImages) { dbConn.query({ name: "insert-assignment-image", text: "INSERT INTO images(s3_path, original_filename, assignment_id) VALUES ($1, $2, $3)", values: [image.key, image.name, assignmentId], }); } // Update the assingment's state. dbConn.query( `UPDATE assignments SET state = 'AWAITING_WATERMARKING' :: assignment_state, note = $1 WHERE id = $2`, [note, assignmentId], ); dbConn.query("COMMIT"); } catch (err) { // We should probably try to delete S3 objects. // We should probably try to delete the job. await dbConn.query("ROLLBACK"); throw err; } } function generateImageKey(): string { return crypto.randomUUID(); }