diff --git a/apps/edr-freight-api/src/migrations/2300000000000-RepairGpsTrackingTables.ts b/apps/edr-freight-api/src/migrations/2300000000000-RepairGpsTrackingTables.ts new file mode 100644 index 000000000..f4628db36 --- /dev/null +++ b/apps/edr-freight-api/src/migrations/2300000000000-RepairGpsTrackingTables.ts @@ -0,0 +1,83 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +/** + * Repair for environments missing the GPS tracking tables. + * + * AddGpsTracking2000000000000 creates freight.gps_devices / gps_positions, but + * some databases have it RECORDED in public.migrations without the tables ever + * landing. TypeORM never re-runs a recorded migration, so those environments + * stay broken through any number of restarts — the GT06 listener accepts tracker + * packets on its TCP port regardless of schema state and fails per packet with + * `relation "freight.gps_devices" does not exist`, dropping position fixes. + * + * This re-issues the same DDL under a new name so it is applied afresh. Every + * statement is IF NOT EXISTS, so it is a no-op where the tables already exist + * and safe on every environment. + * + * Kept byte-identical to the original DDL on purpose: this must converge on the + * schema the entities expect, not a variant of it. + */ +export class RepairGpsTrackingTables2300000000000 implements MigrationInterface { + name = "RepairGpsTrackingTables2300000000000"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE IF NOT EXISTS freight.gps_devices ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + imei varchar(20) NOT NULL UNIQUE, + name varchar, + vehicle_id uuid REFERENCES freight.vehicles(id), + status varchar(16) NOT NULL DEFAULT 'REGISTERED', + last_seen_at timestamptz, + last_lat numeric(10,6), + last_lng numeric(10,6), + last_speed numeric(6,2), + last_course int, + last_fix_at timestamptz, + voltage_level int, + gsm_level int, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz + ) + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_GPS_DEVICES_VEHICLE" + ON freight.gps_devices (vehicle_id) + `); + + await queryRunner.query(` + CREATE TABLE IF NOT EXISTS freight.gps_positions ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + device_id uuid NOT NULL, + imei varchar(20) NOT NULL, + vehicle_id uuid, + lat numeric(10,6) NOT NULL, + lng numeric(10,6) NOT NULL, + speed numeric(6,2) NOT NULL DEFAULT 0, + course int NOT NULL DEFAULT 0, + satellites int NOT NULL DEFAULT 0, + positioned boolean NOT NULL DEFAULT false, + gps_time timestamptz NOT NULL, + alarm int NOT NULL DEFAULT 0, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz + ) + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_GPS_POSITIONS_DEVICE_TIME" + ON freight.gps_positions (device_id, gps_time) + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_GPS_POSITIONS_VEHICLE_TIME" + ON freight.gps_positions (vehicle_id, gps_time) + `); + } + + public async down(): Promise { + // No-op: dropping the tables would discard tracker history on environments + // where this migration was the one that created them. AddGpsTracking owns + // the teardown. + } +}