From c557461257998a911e852861a7fb213a3fd23887 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sun, 9 Jan 2022 21:38:48 +0200 Subject: [PATCH 01/55] PC-1 Add chats & chatsParticipants sql script --- db/db.controller.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/db/db.controller.php b/db/db.controller.php index e470765..b74ac67 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -87,13 +87,32 @@ private function initialize() # Create Users table $users = <<conn->exec($users); } catch (Exception $ex) { From 97c9adbaac56e589b35282fa835f88cbcd696b30 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sun, 9 Jan 2022 21:39:13 +0200 Subject: [PATCH 02/55] PC-1 Seed chats fixtures --- db/db.controller.php | 67 +++++++++++++++++++++++--------------------- fixtures/chats.php | 11 ++++++++ 2 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 fixtures/chats.php diff --git a/db/db.controller.php b/db/db.controller.php index b74ac67..70f5d88 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -1,6 +1,7 @@ connectToDb($dbConfig); - $this->drop(['Users']); + // TODO: Don't run this if we are in prod mode + $this->drop(['Users', 'Chats']); $this->initialize(); - # Include $usersFixtures from global scope here - global $usersFixtures; + # Include fixtures from global scope here + global $usersFixtures, $chatsFixtures; $this->seed('Users', ["id", "username", "password"], $usersFixtures); - } - - public function getConnection() - { - return $this->conn; + $this->seed('Chats', ["id", "name", "private", "inviteLink"], $chatsFixtures); } private function connectToDb(array $dbConfig) @@ -38,30 +36,6 @@ private function connectToDb(array $dbConfig) } } - /** - * Seeds data from array in specified table and columns - * @param {string} $table Table name where we want to seed data - * @param {string[]} $columns Array of fields names we want to insert - * @param {object[]} $fixtures Array of fixtures with fields from $fields in same order - */ - public function seed(string $table, array $columns, array $fixtures) - { - try { - $columnsString = implode(",", $columns); - - foreach ($fixtures as $fixture) { - $fixtureKeys = array_keys($fixture); - $fixtureKeysString = ":" . implode(", :", $fixtureKeys); - - $sql = "INSERT INTO $table ($columnsString) VALUES ($fixtureKeysString)"; - - $this->conn->prepare($sql)->execute($fixture); - } - } catch (Exception $ex) { - httpException("Failed to seed db, table '$table'", 500)['end'](); - } - } - /** * Drop specified table in DB * @param {string[]} $tables Array of tables names @@ -119,4 +93,33 @@ private function initialize() httpException("Failed to initialize db", 500); } } + + /** + * Seeds data from array in specified table and columns + * @param {string} $table Table name where we want to seed data + * @param {string[]} $columns Array of fields names we want to insert + * @param {object[]} $fixtures Array of fixtures with fields from $fields in same order + */ + public function seed(string $table, array $columns, array $fixtures) + { + try { + $columnsString = implode(",", $columns); + + foreach ($fixtures as $fixture) { + $fixtureKeys = array_keys($fixture); + $fixtureKeysString = ":" . implode(", :", $fixtureKeys); + + $sql = "INSERT INTO $table ($columnsString) VALUES ($fixtureKeysString)"; + + $this->conn->prepare($sql)->execute($fixture); + } + } catch (Exception $ex) { + httpException("Failed to seed db, table '$table'", 500)['end'](); + } + } + + public function getConnection() + { + return $this->conn; + } } diff --git a/fixtures/chats.php b/fixtures/chats.php new file mode 100644 index 0000000..00c521e --- /dev/null +++ b/fixtures/chats.php @@ -0,0 +1,11 @@ + 1, + "name" => "First private chat", + "private" => true, + "inviteLink" => "invite_link" + ]; + + $chatsFixtures = [$maxAndIlyaChat]; + \ No newline at end of file From d823338e7e7e43436811c1e8b09b4c8d5895a1de Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 14 Jan 2022 21:31:56 +0200 Subject: [PATCH 03/55] PC-1 Fix db drop --- db/db.controller.php | 10 +++++----- docker-compose.yml | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/db/db.controller.php b/db/db.controller.php index 70f5d88..63ba9bd 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -11,8 +11,8 @@ public function __construct($dbConfig) { $this->connectToDb($dbConfig); - // TODO: Don't run this if we are in prod mode - $this->drop(['Users', 'Chats']); + // TODO: Don't run this if we are in production mode + $this->drop(['ChatParticipants', 'Users', 'Chats']); $this->initialize(); @@ -81,11 +81,11 @@ private function initialize() chatFk BIGINT NOT NULL, userFk BIGINT NOT NULL, permission TINYINT NOT NULL DEFAULT 0, - FOREIGN KEY (chatFk) REFERENCES Chats (id), - FOREIGN KEY (userFk) REFERENCES Users (id) + FOREIGN KEY (chatFk) REFERENCES Chats (id) ON DELETE CASCADE, + FOREIGN KEY (userFk) REFERENCES Users (id) ON DELETE CASCADE ); - ALTER TABLE ChatParticipants DROP CONSTRAINT unique_chat_user; + -- ALTER TABLE ChatParticipants DROP CONSTRAINT unique_chat_user; ALTER TABLE ChatParticipants ADD UNIQUE unique_chat_user(chatFk, userFk); SQL; $this->conn->exec($users); diff --git a/docker-compose.yml b/docker-compose.yml index 986c006..c4dd25b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: web_chat + cap_add: + - SYS_NICE adminer: container_name: web_chat_adminer From 53bc4fc0502fb0b5ae11a66756ed4cdaab92be73 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 14 Jan 2022 22:48:26 +0200 Subject: [PATCH 04/55] PC-1 Improve db seeding by removing hard-coded columns arg --- db/db.controller.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/db/db.controller.php b/db/db.controller.php index 63ba9bd..6668ba7 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -17,9 +17,10 @@ public function __construct($dbConfig) $this->initialize(); # Include fixtures from global scope here - global $usersFixtures, $chatsFixtures; - $this->seed('Users', ["id", "username", "password"], $usersFixtures); - $this->seed('Chats', ["id", "name", "private", "inviteLink"], $chatsFixtures); + global $usersFixtures, $chatsFixtures, $chatParticipantsFixtures; + $this->seed('Users', $usersFixtures); + $this->seed('Chats', $chatsFixtures); + $this->seed('ChatParticipants', $chatParticipantsFixtures); } private function connectToDb(array $dbConfig) @@ -100,13 +101,12 @@ private function initialize() * @param {string[]} $columns Array of fields names we want to insert * @param {object[]} $fixtures Array of fixtures with fields from $fields in same order */ - public function seed(string $table, array $columns, array $fixtures) + public function seed(string $table, array $fixtures) { try { - $columnsString = implode(",", $columns); - foreach ($fixtures as $fixture) { $fixtureKeys = array_keys($fixture); + $columnsString = implode(",", $fixtureKeys); $fixtureKeysString = ":" . implode(", :", $fixtureKeys); $sql = "INSERT INTO $table ($columnsString) VALUES ($fixtureKeysString)"; From a86ec51af8f7fd63e3ae6c37a6bca9b091bc6b43 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 14 Jan 2022 22:48:40 +0200 Subject: [PATCH 05/55] PC-1 Add chat participants fixtures --- fixtures/chatParticipants.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 fixtures/chatParticipants.php diff --git a/fixtures/chatParticipants.php b/fixtures/chatParticipants.php new file mode 100644 index 0000000..e89e7af --- /dev/null +++ b/fixtures/chatParticipants.php @@ -0,0 +1,20 @@ + $MaxAndIlyaChat["id"], + "userFk" => $MaxDmitriev["id"], + "permission" => 2 + ]; + + $IlyaInChatWithMax = [ + "chatFk" => $MaxAndIlyaChat["id"], + "userFk" => $IlyaMehof["id"], + "permission" => 2 + ]; + + $chatParticipantsFixtures = [$MaxInChatWithIlya, $IlyaInChatWithMax]; \ No newline at end of file From 9f99e36fea1d1f074975b5a512f63d4831da62e5 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 14 Jan 2022 22:49:08 +0200 Subject: [PATCH 06/55] PC-1 Seed chat participants fixtures in db --- db/db.controller.php | 5 +++-- fixtures/chats.php | 4 ++-- fixtures/users.php | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/db/db.controller.php b/db/db.controller.php index 6668ba7..0dfa579 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -2,6 +2,7 @@ @include_once("./fixtures/users.php"); @include_once("./fixtures/chats.php"); + @include_once("./fixtures/chatParticipants.php"); class DbController { @@ -82,8 +83,8 @@ private function initialize() chatFk BIGINT NOT NULL, userFk BIGINT NOT NULL, permission TINYINT NOT NULL DEFAULT 0, - FOREIGN KEY (chatFk) REFERENCES Chats (id) ON DELETE CASCADE, - FOREIGN KEY (userFk) REFERENCES Users (id) ON DELETE CASCADE + FOREIGN KEY (chatFk) REFERENCES Chats (id), + FOREIGN KEY (userFk) REFERENCES Users (id) ); -- ALTER TABLE ChatParticipants DROP CONSTRAINT unique_chat_user; diff --git a/fixtures/chats.php b/fixtures/chats.php index 00c521e..a867e5f 100644 --- a/fixtures/chats.php +++ b/fixtures/chats.php @@ -1,11 +1,11 @@ 1, "name" => "First private chat", "private" => true, "inviteLink" => "invite_link" ]; - $chatsFixtures = [$maxAndIlyaChat]; + $chatsFixtures = [$MaxAndIlyaChat]; \ No newline at end of file diff --git a/fixtures/users.php b/fixtures/users.php index c23fd90..cbc0c1f 100644 --- a/fixtures/users.php +++ b/fixtures/users.php @@ -7,6 +7,7 @@ "username" => "dmitriev", "password" => $plainPassword ]; + $IlyaMehof = [ "id" => 2, "username" => "mehoff", From 9d23151b6ce2288d95cf1e38809f56da0bd03a0a Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 14 Jan 2022 23:05:42 +0200 Subject: [PATCH 07/55] PC-1 Init chats controller and service --- chats/chats.controller.php | 51 ++++++++++++++++++++++++++++++++++++++ chats/chats.service.php | 11 ++++++++ 2 files changed, 62 insertions(+) create mode 100644 chats/chats.controller.php create mode 100644 chats/chats.service.php diff --git a/chats/chats.controller.php b/chats/chats.controller.php new file mode 100644 index 0000000..eecdbb6 --- /dev/null +++ b/chats/chats.controller.php @@ -0,0 +1,51 @@ +chatsService = new UsersService($conn); + } + + function getChats() + { + $chats = $this->chatsService->getChats(); + + $response = [ + "chats" => $chats + ]; + + jsonResponse($response)['end'](); + } + + function getChatById($req) + { + global $messages; + + # Parse chat id from url + $chatId = intval(substr($req['resource'], strlen('/api/chats/'))); + + $response = [ + "chat" => [] + ]; + + jsonResponse($response)['end'](); + } + + function createChat($chatDto) + { + $response = [ + "message" => "Chat created", + "chat" => [] + ]; + + jsonResponse($response)['end'](); + } + } diff --git a/chats/chats.service.php b/chats/chats.service.php new file mode 100644 index 0000000..4657922 --- /dev/null +++ b/chats/chats.service.php @@ -0,0 +1,11 @@ +conn = $conn; + } + } From 897c4528b2f290e16657cc1d204a929d9eb058c5 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 15 Jan 2022 13:08:02 +0200 Subject: [PATCH 08/55] PC-1 Add create chat service --- chats/chats.service.php | 16 ++++++++++++++++ db/db.controller.php | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/chats/chats.service.php b/chats/chats.service.php index 4657922..5b62027 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -8,4 +8,20 @@ function __construct($conn) { $this->conn = $conn; } + + function createChat($id, $name, $isPrivate, $inviteLink) + { + $sql = "INSERT INTO Chats (id, name, isPrivate, inviteLink) VALUES (:id, :name, :isPrivate, :inviteLink)"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":id", $id); + $stmt->bindValue(":name", $name); + $stmt->bindValue(":isPrivate", $isPrivate); + $stmt->bindValue(":inviteLink", $inviteLink); + $stmt->execute(); + } + + function findById($id) + { + + } } diff --git a/db/db.controller.php b/db/db.controller.php index 0dfa579..03ffb1d 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -75,7 +75,7 @@ private function initialize() id BIGINT PRIMARY KEY, image VARCHAR(128) NULL, name VARCHAR(30) NOT NULL, - private BOOLEAN NOT NULL DEFAULT TRUE, + isPrivate BOOLEAN NOT NULL DEFAULT TRUE, inviteLink VARCHAR(30) NOT NULL ); From 315e2c758df670b59b53015d275843634e14a762 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sun, 16 Jan 2022 13:42:10 +0200 Subject: [PATCH 09/55] PC-1 Add routes for: create chat, get chats, find chat by id --- chats/chats.controller.php | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/chats/chats.controller.php b/chats/chats.controller.php index eecdbb6..34be2fa 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -11,7 +11,7 @@ class ChatsController function __construct($conn) { - $this->chatsService = new UsersService($conn); + $this->chatsService = new ChatsService($conn); } function getChats() @@ -31,9 +31,15 @@ function getChatById($req) # Parse chat id from url $chatId = intval(substr($req['resource'], strlen('/api/chats/'))); - + + $chat = $this->chatsService->findById($chatId); + + if (is_null($chat)) { + httpException($messages["chat_not_found"], 404)['end'](); + } + $response = [ - "chat" => [] + "chat" => $chat ]; jsonResponse($response)['end'](); @@ -41,9 +47,17 @@ function getChatById($req) function createChat($chatDto) { + global $messages; + + $result = $this->chatsService->createChat($chatDto); + + if (!$result) { + httpException($messages["failed_to_create_chat"])['end'](); + } + $response = [ - "message" => "Chat created", - "chat" => [] + "message" => $messages["chat_created"], + "chat" => $result ]; jsonResponse($response)['end'](); From 36cc864809c520c053a7279306105e241177e551 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sun, 16 Jan 2022 13:42:26 +0200 Subject: [PATCH 10/55] PC-1 Add services for: create chat, get chats, find chat by id --- chats/chats.service.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/chats/chats.service.php b/chats/chats.service.php index 5b62027..1cead66 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -8,7 +8,20 @@ function __construct($conn) { $this->conn = $conn; } - + + function getChats(): array + { + $sql = "SELECT * FROM Chats"; + $stmt = $this->conn->prepare($sql); + $stmt->execute(); + + if ($stmt->rowCount() > 0) { + return $stmt->fetch(PDO::FETCH_ASSOC); + } else { + return []; + } + } + function createChat($id, $name, $isPrivate, $inviteLink) { $sql = "INSERT INTO Chats (id, name, isPrivate, inviteLink) VALUES (:id, :name, :isPrivate, :inviteLink)"; @@ -22,6 +35,15 @@ function createChat($id, $name, $isPrivate, $inviteLink) function findById($id) { - + $sql = "SELECT * FROM Chats WHERE id=:id"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":id", $id); + $stmt->execute(); + + if ($stmt->rowCount() > 0) { + return $stmt->fetch(PDO::FETCH_ASSOC); + } else { + return null; + } } } From 6bbb92703230598bd602af4d432b5360d260035c Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sun, 16 Jan 2022 13:43:20 +0200 Subject: [PATCH 11/55] PC-1 Replace hard coded messages with shared messages in user controller --- locale/en/messages.php | 7 +++++++ users/users.controller.php | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/locale/en/messages.php b/locale/en/messages.php index 3bbb0a3..ae65d13 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -2,10 +2,17 @@ $messages = [ "username_taken" => "Username is already taken", + "user_not_found" => "User not found", + "chat_not_found" => "Chat not found", + "user_created" => "User created", + "chat_created" => "Chat created", + "failed_to_create_user" => "Failed to create user", + "failed_to_create_chat" => "Failed to create chat", "failed_to_sign_in" => "Failed to sign in", "failed_to_sign_up" => "Failed to sign up", + "not_authenticated" => "Not authenticated", ]; \ No newline at end of file diff --git a/users/users.controller.php b/users/users.controller.php index c869466..c8576bb 100644 --- a/users/users.controller.php +++ b/users/users.controller.php @@ -47,14 +47,16 @@ function getUserById($req) function createUser($userDto) { + global $messages; + $result = $this->usersService->createUser($userDto); if (!$result) { - httpException("Failed to create user")['end'](); + httpException($messages["failed_to_create_user"])['end'](); } $response = [ - "message" => "User created", + "message" => $messages["user_created"], "user" => $result ]; From c617fee7544b2b41a6832c187cb2058a801e5ec9 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 18:24:28 +0200 Subject: [PATCH 12/55] PC-1 Fix isPrivate in chats fixtures --- fixtures/chats.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fixtures/chats.php b/fixtures/chats.php index a867e5f..05d0688 100644 --- a/fixtures/chats.php +++ b/fixtures/chats.php @@ -3,7 +3,7 @@ $MaxAndIlyaChat = [ "id" => 1, "name" => "First private chat", - "private" => true, + "isPrivate" => true, "inviteLink" => "invite_link" ]; From 0f12e4e221f36b39c3668d575b0364a7529b5303 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 19:03:16 +0200 Subject: [PATCH 13/55] PC-1 Add new chat fixture --- fixtures/chats.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fixtures/chats.php b/fixtures/chats.php index 05d0688..2874533 100644 --- a/fixtures/chats.php +++ b/fixtures/chats.php @@ -3,9 +3,16 @@ $MaxAndIlyaChat = [ "id" => 1, "name" => "First private chat", - "isPrivate" => true, + "isPrivate" => 1, "inviteLink" => "invite_link" ]; - $chatsFixtures = [$MaxAndIlyaChat]; + $GymPartyPublicChat = [ + "id" => 2, + "name" => "Welcome to the club buddy", + "isPrivate" => 0, + "inviteLink" => "oralcmsht" + ]; + + $chatsFixtures = [$MaxAndIlyaChat, $GymPartyPublicChat]; \ No newline at end of file From d0becf0b2286af707b0eefd6de9ce746ff2d5037 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 19:03:23 +0200 Subject: [PATCH 14/55] PC-1 Add new user fixture --- fixtures/users.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fixtures/users.php b/fixtures/users.php index cbc0c1f..eff879f 100644 --- a/fixtures/users.php +++ b/fixtures/users.php @@ -14,4 +14,10 @@ "password" => $plainPassword ]; - $usersFixtures = [$MaxDmitriev, $IlyaMehof]; + $MatveyGorelik = [ + "id" => 3, + "username" => "offiza", + "password" => $plainPassword + ]; + + $usersFixtures = [$MaxDmitriev, $IlyaMehof, $MatveyGorelik]; From 16bdbbc282520ffb40a8fc2870cf57de854934e1 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 19:04:02 +0200 Subject: [PATCH 15/55] PC-1 Add chatsController to appController, add get chat by id route --- app/app.controller.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/app.controller.php b/app/app.controller.php index 629caf1..2dd3ac1 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -5,6 +5,7 @@ @include_once "./utils/request.php"; @include_once "./users/users.controller.php"; @include_once "./db/db.controller.php"; + @include_once "./chats/chats.controller.php"; @include_once "./auth/auth.controller.php"; @include_once "./guards/jwtAuthGuard.php"; @@ -20,6 +21,7 @@ class AppController # Controllers private $dbController; private $usersController; + private $chatsController; private $authController; # Guards private $jwtAuthGuard; @@ -36,6 +38,7 @@ function __construct($dbConfig) # Initialize controllers $this->usersController = new UsersController($this->conn); $this->authController = new AuthController($this->conn); + $this->chatsController = new ChatsController($this->conn); # Initialize guards $this->jwtAuthGuard = new JwtAuthGuard(new UsersService($this->conn)); @@ -62,7 +65,7 @@ private function router() $this->usersController->getMe($this->_req->getRequest()); return; } - // /users/:userId + # /users/:userId if (strpos($this->req['resource'], '/api/users/') === 0) { $this->usersController->getUserById($this->req); return; @@ -71,6 +74,12 @@ private function router() $this->usersController->getUsers(); return; } + # /chats/:chatId + if (strpos($this->req['resource'], '/api/chats/') === 0) { + $this->_req->useGuard($this->jwtAuthGuard); + $this->chatsController->getChatById($this->req); + return; + } httpException("Route not found " . $this->req['resource'], 404)['end'](); logMessage("Route not found $this->req"); From 4dcdd44ad9003c0301040a5612bd6f846c1a2228 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 19:04:32 +0200 Subject: [PATCH 16/55] PC-1 Add get chat by id tests for TDD --- tests/chats-e2e.php | 87 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/chats-e2e.php diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php new file mode 100644 index 0000000..d831ea2 --- /dev/null +++ b/tests/chats-e2e.php @@ -0,0 +1,87 @@ +

Users e2e

+
+ ["Authorization: Bearer $jwt"]]);
+      
+      $json = json_decode($response['data']);
+      $chatData = $json->data->chat;
+      
+      assertStrict($response['info']['http_code'], 200);
+      assertStrict(intval($chatData->id), $MaxAndIlyaChat['id']);
+      assertStrict($chatData->name, $MaxAndIlyaChat['name']);
+      assertStrict(isset($chatData->isPrivate), false);
+      assertStrict(isset($chatData->inviteLink), false);
+    });
+  
+    it("should return error when trying to get private chat by id for NOT a chat member", function () {
+      global $testsConfig, $messages, $MatveyGorelik, $MaxAndIlyaChat;
+    
+      $jwt = signJwtForUser($MatveyGorelik);
+    
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]);
+    
+      $json = json_decode($response['data']);
+    
+      assertStrict($response['info']['http_code'], 401);
+      assertStrict($json->data->error, $messages["not_authenticated"]);
+      assertStrict(isset($chatData->isPrivate), false);
+      assertStrict(isset($chatData->inviteLink), false);
+    });
+  
+    it("should get public chat by id", function () {
+      global $testsConfig, $MaxDmitriev, $GymPartyPublicChat;
+      
+      $jwt = signJwtForUser($MaxDmitriev);
+    
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]);
+    
+      $json = json_decode($response['data']);
+      $chatData = $json->data->chat;
+    
+      assertStrict($response['info']['http_code'], 200);
+      assertStrict(intval($chatData->id), $GymPartyPublicChat['id']);
+      assertStrict($chatData->name, $GymPartyPublicChat['name']);
+      assertStrict(isset($chatData->isPrivate), false);
+      assertStrict(isset($chatData->inviteLink), false);
+    });
+  
+    it("should return not authorized when trying to get chat by id without authorization", function () {
+      global $testsConfig, $messages, $MaxAndIlyaChat;
+    
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat['id']);
+      $json = json_decode($response['data']);
+    
+      assertStrict($response['info']['http_code'], 401);
+      assertStrict($json->data->error, $messages["not_authenticated"]);
+    });
+    
+    it("should return chat not found", function () {
+      global $testsConfig, $MaxDmitriev, $messages;
+      
+      $jwt = signJwtForUser($MaxDmitriev);
+      
+      $response = request("GET", $testsConfig["host"] . "/api/chats/random_id", ["headers" => ["Authorization: Bearer $jwt"]]);
+      $json = json_decode($response['data']);
+      
+      assertStrict($response['info']['http_code'], 404);
+      assertStrict($json->data->error, $messages["chat_not_found"]);
+    });
+  });
+  
+?>
+
\ No newline at end of file From 1cb725fd25898cf74097107863d534eeaa30f8e6 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 20:30:37 +0200 Subject: [PATCH 17/55] PC-1 Change Fk to Id in chat participants table and fixtures --- db/db.controller.php | 16 ++++++++-------- fixtures/chatParticipants.php | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/db/db.controller.php b/db/db.controller.php index 03ffb1d..85bf5fd 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -13,7 +13,7 @@ public function __construct($dbConfig) $this->connectToDb($dbConfig); // TODO: Don't run this if we are in production mode - $this->drop(['ChatParticipants', 'Users', 'Chats']); + $this->dropTables(['ChatParticipants', 'Users', 'Chats']); $this->initialize(); @@ -42,7 +42,7 @@ private function connectToDb(array $dbConfig) * Drop specified table in DB * @param {string[]} $tables Array of tables names */ - private function drop($tables) + private function dropTables($tables) { try { foreach ($tables as $table) { @@ -50,7 +50,7 @@ private function drop($tables) $this->conn->exec($sql); } } catch (Exception $ex) { - httpException("Failed to drop db", 500); + httpException("Failed to drop db tables", 500); } } @@ -80,15 +80,15 @@ private function initialize() ); CREATE TABLE IF NOT EXISTS ChatParticipants ( - chatFk BIGINT NOT NULL, - userFk BIGINT NOT NULL, + chatId BIGINT NOT NULL, + userId BIGINT NOT NULL, permission TINYINT NOT NULL DEFAULT 0, - FOREIGN KEY (chatFk) REFERENCES Chats (id), - FOREIGN KEY (userFk) REFERENCES Users (id) + FOREIGN KEY (chatId) REFERENCES Chats (id), + FOREIGN KEY (userId) REFERENCES Users (id) ); -- ALTER TABLE ChatParticipants DROP CONSTRAINT unique_chat_user; - ALTER TABLE ChatParticipants ADD UNIQUE unique_chat_user(chatFk, userFk); + ALTER TABLE ChatParticipants ADD UNIQUE unique_chat_user(chatId, userId); SQL; $this->conn->exec($users); } catch (Exception $ex) { diff --git a/fixtures/chatParticipants.php b/fixtures/chatParticipants.php index e89e7af..9dd9f50 100644 --- a/fixtures/chatParticipants.php +++ b/fixtures/chatParticipants.php @@ -6,14 +6,14 @@ global $MaxAndIlyaChat, $MaxDmitriev, $IlyaMehof; $MaxInChatWithIlya = [ - "chatFk" => $MaxAndIlyaChat["id"], - "userFk" => $MaxDmitriev["id"], + "chatId" => $MaxAndIlyaChat["id"], + "userId" => $MaxDmitriev["id"], "permission" => 2 ]; $IlyaInChatWithMax = [ - "chatFk" => $MaxAndIlyaChat["id"], - "userFk" => $IlyaMehof["id"], + "chatId" => $MaxAndIlyaChat["id"], + "userId" => $IlyaMehof["id"], "permission" => 2 ]; From cd632af709c2fa62283a444ceb844f0ef69cbb0a Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 20:32:05 +0200 Subject: [PATCH 18/55] PC-1 Check if user has permission to get chat info, require auth to get chat by id --- app/app.controller.php | 2 +- chats/chats.controller.php | 4 ++++ locale/en/messages.php | 1 + tests/chats-e2e.php | 5 +---- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/app.controller.php b/app/app.controller.php index 2dd3ac1..719a01a 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -77,7 +77,7 @@ private function router() # /chats/:chatId if (strpos($this->req['resource'], '/api/chats/') === 0) { $this->_req->useGuard($this->jwtAuthGuard); - $this->chatsController->getChatById($this->req); + $this->chatsController->getChatById($this->_req->getRequest()); return; } diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 34be2fa..6848395 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -37,6 +37,10 @@ function getChatById($req) if (is_null($chat)) { httpException($messages["chat_not_found"], 404)['end'](); } + + if ($chat["isPrivate"] && !$this->chatsService->isUserChatParticipant($req["user"]["id"], $chatId)) { + httpException($messages["no_access_to_the_chat"], 401)['end'](); + } $response = [ "chat" => $chat diff --git a/locale/en/messages.php b/locale/en/messages.php index ae65d13..3da66ab 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -15,4 +15,5 @@ "failed_to_sign_up" => "Failed to sign up", "not_authenticated" => "Not authenticated", + "no_access_to_the_chat" => "You do not have access to the chat" ]; \ No newline at end of file diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index d831ea2..ede4c3d 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -34,13 +34,10 @@ $jwt = signJwtForUser($MatveyGorelik); $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); - $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 401); - assertStrict($json->data->error, $messages["not_authenticated"]); - assertStrict(isset($chatData->isPrivate), false); - assertStrict(isset($chatData->inviteLink), false); + assertStrict($json->data->error, $messages["no_access_to_the_chat"]); }); it("should get public chat by id", function () { From 223d510ed3d3176acb5cabaaf7cf0aa2b81a7fd2 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 20:32:47 +0200 Subject: [PATCH 19/55] PC-1 Add chat RO to service --- chats/chats.controller.php | 2 +- chats/chats.service.php | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 6848395..bc7fddb 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -43,7 +43,7 @@ function getChatById($req) } $response = [ - "chat" => $chat + "chat" => $this->chatsService->createChatRO($chat) ]; jsonResponse($response)['end'](); diff --git a/chats/chats.service.php b/chats/chats.service.php index 1cead66..40e67b7 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -46,4 +46,25 @@ function findById($id) return null; } } + + function isUserChatParticipant($userId, $chatId): bool + { + $sql = "SELECT * FROM ChatParticipants WHERE userId=:userId AND chatId=:chatId"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":userId", $userId); + $stmt->bindValue(":chatId", $chatId); + $stmt->execute(); + + return $stmt->rowCount() > 0; + } + + function createChatRO($chat) + { + $chatRO = $chat; + + unset($chatRO["isPrivate"]); + unset($chatRO["inviteLink"]); + + return $chatRO; + } } From b3b0a67a80a63893b360a02d80c27d1a772539b1 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 21:05:35 +0200 Subject: [PATCH 20/55] PC-1 Add deleted chat --- fixtures/chats.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fixtures/chats.php b/fixtures/chats.php index 2874533..40ef63f 100644 --- a/fixtures/chats.php +++ b/fixtures/chats.php @@ -14,5 +14,13 @@ "inviteLink" => "oralcmsht" ]; - $chatsFixtures = [$MaxAndIlyaChat, $GymPartyPublicChat]; + $DeletedChatWithMaxAndMatvey = [ + "id" => 3, + "name" => "No one should see this chat", + "isPrivate" => 1, + "isDeleted" => 1, + "inviteLink" => "bebra_lovers" + ]; + + $chatsFixtures = [$MaxAndIlyaChat, $GymPartyPublicChat, $DeletedChatWithMaxAndMatvey]; \ No newline at end of file From 5d06e9c1860ba79ffb29eefb1ad5c290e97a0396 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 21:06:09 +0200 Subject: [PATCH 21/55] PC-1 Add tests for create and delete chats TDD --- locale/en/messages.php | 2 ++ tests/chats-e2e.php | 63 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/locale/en/messages.php b/locale/en/messages.php index 3da66ab..c9c7ab5 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -8,6 +8,8 @@ "user_created" => "User created", "chat_created" => "Chat created", + + "chat_deleted" => "Chat deleted", "failed_to_create_user" => "Failed to create user", "failed_to_create_chat" => "Failed to create chat", diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index ede4c3d..1cd285e 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -11,7 +11,7 @@ @include_once __DIR__ . "/../utils/jwt.php"; describe("[GET] /api/chats/:chatId", function () { - it("should get private chat by id for chat member", function () { + it("should get private chat by id for chat participant", function () { global $testsConfig, $MaxDmitriev, $MaxAndIlyaChat; $jwt = signJwtForUser($MaxDmitriev); @@ -28,7 +28,7 @@ assertStrict(isset($chatData->inviteLink), false); }); - it("should return error when trying to get private chat by id for NOT a chat member", function () { + it("should return error when trying to get private chat by id for NOT a chat participant", function () { global $testsConfig, $messages, $MatveyGorelik, $MaxAndIlyaChat; $jwt = signJwtForUser($MatveyGorelik); @@ -66,6 +66,18 @@ assertStrict($response['info']['http_code'], 401); assertStrict($json->data->error, $messages["not_authenticated"]); }); + + it("should return chat not found for deleted chat", function () { + global $testsConfig, $MaxDmitriev, $messages, $DeletedChatWithMaxAndMatvey; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("GET", $testsConfig["host"] . "/api/chats/" . $DeletedChatWithMaxAndMatvey, ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 404); + assertStrict($json->data->error, $messages["chat_not_found"]); + }); it("should return chat not found", function () { global $testsConfig, $MaxDmitriev, $messages; @@ -80,5 +92,52 @@ }); }); + describe("[POST] /api/chats", function () { + it("should create new chat", function () { + global $testsConfig, $MaxDmitriev; + + $jwt = signJwtForUser($MaxDmitriev); + + $body = [ + "name" => "My private chat", + "isPrivate" => true, + "inviteLink" => "random_invite_link" + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + $createdChat = $json->data->chat; + + assertStrict($response['info']['http_code'], 201); + assertStrict($createdChat["name"], $body["name"]); + }); + }); + + describe("[DELETE] /api/chats/:chatId", function () { + it("should delete chat by chat admin", function () { + global $testsConfig, $messages, $MaxDmitriev, $MaxAndIlyaChat; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats" . $MaxAndIlyaChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 200); + assertStrict($json->message, $messages["chat_deleted"]); + }); + + it("should return chat not found error", function () { + global $testsConfig, $messages, $MaxDmitriev; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats/random_chat_id", ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 404); + assertStrict($json->message, $messages["chat_not_found"]); + }); + }); + ?> \ No newline at end of file From 5e9fa1f4f9605371acb366a583bd9cda27d18e63 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Tue, 18 Jan 2022 21:08:57 +0200 Subject: [PATCH 22/55] PC-1 Add isDeleted field to Chat model, fix deleted chat test id --- db/db.controller.php | 1 + tests/chats-e2e.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/db/db.controller.php b/db/db.controller.php index 85bf5fd..9e71f97 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -76,6 +76,7 @@ private function initialize() image VARCHAR(128) NULL, name VARCHAR(30) NOT NULL, isPrivate BOOLEAN NOT NULL DEFAULT TRUE, + isDeleted BOOLEAN DEFAULT FALSE, inviteLink VARCHAR(30) NOT NULL ); diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 1cd285e..d1b5bde 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -72,7 +72,7 @@ $jwt = signJwtForUser($MaxDmitriev); - $response = request("GET", $testsConfig["host"] . "/api/chats/" . $DeletedChatWithMaxAndMatvey, ["headers" => ["Authorization: Bearer $jwt"]]); + $response = request("GET", $testsConfig["host"] . "/api/chats/" . $DeletedChatWithMaxAndMatvey["id"], ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 404); From 13804fd8f548601f8a79746870dbab1baa94fd76 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Wed, 19 Jan 2022 15:48:06 +0200 Subject: [PATCH 23/55] PC-1 Init regex for /users/:userId/chats --- app/app.controller.php | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/app.controller.php b/app/app.controller.php index 719a01a..8529009 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -58,24 +58,37 @@ private function setHeaders() private function router() { - switch ($this->req['method']) { + switch ($this->req['method']) + { case 'GET': - if ($this->req['resource'] === '/api/users/me') { + var_dump(preg_match("\/api\/users\/([a-zA-Z0-9].+)\/chats", $this->req['resource'])); + + if ($this->req['resource'] === '/api/users/me') + { $this->_req->useGuard($this->jwtAuthGuard); $this->usersController->getMe($this->_req->getRequest()); return; } + # /users/:userId/chats + if (preg_match("\/api\/users\/([a-zA-Z0-9].+)\/chats", $this->req['resource'])) + { + $this->usersController->getUserChats($this->req); + return; + } # /users/:userId - if (strpos($this->req['resource'], '/api/users/') === 0) { + if (strpos($this->req['resource'], '/api/users/') === 0) + { $this->usersController->getUserById($this->req); return; } - if ($this->req['resource'] === '/api/users') { + if ($this->req['resource'] === '/api/users') + { $this->usersController->getUsers(); return; } # /chats/:chatId - if (strpos($this->req['resource'], '/api/chats/') === 0) { + if (strpos($this->req['resource'], '/api/chats/') === 0) + { $this->_req->useGuard($this->jwtAuthGuard); $this->chatsController->getChatById($this->_req->getRequest()); return; @@ -89,15 +102,18 @@ private function router() case 'POST': $reqBody = $this->_req->parseBody(); - if ($this->req['resource'] === '/api/users') { + if ($this->req['resource'] === '/api/users') + { $this->usersController->createUser($reqBody["data"]); return; } - if ($this->req['resource'] === '/api/auth/sign-up') { + if ($this->req['resource'] === '/api/auth/sign-up') + { $this->authController->signUp($reqBody["data"]); return; } - if ($this->req['resource'] === '/api/auth/sign-in') { + if ($this->req['resource'] === '/api/auth/sign-in') + { $this->authController->signIn($reqBody["data"]); return; } From 545768cb691914277263ee4f024efee81809231c Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Wed, 19 Jan 2022 18:58:42 +0200 Subject: [PATCH 24/55] PC-1 Rename get current user chats route, init controller method for this route --- app/app.controller.php | 9 ++++----- users/users.controller.php | 5 +++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/app.controller.php b/app/app.controller.php index 8529009..e11129c 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -61,18 +61,17 @@ private function router() switch ($this->req['method']) { case 'GET': - var_dump(preg_match("\/api\/users\/([a-zA-Z0-9].+)\/chats", $this->req['resource'])); - if ($this->req['resource'] === '/api/users/me') { $this->_req->useGuard($this->jwtAuthGuard); $this->usersController->getMe($this->_req->getRequest()); return; } - # /users/:userId/chats - if (preg_match("\/api\/users\/([a-zA-Z0-9].+)\/chats", $this->req['resource'])) + # /users/me/chats + if ($this->req['resource'] === '/api/users/me/chats') { - $this->usersController->getUserChats($this->req); + $this->_req->useGuard($this->jwtAuthGuard); + $this->usersController->getUserChats($this->_req->getRequest()); return; } # /users/:userId diff --git a/users/users.controller.php b/users/users.controller.php index c8576bb..57545a6 100644 --- a/users/users.controller.php +++ b/users/users.controller.php @@ -69,4 +69,9 @@ function getMe($req) jsonResponse($response); } + + function getUserChats($req) + { + var_dump($req["user"]); + } } From 3890dd016b8f25ab788c92a2a3ec90ff8249e181 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Wed, 19 Jan 2022 18:59:01 +0200 Subject: [PATCH 25/55] PC-1 Add test for current user chats TDD --- tests/users-e2e.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/users-e2e.php b/tests/users-e2e.php index 86432dd..03d74fc 100644 --- a/tests/users-e2e.php +++ b/tests/users-e2e.php @@ -83,6 +83,20 @@ assertStrict($json->data->error, $messages["not_authenticated"]); }); }); + + describe("[GET] /api/users/me/chats", function () { + it("should get current user chats", function () { + global $testsConfig, $MaxDmitriev; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("GET", $testsConfig["host"] . "/api/users/me/chats", ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + $chats = $json->data->chats; + + assertStrict($response['info']['http_code'], 200); + }); + }); ?> From b5e973d9f875c319772465b0773ac36e7a82d1b5 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Wed, 19 Jan 2022 18:59:19 +0200 Subject: [PATCH 26/55] PC-1 Add chat participants to Max and Matvey chat --- fixtures/chatParticipants.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/fixtures/chatParticipants.php b/fixtures/chatParticipants.php index 9dd9f50..00bd4d5 100644 --- a/fixtures/chatParticipants.php +++ b/fixtures/chatParticipants.php @@ -3,7 +3,7 @@ @include_once __DIR__ . "/chats.php"; @include_once __DIR__ . "/users.php"; - global $MaxAndIlyaChat, $MaxDmitriev, $IlyaMehof; + global $MaxAndIlyaChat, $MaxDmitriev, $IlyaMehof, $DeletedChatWithMaxAndMatvey, $MatveyGorelik; $MaxInChatWithIlya = [ "chatId" => $MaxAndIlyaChat["id"], @@ -17,4 +17,16 @@ "permission" => 2 ]; - $chatParticipantsFixtures = [$MaxInChatWithIlya, $IlyaInChatWithMax]; \ No newline at end of file + $MaxInDeletedChatWithMatvey = [ + "chatId" => $DeletedChatWithMaxAndMatvey["id"], + "userId" => $MaxDmitriev["id"], + "permission" => 2 + ]; + + $MatveyInDeletedChatWithMax = [ + "chatId" => $DeletedChatWithMaxAndMatvey["id"], + "userId" => $MatveyGorelik["id"], + "permission" => 2 + ]; + + $chatParticipantsFixtures = [$MaxInChatWithIlya, $IlyaInChatWithMax, $MaxInDeletedChatWithMatvey, $MatveyInDeletedChatWithMax]; \ No newline at end of file From 560c4f78ee2b828e0213800850e746063568e01b Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Wed, 19 Jan 2022 18:59:55 +0200 Subject: [PATCH 27/55] PC-1 Ignore deleted chats when getting chat by id --- chats/chats.service.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chats/chats.service.php b/chats/chats.service.php index 40e67b7..708669d 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -35,7 +35,7 @@ function createChat($id, $name, $isPrivate, $inviteLink) function findById($id) { - $sql = "SELECT * FROM Chats WHERE id=:id"; + $sql = "SELECT * FROM Chats WHERE id=:id AND isDeleted=FALSE"; $stmt = $this->conn->prepare($sql); $stmt->bindValue(":id", $id); $stmt->execute(); From fd1cfbf1fd06d217796415e04f54c5abe0003b34 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Wed, 19 Jan 2022 19:00:20 +0200 Subject: [PATCH 28/55] PC-1 Add test for permission to delete chat TDD --- locale/en/messages.php | 3 ++- tests/chats-e2e.php | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/locale/en/messages.php b/locale/en/messages.php index c9c7ab5..e935b3c 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -17,5 +17,6 @@ "failed_to_sign_up" => "Failed to sign up", "not_authenticated" => "Not authenticated", - "no_access_to_the_chat" => "You do not have access to the chat" + "no_access_to_the_chat" => "You do not have access to the chat", + "not_enough_permission" => "You do not have enough permission to do this" ]; \ No newline at end of file diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index d1b5bde..2e0b94b 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -126,6 +126,18 @@ assertStrict($json->message, $messages["chat_deleted"]); }); + it("should return not enough permission when trying to delete chat by a member", function () { + global $testsConfig, $messages, $MatveyGorelik, $GymPartyPublicChat; + + $jwt = signJwtForUser($MatveyGorelik); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats" . $GymPartyPublicChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 403); + assertStrict($json->message, $messages["not_enough_permission"]); + }); + it("should return chat not found error", function () { global $testsConfig, $messages, $MaxDmitriev; From 9be131b322ed6be33bf4da2ef2a21fc061edfada Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 21 Jan 2022 16:13:45 +0200 Subject: [PATCH 29/55] PC-1 Get user chats --- chats/chats.service.php | 16 +++++++++++++++- tests/users-e2e.php | 3 ++- users/users.controller.php | 9 ++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/chats/chats.service.php b/chats/chats.service.php index 708669d..24d3a3f 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -16,7 +16,7 @@ function getChats(): array $stmt->execute(); if ($stmt->rowCount() > 0) { - return $stmt->fetch(PDO::FETCH_ASSOC); + return $stmt->fetchAll(PDO::FETCH_ASSOC); } else { return []; } @@ -67,4 +67,18 @@ function createChatRO($chat) return $chatRO; } + + function getUserChats($userId): array + { + $sql = "SELECT C.id AS id, C.image AS image, C.name AS name FROM Chats AS C, ChatParticipants AS CP WHERE CP.userId=:userId AND CP.chatId=C.id AND C.isDeleted=FALSE"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":userId", $userId); + $stmt->execute(); + + if ($stmt->rowCount() > 0) { + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } else { + return []; + } + } } diff --git a/tests/users-e2e.php b/tests/users-e2e.php index 03d74fc..5ca734d 100644 --- a/tests/users-e2e.php +++ b/tests/users-e2e.php @@ -93,8 +93,9 @@ $response = request("GET", $testsConfig["host"] . "/api/users/me/chats", ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); $chats = $json->data->chats; - + assertStrict($response['info']['http_code'], 200); + assertStrict(count($chats), 1); }); }); diff --git a/users/users.controller.php b/users/users.controller.php index 57545a6..303fa64 100644 --- a/users/users.controller.php +++ b/users/users.controller.php @@ -3,15 +3,18 @@ @include_once __DIR__ . "/../utils/httpException.php"; @include_once __DIR__ . "/../utils/jsonResponse.php"; @include_once __DIR__ . "/users.service.php"; + @include_once __DIR__ . "../chats/chats.service.php"; @include_once __DIR__ . "/../locale/en/messages.php"; class UsersController { private $usersService; + private $chatsService; function __construct($conn) { $this->usersService = new UsersService($conn); + $this->chatsService = new ChatsService($conn); } function getUsers() @@ -72,6 +75,10 @@ function getMe($req) function getUserChats($req) { - var_dump($req["user"]); + $chats = $this->chatsService->getUserChats($req["user"]["id"]); + + $response = ["chats" => $chats]; + + jsonResponse($response); } } From 35378b91699c2e21bc569436af931aeeecd252de Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 21 Jan 2022 18:10:53 +0200 Subject: [PATCH 30/55] PC-1 Add create chat route --- app/app.controller.php | 4 ++-- chats/chats.controller.php | 29 ++++++++++++++++++----------- tests/chats-e2e.php | 4 ++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/app.controller.php b/app/app.controller.php index e11129c..dfa8c85 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -101,9 +101,9 @@ private function router() case 'POST': $reqBody = $this->_req->parseBody(); - if ($this->req['resource'] === '/api/users') + if ($this->req['resource'] === '/api/chats') { - $this->usersController->createUser($reqBody["data"]); + $this->chatsController->createChat($reqBody["data"]); return; } if ($this->req['resource'] === '/api/auth/sign-up') diff --git a/chats/chats.controller.php b/chats/chats.controller.php index bc7fddb..6bc0c47 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -52,18 +52,25 @@ function getChatById($req) function createChat($chatDto) { global $messages; - - $result = $this->chatsService->createChat($chatDto); - - if (!$result) { + + try { + $chat = $chatDto; + + $chat["id"] = '4'; + $chat["inviteLink"] = 'random_link'; + + $this->chatsService->createChat($chat["id"], $chatDto["name"], $chatDto["isPrivate"], $chat["inviteLink"]); + + $response = [ + "message" => $messages["chat_created"], + "chat" => $chat + ]; + + jsonResponse($response)['end'](); + } + catch (PDOException $ex) + { httpException($messages["failed_to_create_chat"])['end'](); } - - $response = [ - "message" => $messages["chat_created"], - "chat" => $result - ]; - - jsonResponse($response)['end'](); } } diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 2e0b94b..6492712 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -100,11 +100,11 @@ $body = [ "name" => "My private chat", - "isPrivate" => true, - "inviteLink" => "random_invite_link" + "isPrivate" => true ]; $response = request("POST", $testsConfig["host"] . "/api/chats", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + var_dump($response); $json = json_decode($response['data']); $createdChat = $json->data->chat; From 671768663be3257009d2b9f3949ef54dc7d869cc Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 21 Jan 2022 18:11:29 +0200 Subject: [PATCH 31/55] PC-1 Fix setting headers for request with json data and authorization --- tests/lib/index.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/lib/index.php b/tests/lib/index.php index e091aec..fad3cc1 100644 --- a/tests/lib/index.php +++ b/tests/lib/index.php @@ -80,21 +80,24 @@ function request($method, $url, array $options = null): array curl_setopt($req, CURLOPT_RETURNTRANSFER, true); curl_setopt($req, CURLOPT_FRESH_CONNECT, true); + $headers = []; + if ($options["json"]) { $body = json_encode($options["json"], JSON_UNESCAPED_UNICODE); curl_setopt($req, CURLOPT_POSTFIELDS, $body); - curl_setopt($req, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'Content-Length: ' . strlen($body) - ] - ); + $headers[] = 'Content-Type: application/json'; + $headers[] = 'Content-Length: ' . strlen($body); } if ($options["headers"]) { - curl_setopt($req, CURLOPT_HTTPHEADER, $options["headers"]); + foreach ($options["headers"] as $header) { + $headers[] = $header; + } } - + + curl_setopt($req, CURLOPT_HTTPHEADER, $headers); + $response = curl_exec($req); $responseInfo = curl_getinfo($req); From b84af292a92e09aa68aaefafa2eb7b1ff318254a Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 21 Jan 2022 18:31:08 +0200 Subject: [PATCH 32/55] PC-1 Generate and use random id's --- auth/auth.controller.php | 6 ++---- auth/auth.service.php | 11 ----------- chats/chats.controller.php | 20 +++++--------------- db/db.controller.php | 10 +++++----- fixtures/chats.php | 12 ++++++------ fixtures/users.php | 6 +++--- tests/chats-e2e.php | 7 +++---- tests/users-e2e.php | 16 ++-------------- users/users.controller.php | 20 +------------------- utils/randomId.php | 7 +++++++ 10 files changed, 34 insertions(+), 81 deletions(-) delete mode 100644 auth/auth.service.php create mode 100644 utils/randomId.php diff --git a/auth/auth.controller.php b/auth/auth.controller.php index 76835d8..e0cd89c 100644 --- a/auth/auth.controller.php +++ b/auth/auth.controller.php @@ -2,18 +2,16 @@ @include_once __DIR__ . "/../utils/httpException.php"; @include_once __DIR__ . "/../utils/jsonResponse.php"; - @include_once __DIR__ . "/auth.service.php"; @include_once __DIR__ . "/../utils/jwt.php"; @include_once __DIR__ . "/../locale/en/messages.php"; + @include_once __DIR__ . "/../utils/randomId.php"; class AuthController { - private $authService; private $usersService; function __construct($conn) { - $this->authService = new AuthService($conn); $this->usersService = new UsersService($conn); } @@ -30,7 +28,7 @@ function signUp($registerUserDto): array httpException($messages["username_taken"])['end'](); } - $id = random_int(0, 9999999); + $id = randomId(); try { $this->usersService->createUser($id, $registerUserDto["username"], $registerUserDto["password"]); diff --git a/auth/auth.service.php b/auth/auth.service.php deleted file mode 100644 index 1b1ee70..0000000 --- a/auth/auth.service.php +++ /dev/null @@ -1,11 +0,0 @@ -conn = $conn; - } - } diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 6bc0c47..36d5b2d 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -4,6 +4,7 @@ @include_once __DIR__ . "/../utils/jsonResponse.php"; @include_once __DIR__ . "/chats.service.php"; @include_once __DIR__ . "/../locale/en/messages.php"; + @include_once __DIR__ . "/../utils/randomId.php"; class ChatsController { @@ -14,23 +15,12 @@ function __construct($conn) $this->chatsService = new ChatsService($conn); } - function getChats() - { - $chats = $this->chatsService->getChats(); - - $response = [ - "chats" => $chats - ]; - - jsonResponse($response)['end'](); - } - function getChatById($req) { global $messages; # Parse chat id from url - $chatId = intval(substr($req['resource'], strlen('/api/chats/'))); + $chatId = substr($req['resource'], strlen('/api/chats/')); $chat = $this->chatsService->findById($chatId); @@ -56,8 +46,8 @@ function createChat($chatDto) try { $chat = $chatDto; - $chat["id"] = '4'; - $chat["inviteLink"] = 'random_link'; + $chat["id"] = randomId(); + $chat["inviteLink"] = randomId(); $this->chatsService->createChat($chat["id"], $chatDto["name"], $chatDto["isPrivate"], $chat["inviteLink"]); @@ -66,7 +56,7 @@ function createChat($chatDto) "chat" => $chat ]; - jsonResponse($response)['end'](); + jsonResponse($response, 201)['end'](); } catch (PDOException $ex) { diff --git a/db/db.controller.php b/db/db.controller.php index 9e71f97..29e33d8 100644 --- a/db/db.controller.php +++ b/db/db.controller.php @@ -63,7 +63,7 @@ private function initialize() # Create Users table $users = << 1, + "id" => "chat000000000001", "name" => "First private chat", "isPrivate" => 1, - "inviteLink" => "invite_link" + "inviteLink" => "invitelink000001" ]; $GymPartyPublicChat = [ - "id" => 2, + "id" => "chat000000000002", "name" => "Welcome to the club buddy", "isPrivate" => 0, - "inviteLink" => "oralcmsht" + "inviteLink" => "invitelink000002" ]; $DeletedChatWithMaxAndMatvey = [ - "id" => 3, + "id" => "chat000000000003", "name" => "No one should see this chat", "isPrivate" => 1, "isDeleted" => 1, - "inviteLink" => "bebra_lovers" + "inviteLink" => "invitelink000003" ]; $chatsFixtures = [$MaxAndIlyaChat, $GymPartyPublicChat, $DeletedChatWithMaxAndMatvey]; diff --git a/fixtures/users.php b/fixtures/users.php index eff879f..d0dfddf 100644 --- a/fixtures/users.php +++ b/fixtures/users.php @@ -3,19 +3,19 @@ $plainPassword = "qwerty"; $MaxDmitriev = [ - "id" => 1, + "id" => "user000000000001", "username" => "dmitriev", "password" => $plainPassword ]; $IlyaMehof = [ - "id" => 2, + "id" => "user000000000002", "username" => "mehoff", "password" => $plainPassword ]; $MatveyGorelik = [ - "id" => 3, + "id" => "user000000000003", "username" => "offiza", "password" => $plainPassword ]; diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 6492712..b2d6735 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -22,7 +22,7 @@ $chatData = $json->data->chat; assertStrict($response['info']['http_code'], 200); - assertStrict(intval($chatData->id), $MaxAndIlyaChat['id']); + assertStrict($chatData->id, $MaxAndIlyaChat['id']); assertStrict($chatData->name, $MaxAndIlyaChat['name']); assertStrict(isset($chatData->isPrivate), false); assertStrict(isset($chatData->inviteLink), false); @@ -51,7 +51,7 @@ $chatData = $json->data->chat; assertStrict($response['info']['http_code'], 200); - assertStrict(intval($chatData->id), $GymPartyPublicChat['id']); + assertStrict($chatData->id, $GymPartyPublicChat['id']); assertStrict($chatData->name, $GymPartyPublicChat['name']); assertStrict(isset($chatData->isPrivate), false); assertStrict(isset($chatData->inviteLink), false); @@ -104,12 +104,11 @@ ]; $response = request("POST", $testsConfig["host"] . "/api/chats", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); - var_dump($response); $json = json_decode($response['data']); $createdChat = $json->data->chat; assertStrict($response['info']['http_code'], 201); - assertStrict($createdChat["name"], $body["name"]); + assertStrict($createdChat->name, $body["name"]); }); }); diff --git a/tests/users-e2e.php b/tests/users-e2e.php index 5ca734d..34e135c 100644 --- a/tests/users-e2e.php +++ b/tests/users-e2e.php @@ -9,18 +9,6 @@ @include_once __DIR__ . "/../vendor/autoload.php"; @include_once __DIR__ . "/../utils/jwt.php"; - describe("[GET] /api/users", function () { - it("should get users list", function () { - global $testsConfig, $usersFixtures; - - $response = request("GET", $testsConfig["host"] . "/api/users"); - $json = json_decode($response['data']); - - assertStrict($response['info']['http_code'], 200); - assertStrict(count($json->data->users), count($usersFixtures)); - }); - }); - describe("[GET] /api/users/:userId", function () { it("should get user by id", function () { global $testsConfig, $MaxDmitriev; @@ -31,7 +19,7 @@ $userData = $json->data->user; assertStrict($response['info']['http_code'], 200); - assertStrict(intval($userData->id), $MaxDmitriev["id"]); + assertStrict($userData->id, $MaxDmitriev["id"]); assertStrict($userData->username, $MaxDmitriev["username"]); assertStrict(isset($userData->password), false); }); @@ -58,7 +46,7 @@ $userData = $json->data->user; assertStrict($response['info']['http_code'], 200); - assertStrict(intval($userData->id), $MaxDmitriev["id"]); + assertStrict($userData->id, $MaxDmitriev["id"]); assertStrict($userData->username, $MaxDmitriev["username"]); assertStrict(isset($userData->password), false); }); diff --git a/users/users.controller.php b/users/users.controller.php index 303fa64..378836a 100644 --- a/users/users.controller.php +++ b/users/users.controller.php @@ -33,7 +33,7 @@ function getUserById($req) global $messages; # Parse user id from url - $userId = intval(substr($req['resource'], strlen('/api/users/'))); + $userId = substr($req['resource'], strlen('/api/users/')); $user = $this->usersService->getUserById($userId); @@ -48,24 +48,6 @@ function getUserById($req) jsonResponse($response)['end'](); } - function createUser($userDto) - { - global $messages; - - $result = $this->usersService->createUser($userDto); - - if (!$result) { - httpException($messages["failed_to_create_user"])['end'](); - } - - $response = [ - "message" => $messages["user_created"], - "user" => $result - ]; - - jsonResponse($response)['end'](); - } - function getMe($req) { $response = ["user" => $this->usersService->createUserRO($req["user"])]; diff --git a/utils/randomId.php b/utils/randomId.php new file mode 100644 index 0000000..1415bca --- /dev/null +++ b/utils/randomId.php @@ -0,0 +1,7 @@ + Date: Fri, 21 Jan 2022 19:12:54 +0200 Subject: [PATCH 33/55] PC-1 Require authorization to create chat --- app/app.controller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/app.controller.php b/app/app.controller.php index dfa8c85..46e2ff1 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -103,7 +103,8 @@ private function router() if ($this->req['resource'] === '/api/chats') { - $this->chatsController->createChat($reqBody["data"]); + $this->_req->useGuard($this->jwtAuthGuard); + $this->chatsController->createChat($this->_req->getRequest(), $reqBody["data"]); return; } if ($this->req['resource'] === '/api/auth/sign-up') From 17659752d2600ffaa5d7488ba7701dbfd2b2c3df Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Fri, 21 Jan 2022 19:13:49 +0200 Subject: [PATCH 34/55] PC-1 Add user who created chat to chat participants --- chats/chats.controller.php | 4 +++- chats/chats.service.php | 10 ++++++++++ tests/chats-e2e.php | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 36d5b2d..e7e9ff4 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -39,7 +39,7 @@ function getChatById($req) jsonResponse($response)['end'](); } - function createChat($chatDto) + function createChat($req, $chatDto) { global $messages; @@ -51,6 +51,8 @@ function createChat($chatDto) $this->chatsService->createChat($chat["id"], $chatDto["name"], $chatDto["isPrivate"], $chat["inviteLink"]); + $this->chatsService->addParticipantToChat($req["user"]["id"], $chat["id"], 2); + $response = [ "message" => $messages["chat_created"], "chat" => $chat diff --git a/chats/chats.service.php b/chats/chats.service.php index 24d3a3f..9d94e4a 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -81,4 +81,14 @@ function getUserChats($userId): array return []; } } + + function addParticipantToChat($userId, $chatId, $permission = 0) + { + $sql = "INSERT INTO ChatParticipants (userId, chatId, permission) VALUES (:userId, :chatId, :permission)"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":userId", $userId); + $stmt->bindValue(":chatId", $chatId); + $stmt->bindValue(":permission", $permission); + $stmt->execute(); + } } diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index b2d6735..3884c9a 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -93,7 +93,7 @@ }); describe("[POST] /api/chats", function () { - it("should create new chat", function () { + it("should be able to create new chat", function () { global $testsConfig, $MaxDmitriev; $jwt = signJwtForUser($MaxDmitriev); From 4b525ee646c9b7bda35bd72ad6cd1fb1ed9bd69b Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 00:54:20 +0200 Subject: [PATCH 35/55] PC-1 Add delete chat route, controller and service --- app/app.controller.php | 12 ++++++++++++ chats/chats.controller.php | 21 +++++++++++++++++++++ chats/chats.service.php | 8 ++++++++ locale/en/messages.php | 1 + 4 files changed, 42 insertions(+) diff --git a/app/app.controller.php b/app/app.controller.php index 46e2ff1..9d3aaf4 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -121,6 +121,18 @@ private function router() httpException("Route not found", 404)['end'](); break; + + case 'DELETE': + $reqBody = $this->_req->parseBody(); + + if (strpos($this->req['resource'], '/api/chats/') === 0) + { + $this->_req->useGuard($this->jwtAuthGuard); + $this->chatsController->deleteChat($this->_req->getRequest(), $reqBody["data"]); + return; + } + + break; default: httpException("Method not supported", 404)['end'](); diff --git a/chats/chats.controller.php b/chats/chats.controller.php index e7e9ff4..87f6382 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -65,4 +65,25 @@ function createChat($req, $chatDto) httpException($messages["failed_to_create_chat"])['end'](); } } + + function deleteChat($req) + { + global $messages; + + try { + $chatId = substr($req['resource'], strlen('/api/chats/')); + + $this->chatsService->deleteChatById($chatId); + + $response = [ + "message" => $messages["chat_deleted"] + ]; + + jsonResponse($response)['end'](); + } + catch (PDOException $ex) + { + httpException($messages["failed_to_delete_chat"])['end'](); + } + } } diff --git a/chats/chats.service.php b/chats/chats.service.php index 9d94e4a..4e216c3 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -91,4 +91,12 @@ function addParticipantToChat($userId, $chatId, $permission = 0) $stmt->bindValue(":permission", $permission); $stmt->execute(); } + + function deleteChatById($chatId) + { + $sql = "UPDATE Chats SET isDeleted=TRUE WHERE id=:chatId"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":chatId", $chatId); + $stmt->execute(); + } } diff --git a/locale/en/messages.php b/locale/en/messages.php index e935b3c..44057e6 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -13,6 +13,7 @@ "failed_to_create_user" => "Failed to create user", "failed_to_create_chat" => "Failed to create chat", + "failed_to_delete_chat" => "Failed to delete chat", "failed_to_sign_in" => "Failed to sign in", "failed_to_sign_up" => "Failed to sign up", From dd899a80bf8735dc819cd0fa9207b46629491d18 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 00:54:30 +0200 Subject: [PATCH 36/55] PC-1 Fix delete chat tests --- tests/chats-e2e.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 3884c9a..7c79e37 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -118,11 +118,11 @@ $jwt = signJwtForUser($MaxDmitriev); - $response = request("DELETE", $testsConfig["host"] . "/api/chats" . $MaxAndIlyaChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 200); - assertStrict($json->message, $messages["chat_deleted"]); + assertStrict($json->data->message, $messages["chat_deleted"]); }); it("should return not enough permission when trying to delete chat by a member", function () { @@ -130,11 +130,11 @@ $jwt = signJwtForUser($MatveyGorelik); - $response = request("DELETE", $testsConfig["host"] . "/api/chats" . $GymPartyPublicChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 403); - assertStrict($json->message, $messages["not_enough_permission"]); + assertStrict($json->data->message, $messages["not_enough_permission"]); }); it("should return chat not found error", function () { @@ -146,7 +146,7 @@ $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 404); - assertStrict($json->message, $messages["chat_not_found"]); + assertStrict($json->data->message, $messages["chat_not_found"]); }); }); From a8917c549fbc9eb81e7397c05a5e6123aab4e492 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 00:54:39 +0200 Subject: [PATCH 37/55] PC-1 Improve request --- utils/request.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/utils/request.php b/utils/request.php index 3f4f87f..cd5ee2f 100644 --- a/utils/request.php +++ b/utils/request.php @@ -51,17 +51,19 @@ public function parseBody(): array # Parsed json from body $data = []; - if ($this->contentType == 'application/json') { + if ($this->contentType == 'application/json') + { $body = file_get_contents("php://input"); $data = json_decode($body, true); - if (json_last_error() !== JSON_ERROR_NONE) { + if (json_last_error() !== JSON_ERROR_NONE) + { httpException("Error parsing json")['end'](); } - } else if ($this->contentType == 'application/x-www-form-urlencoded') { - httpException("Form content type not supported yet")['end'](); - } else { - httpException("Unsupported Content-Type $this->contentType")['end'](); + } + else if ($this->contentType == 'application/x-www-form-urlencoded') + { + httpException("Form content type not supported")['end'](); } return ["body" => $body, "data" => $data]; From 65f6e294bb7069e781490112d8c536441f126898 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 01:08:45 +0200 Subject: [PATCH 38/55] PC-1 Add getParticipantByUserId method to chats service --- chats/chats.service.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/chats/chats.service.php b/chats/chats.service.php index 4e216c3..249ccfb 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -58,6 +58,21 @@ function isUserChatParticipant($userId, $chatId): bool return $stmt->rowCount() > 0; } + function getChatParticipantByUserId($userId, $chatId) + { + $sql = "SELECT * FROM ChatParticipants WHERE userId=:userId AND chatId=:chatId"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":userId", $userId); + $stmt->bindValue(":chatId", $chatId); + $stmt->execute(); + + if ($stmt->rowCount() > 0) { + return $stmt->fetch(PDO::FETCH_ASSOC); + } else { + return null; + } + } + function createChatRO($chat) { $chatRO = $chat; From 88f6cae4fd164f248fb3780d1d32e0ddfa1b309b Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 01:09:05 +0200 Subject: [PATCH 39/55] PC-1 Check if user has enough permission to delete chat, and other checks --- app/app.controller.php | 2 +- chats/chats.controller.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/app.controller.php b/app/app.controller.php index 9d3aaf4..0d46a8c 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -128,7 +128,7 @@ private function router() if (strpos($this->req['resource'], '/api/chats/') === 0) { $this->_req->useGuard($this->jwtAuthGuard); - $this->chatsController->deleteChat($this->_req->getRequest(), $reqBody["data"]); + $this->chatsController->deleteChat($this->_req->getRequest()); return; } diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 87f6382..2057fb6 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -72,6 +72,19 @@ function deleteChat($req) try { $chatId = substr($req['resource'], strlen('/api/chats/')); + + $chat = $this->chatsService->findById($chatId); + + if (is_null($chat)) { + httpException($messages["chat_not_found"], 404)['end'](); + } + + $chatParticipant = $this->chatsService->getChatParticipantByUserId($req["user"]["id"], $chatId); + + if (is_null($chatParticipant) || intval($chatParticipant["permission"]) !== 2) + { + httpException($messages["not_enough_permission"], 403)['end'](); + } $this->chatsService->deleteChatById($chatId); From 2ae808bcb907b40b50ae6d803d003a0913a73a7a Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 01:09:25 +0200 Subject: [PATCH 40/55] PC-1 Fix delete chat endpoints tests --- tests/chats-e2e.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 7c79e37..08a91f7 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -134,7 +134,7 @@ $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 403); - assertStrict($json->data->message, $messages["not_enough_permission"]); + assertStrict($json->data->error, $messages["not_enough_permission"]); }); it("should return chat not found error", function () { @@ -146,7 +146,7 @@ $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 404); - assertStrict($json->data->message, $messages["chat_not_found"]); + assertStrict($json->data->error, $messages["chat_not_found"]); }); }); From 1b17f92b850e4c4de54ba4cf6ce5808a8318bf70 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 01:19:43 +0200 Subject: [PATCH 41/55] PC-1 Add tests for delete chat participant TDD --- locale/en/messages.php | 2 ++ tests/chats-e2e.php | 54 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/locale/en/messages.php b/locale/en/messages.php index 44057e6..6a77fdd 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -5,11 +5,13 @@ "user_not_found" => "User not found", "chat_not_found" => "Chat not found", + "participant_not_found" => "Participant not found", "user_created" => "User created", "chat_created" => "Chat created", "chat_deleted" => "Chat deleted", + "participant_deleted" => "Participant deleted", "failed_to_create_user" => "Failed to create user", "failed_to_create_chat" => "Failed to create chat", diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 08a91f7..2cc6f4c 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -79,7 +79,7 @@ assertStrict($json->data->error, $messages["chat_not_found"]); }); - it("should return chat not found", function () { + it("should return chat not found for random chat id", function () { global $testsConfig, $MaxDmitriev, $messages; $jwt = signJwtForUser($MaxDmitriev); @@ -137,7 +137,7 @@ assertStrict($json->data->error, $messages["not_enough_permission"]); }); - it("should return chat not found error", function () { + it("should return chat not found error for random chat id", function () { global $testsConfig, $messages, $MaxDmitriev; $jwt = signJwtForUser($MaxDmitriev); @@ -150,5 +150,55 @@ }); }); + describe("[DELETE] /api/chats/:chatId/users/:userId", function () { + it("should delete chat participant by chat admin", function () { + global $testsConfig, $messages, $MaxDmitriev, $MaxAndIlyaChat, $IlyaMehof; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"] . "/users/" . $IlyaMehof["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 200); + assertStrict($json->data->message, $messages["participant_deleted"]); + }); + + it("should return not enough permission when trying to delete chat by a member", function () { + global $testsConfig, $messages, $MatveyGorelik, $GymPartyPublicChat, $IlyaMehof; + + $jwt = signJwtForUser($MatveyGorelik); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users/" . $IlyaMehof["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 403); + assertStrict($json->data->error, $messages["not_enough_permission"]); + }); + + it("should return chat not found error for random chat id", function () { + global $testsConfig, $messages, $MaxDmitriev; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats/random_chat_id/users/" . $MaxDmitriev["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 404); + assertStrict($json->data->error, $messages["chat_not_found"]); + }); + + it("should return participant not found error for random user id", function () { + global $testsConfig, $messages, $GymPartyPublicChat, $MaxDmitriev; + + $jwt = signJwtForUser($MaxDmitriev); + + $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users/random_id", ["headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 404); + assertStrict($json->data->error, $messages["participant_not_found"]); + }); + }); + ?> \ No newline at end of file From 09cb36b4c0656f221ea759b6a45d6d80a62f98d0 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 01:32:21 +0200 Subject: [PATCH 42/55] PC-1 Add tests for get chat participants TDD --- tests/chats-e2e.php | 84 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 2cc6f4c..0272cab 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -1,4 +1,4 @@ -

Users e2e

+

Chats e2e

  ["Authorization: Bearer $jwt"]]);
+      
+      $json = json_decode($response['data']);
+      $chatData = $json->data->chat;
+      
+      assertStrict($response['info']['http_code'], 200);
+      assertStrict($chatData->id, $MaxAndIlyaChat['id']);
+      assertStrict($chatData->name, $MaxAndIlyaChat['name']);
+      assertStrict(isset($chatData->isPrivate), false);
+      assertStrict(isset($chatData->inviteLink), false);
+    });
+    
+    it("should return error when trying to get private chat by id for NOT a chat participant", function () {
+      global $testsConfig, $messages, $MatveyGorelik, $MaxAndIlyaChat;
+      
+      $jwt = signJwtForUser($MatveyGorelik);
+      
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]);
+      $json = json_decode($response['data']);
+      
+      assertStrict($response['info']['http_code'], 401);
+      assertStrict($json->data->error, $messages["no_access_to_the_chat"]);
+    });
+    
+    it("should get public chat participants by id", function () {
+      global $testsConfig, $MaxDmitriev, $GymPartyPublicChat;
+      
+      $jwt = signJwtForUser($MaxDmitriev);
+      
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]);
+      
+      $json = json_decode($response['data']);
+      $chatData = $json->data->chat;
+      
+      assertStrict($response['info']['http_code'], 200);
+      assertStrict($chatData->id, $GymPartyPublicChat['id']);
+      assertStrict($chatData->name, $GymPartyPublicChat['name']);
+      assertStrict(isset($chatData->isPrivate), false);
+      assertStrict(isset($chatData->inviteLink), false);
+    });
+    
+    it("should return not authorized when trying to get chat by id without authorization", function () {
+      global $testsConfig, $messages, $MaxAndIlyaChat;
+      
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat['id'] . "/users");
+      $json = json_decode($response['data']);
+      
+      assertStrict($response['info']['http_code'], 401);
+      assertStrict($json->data->error, $messages["not_authenticated"]);
+    });
+    
+    it("should return chat not found for deleted chat", function () {
+      global $testsConfig, $MaxDmitriev, $messages, $DeletedChatWithMaxAndMatvey;
+      
+      $jwt = signJwtForUser($MaxDmitriev);
+      
+      $response = request("GET", $testsConfig["host"] . "/api/chats/" . $DeletedChatWithMaxAndMatvey["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]);
+      $json = json_decode($response['data']);
+      
+      assertStrict($response['info']['http_code'], 404);
+      assertStrict($json->data->error, $messages["chat_not_found"]);
+    });
+    
+    it("should return chat not found for random chat id", function () {
+      global $testsConfig, $MaxDmitriev, $messages;
+      
+      $jwt = signJwtForUser($MaxDmitriev);
+      
+      $response = request("GET", $testsConfig["host"] . "/api/chats/random_id/users", ["headers" => ["Authorization: Bearer $jwt"]]);
+      $json = json_decode($response['data']);
+      
+      assertStrict($response['info']['http_code'], 404);
+      assertStrict($json->data->error, $messages["chat_not_found"]);
+    });
+  });
+  
 ?>
 
\ No newline at end of file From 641b8804812dbc142128c2684a0e894007d15d2f Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 01:35:11 +0200 Subject: [PATCH 43/55] PC-1 Add test for case when not authorized user trying to create chat --- tests/chats-e2e.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 0272cab..c67c38d 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -93,6 +93,21 @@ }); describe("[POST] /api/chats", function () { + it("should return not authorized error when trying to create chat without authorization", function () { + global $testsConfig, $messages; + + $body = [ + "name" => "My private chat", + "isPrivate" => true + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats", ["json" => $body]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 401); + assertStrict($json->data->error, $messages["not_authenticated"]); + }); + it("should be able to create new chat", function () { global $testsConfig, $MaxDmitriev; From 71ba9707a3b3ab8b8ce7ce61a18dcd759b6f3dd0 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 02:50:37 +0200 Subject: [PATCH 44/55] PC-1 Add tests for add participant to chat endpoints TDD --- locale/en/messages.php | 1 + tests/chats-e2e.php | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/locale/en/messages.php b/locale/en/messages.php index 6a77fdd..5160d66 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -9,6 +9,7 @@ "user_created" => "User created", "chat_created" => "Chat created", + "participant_added" => "Participant added", "chat_deleted" => "Chat deleted", "participant_deleted" => "Participant deleted", diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index c67c38d..b395bcc 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -297,5 +297,69 @@ }); }); + describe("[POST] /api/chats/:chatId/users", function () { + it("should return not authorized error when trying to add participant to chat without authorization", function () { + global $testsConfig, $messages, $MaxDmitriev, $GymPartyPublicChat; + + $body = [ + "userId" => $MaxDmitriev + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 401); + assertStrict($json->data->error, $messages["not_authenticated"]); + }); + + it("chat admin should be able to add chat participant", function () { + global $testsConfig, $messages, $MaxDmitriev, $MatveyGorelik, $GymPartyPublicChat; + + $jwt = signJwtForUser($MaxDmitriev); + + $body = [ + "userId" => $MatveyGorelik + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 201); + assertStrict($json->data->message, $messages["participant_added"]); + }); + + it("chat member should not be able to add chat participant", function () { + global $testsConfig, $messages, $MaxDmitriev, $MatveyGorelik, $GymPartyPublicChat; + + $jwt = signJwtForUser($MaxDmitriev); + + $body = [ + "userId" => $MatveyGorelik + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 201); + assertStrict($json->data->message, $messages["participant_added"]); + }); + + it("should return error when not chat participant trying to add user to chat", function () { + global $testsConfig, $messages, $MaxDmitriev, $MatveyGorelik, $GymPartyPublicChat; + + $jwt = signJwtForUser($MatveyGorelik); + + $body = [ + "userId" => $MaxDmitriev + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 401); + assertStrict($json->data->message, $messages["not_enough_permission"]); + }); + }); + ?> \ No newline at end of file From 3350add803319cce54a5fd741b4b0ba7765cce1d Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 14:36:11 +0200 Subject: [PATCH 45/55] PC-1 Add delete chat participant route --- app/app.controller.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/app.controller.php b/app/app.controller.php index 0d46a8c..48dc6a9 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -63,7 +63,9 @@ private function router() case 'GET': if ($this->req['resource'] === '/api/users/me') { + // When we require authorization and want to get user in controller $this->_req->useGuard($this->jwtAuthGuard); + // We need to call getRequest() method in original request method $this->usersController->getMe($this->_req->getRequest()); return; } @@ -125,6 +127,12 @@ private function router() case 'DELETE': $reqBody = $this->_req->parseBody(); + if (preg_match("/\/api\/chats\/(?'chatId'[a-z0-9]+)\/users\/(?'userId'[a-z0-9]+)/", $this->req['resource'])) + { + $this->_req->useGuard($this->jwtAuthGuard); + $this->chatsController->deleteChatParticipant($this->_req->getRequest()); + return; + } if (strpos($this->req['resource'], '/api/chats/') === 0) { $this->_req->useGuard($this->jwtAuthGuard); From a1b7611b48a2401a63345e2cd116b7bf2b88efcc Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 14:36:36 +0200 Subject: [PATCH 46/55] PC-1 Fix delete chat participant endpoints tests --- locale/en/messages.php | 1 + tests/chats-e2e.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/locale/en/messages.php b/locale/en/messages.php index 5160d66..805cce8 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -17,6 +17,7 @@ "failed_to_create_user" => "Failed to create user", "failed_to_create_chat" => "Failed to create chat", "failed_to_delete_chat" => "Failed to delete chat", + "failed_to_delete_chat_participant" => "Failed to delete chat participant", "failed_to_sign_in" => "Failed to sign in", "failed_to_sign_up" => "Failed to sign up", diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index b395bcc..90085c4 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -195,7 +195,7 @@ $jwt = signJwtForUser($MaxDmitriev); - $response = request("DELETE", $testsConfig["host"] . "/api/chats/random_chat_id/users/" . $MaxDmitriev["id"], ["headers" => ["Authorization: Bearer $jwt"]]); + $response = request("DELETE", $testsConfig["host"] . "/api/chats/randomchatid/users/" . $MaxDmitriev["id"], ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 404); @@ -203,11 +203,11 @@ }); it("should return participant not found error for random user id", function () { - global $testsConfig, $messages, $GymPartyPublicChat, $MaxDmitriev; + global $testsConfig, $messages, $MaxAndIlyaChat, $MaxDmitriev; $jwt = signJwtForUser($MaxDmitriev); - $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users/random_id", ["headers" => ["Authorization: Bearer $jwt"]]); + $response = request("DELETE", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"] . "/users/randomid", ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); assertStrict($response['info']['http_code'], 404); From bd5765ca9788224b03da01bbbabb2aaa2f59a067 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 14:36:56 +0200 Subject: [PATCH 47/55] PC-1 Add delete chat participant controller and service --- chats/chats.controller.php | 41 ++++++++++++++++++++++++++++++++++++++ chats/chats.service.php | 9 +++++++++ 2 files changed, 50 insertions(+) diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 2057fb6..67bf125 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -99,4 +99,45 @@ function deleteChat($req) httpException($messages["failed_to_delete_chat"])['end'](); } } + + function deleteChatParticipant($req) { + global $messages; + + try { + preg_match("/\/api\/chats\/(?'chatId'[a-z0-9]+)\/users\/(?'userId'[a-z0-9]+)/", $req['resource'], $parsedUrl); + + $chat = $this->chatsService->findById($parsedUrl["chatId"]); + + if (is_null($chat)) { + httpException($messages["chat_not_found"], 404)['end'](); + } + + $initiatorParticipant = $this->chatsService->getChatParticipantByUserId($req["user"]["id"], $chat["id"]); + + if (is_null($initiatorParticipant) || intval($initiatorParticipant["permission"]) !== 2) + { + httpException($messages["not_enough_permission"], 403)['end'](); + } + + $chatParticipantToDelete = $this->chatsService->getChatParticipantByUserId($parsedUrl["userId"], $chat["id"]); + + if (is_null($chatParticipantToDelete)) + { + httpException($messages["participant_not_found"], 404)['end'](); + } + + $this->chatsService->deleteChatParticipant($parsedUrl["userId"], $chat["id"]); + + $response = [ + "message" => $messages["participant_deleted"] + ]; + + jsonResponse($response)['end'](); + } + catch (PDOException $ex) + { + var_dump($ex); + httpException($messages["failed_to_delete_chat_participant"])['end'](); + } + } } diff --git a/chats/chats.service.php b/chats/chats.service.php index 249ccfb..019bdda 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -106,6 +106,15 @@ function addParticipantToChat($userId, $chatId, $permission = 0) $stmt->bindValue(":permission", $permission); $stmt->execute(); } + + function deleteChatParticipant($userId, $chatId) + { + $sql = "DELETE FROM ChatParticipants WHERE userId=:userId AND chatId=:chatId"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":userId", $userId); + $stmt->bindValue(":chatId", $chatId); + $stmt->execute(); + } function deleteChatById($chatId) { From 0ed9447800a762ea70478abc9eabc535f5bb3f52 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 18:19:01 +0200 Subject: [PATCH 48/55] PC-1 Create add user to chat route --- app/app.controller.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/app.controller.php b/app/app.controller.php index 48dc6a9..63a433d 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -102,7 +102,13 @@ private function router() case 'POST': $reqBody = $this->_req->parseBody(); - + + if (preg_match("/\/api\/chats\/(?'chatId'[a-z0-9]+)\/users/", $this->req['resource'])) + { + $this->_req->useGuard($this->jwtAuthGuard); + $this->chatsController->addUserToChat($this->_req->getRequest(), $reqBody["data"]); + return; + } if ($this->req['resource'] === '/api/chats') { $this->_req->useGuard($this->jwtAuthGuard); From 93caafbb3e6209709453b05b12d790c12e255a8e Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 18:19:29 +0200 Subject: [PATCH 49/55] PC-1 Add new chat participants fixtures and fix users tests --- fixtures/chatParticipants.php | 16 ++++++++++++++-- tests/users-e2e.php | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/fixtures/chatParticipants.php b/fixtures/chatParticipants.php index 00bd4d5..1f28bc3 100644 --- a/fixtures/chatParticipants.php +++ b/fixtures/chatParticipants.php @@ -3,7 +3,7 @@ @include_once __DIR__ . "/chats.php"; @include_once __DIR__ . "/users.php"; - global $MaxAndIlyaChat, $MaxDmitriev, $IlyaMehof, $DeletedChatWithMaxAndMatvey, $MatveyGorelik; + global $MaxAndIlyaChat, $MaxDmitriev, $IlyaMehof, $DeletedChatWithMaxAndMatvey, $MatveyGorelik, $GymPartyPublicChat; $MaxInChatWithIlya = [ "chatId" => $MaxAndIlyaChat["id"], @@ -29,4 +29,16 @@ "permission" => 2 ]; - $chatParticipantsFixtures = [$MaxInChatWithIlya, $IlyaInChatWithMax, $MaxInDeletedChatWithMatvey, $MatveyInDeletedChatWithMax]; \ No newline at end of file + $MaxInGymChat = [ + "chatId" => $GymPartyPublicChat["id"], + "userId" => $MaxDmitriev["id"], + "permission" => 2 + ]; + + $IlyaInGymChat = [ + "chatId" => $GymPartyPublicChat["id"], + "userId" => $IlyaMehof["id"], + "permission" => 0 + ]; + + $chatParticipantsFixtures = [$MaxInChatWithIlya, $IlyaInChatWithMax, $MaxInDeletedChatWithMatvey, $MatveyInDeletedChatWithMax, $IlyaInGymChat, $MaxInGymChat]; \ No newline at end of file diff --git a/tests/users-e2e.php b/tests/users-e2e.php index 34e135c..bbd700d 100644 --- a/tests/users-e2e.php +++ b/tests/users-e2e.php @@ -83,7 +83,7 @@ $chats = $json->data->chats; assertStrict($response['info']['http_code'], 200); - assertStrict(count($chats), 1); + assertStrict(count($chats), 2); }); }); From 0272cad090d6f9b8eda296b7451eda1dcd6ba307 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 18:20:07 +0200 Subject: [PATCH 50/55] PC-1 Create new add user to chat endpoint tests and fix existing --- tests/chats-e2e.php | 51 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 90085c4..1608736 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -302,7 +302,7 @@ global $testsConfig, $messages, $MaxDmitriev, $GymPartyPublicChat; $body = [ - "userId" => $MaxDmitriev + "userId" => $MaxDmitriev["id"] ]; $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body]); @@ -318,7 +318,7 @@ $jwt = signJwtForUser($MaxDmitriev); $body = [ - "userId" => $MatveyGorelik + "userId" => $MatveyGorelik["id"] ]; $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); @@ -328,13 +328,13 @@ assertStrict($json->data->message, $messages["participant_added"]); }); - it("chat member should not be able to add chat participant", function () { - global $testsConfig, $messages, $MaxDmitriev, $MatveyGorelik, $GymPartyPublicChat; + it("chat member should be able to add chat participant", function () { + global $testsConfig, $messages, $IlyaMehof, $MatveyGorelik, $GymPartyPublicChat; - $jwt = signJwtForUser($MaxDmitriev); + $jwt = signJwtForUser($IlyaMehof); $body = [ - "userId" => $MatveyGorelik + "userId" => $MatveyGorelik["id"] ]; $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); @@ -344,20 +344,53 @@ assertStrict($json->data->message, $messages["participant_added"]); }); + it("chat member should not be able to add chat participant with higher privilege", function () { + global $testsConfig, $messages, $IlyaMehof, $MatveyGorelik, $GymPartyPublicChat; + + $jwt = signJwtForUser($IlyaMehof); + + $body = [ + "userId" => $MatveyGorelik["id"], + "permission" => 2 + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 403); + assertStrict($json->data->error, $messages["not_enough_permission"]); + }); + it("should return error when not chat participant trying to add user to chat", function () { global $testsConfig, $messages, $MaxDmitriev, $MatveyGorelik, $GymPartyPublicChat; $jwt = signJwtForUser($MatveyGorelik); $body = [ - "userId" => $MaxDmitriev + "userId" => $MaxDmitriev["id"] ]; $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); - assertStrict($response['info']['http_code'], 401); - assertStrict($json->data->message, $messages["not_enough_permission"]); + assertStrict($response['info']['http_code'], 403); + assertStrict($json->data->error, $messages["not_enough_permission"]); + }); + + it("should return error when trying to add not existing user", function () { + global $testsConfig, $messages, $MaxDmitriev, $GymPartyPublicChat; + + $jwt = signJwtForUser($MaxDmitriev); + + $body = [ + "userId" => "randomid" + ]; + + $response = request("POST", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["json" => $body, "headers" => ["Authorization: Bearer $jwt"]]); + $json = json_decode($response['data']); + + assertStrict($response['info']['http_code'], 404); + assertStrict($json->data->error, $messages["user_not_found"]); }); }); From 4aa8389464d5494e90ce5659357fefd9a2f47442 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 18:20:26 +0200 Subject: [PATCH 51/55] PC-1 Create add user to chat controller --- chats/chats.controller.php | 52 ++++++++++++++++++++++++++++++++++++-- locale/en/messages.php | 1 + 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/chats/chats.controller.php b/chats/chats.controller.php index 67bf125..dabbfec 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -3,16 +3,18 @@ @include_once __DIR__ . "/../utils/httpException.php"; @include_once __DIR__ . "/../utils/jsonResponse.php"; @include_once __DIR__ . "/chats.service.php"; + @include_once __DIR__ . "/../users/users.service.php"; @include_once __DIR__ . "/../locale/en/messages.php"; @include_once __DIR__ . "/../utils/randomId.php"; class ChatsController { - private $chatsService; + private $chatsService, $usersService; function __construct($conn) { $this->chatsService = new ChatsService($conn); + $this->usersService = new UsersService($conn); } function getChatById($req) @@ -136,8 +138,54 @@ function deleteChatParticipant($req) { } catch (PDOException $ex) { - var_dump($ex); httpException($messages["failed_to_delete_chat_participant"])['end'](); } } + + function addUserToChat($req, $addUserToChatDto) + { + global $messages; + + try { + preg_match("/\/api\/chats\/(?'chatId'[a-z0-9]+)\/users/", $req['resource'], $parsedUrl); + + $chat = $this->chatsService->findById($parsedUrl["chatId"]); + + if (is_null($chat)) { + httpException($messages["chat_not_found"], 404)['end'](); + } + + $initiatorParticipant = $this->chatsService->getChatParticipantByUserId($req["user"]["id"], $chat["id"]); + + if (is_null($addUserToChatDto["permission"])) + { + $addUserToChatDto["permission"] = 0; + } + + if (is_null($initiatorParticipant) || (intval($initiatorParticipant["permission"]) !== 2 && $addUserToChatDto["permission"] >= 1)) + { + httpException($messages["not_enough_permission"], 403)['end'](); + } + + $userToAdd = $this->usersService->getUserById($addUserToChatDto["userId"]); + + if (is_null($userToAdd)) + { + httpException($messages["user_not_found"], 404)['end'](); + } + + $this->chatsService->addParticipantToChat($addUserToChatDto["userId"], $chat["id"], $addUserToChatDto["permission"]); + + $response = [ + "message" => $messages["participant_added"] + ]; + + jsonResponse($response, 201)['end'](); + } + catch (PDOException $ex) + { + var_dump($ex); + httpException($messages["failed_to_add_chat_participant"])['end'](); + } + } } diff --git a/locale/en/messages.php b/locale/en/messages.php index 805cce8..299fca1 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -18,6 +18,7 @@ "failed_to_create_chat" => "Failed to create chat", "failed_to_delete_chat" => "Failed to delete chat", "failed_to_delete_chat_participant" => "Failed to delete chat participant", + "failed_to_add_chat_participant" => "Failed to add chat participant", "failed_to_sign_in" => "Failed to sign in", "failed_to_sign_up" => "Failed to sign up", From 3df00c8e8d08c5538fa6c9023b659e7063db7ab7 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 22:10:47 +0200 Subject: [PATCH 52/55] PC-1 Create get chat participants route --- app/app.controller.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/app.controller.php b/app/app.controller.php index 63a433d..d1612a3 100644 --- a/app/app.controller.php +++ b/app/app.controller.php @@ -87,6 +87,13 @@ private function router() $this->usersController->getUsers(); return; } + # /chats/:chatId/users + if (preg_match("/\/api\/chats\/(?'chatId'[a-z0-9]+)\/users/", $this->req['resource'])) + { + $this->_req->useGuard($this->jwtAuthGuard); + $this->chatsController->getChatParticipants($this->_req->getRequest()); + return; + } # /chats/:chatId if (strpos($this->req['resource'], '/api/chats/') === 0) { From 2cb4103493032893123354b4568efcab7491066f Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 22:11:02 +0200 Subject: [PATCH 53/55] PC-1 Create get chat participants controller --- chats/chats.controller.php | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/chats/chats.controller.php b/chats/chats.controller.php index dabbfec..13123d6 100644 --- a/chats/chats.controller.php +++ b/chats/chats.controller.php @@ -31,7 +31,7 @@ function getChatById($req) } if ($chat["isPrivate"] && !$this->chatsService->isUserChatParticipant($req["user"]["id"], $chatId)) { - httpException($messages["no_access_to_the_chat"], 401)['end'](); + httpException($messages["not_enough_permission"], 403)['end'](); } $response = [ @@ -184,8 +184,40 @@ function addUserToChat($req, $addUserToChatDto) } catch (PDOException $ex) { - var_dump($ex); httpException($messages["failed_to_add_chat_participant"])['end'](); } } + + function getChatParticipants($req) + { + global $messages; + + try { + preg_match("/\/api\/chats\/(?'chatId'[a-z0-9]+)\/users/", $req['resource'], $parsedUrl); + + $chat = $this->chatsService->findById($parsedUrl["chatId"]); + + if (is_null($chat)) { + httpException($messages["chat_not_found"], 404)['end'](); + } + + $initiatorParticipant = $this->chatsService->getChatParticipantByUserId($req["user"]["id"], $chat["id"]); + + if (is_null($initiatorParticipant) && $chat["isPrivate"]) + { + httpException($messages["not_enough_permission"], 403)['end'](); + } + + $chatParticipants = $this->chatsService->getChatParticipants($chat["id"]); + + $response = [ + "participants" => $chatParticipants + ]; + + jsonResponse($response)['end'](); + } + catch (PDOException $ex) { + httpException($messages["failed_to_get_chat_participants"])['end'](); + } + } } From 1d08789916629de44c1676a10fe929101b52171d Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 22:11:10 +0200 Subject: [PATCH 54/55] PC-1 Create get chat participants service --- chats/chats.service.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chats/chats.service.php b/chats/chats.service.php index 019bdda..2715833 100644 --- a/chats/chats.service.php +++ b/chats/chats.service.php @@ -123,4 +123,18 @@ function deleteChatById($chatId) $stmt->bindValue(":chatId", $chatId); $stmt->execute(); } + + function getChatParticipants($chatId) + { + $sql = "SELECT U.id AS id, U.username AS username, U.fullname AS fullname, U.profileImage as profileImage, U.description as description FROM Users AS U, ChatParticipants AS CP WHERE CP.chatId=:chatId AND U.id=CP.userId"; + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(":chatId", $chatId); + $stmt->execute(); + + if ($stmt->rowCount() > 0) { + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } else { + return []; + } + } } From 3c0985047d489f552497fdfffa7a454913005a81 Mon Sep 17 00:00:00 2001 From: itmaxxx Date: Sat, 22 Jan 2022 22:12:30 +0200 Subject: [PATCH 55/55] PC-1 Improve and fix chats tests --- locale/en/messages.php | 2 +- tests/chats-e2e.php | 51 +++++++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/locale/en/messages.php b/locale/en/messages.php index 299fca1..24c9711 100644 --- a/locale/en/messages.php +++ b/locale/en/messages.php @@ -19,10 +19,10 @@ "failed_to_delete_chat" => "Failed to delete chat", "failed_to_delete_chat_participant" => "Failed to delete chat participant", "failed_to_add_chat_participant" => "Failed to add chat participant", + "failed_to_get_chat_participants" => "Failed to get chat participants", "failed_to_sign_in" => "Failed to sign in", "failed_to_sign_up" => "Failed to sign up", "not_authenticated" => "Not authenticated", - "no_access_to_the_chat" => "You do not have access to the chat", "not_enough_permission" => "You do not have enough permission to do this" ]; \ No newline at end of file diff --git a/tests/chats-e2e.php b/tests/chats-e2e.php index 1608736..aec4d4c 100644 --- a/tests/chats-e2e.php +++ b/tests/chats-e2e.php @@ -36,8 +36,8 @@ $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"], ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); - assertStrict($response['info']['http_code'], 401); - assertStrict($json->data->error, $messages["no_access_to_the_chat"]); + assertStrict($response['info']['http_code'], 403); + assertStrict($json->data->error, $messages["not_enough_permission"]); }); it("should get public chat by id", function () { @@ -216,24 +216,22 @@ }); describe("[GET] /api/chats/:chatId/users", function () { - it("should get private chat participants by chat id for chat participant", function () { - global $testsConfig, $MaxDmitriev, $MaxAndIlyaChat; + it("should get private chat participants for chat participant", function () { + global $testsConfig, $MaxDmitriev, $IlyaMehof, $MaxAndIlyaChat; $jwt = signJwtForUser($MaxDmitriev); $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); - $chatData = $json->data->chat; + $chatParticipants = $json->data->participants; assertStrict($response['info']['http_code'], 200); - assertStrict($chatData->id, $MaxAndIlyaChat['id']); - assertStrict($chatData->name, $MaxAndIlyaChat['name']); - assertStrict(isset($chatData->isPrivate), false); - assertStrict(isset($chatData->inviteLink), false); + assertStrict($chatParticipants[0]->username, $MaxDmitriev["username"]); + assertStrict($chatParticipants[1]->username, $IlyaMehof["username"]); }); - it("should return error when trying to get private chat by id for NOT a chat participant", function () { + it("should return error when trying to get private chat for NOT a chat participant", function () { global $testsConfig, $messages, $MatveyGorelik, $MaxAndIlyaChat; $jwt = signJwtForUser($MatveyGorelik); @@ -241,28 +239,41 @@ $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); - assertStrict($response['info']['http_code'], 401); - assertStrict($json->data->error, $messages["no_access_to_the_chat"]); + assertStrict($response['info']['http_code'], 403); + assertStrict($json->data->error, $messages["not_enough_permission"]); }); - it("should get public chat participants by id", function () { - global $testsConfig, $MaxDmitriev, $GymPartyPublicChat; + it("should get public chat participants", function () { + global $testsConfig, $MaxDmitriev, $IlyaMehof, $GymPartyPublicChat; $jwt = signJwtForUser($MaxDmitriev); $response = request("GET", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]); $json = json_decode($response['data']); - $chatData = $json->data->chat; + $chatParticipants = $json->data->participants; assertStrict($response['info']['http_code'], 200); - assertStrict($chatData->id, $GymPartyPublicChat['id']); - assertStrict($chatData->name, $GymPartyPublicChat['name']); - assertStrict(isset($chatData->isPrivate), false); - assertStrict(isset($chatData->inviteLink), false); + assertStrict($chatParticipants[0]->username, $MaxDmitriev["username"]); + assertStrict($chatParticipants[1]->username, $IlyaMehof["username"]); }); + + it("should get public chat participants for not a chat participant", function () { + global $testsConfig, $MatveyGorelik, $MaxDmitriev, $IlyaMehof, $GymPartyPublicChat; - it("should return not authorized when trying to get chat by id without authorization", function () { + $jwt = signJwtForUser($MatveyGorelik); + + $response = request("GET", $testsConfig["host"] . "/api/chats/" . $GymPartyPublicChat["id"] . "/users", ["headers" => ["Authorization: Bearer $jwt"]]); + + $json = json_decode($response['data']); + $chatParticipants = $json->data->participants; + + assertStrict($response['info']['http_code'], 200); + assertStrict($chatParticipants[0]->username, $MaxDmitriev["username"]); + assertStrict($chatParticipants[1]->username, $IlyaMehof["username"]); + }); + + it("should return not authorized when trying to get chat participants without authorization", function () { global $testsConfig, $messages, $MaxAndIlyaChat; $response = request("GET", $testsConfig["host"] . "/api/chats/" . $MaxAndIlyaChat['id'] . "/users");