From 21bb9e3914641e33c0ce4261a4d9a76f84e9ce31 Mon Sep 17 00:00:00 2001 From: Ladislav Hano Date: Thu, 29 Jun 2023 14:34:22 +0200 Subject: [PATCH] Final --- .gitignore | 1 + .htaccess | 3 + api/APIHandler.php | 57 ++++++++++++ api/Court.php | 24 +++++ api/Pricing.php | 22 +++++ api/Reservation.php | 214 ++++++++++++++++++++++++++++++++++++++++++++ api/Security.php | 21 +++++ index.html | 0 index.php | 17 ++++ tenis.db | Bin 0 -> 32768 bytes 10 files changed, 359 insertions(+) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 api/APIHandler.php create mode 100644 api/Court.php create mode 100644 api/Pricing.php create mode 100644 api/Reservation.php create mode 100644 api/Security.php create mode 100644 index.html create mode 100644 index.php create mode 100644 tenis.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3598c30 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tests \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..5c45dac --- /dev/null +++ b/.htaccess @@ -0,0 +1,3 @@ +RewriteEngine On +RewriteRule . index.php +ErrorDocument 404 /error/not_found.php \ No newline at end of file diff --git a/api/APIHandler.php b/api/APIHandler.php new file mode 100644 index 0000000..d34b49a --- /dev/null +++ b/api/APIHandler.php @@ -0,0 +1,57 @@ +process($_SERVER["REQUEST_METHOD"]); + break; + case "court": + $court = new Court($pdo); + echo $court->process($_SERVER["REQUEST_METHOD"]); + break; + default: + http_response_code(400); + exit; + } + } + catch (InvalidArgumentException $e) { + http_response_code(400); + echo json_encode([ + "message" => $e->getMessage() + ]); + } + catch (Throwable $e) { + http_response_code(500); + echo json_encode([ + "message" => $e->getMessage(), + "code" => $e->getCode(), + "line" => $e->getLine() + ]); + } + } +} diff --git a/api/Court.php b/api/Court.php new file mode 100644 index 0000000..43837c4 --- /dev/null +++ b/api/Court.php @@ -0,0 +1,24 @@ +pdo = $pdo; + } + + public function process(string $request) { + switch ($request) { + case "GET": + return $this->get_all(); + default: + throw new Exception(); + } + } + + private function get_all() { + $query = $this->pdo->query("SELECT * FROM courts"); + $rows = $query->fetchAll(PDO::FETCH_ASSOC); + return json_encode($rows); + } +} \ No newline at end of file diff --git a/api/Pricing.php b/api/Pricing.php new file mode 100644 index 0000000..d96f721 --- /dev/null +++ b/api/Pricing.php @@ -0,0 +1,22 @@ +pdo = $pdo; + } + + public function process(string $request) { + // null will become empty array + $data = (array) json_decode(file_get_contents("php://input"), true); + switch ($request) { + case "GET": + return $this->get_data($data); + case "POST": + return $this->create_reservation($data); + case "DELETE": + $this->delete($data); + return null; + default: + throw new Exception("Unknown request"); + } + } + + private function get_data($data) { + $stmt = null; + if (empty($data)) { + $stmt = $this->get_all(); + } + elseif (isset($data["court"])) { + $stmt = $this->get_by_court($data["court"]); + } + elseif (isset($data["phone"])) { + $stmt = $this->get_by_phone($data["phone"]); + } + else { + throw new InvalidArgumentException("Expected court id or phone number got none"); + } + + $stmt->execute(); + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + return json_encode($result ? $result : []); + } + + private function get_all() { + return $this->pdo->prepare("SELECT * FROM reservations"); + } + + private function get_by_court(int $id) { + if (!is_int($id)) { + throw new InvalidArgumentException("id not an integer"); + } + + $query = "SELECT * FROM reservations WHERE court_id=:id"; + $stmt = $this->pdo->prepare($query); + $stmt->bindValue(":id", $id, PDO::PARAM_INT); + return $stmt; + } + + private function get_by_phone(string $phone_number) { + if (!Security::is_phone_number($phone_number)) { + throw new InvalidArgumentException("phone number not in valid format"); + } + + $query = "SELECT * FROM reservations WHERE phone_number=:phone"; + $stmt = $this->pdo->prepare($query); + $stmt->bindValue(":phone", $phone_number, PDO::PARAM_STR); + return $stmt; + } + + private function create_reservation($data) { + $this->verify_create_data($data); + + $stmt = $this->pdo->prepare("SELECT surface FROM courts WHERE id=:id"); + $stmt->bindValue(":id", $data["court_id"], PDO::PARAM_INT); + $stmt->execute(); + $surface = $stmt->fetchAll()[0][0]; + $price = Pricing::for_tenis((int)$data["end"] - (int)$data["start"], $data["game"], $surface); + + $password = Security::generate_password(6); + $password_hash = hash("sha256", $password); + + $phone = preg_replace('/\s*/', '', $data["phone"]); + $phone_hash = hash("sha256", $phone); + + $query = "INSERT INTO reservations VALUES (NULL, :court_id, :phone, :password, :game, :start, :end)"; + $stmt = $this->pdo->prepare($query); + + $stmt->bindValue(":court_id", $data["court_id"], PDO::PARAM_INT); + $stmt->bindValue(":phone", $phone_hash, PDO::PARAM_STR); + $stmt->bindValue(":password", $password_hash, PDO::PARAM_STR); + $stmt->bindValue(":game", $data["game"], PDO::PARAM_INT); + $stmt->bindValue(":start", $data["start"], PDO::PARAM_INT); + $stmt->bindValue(":end", $data["end"], PDO::PARAM_INT); + + $result = $stmt->execute(); + if ($result) { + http_response_code(201); + echo json_encode([ + "price" => $price, + "password" => $password + ]); + return; + } + + http_response_code(500); + exit; + } + + private function verify_create_data($data) { + if (!isset($data["court_id"], $data["phone"], $data["game"], $data["start"], $data["end"])) { + throw new InvalidArgumentException("some arguments are missing"); + } + + if (!is_int($data["court_id"])) { + throw new InvalidArgumentException("court id not in correct format"); + } + + $court_id = (int)$data["court_id"]; + $court_count = $this->pdo->query("SELECT COUNT(*) FROM courts")->fetchAll()[0][0]; + + if ($court_id < 1 || $court_id > $court_count) { + throw new InvalidArgumentException("court id is not valid"); + } + + if (!Security::is_phone_number($data["phone"])) { + throw new InvalidArgumentException("phone number not in correct format"); + } + + if (!is_int($data["game"]) || $data["game"] < 0 || $data["game"] >= Pricing::get_games_count()) { + throw new InvalidArgumentException("game type is not in valid format"); + } + + if (!is_int($data["start"]) || !is_int($data["end"])) + { + throw new InvalidArgumentException("start or end is not in valid format"); + } + + $start = (int)$data["start"]; + $end = (int)$data["end"]; + + $this->verify_interval($start, $end, $data["court_id"]); + } + + private function verify_interval(int $start, int $end, int $id) { + if ($start > $end || $end - $start > $this->MAX_DURATION || $end - $start < $this->MIN_DURATION) { + throw new InvalidArgumentException("reservation interval not valid"); + } + + if ($start <= time()) { + throw new InvalidArgumentException("can't create reservation in the past"); + } + + $query = 'SELECT * FROM reservations WHERE court_id=:court_id AND start>=:start AND end<=:end'; + $stmt = $this->pdo->prepare($query); + + $stmt->bindValue(":start", $start, PDO::PARAM_INT); + $stmt->bindValue(":end", $end, PDO::PARAM_INT); + $stmt->bindValue(":court_id", $id, PDO::PARAM_INT); + $stmt->execute(); + + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + if ($result) { + throw new InvalidArgumentException("reservation in this interval already exists"); + } + } + + private function delete($data) { + if (!isset($data["id"], $data["password"], $data["phone"])) { + throw new InvalidArgumentException("invalid arguments"); + } + + if (!is_int($data["id"])) { + throw new InvalidArgumentException("id not in correct format"); + } + + if (!Security::is_phone_number($data["phone"])) { + throw new InvalidArgumentException("phone number not in correct format"); + } + + + $phone = preg_replace('/\s*/', '', $data["phone"]); + $phone_hash = hash("sha256", $phone); + $password_hash = hash("sha256", $data["password"]); + + $query = "SELECT COUNT(*) FROM reservations WHERE id=:id AND phone_number=:phone AND password=:password"; + $stmt = $this->pdo->prepare($query); + + $stmt->bindValue(":id", $data["id"], PDO::PARAM_INT); + $stmt->bindValue(":password", $password_hash, PDO::PARAM_STR); + $stmt->bindValue(":phone", $phone_hash, PDO::PARAM_STR); + + $stmt->execute(); + $count = $stmt->fetchAll()[0][0]; + if ($count == 0) { + throw new InvalidArgumentException("invalid entry (does not exist or not correct information)"); + } + + $query = "DELETE FROM reservations WHERE id=:id AND phone_number=:phone AND password=:password"; + $stmt = $this->pdo->prepare($query); + + $stmt->bindValue(":id", $data["id"], PDO::PARAM_INT); + $stmt->bindValue(":password", $password_hash, PDO::PARAM_STR); + $stmt->bindValue(":phone", $phone_hash, PDO::PARAM_STR); + + $result = $stmt->execute(); + if (!$result) { + throw new InvalidArgumentException("something went wrong"); + } + } +} \ No newline at end of file diff --git a/api/Security.php b/api/Security.php new file mode 100644 index 0000000..72d3473 --- /dev/null +++ b/api/Security.php @@ -0,0 +1,21 @@ +6&Ww5}q1skaQ>S$ui3*8yUJFrM zo7xVK69jSQ$f@le4*VmWdZ5>$f*?YOhlDBw9GJC}CT+t*N-t@?m1ex_$*jNc_pKwX z6VK&KOKSrYWZm8kF$jor)Nx(sl^}2&=Y(w)+sd{TYbe(}Xamoy}NkOdm|-D{)x zKYC+gY;tDC{rH1{kk?Jm^iA&_F<9$%`n&hX&a~2nb{e!7UR_Fq>D@!q!ECKMy{4zD zi_La=E^P(P<#y0qSy~EKnv0iK(vx;rb=P}?)xCZwAAS3+ZpW;4)^AADo36Ig*X;bQ za}0+?zyEHxXJ6r|p*O`1^YjbnlllYE8|*z~I(r0b=bT+`rHkj9!TI#c>`ugUK`T9* zw$kRA^l~tIx<5UeCp>58Tx=~~SZG}t&9Tn~3oGsA#ir%DkT%1Fcx2~&Veb5~qukNN@MZ1qG7rpZ2{JeW*SgEaveRFf&bd-7Gu=rTL zo*10%ShbUO=G=zwj!jNYxf|~f&pE78f7BNqJHvL~|EJdd^&wfB*=900@8p2!H?xfB*=900-c}!`~P43AM7q{AOHd&00JNY0w4eaAOHd& z00JNY0tZl_#5b{hytJf7kKv`rjPD)F5OK009sH0T2KI z5C8!X009sH0T4Kx0!PQZ8F%-0AL_M=H|380?Z0GAUO1dCkTnQ^00@8p2!H?xfB*=9 z00@8p2!KGomS6ti0Bj%t0w4eaAOHd&00JNY0w4eaAOHf-p}=tczxr{9`y9je5m?2{==6NU%{6iz1RCiVQg5ywd5t|GvY9h{+mQ*An=CMFVVZ^d1#j!Mmh=d~)5~Lz(xWt+fg$<{aNp50ELloQCcA^^JiOSorRbnrwEWwx= zM!8b9Qp_&WFA^K5wj~S=OK3!>l3FlhYpm>Bi)hK1&P1jVepJDU^0T|FaE3x8Fh^37 zn8aBo3@038&V;?3aKtssj3SELz#>DrjVjhQOf8~JCPZyhjn@Br;P`*|Klz*f1OGSw zo`3I9{bGb!3m00ck)1V8`;KmY_l00cnb;0he6=YQHSA32h*^;fIqeYKkRl}g@^ JkLP{4{5Q@~Ap-ya literal 0 HcmV?d00001