import { rdsConnection } from "@/lib/aws"; import type { Country, LineUp, Port, Position, Vessel } from "@/types"; import { emptyVessel } from "@/types"; import type { MysqlError } from "mysql"; import mysql from "mysql"; import { VesselDetails } from "@/types/vessel"; export async function getVesselFromIMO(imo: string) { return await new Promise<Vessel[]>((resolve, reject) => { rdsConnection.query( `SELECT vessel_id, dwt, imo, flag, mmsi, vessel_name, cargo_type, cargo_sub_type, year_of_build, manager FROM spotship_vessel WHERE imo LIKE ? LIMIT 20`, [`${imo}%`], (error: unknown, elements: Vessel[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getVesselFromMMSI(mmsi: string) { return await new Promise<Vessel[]>((resolve, reject) => { rdsConnection.query( `SELECT vessel_id, dwt, imo, flag, mmsi, vessel_name, cargo_type, cargo_sub_type, year_of_build, manager FROM spotship_vessel WHERE mmsi LIKE ? LIMIT 20`, [`${mmsi}%`], (error: unknown, elements: Vessel[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getVesselFromName(name: string) { return await new Promise<Vessel[]>((resolve, reject) => { rdsConnection.query( `SELECT vessel_id, dwt, imo, flag, mmsi, vessel_name, cargo_type, cargo_sub_type, year_of_build, manager FROM spotship_vessel WHERE vessel_name LIKE ? ORDER BY vessel_name LIMIT 250`, [`%${name.toUpperCase()}%`], (error: unknown, elements: Vessel[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getVesselFromId(vessel_id: string) { return await new Promise<Vessel[]>((resolve, reject) => { rdsConnection.query( `SELECT vessel_id, dwt, imo, flag, mmsi, vessel_name, cargo_type, cargo_sub_type, year_of_build FROM spotship_vessel WHERE vessel_id = ?`, [vessel_id], (error: unknown, elements: Vessel[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getPortFromName(name: string) { return await new Promise<Port[]>((resolve, reject) => { rdsConnection.query( `SELECT port.id, port.name, port.locode, country.name AS country, country.code as flag, port.longitude, port.latitude, port.number_of_berths, port.port_region, port.super_region, port.type FROM spotship_port AS port INNER JOIN spotship_country AS country ON port.country_id = country.id WHERE port.name LIKE ? ORDER BY port.name LIMIT 1000`, [`%${name.toUpperCase()}%`], (error: unknown, elements: Port[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getPortFromId(port_id: number) { return await new Promise<Port[]>((resolve, reject) => { rdsConnection.query( `SELECT port.name, port.locode, country.name AS country, country.code as flag, port.longitude, port.latitude, port.number_of_berths, port.port_region, port.super_region, port.type FROM spotship_port AS port INNER JOIN spotship_country AS country ON port.country_id = country.id WHERE port.id = ?`, [port_id], (error: unknown, elements: Port[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getCountryFromName(name: string) { return await new Promise<Country[]>((resolve, reject) => { rdsConnection.query( `SELECT id, name, code FROM spotship_country WHERE name LIKE ? ORDER BY name LIMIT 1000`, [`${name}%`], (error: unknown, elements: Country[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getRegionFromName(name: string) { return await new Promise<Array<{ name: string }>>((resolve, reject) => { rdsConnection.query( `SELECT DISTINCT port_region AS name FROM spotship_port WHERE port_region LIKE ? ORDER BY port_region LIMIT 1000`, [`%${name}%`], (error: unknown, elements: Array<{ name: string }>) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } export async function getSuperRegionFromName(name: string) { return await new Promise<Array<{ name: string }>>((resolve, reject) => { rdsConnection.query( `SELECT DISTINCT super_region AS name FROM spotship_port WHERE super_region LIKE ? ORDER BY super_region LIMIT 1000`, [`%${name}%`], (error: unknown, elements: Array<{ name: string }>) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } function translateDirtyClean(dirtyClean: "CPP" | "DPP" | "N/A" | null) { switch (dirtyClean) { case "CPP": return "WET - CLEAN"; case "DPP": return "WET - DIRTY"; case "N/A": return "WET"; default: return null; } } function dirtyOrClean(dirtyClean: "CPP" | "DPP" | "N/A" | null) { switch (dirtyClean) { case "CPP": return "CLEAN"; case "DPP": return "DIRTY"; case "N/A": return ""; default: return ""; } } async function updateVesselDirtyClean( vessel_id: string, status: "CPP" | "DPP" | "N/A" | null, ) { return await new Promise((resolve, reject) => { rdsConnection.query( `UPDATE spotship_vessel SET dirty_clean = ? WHERE vessel_id = ?`, [dirtyOrClean(status), vessel_id], (error: unknown, elements: unknown) => { if (error) { console.error( "Error origin: Updating dirty/clean in Spotship Database | ", error, ); return reject(error); } return resolve(elements); }, ); }); } export function insertPositionValue( filename: string, broker: number, source: string, position: Position, ) { return `( ${mysql.escape(broker)}, 'DATA TEAM', ${mysql.escape( source.indexOf("<") === -1 || source.indexOf(">") === -1 ? source : source.substring(source.indexOf("<") + 1, source.lastIndexOf(">")), )}, ${mysql.escape(filename)}, ${mysql.escape(position.vessel.vessel_id)}, ${mysql.escape(position.open?.startDate ?? "")}, ${mysql.escape(position.open?.endDate ?? "")}, ${mysql.escape(position.port.id)}, ${mysql.escape(position.status)}, ${mysql.escape(position.cargo)}, ${mysql.escape(position.notes)}, ${mysql.escape(translateDirtyClean(position.dirtyClean))}, 'AVAILABLE', ${mysql.escape(new Date().toISOString())} ),`; } export async function bulkInsertPosition( brokers: Array<number>, positions: Array<Position>, filename: string, source: string, ) { if (positions.length == 0 || brokers.length == 0) { console.error( "Error origin: Inserting Position into Spotship Database | Broker list or positions list was empty", ); return; } let query = `INSERT INTO spotship_vesselavailability (broker_id, source, origin, origin_id, vessel_id, open_date, close_date, open_location, status, last_3_cargoes, details, wet_dry, current_status, created_at) VALUES `; brokers.forEach((id) => { if (id > 0) { positions.forEach((position: Position) => { query += insertPositionValue(filename, id, source, position); }); } }); // Remove last comma query = query.replace(/,$/, ""); return await new Promise((resolve, reject) => { rdsConnection.query( query, async (error: MysqlError | null, elements: unknown) => { if (error) { if (error.code === "ER_LOCK_DEADLOCK") { console.warn( "Warning origin: Inserting Position into Spotship Database - Retrying due to ER_LOCK_DEADLOCK | ", error, ); // N.B. Recursion used for infinite retries. return bulkInsertPosition(brokers, positions, filename, source); } console.error( "Error origin: Inserting Position into Spotship Database | ", error, ); return reject(error); } await Promise.all( positions.map(async (position) => { try { await updateVesselDirtyClean( position.vessel.vessel_id, position.dirtyClean, ); } catch (e) { console.error( "Error origin: Updating vessel dirty/clean in Spotship Database | ", e, ); } }), ); return resolve(elements); }, ); }); } export function insertLineupValue( filename: string, broker: number, source: string, lineup: LineUp, ) { let open = lineup.eta ?? new Date(); if (lineup.etb !== null && open < lineup.etb) { open = lineup.etb; } if (lineup.etd !== null && open < lineup.etd) { open = lineup.etd; } return `( ${mysql.escape(broker)}, 'DATA TEAM', ${mysql.escape( source.indexOf("<") === -1 || source.indexOf(">") === -1 ? source : source.substring(source.indexOf("<") + 1, source.lastIndexOf(">")), )}, ${mysql.escape(filename)}, ${mysql.escape(lineup.vessel.vessel_id)}, ${mysql.escape(lineup.port.id)}, ${mysql.escape( `${ lineup.toleranceType === "Min/Max" ? `${lineup.quantity} - ${lineup.tolerance}` : `${lineup.quantity}` }${lineup.unit}${ lineup.toleranceType === "%" || lineup.toleranceType === "Unit" ? ` ± ${lineup.tolerance}${lineup.toleranceType === "%" ? "%" : ""}` : "" } ${lineup.cargo}`, )}, 'AVAILABLE', ${mysql.escape(new Date().toISOString())}, ${mysql.escape(lineup.eta?.toISOString())}, ${mysql.escape(lineup.etb?.toISOString())}, ${mysql.escape(lineup.etd?.toISOString())}, ${mysql.escape(lineup.loading !== null ? (lineup.loading ? 1 : 0) : null)}, ${mysql.escape(`${lineup.terminal !== null && lineup.terminal !== "" ? `TERMINAL: ${lineup.terminal}` : ""} ${lineup.pier !== null && lineup.pier !== "" ? `PIER: ${lineup.pier}` : ""} ${lineup.destination !== null && lineup.destination.name !== "" ? `DESTINATION: ${lineup.destination.name} ` : ""}`)}, ${mysql.escape(open.toISOString())} ),`; } export async function bulkInsertLineup( brokers: Array<number>, lineups: Array<LineUp>, filename: string, source: string, ) { if (lineups.length == 0 || brokers.length == 0) { console.error( "Error origin: Inserting Line ups into Spotship Database | Broker list or line up list was empty", ); return; } let query = `INSERT INTO spotship_vesselavailability (broker_id, source, origin, origin_id, vessel_id, open_location, last_3_cargoes, current_status, created_at, et_a, etb, etd, load_discharge, details, open_date) VALUES `; brokers.forEach((id) => { if (id > 0) { lineups.forEach((lineup: LineUp) => { query += insertLineupValue(filename, id, source, lineup); }); } }); // Remove last comma query = query.replace(/,$/, ""); return await new Promise((resolve, reject) => { rdsConnection.query( query, async (error: MysqlError | null, elements: unknown) => { if (error) { if (error.code === "ER_LOCK_DEADLOCK") { console.warn( "Warning origin: Inserting Lineup into Spotship Database - Retrying due to ER_LOCK_DEADLOCK | ", error, ); // N.B. Recursion used for infinite retries. return bulkInsertLineup(brokers, lineups, filename, source); } console.error( "Error origin: Inserting Line up into Spotship Database | ", error, ); return reject(error); } return resolve(elements); }, ); }); } // QUERY FOR VESSEL INFO - Raf export async function queryVesselFromIMO(imo: string) { return await new Promise<Vessel[]>((resolve, reject) => { rdsConnection.query( `SELECT vessel_id, dwt, imo, flag, mmsi, vessel_name, cargo_type, cargo_sub_type, year_of_build, maximum_draft, manager FROM spotship_vessel WHERE imo LIKE ? LIMIT 20`, [`${imo}%`], (error: unknown, elements: Vessel[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } // QUERY FOR VESSEL DETAILS INFO - Raf export async function queryVesselDetails(vessel_id: string) { return await new Promise<VesselDetails[]>((resolve, reject) => { rdsConnection.query( `SELECT vessel_id, geared, gear_details FROM spotship_vesseldetails WHERE vessel_id = ?`, [vessel_id], (error: unknown, elements: VesselDetails[]) => { if (error) { return reject(error); } return resolve(elements); }, ); }); } // ADD VESSEL TO DB - Raf export async function addVessel(vessel: Vessel) { return await new Promise<void>((resolve, reject) => { rdsConnection.query( `INSERT INTO spotship_vessel (vessel_id, dwt, imo, flag, mmsi, vessel_name, cargo_type, cargo_sub_type, year_of_build, maximum_draft, manager) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ vessel.vessel_id, vessel.dwt, vessel.imo, vessel.flag, vessel.mmsi, vessel.vessel_name, vessel.cargo_type, vessel.cargo_sub_type, vessel.year_of_build, vessel.maximum_draft, vessel.manager, ], (error: unknown) => { if (error) { return reject(error); } return resolve(); }, ); }); } // ADD VESSEL DETAILS TO DB - Raf export async function addVesselDetails(details: VesselDetails) { return await new Promise<void>((resolve, reject) => { rdsConnection.query( `INSERT INTO spotship_vesseldetails (vessel_id, geared, gear_details) VALUES (?, ?, ?)`, [details.vessel_id, details.geared, details.gear_details], (error: unknown) => { if (error) { return reject(error); } return resolve(); }, ); }); } // UPDATE EXISTING VESSEL - Raf export async function modernizeVessel(vessel: Vessel) { return await new Promise<void>((resolve, reject) => { rdsConnection.query( `UPDATE spotship_vessel SET dwt = ?, imo = ?, flag = ?, mmsi = ?, vessel_name = ?, cargo_type = ?, cargo_sub_type = ?, year_of_build = ?, beam = ?, overall_length = ?, maximum_draft = ?, manager = ? WHERE vessel_id = ?`, [ vessel.dwt, vessel.imo, vessel.flag, vessel.mmsi, vessel.vessel_name, vessel.cargo_type, vessel.cargo_sub_type, vessel.year_of_build, vessel.beam, vessel.overall_length vessel.maximum_draft, vessel.manager, vessel.vessel_id, ], (error: unknown) => { if (error) { return reject(error); } return resolve(); }, ); }); } // UPDATE EXISTING VESSEL DETAILS - Raf export async function modernizeVesselDetails(details: VesselDetails) { return await new Promise<void>((resolve, reject) => { rdsConnection.query( `UPDATE spotship_vesseldetails SET geared = ?, gear_details = ? WHERE vessel_id = ?`, [details.geared, details.gear_details, details.vessel_id], (error: unknown) => { if (error) { return reject(error); } return resolve(); }, ); }); } export async function inssertVesselDetail(port: Port) { return await new Promise((resolve, reject) => { rdsConnection.query( `INSERT INTO spotship_vesseldetails (geared, gear_details) VALUES (?, ?)`, [emptyVessel.vessel_id, emptyVessel.dwt], (error: unknown, elements: unknown) => { if (error) { console.error( "Error origin: Inserting a vessel into Spotship Database | ", error, ); return reject(error); } return resolve(elements); }, ); }); } export async function insertPort(port: Port) { return await new Promise((resolve, reject) => { rdsConnection.query( `INSERT INTO spotship_port (name, country_id, latitude, longitude, locode, port_region, super_region, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ port.name, port.country, port.latitude, port.longitude, port.locode, port.port_region, port.super_region, port.type, ], (error: unknown, elements: unknown) => { if (error) { console.error( "Error origin: Inserting a port into Spotship Database | ", error, ); return reject(error); } return resolve(elements); }, ); }); } export async function editVessel(vessel_id: Vessel, name: string) { return await new Promise((resolve, reject) => { rdsConnection.query( `UPDATE spotship_vessel SET vessel_name = ? WHERE vessel_id = ?`, [name, vessel_id], (error: unknown, elements: unknown) => { if (error) { console.error( "Error origin: Updating a vessel in Spotship Database | ", error, ); return reject(error); } return resolve(elements); }, ); }); }