Skip to content

Commit 4f8e231

Browse files
TatevikGrtatevikg1
andauthored
ISSUE-345: login/logout (#70)
* ISSUE-345: set up router * ISSUE-345: twig through core * ISSUE-345: login/logout * ISSUE-345: readme * ISSUE-345: update ubuntu version * ISSUE-345: phpcs fix * ISSUE-345: UnauthorizedSubscriber * ISSUE-345: tests * ISSUE-345: phpstan * ISSUE-345: add vue.js * ISSUE-345: add pre-commit hook * ISSUE-345: vue.js fix * ISSUE-345: vue.js fix * ISSUE-345: rename folder * ISSUE-345: login test * ISSUE-345: PantherTestCase in ci * ISSUE-345: remove fos * ISSUE-345: version --------- Co-authored-by: Tatevik <[email protected]>
1 parent 9d05218 commit 4f8e231

File tree

27 files changed

+3964
-7
lines changed

27 files changed

+3964
-7
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ on: [push, pull_request]
33
jobs:
44
main:
55
name: phpList Base Dist on PHP ${{ matrix.php-versions }}, with dist ${{ matrix.dependencies }} [Build, Test]
6-
runs-on: ubuntu-20.04
6+
runs-on: ubuntu-22.04
77
env:
88
DB_DATABASE: phplist
99
DB_USERNAME: root
@@ -37,6 +37,16 @@ jobs:
3737
curl -sS https://get.symfony.com/cli/installer | bash
3838
mv $HOME/.symfony*/bin/symfony /usr/local/bin/symfony
3939
symfony version
40+
- name: Install Google Chrome
41+
run: |
42+
curl -sSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor | sudo tee /usr/share/keyrings/google.gpg > /dev/null
43+
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
44+
sudo apt-get update
45+
sudo apt-get install -y google-chrome-stable
46+
- name: Set Panther to use Chrome
47+
run: |
48+
echo "PANTHER_NO_HEADLESS=0" >> .env.test
49+
echo "PANTHER_CHROME_BINARY=/usr/bin/google-chrome" >> .env.test
4050
- name: Start mysql service
4151
run: sudo /etc/init.d/mysql start
4252
- name: Verify MySQL connection on host

.gitignore

100644100755
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@
1414
/var/
1515
/vendor/
1616
.phpunit.result.cache
17+
.env
18+
/node_modules
19+
/drivers/

.husky/pre-commit

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
echo "🔍 Running PHPStan..."
5+
php vendor/bin/phpstan analyse -l 5 src/ tests/ || exit 1
6+
7+
echo "📏 Running PHPMD..."
8+
php vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml || exit 1
9+
10+
echo "🧹 Running PHPCS..."
11+
php vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/ || exit 1

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ phpList is an open source newsletter manager.
1515
## About this package
1616

1717
This module will contain the web frontend for phpList 4. It will not have any
18-
SQL queries, but use functionality from the phpList 4 core for DB access.
18+
SQL queries but use functionality from the phpList 4 core for DB access.
1919

2020
This module is optional, i.e., it will be possible to run phpList 4 without a
2121
web frontend.
@@ -38,3 +38,24 @@ contribute and how to run the unit tests and style checks locally.
3838
This project adheres to a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
3939
By participating in this project and its community, you are expected to uphold
4040
this code.
41+
42+
## Commands for running this project for local testing
43+
```bash
44+
# Start the Symfony local server
45+
symfony local:server:start
46+
```
47+
48+
```bash
49+
# Compile and watch assets (including Vue.js components)
50+
yarn encore dev --watch
51+
```
52+
53+
## Vue.js Integration
54+
This project uses Vue.js for interactive UI components. Vue components are located in the `assets/vue/` directory and are mounted to specific DOM elements:
55+
56+
- `App.vue` is mounted to the element with ID `vue-app`
57+
58+
To add new Vue components:
59+
1. Create the component in the `assets/vue/` directory
60+
2. Import and mount it in `assets/app.js`
61+
3. Add a mount point in the appropriate template

assets/app.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createApp } from 'vue';
2+
import App from './vue/App.vue';
3+
4+
// Mount the main app if the element exists
5+
const appElement = document.getElementById('vue-app');
6+
if (appElement) {
7+
createApp(App).mount('#vue-app');
8+
}
9+

assets/vue/App.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<div>
3+
<h2>Hello from Vue</h2>
4+
<p>{{ message }}</p>
5+
</div>
6+
</template>
7+
8+
<script>
9+
export default {
10+
name: 'App',
11+
data() {
12+
return {
13+
message: 'This is a reusable component!'
14+
}
15+
},
16+
created() {
17+
console.log('App component created');
18+
},
19+
mounted() {
20+
console.log('App component mounted');
21+
},
22+
updated() {
23+
console.log('App component updated');
24+
}
25+
}
26+
</script>

composer.json

100644100755
Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
{
1616
"name": "Xheni Myrtaj",
1717
"email": "[email protected]",
18-
"role": "Maintainer"
18+
"role": "Former developer"
1919
},
2020
{
2121
"name": "Oliver Klee",
2222
"email": "[email protected]",
2323
"role": "Former developer"
24+
},
25+
{
26+
"name": "Tatevik Grigoryan",
27+
"email": "[email protected]",
28+
"role": "Maintainer"
2429
}
2530
],
2631
"support": {
@@ -30,7 +35,9 @@
3035
},
3136
"require": {
3237
"php": "^8.1",
33-
"phplist/core": "v5.0.0-alpha7"
38+
"phplist/core": "v5.0.0-alpha8",
39+
"symfony/twig-bundle": "^6.4",
40+
"symfony/webpack-encore-bundle": "^2.2"
3441
},
3542
"require-dev": {
3643
"phpunit/phpunit": "^9.5",
@@ -40,7 +47,10 @@
4047
"nette/caching": "^3.1.0",
4148
"nikic/php-parser": "^v4.10.4",
4249
"phpmd/phpmd": "^2.9.1",
43-
"symfony/process": "^6.4"
50+
"symfony/process": "^6.4",
51+
"symfony/panther": "*",
52+
"dbrekelmans/bdi": "*",
53+
"bshaffer/phpunit-retry-annotations": "^0.3.0"
4454
},
4555
"autoload": {
4656
"psr-4": {
@@ -83,8 +93,16 @@
8393
"symfony-web-dir": "public",
8494
"symfony-tests-dir": "tests",
8595
"phplist/core": {
86-
"bundles": [],
87-
"routes": {}
96+
"bundles": [
97+
"PhpList\\WebFrontend\\PhpListFrontendBundle"
98+
],
99+
"routes": {
100+
"rest-api": {
101+
"resource": "@PhpListFrontendBundle/Controller/",
102+
"type": "attribute",
103+
"prefix": "/"
104+
}
105+
}
88106
}
89107
}
90108
}

config/.gitkeep

Whitespace-only changes.

config/packages/framework.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
framework:
2+
secret: '%kernel.secret%'
3+
http_method_override: false
4+
php_errors:
5+
log: true

config/packages/twig.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
twig:
2+
debug: '%kernel.debug%'
3+
strict_variables: '%kernel.debug%'
4+
default_path: '%kernel.application_dir%/templates'
5+
auto_reload: true

config/services.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# config/services.yaml
2+
parameters:
3+
api_base_url: '%env(API_BASE_URL)%'
4+
env(API_BASE_URL): 'http://api.phplist.local/api/v2'
5+
6+
services:
7+
_defaults:
8+
autowire: true
9+
autoconfigure: true
10+
public: false
11+
12+
PhpList\WebFrontend\:
13+
resource: '../src/'
14+
exclude:
15+
- '../src/DependencyInjection/'
16+
- '../src/Entity/'
17+
- '../src/Kernel.php'
18+
19+
PhpList\WebFrontend\Service\ApiClient:
20+
arguments:
21+
$baseUrl: '%api_base_url%'
22+
# calls:
23+
# - setAuthToken: ['%session.auth_token%']
24+
25+
PhpList\WebFrontend\Controller\:
26+
resource: '../src/Controller'
27+
public: true
28+
autowire: true
29+
tags: ['controller.service_arguments']
30+
31+
Symfony\Component\HttpFoundation\Session\SessionInterface: '@session'

package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"devDependencies": {
3+
"@babel/core": "^7.27.4",
4+
"@babel/preset-env": "^7.27.2",
5+
"@symfony/webpack-encore": "^5.1.0",
6+
"babel-loader": "^10.0.0",
7+
"husky": "^9.1.7",
8+
"vue-loader": "^17.3.1",
9+
"vue-template-compiler": "^2.7.14",
10+
"webpack": "^5.99.9",
11+
"webpack-cli": "^6.0.1",
12+
"webpack-notifier": "^1.15.0"
13+
},
14+
"dependencies": {
15+
"vue": "^3.5.16"
16+
},
17+
"scripts": {
18+
"prepare": "husky install"
19+
}
20+
}

phpstan.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
level: 5
3+
paths:
4+
- bin
5+
- src
6+
- tests
7+
- public

src/.gitkeep

Whitespace-only changes.

src/Controller/AuthController.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\WebFrontend\Controller;
6+
7+
use Exception;
8+
use GuzzleHttp\Exception\GuzzleException;
9+
use PhpList\WebFrontend\Service\ApiClient;
10+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
11+
use Symfony\Component\HttpFoundation\Request;
12+
use Symfony\Component\HttpFoundation\Response;
13+
use Symfony\Component\Routing\Attribute\Route;
14+
15+
class AuthController extends AbstractController
16+
{
17+
private ApiClient $apiClient;
18+
19+
public function __construct(ApiClient $apiClient)
20+
{
21+
$this->apiClient = $apiClient;
22+
}
23+
24+
#[Route('/login', name: 'login', methods: ['GET', 'POST'])]
25+
public function login(Request $request): Response
26+
{
27+
if ($request->getSession()->has('auth_token')) {
28+
return $this->redirectToRoute('empty_start_page');
29+
}
30+
31+
$error = null;
32+
$session = $request->getSession();
33+
if ($session->has('login_error')) {
34+
$error = $session->get('login_error');
35+
$session->remove('login_error');
36+
}
37+
38+
if ($request->isMethod('POST')) {
39+
$username = $request->request->get('username');
40+
$password = $request->request->get('password');
41+
42+
try {
43+
$authData = $this->apiClient->authenticate($username, $password);
44+
$request->getSession()->set('auth_token', $authData['key']);
45+
$request->getSession()->set('auth_expiry_date', $authData['key']);
46+
$this->apiClient->setAuthToken($authData['key']);
47+
48+
return $this->redirectToRoute('empty_start_page');
49+
} catch (Exception $e) {
50+
$error = 'Invalid credentials or server error: ' . $e->getMessage();
51+
} catch (GuzzleException $e) {
52+
$error = 'Invalid credentials or server error: ' . $e->getMessage();
53+
}
54+
}
55+
56+
return $this->render('auth/login.html.twig', [
57+
'error' => $error,
58+
]);
59+
}
60+
61+
#[Route('/logout', name: 'logout')]
62+
public function logout(Request $request): Response
63+
{
64+
$request->getSession()->remove('auth_token');
65+
66+
return $this->redirectToRoute('login');
67+
}
68+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\WebFrontend\DependencyInjection;
6+
7+
use Exception;
8+
use InvalidArgumentException;
9+
use Symfony\Component\Config\FileLocator;
10+
use Symfony\Component\DependencyInjection\ContainerBuilder;
11+
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
12+
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
13+
14+
class PhpListFrontendExtension extends Extension
15+
{
16+
/**
17+
* Loads a specific configuration.
18+
*
19+
* @param array $configs configuration values
20+
* @param ContainerBuilder $container
21+
*
22+
* @return void
23+
*
24+
* @throws InvalidArgumentException|Exception if the provided tag is not defined in this extension
25+
*/
26+
public function load(array $configs, ContainerBuilder $container): void
27+
{
28+
// @phpstan-ignore-next-line
29+
$configs;
30+
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
31+
$loader->load('services.yml');
32+
}
33+
}

0 commit comments

Comments
 (0)