fix(GRO-643): add appointment indexes to schema and S3 error handling

- Add idx_appointments_client_id, idx_appointments_staff_id,
  idx_appointments_start_time, idx_appointments_status to schema.
  Migration 0029 already handles the DB side; this brings schema.ts
  in sync so drizzle-kit push is clean going forward.
- Wrap deleteObject calls in try/catch (POST /photo/confirm and
  DELETE /:petId/photo endpoints) so S3 failures don't abort the
  DB update — orphaned objects are logged as warnings instead.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Test User
2026-04-17 05:23:40 +00:00
parent 2577e33c50
commit 156e1fa4ff
2 changed files with 64 additions and 47 deletions
+8
View File
@@ -213,7 +213,11 @@ petsRouter.post(
// Delete the previous photo from storage to avoid orphaned objects // Delete the previous photo from storage to avoid orphaned objects
if (pet.photoKey) { if (pet.photoKey) {
try {
await deleteObject(pet.photoKey); await deleteObject(pet.photoKey);
} catch (err) {
console.warn(`Failed to delete previous photo ${pet.photoKey}, orphaned object may remain:`, err);
}
} }
const [row] = await db const [row] = await db
@@ -240,7 +244,11 @@ petsRouter.delete("/:petId/photo", async (c) => {
if (!pet) return c.json({ error: "Pet not found" }, 404); if (!pet) return c.json({ error: "Pet not found" }, 404);
if (!pet.photoKey) return c.json({ error: "No photo on file" }, 404); if (!pet.photoKey) return c.json({ error: "No photo on file" }, 404);
try {
await deleteObject(pet.photoKey); await deleteObject(pet.photoKey);
} catch (err) {
console.warn(`Failed to delete photo ${pet.photoKey} from S3, orphaned object may remain:`, err);
}
await db await db
.update(pets) .update(pets)
.set({ photoKey: null, photoUploadedAt: null, updatedAt: new Date() }) .set({ photoKey: null, photoUploadedAt: null, updatedAt: new Date() })
+11 -2
View File
@@ -200,7 +200,9 @@ export const appointmentGroups = pgTable("appointment_groups", {
updatedAt: timestamp("updated_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(),
}); });
export const appointments = pgTable("appointments", { export const appointments = pgTable(
"appointments",
{
id: uuid("id").primaryKey().defaultRandom(), id: uuid("id").primaryKey().defaultRandom(),
clientId: uuid("client_id") clientId: uuid("client_id")
.notNull() .notNull()
@@ -244,7 +246,14 @@ export const appointments = pgTable("appointments", {
customerNotes: text("customer_notes"), customerNotes: text("customer_notes"),
createdAt: timestamp("created_at").notNull().defaultNow(), createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(),
}); },
(t) => [
index("idx_appointments_client_id").on(t.clientId),
index("idx_appointments_staff_id").on(t.staffId),
index("idx_appointments_start_time").on(t.startTime),
index("idx_appointments_status").on(t.status),
]
);
export const invoices = pgTable( export const invoices = pgTable(
"invoices", "invoices",