From 0880298cf53d83e487c706e73854e0874ae2d9da Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Wed, 8 Apr 2026 15:57:20 -0500 Subject: [PATCH] refactor(opendock refactor on how releases are posted): this was a bug maybe just a better refactory --- backend/db/schema/opendock.schema.ts | 26 +- .../opendock/openDockRreleaseMonitor.utils.ts | 306 +++++++----------- backend/opendock/opendock.utils.ts | 2 +- .../opendock/opendockSocketMonitor.utils.ts | 8 +- 4 files changed, 132 insertions(+), 210 deletions(-) diff --git a/backend/db/schema/opendock.schema.ts b/backend/db/schema/opendock.schema.ts index 140ddb4..30a8fbd 100644 --- a/backend/db/schema/opendock.schema.ts +++ b/backend/db/schema/opendock.schema.ts @@ -1,4 +1,5 @@ import { + index, integer, jsonb, pgTable, @@ -9,14 +10,23 @@ import { import { createInsertSchema, createSelectSchema } from "drizzle-zod"; import type { z } from "zod"; -export const opendockApt = pgTable("opendock_apt", { - id: uuid("id").defaultRandom().primaryKey(), - release: integer("release").unique(), - openDockAptId: text("open_dock_apt_id").notNull(), - appointment: jsonb("appointment").default([]), - upd_date: timestamp("upd_date").defaultNow(), - createdAt: timestamp("created_at").defaultNow(), -}); +export const opendockApt = pgTable( + "opendock_apt", + { + id: uuid("id").defaultRandom().primaryKey(), + release: integer("release").notNull().unique(), + openDockAptId: text("open_dock_apt_id").notNull(), + appointment: jsonb("appointment").notNull().default([]), + upd_date: timestamp("upd_date").notNull().defaultNow(), + createdAt: timestamp("created_at").notNull().defaultNow(), + }, + (table) => ({ + releaseIdx: index("opendock_apt_release_idx").on(table.release), + openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on( + table.openDockAptId, + ), + }), +); export const opendockAptSchema = createSelectSchema(opendockApt); export const newOpendockAptSchema = createInsertSchema(opendockApt); diff --git a/backend/opendock/openDockRreleaseMonitor.utils.ts b/backend/opendock/openDockRreleaseMonitor.utils.ts index 79f0757..cc2c8d3 100644 --- a/backend/opendock/openDockRreleaseMonitor.utils.ts +++ b/backend/opendock/openDockRreleaseMonitor.utils.ts @@ -17,15 +17,6 @@ import { returnFunc } from "../utils/returnHelper.utils.js"; import { tryCatch } from "../utils/trycatch.utils.js"; import { getToken, odToken } from "./opendock.utils.js"; -let lastCheck = formatInTimeZone( - new Date().toISOString(), - "America/New_York", - "yyyy-MM-dd HH:mm:ss", -); - -//const queue: unknown[] = []; -//const isProcessing: boolean = false; - type Releases = { ReleaseNumber: number; DeliveryState: number; @@ -37,10 +28,38 @@ type Releases = { LineItemArticleWeight: number; CustomerReleaseNumber: string; }; - +const timeZone = process.env.TIMEZONE as string; const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000; const log = createLogger({ module: "opendock", subModule: "releaseMonitor" }); +// making the cron more safe when it comes to buffer stuff +let opendockSyncRunning = false; + +let lastCheck = formatInTimeZone( + new Date().toISOString(), + timeZone, + "yyyy-MM-dd HH:mm:ss", +); + +// const lastCheck = formatInTimeZone( +// new Date().toISOString(), +// `America/New_York`, //TODO: Pull timezone from the .env last as process.env.TIME_ZONE is not working so need to figure itout +// "yyyy-MM-dd HH:mm:ss", +// ); + +//const queue: unknown[] = []; +//const isProcessing: boolean = false; + +// const parseDbDate = (value: string | Date) => { +// if (value instanceof Date) return value; + +// // normalize "2026-04-08 13:10:43.280" -> "2026-04-08T13:10:43.280" +// const normalized = value.replace(" ", "T"); + +// // interpret that wall-clock time as America/New_York +// return fromZonedTime(normalized, timeZone); +// }; + const postRelease = async (release: Releases) => { if (!odToken.odToken) { log.info({}, "Getting Auth Token"); @@ -152,22 +171,25 @@ const postRelease = async (release: Releases) => { }; // TODO: pull the current added releases from the db and if one matches then we want to get its id and run the update vs create - const { data: apt, error: aptError } = await tryCatch( - db.select().from(opendockApt), + const { data: existingApt, error: aptError } = await tryCatch( + db + .select() + .from(opendockApt) + .where(eq(opendockApt.release, release.ReleaseNumber)) + .limit(1), ); - if (aptError) { log.error({ error: aptError }, "Error getting apt data"); // TODO: send an error email on this one as it will cause issues return; } - const releaseCheck = apt.filter((r) => r.release === release.ReleaseNumber); + const existing = existingApt[0]; //console.log(releaseCheck); - if (releaseCheck.length > 0) { - const id = releaseCheck[0]?.openDockAptId; + if (existing) { + const id = existing.openDockAptId; try { const response = await axios.patch( `${process.env.OPENDOCK_URL}/appointment/${id}`, @@ -196,7 +218,11 @@ const postRelease = async (release: Releases) => { }) .onConflictDoUpdate({ target: opendockApt.release, - set: { appointment: response.data.data, upd_date: sql`NOW()` }, + set: { + openDockAptId: response.data.data.id, + appointment: response.data.data, + upd_date: sql`NOW()`, + }, }) .returning(); @@ -250,8 +276,12 @@ const postRelease = async (release: Releases) => { appointment: response.data.data, }) .onConflictDoUpdate({ - target: opendockApt.id, - set: { appointment: response.data.data, upd_date: sql`NOW()` }, + target: opendockApt.release, + set: { + openDockAptId: response.data.data.id, + appointment: response.data.data, + upd_date: sql`NOW()`, + }, }) .returning(); @@ -270,7 +300,7 @@ const postRelease = async (release: Releases) => { } } - await delay(500); // rate limit protection + await delay(750); // rate limit protection }; export const monitorReleaseChanges = async () => { @@ -298,184 +328,66 @@ export const monitorReleaseChanges = async () => { } if (openDockMonitor[0]?.active) { - createCronJob("opendock_sync", "*/15 * * * * *", async () => { - try { - const result = await prodQuery( - sqlQuery.query.replace("[dateCheck]", `'${lastCheck}'`), - "Get release info", - ); + // const BUFFER_MS = + // Math.floor(parseInt(openDockMonitor[0]?.value, 10) || 30) * 1.5 * 1000; // this should be >= to the interval we set in the cron TODO: should pull the buffer from the setting and give it an extra 10% then round to nearest int. - if (result.data.length) { - for (const release of result.data) { - await postRelease(release); - - lastCheck = formatInTimeZone( - new Date(release.Upd_Date).toISOString(), - "UTC", - "yyyy-MM-dd HH:mm:ss", - ); - - await delay(500); - } + createCronJob( + "opendock_sync", + `*/${parseInt(openDockMonitor[0]?.value, 10) || 30} * * * * *`, + async () => { + if (opendockSyncRunning) { + log.warn( + {}, + "Skipping opendock_sync because previous run is still active", + ); + return; } - } catch (e) { - console.error( - { error: e }, - "Error occurred while running the monitor job", - ); - log.error({ error: e }, "Error occurred while running the monitor job"); - } - }); + + opendockSyncRunning = true; + try { + // set this to the latest time. + + const result = await prodQuery( + sqlQuery.query.replace("[dateCheck]", `'${lastCheck}'`), + "Get release info", + ); + + log.debug( + { lastCheck }, + `${result.data.length} Changes to a release have been made`, + ); + + if (result.data.length) { + for (const release of result.data) { + await postRelease(release); + + // add a 2 seconds to account for a massive influx of orders and when we dont finish in 1 go it wont try to grab the same amount again + const nDate = new Date(release.Upd_Date); + nDate.setSeconds(nDate.getSeconds() + 2); + + lastCheck = formatInTimeZone( + nDate.toISOString(), + "UTC", + "yyyy-MM-dd HH:mm:ss", + ); + log.debug({ lastCheck }, "Changes to a release have been made"); + await delay(500); + } + } + } catch (e) { + console.error( + { error: e }, + "Error occurred while running the monitor job", + ); + log.error( + { error: e }, + "Error occurred while running the monitor job", + ); + } finally { + opendockSyncRunning = false; + } + }, + "monitorReleaseChanges", + ); } - - // run the main game loop - // while (openDockSetting) { - // try { - // const result = await prodQuery( - // sqlQuery.query.replace("[dateCheck]", `'${lastCheck}'`), - // "Get release info", - // ); - - // if (result.data.length) { - // for (const release of result.data) { - // // potentially move this to a buffer table to easy up on memory - // await postRelease(release); - - // // Move checkpoint AFTER successful post - // lastCheck = formatInTimeZone( - // new Date(release.Upd_Date).toISOString(), - // "UTC", - // "yyyy-MM-dd HH:mm:ss", - // ); - - // await delay(500); - // } - // } - // } catch (e) { - // console.error("Monitor error:", e); - // } - - // await delay(15 * 1000); // making this 15 seconds as we would really only see issues if we have a mass burst. - // } }; - -// export const monitorReleaseChanges = async () => { -// console.log("Starting release monitor", lastCheck); -// setInterval(async () => { -// try { -// const result = await prodQuery( -// releaseQuery.replace("[dateCheck]", `'${lastCheck}'`), -// "get last release change", -// ); - -// //console.log(releaseQuery.replace("[dateCheck]", `'${lastCheck}'`)); -// if (result.data.length > 0) { -// console.log( -// formatInTimeZone( -// result.data[result.data.length - 1].Upd_Date, -// "UTC", -// "yyyy-MM-dd HH:mm:ss", -// ), -// lastCheck, -// ); -// lastCheck = formatInTimeZone( -// result.data[result.data.length - 1].Upd_Date, -// "UTC", -// "yyyy-MM-dd HH:mm:ss", -// ); -// const releases = result.data; -// for (let i = 0; i < releases.length; i++) { -// const newDockApt = { -// status: "Scheduled", -// userId: "ee956455-e193-47fc-b53b-dff30fabdf4b", // this should be the carrierid -// loadTypeId: "0aa7988e-b17b-4f10-acdd-3d029b44a773", // well get this and make it a default one -// dockId: "00ba4386-ce5a-4dd1-9356-6e6d10a24609", // this the warehouse we want it in to start out -// refNumbers: [releases[i].ReleaseNumber], -// refNumber: releases[i].ReleaseNumber, -// start: releases[i].DeliveryDate, -// end: addHours(releases[i].DeliveryDate, 1), -// notes: "", -// ccEmails: [""], -// muteNotifications: true, -// metadata: { -// externalValidationFailed: false, -// externalValidationErrorMessage: null, -// }, -// units: null, -// customFields: [ -// { -// name: "strArticle", -// type: "str", -// label: "Article", -// value: `${releases[i].LineItemHumanReadableId} - ${releases[i].ArticleAlias}`, -// description: "What bottle are we sending ", -// placeholder: "", -// dropDownValues: [], -// minLengthOrValue: 1, -// hiddenFromCarrier: false, -// requiredForCarrier: false, -// requiredForWarehouse: false, -// }, -// { -// name: "intPallet Count", -// type: "int", -// label: "Pallet Count", -// value: parseInt(releases[i].LoadingUnits, 10), -// description: "How many pallets", -// placeholder: "22", -// dropDownValues: [], -// minLengthOrValue: 1, -// hiddenFromCarrier: false, -// requiredForCarrier: false, -// requiredForWarehouse: false, -// }, -// { -// name: "strTotal Weight", -// type: "str", -// label: "Total Weight", -// value: `${(((releases[i].Quantity * releases[i].LineItemArticleWeight) / 1000) * 2.20462).toFixed(2)}`, -// description: "What is the total weight of the load", -// placeholder: "", -// dropDownValues: [], -// minLengthOrValue: 1, -// hiddenFromCarrier: false, -// requiredForCarrier: false, -// requiredForWarehouse: false, -// }, -// { -// name: "strCustomer ReleaseNumber", -// type: "str", -// label: "Customer Release Number", -// value: `${releases[i].CustomerReleaseNumber}`, -// description: "What is the customer release number", -// placeholder: "", -// dropDownValues: [], -// minLengthOrValue: 1, -// hiddenFromCarrier: false, -// requiredForCarrier: false, -// requiredForWarehouse: false, -// }, -// ], -// }; - -// //console.log(newDockApt); - -// const newDockResult = await axios.post( -// "https://neutron.staging.opendock.com/appointment", -// newDockApt, -// { -// headers: { -// "content-type": "application/json; charset=utf-8", -// }, -// }, -// ); - -// console.log(newDockResult.statusText); -// await delay(500); -// } -// } -// } catch (e) { -// console.log(e); -// } -// }, 5 * 1000); -// }; diff --git a/backend/opendock/opendock.utils.ts b/backend/opendock/opendock.utils.ts index 525fe8b..00e0aaf 100644 --- a/backend/opendock/opendock.utils.ts +++ b/backend/opendock/opendock.utils.ts @@ -28,7 +28,7 @@ export const getToken = async () => { } odToken = { odToken: data.access_token, tokenDate: new Date() }; - log.info({}, "Token added"); + log.info({ odToken }, "Token added"); } catch (e) { log.error({ error: e }, "Error getting/refreshing token"); } diff --git a/backend/opendock/opendockSocketMonitor.utils.ts b/backend/opendock/opendockSocketMonitor.utils.ts index bf82932..7fa4fdf 100644 --- a/backend/opendock/opendockSocketMonitor.utils.ts +++ b/backend/opendock/opendockSocketMonitor.utils.ts @@ -36,12 +36,12 @@ export const opendockSocketMonitor = async () => { // console.log(data); // }); - socket.on("create-Appointment", (data) => { - console.log("appt create:", data); + socket.on("create-Appointment", () => { + //console.log("appt create:", data); }); - socket.on("update-Appointment", (data) => { - console.log("appt update:", data); + socket.on("update-Appointment", () => { + //console.log("appt update:", data); }); socket.on("error", (data) => {