From 4af44feb7ab18f6ae55c8c2c06e93345a4f079c7 Mon Sep 17 00:00:00 2001 From: Jonas Hinterdorfer Date: Wed, 12 Mar 2025 21:51:06 +0100 Subject: [PATCH] implementet Unit --- README.md | 153 ++++++++++++++++++++++++++++++++++++++++++- src/app.ts | 7 ++ src/database/Unit.ts | 51 +++++++++++++++ src/database/data.ts | 27 ++++++++ 4 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 src/app.ts create mode 100644 src/database/Unit.ts create mode 100644 src/database/data.ts diff --git a/README.md b/README.md index 96551f4..777e4dc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,153 @@ -# db-04-flights +# Flight Management +We return to the flight database we used in one of the first assignments. +However, this time it got extended by a passenger list instead of a simple count. + +```plantuml +@startuml +hide circle +hide empty methods +skinparam nodesep 80 +skinparam linetype ortho + +entity Airport { + * icao: string <> + * name: string + * country: string + * runwayLength: int +} +entity Plane { + * tailNo: string <> + * model: string + * manufacturer: string + * capacity: int +} +entity Flight { + * flightNo: string <> + * departure: datetime + arrival: datetime +} +entity Passenger { + * ssn: string <> + * name: string + * dateOfBirth: datetime + isFemale: bool +} +entity Reservation { + * ticketNo: int <> + * seat: string +} + +Airport ||-l-o{ Flight: departs +Airport ||-l-o{ Flight: arrives +Flight }o-l-|| Plane: operates +Flight ||--o{ Reservation: has +Passenger ||-r-|{ Reservation: has +@enduml +``` + +## Task 1 +- Create the tables + - The original three tables are _mostly_ unchanged and can be taken from the previous assignment +- Make sure to use proper data types & foreign keys again +- Mind the following hints for the new tables + - Passenger: `isFemale` is _nullable_ on purpose, so we can put null in case a person is neither male nor female + - Reservation: we don't use a composite primary key here (despite it normally being the way to go here), because + the `ticketNo` has to use the 'autoincrement' feature + - You already know this feature from DBI (a sequence in Oracle) + - [Read up on how to use it in SQLite](https://www.sqlite.org/autoinc.html) - you _won't_ need any keywords, + read the page 😉 + - During inserts the ticketNo has _not_ to be provided for this to work + - **You'll have to figure out how to get the last insert (row)id on your own** - a short internet research will + turn up the answer + +## Task 2 +- To get things going populate the database with several planes and airports +- No endpoint is needed here, just run the script when the server boots + - You have to check if the data already exists before inserting to prevent unique constraint violations + - Hint: prepared statements can be `reset` and then used again +- You may use the following data + +| **Tail Number** | **Model** | **Manufacturer** | **Capacity** | +|-----------------|-----------|------------------|--------------| +| OE-LAA | A320 Neo | Airbus | 164 | +| OE-LAB | B737-800 | Boeing | 188 | +| OE-LAC | A319 | Airbus | 152 | +| OE-LAD | B787-900 | Boeing | 260 | +| OE-LAE | B737-800 | Boeing | 188 | +| OH-LPE | B777-200 | Boeing | 330 | + +| **ICAO** | **Name** | **Country** | **RunwayLength** | +|----------|------------------------------------------|-------------|------------------| +| LOWL | Blue Danube Airport Linz | Austria | 9842 | +| LOWW | Vienna International | Austria | 11811 | +| LIRF | Leonardo da Vinci–Fiumicino | Italy | 12795 | +| EGLL | Heathrow | UK | 12802 | +| EDDF | Frankfurt | Germany | 13123 | +| RJAA | Narita International | Japan | 13123 | +| KATL | Hartsfield–Jackson Atlanta International | USA | 12390 | + +## Task 3 +- Allow passenger registration +- Create an endpoint for adding new and updating existing passengers to/in the database +- Data is provided as JSON in the body of the request +- Use a `PUT` operation (with proper resource URI using the ssn) + - This allows us to use this method for updates (by replacement) as well + - Be careful when reading entities back from the database: types are lost at runtime, so what you expect to be + a `Date` may not be one - the same is true for a `boolean`, some casting/mapping may be required +- Provide another endpoint for retrieving: + 1. a specific passenger by ssn + 2. a list of all passengers +- Apply the best practices you know by now + +## Task 4 +- Provide information about airports +- Two-fold process: + 1. One endpoint provides a list of all ICAO codes + 2. A second endpoint allows retrieving detail information of an airport _by_ its ICAO code + +## Task 5 +- Provide information about planes +- A list of available planes + - A new flight can only be planned for a plane which does not already operate a different flight at the same time + - The request has to contain departure time information + - We will use a simplified method to determine that: + - We assume that any flight takes at most 12 hours (no matter which airports are connected) + - Flights can start at any airport, independent of the last destination of the plane - it can reach any departure airport within 12 hours as well + - Thus, we _block_ a plane for _24_ hours after its last _departure_ time + - All airports in our database have runways long enough for each plane in our database (it might be close for the 777 in Linz, but we'll ignore that 🙂) +- Again, a two-step process: + 1. Query for a list of airplane ids which are available in the specified time frame + 2. Query for details of a plane by id + +## Task 6 +- To create a flight the following information has to be supplied: + - Departure & destination airport + - Both have to exist + - Departure time + - Has to be in the future + - Plane which will operate the flight + - Has to be one of the available planes +- Implement an endpoint for creating such flights +- Also allow for updates - for which all conditions still apply + - _Hint_: if the plane does not change, that one will be occupied during an update, handle that case accordingly + +## Task 7 +- Finally, we will book passengers on flights +- The endpoint will receive + - The SSN + - The seat + - the flight number +- It has to be verified that + - Person exists + - As a simplification you do _not_ have to check if the Person is already booked on _another_ flight at the same time + - Flight exists + - Flight still has open seats (= capacity not exceeded) + - Seat not taken by another passenger +- The ticket number is created automatically by the database and returned to the user +- To make it easier you don't have to worry about updates or delete this time + +## Task 8 +- Provide an endpoint for cancelling (= deleting a flight) +- This includes cancelling all reservations as well + - Return the ticket numbers which have been removed so passengers can be notified diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..f401f6b --- /dev/null +++ b/src/app.ts @@ -0,0 +1,7 @@ +import express from 'express'; + +const app = express(); + +app.listen(3000, () => { + console.log("Started Server on Port 3000") +}) diff --git a/src/database/Unit.ts b/src/database/Unit.ts new file mode 100644 index 0000000..d48fe9d --- /dev/null +++ b/src/database/Unit.ts @@ -0,0 +1,51 @@ +import { Database, Statement } from "sqlite"; +import { DB } from "./data"; + +// the following code uses the bang operator in several places when non-null is guaranteed by context for brevity - not a recommended approach overall + +export class Unit { + + private db: Database | null; + private readonly statements: Statement[]; + + private constructor(public readonly readOnly: boolean) + { + this.db = null; + this.statements = []; + } + + private async init(): Promise { + this.db = await DB.createDBConnection(); + if (!this.readOnly) { + await DB.beginTransaction(this.db); + } + } + + public async prepare(sql: string, bindings: any | null = null): Promise { + // ! due to late init and logically guaranteed non-null - don't do that at home! + const stmt = await this.db!.prepare(sql); + if (bindings !== null){ + await stmt!.bind(bindings); + } + this.statements.push(stmt); + return stmt!; + } + + public async complete(commit: boolean | null = null): Promise { + if (commit !== null) { + await (commit ? DB.commitTransaction(this.db!) : DB.rollbackTransaction(this.db!)); + } else if (!this.readOnly) { + throw new Error('transaction has been opened, requires information if commit or rollback needed'); + } + for (const stmt of this.statements) { + await stmt.finalize(); + } + await this.db!.close(); + } + + public static async create(readOnly: boolean): Promise { + const unit = new Unit(readOnly); + await unit.init(); + return unit; + } +} \ No newline at end of file diff --git a/src/database/data.ts b/src/database/data.ts new file mode 100644 index 0000000..d1984d2 --- /dev/null +++ b/src/database/data.ts @@ -0,0 +1,27 @@ +import {Database,open} from "sqlite"; +import sqlite3 from "sqlite"; + +export class DB { + + static async createDBConnection() : Promise { + const connection = await open({ + filename: `./flights.db`, + driver: sqlite3.Database + }); + await connection.exec('PRAGMA foreign_keys = ON;'); + return connection; + } + + static async beginTransaction(db: Database) { + await db.exec("begin transaction;"); + } + + + static async commitTransaction(db: Database) { + await db.exec("commit;") + } + + static async rollbackTransaction(db: Database) { + await db.exec("rollback;") + } +} \ No newline at end of file