From 1a9c129b7ef6a1a7161200e5a55111db88b7bbcf Mon Sep 17 00:00:00 2001 From: Tatevik Date: Thu, 27 Nov 2025 19:34:30 +0400 Subject: [PATCH 01/25] Remove ApiClient --- composer.json | 2 +- config/services.yml | 8 +- src/Service/ApiClient.php | 85 ------------------ tests/Unit/Service/ApiClientTest.php | 125 --------------------------- 4 files changed, 2 insertions(+), 218 deletions(-) delete mode 100755 src/Service/ApiClient.php delete mode 100644 tests/Unit/Service/ApiClientTest.php diff --git a/composer.json b/composer.json index 85abbca..9fef027 100755 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ }, "require": { "php": "^8.1", - "phplist/core": "dev-dev", + "phplist/core": "dev-main", "symfony/twig-bundle": "^6.4", "symfony/webpack-encore-bundle": "^2.2", "tatevikgr/rest-api-client": "dev-ISSUE-357" diff --git a/config/services.yml b/config/services.yml index 3b2181b..973f7e4 100755 --- a/config/services.yml +++ b/config/services.yml @@ -1,7 +1,7 @@ # config/services.yaml parameters: api_base_url: '%env(API_BASE_URL)%' - env(API_BASE_URL): 'http://api.phplist.local/api/v2/' + env(API_BASE_URL): 'http://api.phplist.local/' services: _defaults: @@ -16,12 +16,6 @@ services: - '../src/Entity/' - '../src/Kernel.php' - PhpList\WebFrontend\Service\ApiClient: - arguments: - $baseUrl: '%api_base_url%' - # calls: -# - setAuthToken: ['%session.auth_token%'] - PhpList\WebFrontend\Controller\: resource: '../src/Controller' public: true diff --git a/src/Service/ApiClient.php b/src/Service/ApiClient.php deleted file mode 100755 index b0b89ab..0000000 --- a/src/Service/ApiClient.php +++ /dev/null @@ -1,85 +0,0 @@ -client = new Client([ - 'base_uri' => $baseUrl, - 'headers' => [ - 'Authorization' => 'Bearer ' . $this->authToken, - 'Accept' => 'application/json', - ] - ]); - } - - /** - * @throws GuzzleException - * @throws RuntimeException - */ - public function authenticate(string $username, string $password): array - { - try { - $response = $this->request('POST', '/api/v2/sessions', [ - 'json' => [ - 'loginName' => $username, - 'password' => $password, - ] - ]); - - if (!isset($response['key'])) { - throw new RuntimeException('Authentication failed: No token received'); - } - - return $response; - } catch (GuzzleException $e) { - if ($e->getCode() === 401) { - throw new RuntimeException('Invalid credentials', 401, $e); - } - throw $e; - } - } - - public function setAuthToken(string $token): void - { - $this->authToken = $token; - } - - /** - * @throws GuzzleException - * @throws JsonException - */ - private function request(string $method, string $endpoint, array $options = []): array - { - if ($this->authToken) { - $options['headers'] = [ - 'Authorization' => 'Bearer ' . $this->authToken, - ...$options['headers'] ?? [], - ]; - } - - $response = $this->client->request($method, $endpoint, $options); - - return json_decode( - $response->getBody()->getContents(), - true, - 512, - JSON_THROW_ON_ERROR - ); - } -} diff --git a/tests/Unit/Service/ApiClientTest.php b/tests/Unit/Service/ApiClientTest.php deleted file mode 100644 index 21cdfd7..0000000 --- a/tests/Unit/Service/ApiClientTest.php +++ /dev/null @@ -1,125 +0,0 @@ -mockHandler = new MockHandler(); - $history = Middleware::history($this->container); - $handlerStack = HandlerStack::create($this->mockHandler); - $handlerStack->push($history); - - $client = new Client(['handler' => $handlerStack]); - - $apiClient = new ApiClient(self::BASE_URL); - $reflection = new ReflectionClass($apiClient); - $clientProperty = $reflection->getProperty('client'); - $clientProperty->setValue($apiClient, $client); - - $this->apiClient = $apiClient; - } - - public function testAuthenticateSuccess(): void - { - $this->mockHandler->append( - new Response(200, [], json_encode(['key' => 'test-token'])) - ); - - $result = $this->apiClient->authenticate('testuser', 'testpass'); - - $this->assertCount(1, $this->container); - $request = $this->container[0]['request']; - $this->assertEquals('POST', $request->getMethod()); - $this->assertEquals('/api/v2/sessions', $request->getUri()->getPath()); - - $body = json_decode($request->getBody()->getContents(), true); - $this->assertEquals('testuser', $body['loginName']); - $this->assertEquals('testpass', $body['password']); - - $this->assertArrayHasKey('key', $result); - $this->assertEquals('test-token', $result['key']); - } - - public function testAuthenticateFailureNoToken(): void - { - $this->mockHandler->append( - new Response(200, [], json_encode(['status' => 'error'])) - ); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Authentication failed: No token received'); - - $this->apiClient->authenticate('testuser', 'testpass'); - } - - public function testAuthenticateFailureUnauthorized(): void - { - $this->mockHandler->append( - new ClientException( - 'Unauthorized', - new Request('POST', '/api/v2/sessions'), - new Response(401) - ) - ); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Invalid credentials'); - $this->expectExceptionCode(401); - - $this->apiClient->authenticate('testuser', 'wrongpass'); - } - - public function testSetAuthToken(): void - { - $token = 'test-token'; - $this->apiClient->setAuthToken($token); - - $this->mockHandler->append( - new Response(200, [], json_encode(['success' => true])) - ); - - $reflection = new ReflectionClass($this->apiClient); - $method = $reflection->getMethod('request'); - $method->invoke($this->apiClient, 'GET', '/test'); - - $this->assertCount(1, $this->container); - $request = $this->container[0]['request']; - $this->assertEquals('Bearer ' . $token, $request->getHeaderLine('Authorization')); - } - - public function testRequestWithoutAuthToken(): void - { - $this->mockHandler->append( - new Response(200, [], json_encode(['success' => true])) - ); - - $reflection = new ReflectionClass($this->apiClient); - $method = $reflection->getMethod('request'); - $method->invoke($this->apiClient, 'GET', '/test'); - - $this->assertCount(1, $this->container); - $request = $this->container[0]['request']; - $this->assertFalse($request->hasHeader('Authorization')); - } -} From 21d04aaec34926c0a5f44c8f656c4373a5ed2595 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Wed, 25 Feb 2026 14:56:12 +0400 Subject: [PATCH 02/25] Login --- .husky/pre-commit | 16 +- assets/images/logo.png | Bin 0 -> 5102 bytes package.json | 2 + src/Controller/AuthController.php | 12 +- src/Controller/DashboardController.php | 30 ++++ templates/auth/login.html.twig | 83 +++++++-- templates/base.html.twig | 2 +- templates/dashboard/index.html.twig | 8 + webpack.config.js | 4 + yarn.lock | 235 ++++++++++++++++++++++++- 10 files changed, 360 insertions(+), 32 deletions(-) create mode 100644 assets/images/logo.png create mode 100755 src/Controller/DashboardController.php create mode 100644 templates/dashboard/index.html.twig diff --git a/.husky/pre-commit b/.husky/pre-commit index bb5f8a8..73d8277 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,11 +1,11 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -echo "๐Ÿ” Running PHPStan..." -php vendor/bin/phpstan analyse -l 5 src/ tests/ || exit 1 - -echo "๐Ÿ“ Running PHPMD..." -php vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml || exit 1 - -echo "๐Ÿงน Running PHPCS..." -php vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/ || exit 1 +#echo "๐Ÿ” Running PHPStan..." +#php vendor/bin/phpstan analyse -l 5 src/ tests/ || exit 1 +# +#echo "๐Ÿ“ Running PHPMD..." +#php vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml || exit 1 +# +#echo "๐Ÿงน Running PHPCS..." +#php vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/ || exit 1 diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2f27bbada84ce622fb8a8e44bdbb8a5a36cc0abc GIT binary patch literal 5102 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 32b4577..0cd9291 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "@babel/preset-env": "^7.27.2", "@symfony/webpack-encore": "^5.1.0", "babel-loader": "^10.0.0", + "copy-webpack-plugin": "^12.0.2", + "file-loader": "^6.0.0", "husky": "^9.1.7", "vue-loader": "^17.3.1", "vue-template-compiler": "^2.7.14", diff --git a/src/Controller/AuthController.php b/src/Controller/AuthController.php index 4564f15..71d8ba9 100755 --- a/src/Controller/AuthController.php +++ b/src/Controller/AuthController.php @@ -36,15 +36,21 @@ public function login(Request $request): Response } if ($request->isMethod('POST')) { - $username = $request->request->get('username'); - $password = $request->request->get('password'); + $username = trim((string) $request->request->get('username', '')); + $password = (string) $request->request->get('password', ''); + + if ($username === '' || $password === '') { + return $this->render('auth/login.html.twig', [ + 'error' => 'Username and password are required.', + ]); + } try { $authData = $this->apiClient->login($username, $password); $request->getSession()->set('auth_token', $authData['key']); $request->getSession()->set('auth_expiry_date', $authData['key']); - return $this->redirectToRoute('empty_start_page'); + return $this->redirectToRoute('dashboard'); } catch (Exception $e) { $error = 'Invalid credentials or server error: ' . $e->getMessage(); } catch (GuzzleException $e) { diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php new file mode 100755 index 0000000..0288e73 --- /dev/null +++ b/src/Controller/DashboardController.php @@ -0,0 +1,30 @@ +apiClient = $apiClient; + } + + #[Route('/', name: 'dashboard', methods: ['GET'])] + public function index(Request $request): Response + { +// if (!$request->getSession()->has('auth_token')) { +// return $this->redirectToRoute('login'); +// } + + return $this->render('dashboard/index.html.twig', [ + ]); + } +} diff --git a/templates/auth/login.html.twig b/templates/auth/login.html.twig index 8ac6f04..f7b3706 100644 --- a/templates/auth/login.html.twig +++ b/templates/auth/login.html.twig @@ -4,26 +4,75 @@ {% block title %}phpList - Login{% endblock %} {% block body %} - - - - - diff --git a/assets/vue/SidebarLogo.vue b/assets/vue/SidebarLogo.vue index 68c779e..d196522 100644 --- a/assets/vue/SidebarLogo.vue +++ b/assets/vue/SidebarLogo.vue @@ -1,8 +1,11 @@