mirror of
https://github.com/SrIzan10/flight-slack.git
synced 2026-06-06 00:56:52 +00:00
feat: barebones db storing
This commit is contained in:
@@ -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
|
||||
|
||||
55
prisma/migrations/20250714144126_flight_init/migration.sql
Normal file
55
prisma/migrations/20250714144126_flight_init/migration.sql
Normal 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");
|
||||
@@ -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;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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"
|
||||
@@ -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])
|
||||
}
|
||||
58
src/index.ts
58
src/index.ts
@@ -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.' });
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user