Compare commits
4 Commits
fa50df3e27
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| ffced44bfb | |||
| f7eed33a5b | |||
| 79089aeb70 | |||
| d6bb8cb3f8 |
@@ -2,7 +2,7 @@ name: Build and Push processor
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [dev]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Login to Gitea Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.dev.microservices.al
|
||||
registry: git.dev.trmtracking.org
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
@@ -61,8 +61,18 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: git.dev.microservices.al/trm/processor:main
|
||||
tags: git.dev.trmtracking.org/trm/processor:dev
|
||||
|
||||
- name: Trigger Portainer Deploy
|
||||
- name: Trigger Komodo Stack redeploy
|
||||
if: success()
|
||||
run: curl -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}"
|
||||
env:
|
||||
URL: ${{ secrets.KOMODO_STACK_WEBHOOK_URL }}
|
||||
SECRET: ${{ secrets.KOMODO_WEBHOOK_SECRET }}
|
||||
run: |
|
||||
body='{"ref":"refs/heads/dev"}'
|
||||
sig=$(printf '%s' "$body" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
|
||||
curl -fsS -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "X-Hub-Signature-256: sha256=$sig" \
|
||||
-d "$body" \
|
||||
"$URL"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
# day-to-day development, run `pnpm dev` directly against host-exposed services.
|
||||
#
|
||||
# For STAGE and PRODUCTION deployment, use the multi-service compose in
|
||||
# the sibling `deploy/` repo (https://git.dev.microservices.al/trm/deploy),
|
||||
# the sibling `deploy/` repo (https://git.dev.trmtracking.org/trm/deploy),
|
||||
# which references this service by its registry image tag instead of
|
||||
# building locally.
|
||||
#
|
||||
|
||||
@@ -60,9 +60,14 @@ export function createDeviceEventMap(
|
||||
async function refresh(): Promise<void> {
|
||||
const start = performance.now();
|
||||
try {
|
||||
// Cache keys must be IMEIs because broadcast.ts looks up by
|
||||
// position.device_id (the IMEI). entry_devices.device_id is a uuid FK
|
||||
// to devices.id, so we hop through the devices table and alias
|
||||
// d.imei AS device_id to keep the row shape stable.
|
||||
const result = await pool.query<DeviceEventRow>(
|
||||
`SELECT ed.device_id, e.event_id
|
||||
`SELECT d.imei AS device_id, e.event_id
|
||||
FROM entry_devices ed
|
||||
JOIN devices d ON d.id = ed.device_id
|
||||
JOIN entries e ON e.id = ed.entry_id`,
|
||||
);
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ export function createSnapshotProvider(
|
||||
* - the registered devices have no positions yet.
|
||||
* - all positions for a device are faulty.
|
||||
*
|
||||
* Type bridge: positions.device_id stores the IMEI (text), while
|
||||
* entry_devices.device_id is a uuid FK to devices.id. The join translates
|
||||
* via devices.imei = positions.device_id (text=text) → devices.id =
|
||||
* entry_devices.device_id (uuid=uuid). Without this hop Postgres rejects
|
||||
* the comparison with `operator does not exist: uuid = text` (42883).
|
||||
*
|
||||
* Never throws — the caller (registry.fetchSnapshot) already wraps in a
|
||||
* try/catch that falls back to an empty snapshot.
|
||||
*/
|
||||
@@ -67,7 +73,8 @@ export function createSnapshotProvider(
|
||||
p.speed,
|
||||
p.angle
|
||||
FROM positions p
|
||||
JOIN entry_devices ed ON ed.device_id = p.device_id
|
||||
JOIN devices d ON d.imei = p.device_id
|
||||
JOIN entry_devices ed ON ed.device_id = d.id
|
||||
JOIN entries e ON e.id = ed.entry_id
|
||||
WHERE e.event_id = $1
|
||||
AND p.faulty = false
|
||||
|
||||
Vendored
+26
-20
@@ -1,39 +1,45 @@
|
||||
-- test/fixtures/test-schema.sql
|
||||
--
|
||||
-- Minimum subset of the production schema required by live.integration.test.ts.
|
||||
-- This is intentionally a simplified version — NOT the full Directus-managed schema.
|
||||
-- Intentionally a simplified subset of the Directus-managed schema — keeps only
|
||||
-- the columns the Processor's live-broadcast queries actually read.
|
||||
--
|
||||
-- Maintenance note: keep in sync with the real schema when column types change on
|
||||
-- these tables. Specifically: entries.event_id, entry_devices.device_id (Phase 1
|
||||
-- uses IMEI text; Phase 2 introduces UUID-based devices table).
|
||||
--
|
||||
-- Phase 1 deviation: entry_devices.device_id is TEXT (IMEI) here, matching
|
||||
-- positions.device_id. The real Directus schema uses a UUID FK to devices.id.
|
||||
-- The integration test uses the real queries from device-event-map.ts and
|
||||
-- snapshot.ts, so this simplified schema must satisfy those joins.
|
||||
-- Maintenance note: keep in sync with the real schema when join shapes change.
|
||||
-- The integration test runs the real queries from device-event-map.ts and
|
||||
-- snapshot.ts unmodified, so the column types here must match production.
|
||||
|
||||
-- events — the container for entries
|
||||
-- The Processor reads events.id (used in snapshot WHERE e.event_id = $1).
|
||||
-- events — the container for entries.
|
||||
-- Processor reads events.id (snapshot WHERE e.event_id = $1).
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid()
|
||||
-- Real schema also has: organization_id FK, name, slug, discipline, starts_at, ends_at.
|
||||
-- Only columns the Processor queries are included here.
|
||||
);
|
||||
|
||||
-- entries — race entries belonging to an event
|
||||
-- The Processor reads entries.id and entries.event_id.
|
||||
-- entries — race entries belonging to an event.
|
||||
-- Processor reads entries.id and entries.event_id.
|
||||
CREATE TABLE IF NOT EXISTS entries (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
event_id uuid NOT NULL REFERENCES events (id) ON DELETE CASCADE
|
||||
-- Real schema also has: vehicle_id, class_id, number, etc.
|
||||
-- Real schema also has: vehicle_id, class_id, race_number, status, etc.
|
||||
);
|
||||
|
||||
-- entry_devices — maps a device (IMEI) to an entry.
|
||||
-- Phase 1: device_id is IMEI text, matching positions.device_id.
|
||||
-- Real schema: device_id is UUID FK to devices.id, joined via devices.imei.
|
||||
-- This simplified form is intentional for the integration test fixture.
|
||||
-- devices — durable hardware catalog.
|
||||
-- Processor reads devices.id and devices.imei (joined to positions.device_id).
|
||||
-- positions.device_id stores the IMEI text; entry_devices.device_id stores the
|
||||
-- devices.id uuid. This table is the bridge that lets snapshot.ts and
|
||||
-- device-event-map.ts translate between the two without `uuid = text` errors.
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
imei text NOT NULL UNIQUE
|
||||
-- Real schema also has: model, serial_number, notes, date_created, date_updated.
|
||||
);
|
||||
|
||||
-- entry_devices — maps a device (uuid FK) to an entry.
|
||||
-- Real schema (and this fixture): device_id is uuid FK to devices.id.
|
||||
-- Live-broadcast joins translate to/from positions.device_id (IMEI text)
|
||||
-- via the devices table.
|
||||
CREATE TABLE IF NOT EXISTS entry_devices (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
entry_id uuid NOT NULL REFERENCES entries (id) ON DELETE CASCADE,
|
||||
device_id text NOT NULL -- IMEI in Phase 1
|
||||
device_id uuid NOT NULL REFERENCES devices (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
@@ -63,9 +63,11 @@ const BROADCAST_GROUP_PREFIX = 'live-broadcast';
|
||||
const EVENT_ID = 'ee000000-0000-0000-0000-000000000001';
|
||||
const OTHER_EVENT_ID = 'ee000000-0000-0000-0000-000000000002';
|
||||
const ENTRY_ID = 'aa000000-0000-0000-0000-000000000001';
|
||||
const DEVICE_1 = '111111111111111'; // IMEI
|
||||
const DEVICE_2 = '222222222222222'; // IMEI
|
||||
const DEVICE_ORPHAN = '999999999999999'; // not in entry_devices
|
||||
const DEVICE_1_ID = 'dd000000-0000-0000-0000-000000000001';
|
||||
const DEVICE_2_ID = 'dd000000-0000-0000-0000-000000000002';
|
||||
const DEVICE_1 = '111111111111111'; // IMEI for DEVICE_1_ID
|
||||
const DEVICE_2 = '222222222222222'; // IMEI for DEVICE_2_ID
|
||||
const DEVICE_ORPHAN = '999999999999999'; // not registered to any entry
|
||||
|
||||
const USER_A: FakeUser = {
|
||||
id: 'user-aaaa-0000-0000-0000-000000000001',
|
||||
@@ -220,12 +222,20 @@ async function seedDatabase(pool: pg.Pool): Promise<void> {
|
||||
[ENTRY_ID, EVENT_ID],
|
||||
);
|
||||
|
||||
// entry_devices — Phase 1 uses IMEI as device_id
|
||||
// devices — durable catalog. positions.device_id stores the IMEI text;
|
||||
// entry_devices.device_id is a uuid FK to devices.id. The live-broadcast
|
||||
// joins translate via devices.imei.
|
||||
await pool.query(
|
||||
`INSERT INTO devices (id, imei) VALUES ($1, $2), ($3, $4)`,
|
||||
[DEVICE_1_ID, DEVICE_1, DEVICE_2_ID, DEVICE_2],
|
||||
);
|
||||
|
||||
// entry_devices — uuid FK to devices.id (matches production schema).
|
||||
await pool.query(
|
||||
`INSERT INTO entry_devices (id, entry_id, device_id) VALUES
|
||||
(gen_random_uuid(), $1, $2),
|
||||
(gen_random_uuid(), $1, $3)`,
|
||||
[ENTRY_ID, DEVICE_1, DEVICE_2],
|
||||
[ENTRY_ID, DEVICE_1_ID, DEVICE_2_ID],
|
||||
);
|
||||
|
||||
// positions for DEVICE_1 (two: one non-faulty, one faulty)
|
||||
|
||||
Reference in New Issue
Block a user