Skip to content

ISSUE-345: login/logout #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on: [push, pull_request]
jobs:
main:
name: phpList Base Dist on PHP ${{ matrix.php-versions }}, with dist ${{ matrix.dependencies }} [Build, Test]
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
env:
DB_DATABASE: phplist
DB_USERNAME: root
Expand Down Expand Up @@ -37,6 +37,16 @@ jobs:
curl -sS https://get.symfony.com/cli/installer | bash
mv $HOME/.symfony*/bin/symfony /usr/local/bin/symfony
symfony version
- name: Install Google Chrome
run: |
curl -sSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor | sudo tee /usr/share/keyrings/google.gpg > /dev/null
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
sudo apt-get update
sudo apt-get install -y google-chrome-stable
- name: Set Panther to use Chrome
run: |
echo "PANTHER_NO_HEADLESS=0" >> .env.test
echo "PANTHER_CHROME_BINARY=/usr/bin/google-chrome" >> .env.test
- name: Start mysql service
run: sudo /etc/init.d/mysql start
- name: Verify MySQL connection on host
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
/var/
/vendor/
.phpunit.result.cache
.env
/node_modules
/drivers/
11 changes: 11 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +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
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ phpList is an open source newsletter manager.
## About this package

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

This module is optional, i.e., it will be possible to run phpList 4 without a
web frontend.
Expand All @@ -38,3 +38,24 @@ contribute and how to run the unit tests and style checks locally.
This project adheres to a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
By participating in this project and its community, you are expected to uphold
this code.

## Commands for running this project for local testing
```bash
# Start the Symfony local server
symfony local:server:start
```

```bash
# Compile and watch assets (including Vue.js components)
yarn encore dev --watch
```

## Vue.js Integration
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:

- `App.vue` is mounted to the element with ID `vue-app`

To add new Vue components:
1. Create the component in the `assets/vue/` directory
2. Import and mount it in `assets/app.js`
3. Add a mount point in the appropriate template
9 changes: 9 additions & 0 deletions assets/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createApp } from 'vue';
import App from './vue/App.vue';

// Mount the main app if the element exists
const appElement = document.getElementById('vue-app');
if (appElement) {
createApp(App).mount('#vue-app');
}

26 changes: 26 additions & 0 deletions assets/vue/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<div>
<h2>Hello from Vue</h2>
<p>{{ message }}</p>
</div>
</template>

<script>
export default {
name: 'App',
data() {
return {
message: 'This is a reusable component!'
}
},
created() {
console.log('App component created');
},
mounted() {
console.log('App component mounted');
},
updated() {
console.log('App component updated');
}
}
</script>
28 changes: 23 additions & 5 deletions composer.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
{
"name": "Xheni Myrtaj",
"email": "[email protected]",
"role": "Maintainer"
"role": "Former developer"
},
{
"name": "Oliver Klee",
"email": "[email protected]",
"role": "Former developer"
},
{
"name": "Tatevik Grigoryan",
"email": "[email protected]",
"role": "Maintainer"
}
],
"support": {
Expand All @@ -30,7 +35,9 @@
},
"require": {
"php": "^8.1",
"phplist/core": "v5.0.0-alpha7"
"phplist/core": "dev-ISSUE-345",
"symfony/twig-bundle": "^6.4",
"symfony/webpack-encore-bundle": "^2.2"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
Expand All @@ -40,7 +47,10 @@
"nette/caching": "^3.1.0",
"nikic/php-parser": "^v4.10.4",
"phpmd/phpmd": "^2.9.1",
"symfony/process": "^6.4"
"symfony/process": "^6.4",
"symfony/panther": "*",
"dbrekelmans/bdi": "*",
"bshaffer/phpunit-retry-annotations": "^0.3.0"
},
"autoload": {
"psr-4": {
Expand Down Expand Up @@ -83,8 +93,16 @@
"symfony-web-dir": "public",
"symfony-tests-dir": "tests",
"phplist/core": {
"bundles": [],
"routes": {}
"bundles": [
"PhpList\\WebFrontend\\PhpListFrontendBundle"
],
"routes": {
"rest-api": {
"resource": "@PhpListFrontendBundle/Controller/",
"type": "attribute",
"prefix": "/"
}
}
}
}
}
Empty file removed config/.gitkeep
Empty file.
5 changes: 5 additions & 0 deletions config/packages/framework.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
framework:
secret: '%kernel.secret%'
http_method_override: false
php_errors:
log: true
5 changes: 5 additions & 0 deletions config/packages/twig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
default_path: '%kernel.application_dir%/templates'
auto_reload: true
31 changes: 31 additions & 0 deletions config/services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# config/services.yaml
parameters:
api_base_url: '%env(API_BASE_URL)%'
env(API_BASE_URL): 'http://api.phplist.local/api/v2'

services:
_defaults:
autowire: true
autoconfigure: true
public: false

PhpList\WebFrontend\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../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
autowire: true
tags: ['controller.service_arguments']

Symfony\Component\HttpFoundation\Session\SessionInterface: '@session'
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"devDependencies": {
"@babel/core": "^7.27.4",
"@babel/preset-env": "^7.27.2",
"@symfony/webpack-encore": "^5.1.0",
"babel-loader": "^10.0.0",
"husky": "^9.1.7",
"vue-loader": "^17.3.1",
"vue-template-compiler": "^2.7.14",
"webpack": "^5.99.9",
"webpack-cli": "^6.0.1",
"webpack-notifier": "^1.15.0"
},
"dependencies": {
"vue": "^3.5.16"
},
"scripts": {
"prepare": "husky install"
}
}
7 changes: 7 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
level: 5
paths:
- bin
- src
- tests
- public
Empty file removed src/.gitkeep
Empty file.
68 changes: 68 additions & 0 deletions src/Controller/AuthController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace PhpList\WebFrontend\Controller;

use Exception;
use GuzzleHttp\Exception\GuzzleException;
use PhpList\WebFrontend\Service\ApiClient;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class AuthController extends AbstractController
{
private ApiClient $apiClient;

public function __construct(ApiClient $apiClient)
{
$this->apiClient = $apiClient;
}

#[Route('/login', name: 'login', methods: ['GET', 'POST'])]
public function login(Request $request): Response
{
if ($request->getSession()->has('auth_token')) {
return $this->redirectToRoute('empty_start_page');
}

$error = null;
$session = $request->getSession();
if ($session->has('login_error')) {
$error = $session->get('login_error');
$session->remove('login_error');
}

if ($request->isMethod('POST')) {
$username = $request->request->get('username');
$password = $request->request->get('password');

try {
$authData = $this->apiClient->authenticate($username, $password);
$request->getSession()->set('auth_token', $authData['key']);
$request->getSession()->set('auth_expiry_date', $authData['key']);
$this->apiClient->setAuthToken($authData['key']);

return $this->redirectToRoute('empty_start_page');
} catch (Exception $e) {
$error = 'Invalid credentials or server error: ' . $e->getMessage();
} catch (GuzzleException $e) {
$error = 'Invalid credentials or server error: ' . $e->getMessage();
}
}

return $this->render('auth/login.html.twig', [
'error' => $error,
]);
}

#[Route('/logout', name: 'logout')]
public function logout(Request $request): Response
{
$request->getSession()->remove('auth_token');

return $this->redirectToRoute('login');
}
}
33 changes: 33 additions & 0 deletions src/DependencyInjection/PhpListFrontendExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace PhpList\WebFrontend\DependencyInjection;

use Exception;
use InvalidArgumentException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class PhpListFrontendExtension extends Extension
{
/**
* Loads a specific configuration.
*
* @param array $configs configuration values
* @param ContainerBuilder $container
*
* @return void
*
* @throws InvalidArgumentException|Exception if the provided tag is not defined in this extension
*/
public function load(array $configs, ContainerBuilder $container): void
{
// @phpstan-ignore-next-line
$configs;
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
$loader->load('services.yml');
}
}
Loading
Loading