feat: barebones db storing

This commit is contained in:
2025-07-14 19:08:42 +00:00
parent 97155193be
commit ead012942a
7 changed files with 334 additions and 6 deletions

View File

@@ -35,7 +35,7 @@ A Slack bot that allows users to track their flights and automatically posts upd
## Data Models
### Usere airport coordinate data to determine proxim
### User
- `id`: Unique identifier
- `slack_user_id`: Slack user ID
- `slack_channel_id`: User's personal channel ID

View File

@@ -0,0 +1,55 @@
-- CreateTable
CREATE TABLE "Flight" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"faFlightId" TEXT NOT NULL,
"ident" TEXT NOT NULL,
"identIcao" TEXT NOT NULL,
"identIata" TEXT,
"registration" TEXT,
"aircraftType" TEXT,
"originCode" TEXT NOT NULL,
"originIata" TEXT NOT NULL,
"originName" TEXT NOT NULL,
"originCity" TEXT NOT NULL,
"destinationCode" TEXT NOT NULL,
"destinationIata" TEXT NOT NULL,
"destinationName" TEXT NOT NULL,
"destinationCity" TEXT NOT NULL,
"scheduledOut" DATETIME NOT NULL,
"scheduledOff" DATETIME NOT NULL,
"scheduledOn" DATETIME NOT NULL,
"scheduledIn" DATETIME NOT NULL,
"estimatedOut" DATETIME,
"estimatedOff" DATETIME,
"estimatedOn" DATETIME,
"estimatedIn" DATETIME,
"actualOut" DATETIME,
"actualOff" DATETIME,
"actualOn" DATETIME,
"actualIn" DATETIME,
"status" TEXT NOT NULL,
"departureDelay" INTEGER,
"arrivalDelay" INTEGER,
"cancelled" BOOLEAN NOT NULL DEFAULT false,
"diverted" BOOLEAN NOT NULL DEFAULT false,
"progressPercent" INTEGER,
"gateOrigin" TEXT,
"gateDestination" TEXT,
"terminalOrigin" TEXT,
"terminalDestination" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Flight_faFlightId_key" ON "Flight"("faFlightId");
-- CreateIndex
CREATE INDEX "Flight_userId_idx" ON "Flight"("userId");
-- CreateIndex
CREATE INDEX "Flight_faFlightId_idx" ON "Flight"("faFlightId");
-- CreateIndex
CREATE INDEX "Flight_scheduledOff_idx" ON "Flight"("scheduledOff");

View File

@@ -0,0 +1,63 @@
/*
Warnings:
- You are about to drop the column `destinationCode` on the `Flight` table. All the data in the column will be lost.
- You are about to drop the column `originCode` on the `Flight` table. All the data in the column will be lost.
- Added the required column `destinationIcao` to the `Flight` table without a default value. This is not possible if the table is not empty.
- Added the required column `originIcao` to the `Flight` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Flight" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"faFlightId" TEXT NOT NULL,
"ident" TEXT NOT NULL,
"identIcao" TEXT NOT NULL,
"identIata" TEXT,
"registration" TEXT,
"aircraftType" TEXT,
"originIcao" TEXT NOT NULL,
"originIata" TEXT NOT NULL,
"originName" TEXT NOT NULL,
"originCity" TEXT NOT NULL,
"destinationIcao" TEXT NOT NULL,
"destinationIata" TEXT NOT NULL,
"destinationName" TEXT NOT NULL,
"destinationCity" TEXT NOT NULL,
"scheduledOut" DATETIME NOT NULL,
"scheduledOff" DATETIME NOT NULL,
"scheduledOn" DATETIME NOT NULL,
"scheduledIn" DATETIME NOT NULL,
"estimatedOut" DATETIME,
"estimatedOff" DATETIME,
"estimatedOn" DATETIME,
"estimatedIn" DATETIME,
"actualOut" DATETIME,
"actualOff" DATETIME,
"actualOn" DATETIME,
"actualIn" DATETIME,
"status" TEXT NOT NULL,
"departureDelay" INTEGER,
"arrivalDelay" INTEGER,
"cancelled" BOOLEAN NOT NULL DEFAULT false,
"diverted" BOOLEAN NOT NULL DEFAULT false,
"progressPercent" INTEGER,
"gateOrigin" TEXT,
"gateDestination" TEXT,
"terminalOrigin" TEXT,
"terminalDestination" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Flight" ("actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId") SELECT "actualIn", "actualOff", "actualOn", "actualOut", "aircraftType", "arrivalDelay", "cancelled", "createdAt", "departureDelay", "destinationCity", "destinationIata", "destinationName", "diverted", "estimatedIn", "estimatedOff", "estimatedOn", "estimatedOut", "faFlightId", "gateDestination", "gateOrigin", "id", "ident", "identIata", "identIcao", "originCity", "originIata", "originName", "progressPercent", "registration", "scheduledIn", "scheduledOff", "scheduledOn", "scheduledOut", "status", "terminalDestination", "terminalOrigin", "updatedAt", "userId" FROM "Flight";
DROP TABLE "Flight";
ALTER TABLE "new_Flight" RENAME TO "Flight";
CREATE UNIQUE INDEX "Flight_faFlightId_key" ON "Flight"("faFlightId");
CREATE INDEX "Flight_userId_idx" ON "Flight"("userId");
CREATE INDEX "Flight_faFlightId_idx" ON "Flight"("faFlightId");
CREATE INDEX "Flight_scheduledOff_idx" ON "Flight"("scheduledOff");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"

View File

@@ -9,3 +9,67 @@ datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Flight {
id String @id @default(cuid())
userId String
// identifiers
faFlightId String @unique // fa_flight_id
ident String // flight number
identIcao String // ICAO ident
identIata String? // IATA ident (can be null)
// aircraft info
registration String? // tail number
aircraftType String? // e.g., "A320"
// route info
originIcao String // icao code (lemg)
originIata String // iata code (agp)
originName String // airport name
originCity String // city name
destinationIcao String // icao code
destinationIata String // iata code
destinationName String // airport name
destinationCity String // city name
// timing (stored in iso 8601 format on the api)
scheduledOut DateTime
scheduledOff DateTime // takeoff time
scheduledOn DateTime // landing time
scheduledIn DateTime
estimatedOut DateTime?
estimatedOff DateTime?
estimatedOn DateTime?
estimatedIn DateTime?
actualOut DateTime?
actualOff DateTime?
actualOn DateTime?
actualIn DateTime?
// status and delays
status String // "Scheduled", "Delayed", etc.
departureDelay Int? // minutes
arrivalDelay Int? // minutes
cancelled Boolean @default(false)
diverted Boolean @default(false)
// more useful info
progressPercent Int? // 0-100
gateOrigin String?
gateDestination String?
terminalOrigin String?
terminalDestination String?
// metadata
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([faFlightId])
@@index([scheduledOff])
}

View File

@@ -21,7 +21,7 @@ export const flightAware = new FlightAware();
export const adsbDb = new AdsBDB();
app.command('/flight-add', async ({ command, ack, respond }) => {
const EXAMPLE = `_Example: \`/flight-add AGP today 11\` looks for flights from AGP today at 11:00 (24-hour)._`;
const EXAMPLE = `_Example: \`/flight-add AGP today 11\` looks for flights from AGP today at 11:00 (24-hour UTC)._`;
await ack();
const parts = command.text.split(' ');
@@ -71,7 +71,7 @@ app.command('/flight-add', async ({ command, ack, respond }) => {
const flights = response.scheduled_departures;
if (flights.length === 0) {
const timeInfo = hour !== undefined ? ` at hour ${hour}:00` : '';
const timeInfo = hour !== undefined ? ` at hour ${hour}:00 UTC` : '';
await respond({
text: `No flights found for ${airportCode} on ${formatDate(new Date(date!.begin * 1000))}${timeInfo}`,
});
@@ -126,7 +126,7 @@ async function showFlightPage(
type: 'plain_text' as const,
text: displayText.substring(0, 75),
},
value: flight.ident_icao,
value: flight.fa_flight_id,
};
});
@@ -177,7 +177,7 @@ async function showFlightPage(
}
await respond({
text: `Found flights for ${requestParams.airportCode} on ${formatDate(
text: `Found ${flights.length} flights for ${requestParams.airportCode} on ${formatDate(
new Date(requestParams.originalDate * 1000)
)}`,
blocks,
@@ -201,10 +201,58 @@ app.action('flight_selection', async ({ body, ack, respond }) => {
const callsign = selectedValue;
const flight = await flightAware.getFlightInfo(callsign);
const flightData = flight.flights[0]!;
await db.flight.create({
data: {
userId: body.user.id,
faFlightId: flightData.fa_flight_id,
ident: flightData.ident,
identIcao: flightData.ident_icao,
identIata: flightData.ident_iata,
registration: flightData.registration,
aircraftType: flightData.aircraft_type,
originIcao: flightData.origin.code_icao,
originIata: flightData.origin.code_iata,
originName: flightData.origin.name,
originCity: flightData.origin.city,
destinationIcao: flightData.destination.code_icao,
destinationIata: flightData.destination.code_iata,
destinationName: flightData.destination.name,
destinationCity: flightData.destination.city,
scheduledOut: flightData.scheduled_out,
scheduledOff: flightData.scheduled_off,
scheduledOn: flightData.scheduled_on,
scheduledIn: flightData.scheduled_in,
estimatedOut: flightData.estimated_out,
estimatedOff: flightData.estimated_off,
estimatedOn: flightData.estimated_on,
estimatedIn: flightData.estimated_in,
actualOut: flightData.actual_out,
actualOff: flightData.actual_off,
actualOn: flightData.actual_on,
actualIn: flightData.actual_in,
status: flightData.status,
departureDelay: flightData.departure_delay,
arrivalDelay: flightData.arrival_delay,
cancelled: flightData.cancelled,
diverted: flightData.diverted,
progressPercent: flightData.progress_percent,
gateOrigin: flightData.gate_origin,
gateDestination: flightData.gate_destination,
terminalOrigin: flightData.terminal_origin,
terminalDestination: flightData.terminal_destination,
}
});
await respond({
text: `Added flight ${callsign} to your tracking list!`,
text: `Now tracking \`${callsign}\` in this channel!`,
replace_original: true,
});
await app.client.chat.postMessage({
channel: body.user.id,
text: `Flight \`${callsign}\` is now being tracked inside this channel!`,
});
} catch (error) {
console.error('Error handling flight selection:', error);
await respond({ text: 'Error adding flight to tracking. Please try again.' });

View File

@@ -46,6 +46,21 @@ export class FlightAware {
}
}).json<ScheduledDeparturesResponse>();
}
public async getFlightInfo(flightId: string): Promise<FlightsData> {
const apiKey = process.env.FLIGHTAWARE;
if (!apiKey) {
throw new Error('FLIGHTAWARE environment variable is required');
}
const url = `https://aeroapi.flightaware.com/aeroapi/flights/${flightId}`;
return ky.get(url, {
headers: {
'Accept': 'application/json; charset=UTF-8',
'User-Agent': 'flight-slack/1.0',
'x-apikey': apiKey,
}
}).json<FlightsData>();
}
}
interface Airport {
@@ -120,4 +135,84 @@ export interface ScheduledDeparturesResponse {
next?: string;
} | null;
num_pages: number;
}
export interface Flight {
ident: string;
ident_icao: string;
ident_iata: string;
actual_runway_off: string;
actual_runway_on: string;
fa_flight_id: string;
operator: string;
operator_icao: string;
operator_iata: string;
flight_number: string;
registration: string;
atc_ident: string;
inbound_fa_flight_id: string;
codeshares: any[];
codeshares_iata: any[];
blocked: boolean;
diverted: boolean;
cancelled: boolean;
position_only: boolean;
origin: {
code: string;
code_icao: string;
code_iata: string;
code_lid: null;
timezone: string;
name: string;
city: string;
airport_info_url: string;
};
destination: {
code: string;
code_icao: string;
code_iata: string;
code_lid: null;
timezone: string;
name: string;
city: string;
airport_info_url: string;
};
departure_delay: number;
arrival_delay: number;
filed_ete: number;
foresight_predictions_available: boolean;
scheduled_out: string;
estimated_out: string;
actual_out: string;
scheduled_off: string;
estimated_off: string;
actual_off: string;
scheduled_on: string;
estimated_on: string;
actual_on: string;
scheduled_in: string;
estimated_in: string;
actual_in: null;
progress_percent: number;
status: string;
aircraft_type: string;
route_distance: number;
filed_airspeed: number;
filed_altitude: null;
route: null;
baggage_claim: null;
seats_cabin_business: null;
seats_cabin_coach: null;
seats_cabin_first: null;
gate_origin: string;
gate_destination: null;
terminal_origin: null;
terminal_destination: string;
type: string;
}
export interface FlightsData {
flights: Flight[];
links: null;
num_pages: number;
}