www: Add dedicated database for Users (separated from birdnet birds data)
This commit is contained in:
parent
7a39330a6f
commit
3ca0abe147
4
www/.env
4
www/.env
|
@ -31,3 +31,7 @@ DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=14&ch
|
||||||
### records folder
|
### records folder
|
||||||
RECORDS_DIR=%kernel.project_dir%/../var/chunks # adapt to your needs
|
RECORDS_DIR=%kernel.project_dir%/../var/chunks # adapt to your needs
|
||||||
###
|
###
|
||||||
|
|
||||||
|
###> symfony/mailer ###
|
||||||
|
# MAILER_DSN=null://null
|
||||||
|
###< symfony/mailer ###
|
||||||
|
|
|
@ -14,13 +14,18 @@
|
||||||
"symfony/console": "6.1.*",
|
"symfony/console": "6.1.*",
|
||||||
"symfony/dotenv": "6.1.*",
|
"symfony/dotenv": "6.1.*",
|
||||||
"symfony/flex": "^2",
|
"symfony/flex": "^2",
|
||||||
|
"symfony/form": "6.1.*",
|
||||||
"symfony/framework-bundle": "6.1.*",
|
"symfony/framework-bundle": "6.1.*",
|
||||||
|
"symfony/mailer": "6.1.*",
|
||||||
"symfony/proxy-manager-bridge": "6.1.*",
|
"symfony/proxy-manager-bridge": "6.1.*",
|
||||||
"symfony/runtime": "6.1.*",
|
"symfony/runtime": "6.1.*",
|
||||||
|
"symfony/security-bundle": "6.1.*",
|
||||||
"symfony/translation": "6.1.*",
|
"symfony/translation": "6.1.*",
|
||||||
"symfony/twig-bundle": "6.1.*",
|
"symfony/twig-bundle": "6.1.*",
|
||||||
|
"symfony/validator": "6.1.*",
|
||||||
"symfony/webpack-encore-bundle": "^1.15",
|
"symfony/webpack-encore-bundle": "^1.15",
|
||||||
"symfony/yaml": "6.1.*",
|
"symfony/yaml": "6.1.*",
|
||||||
|
"symfonycasts/verify-email-bundle": "^1.11",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"twig/intl-extra": "^3.4",
|
"twig/intl-extra": "^3.4",
|
||||||
"twig/string-extra": "^3.4",
|
"twig/string-extra": "^3.4",
|
||||||
|
@ -79,6 +84,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/maker-bundle": "^1.45"
|
"symfony/maker-bundle": "^1.45",
|
||||||
|
"symfony/stopwatch": "6.1.*",
|
||||||
|
"symfony/web-profiler-bundle": "6.1.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,4 +9,7 @@ return [
|
||||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||||
|
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
doctrine:
|
doctrine:
|
||||||
dbal:
|
dbal:
|
||||||
url: '%env(resolve:DATABASE_URL)%'
|
default_connection: default
|
||||||
|
connections:
|
||||||
|
default:
|
||||||
|
dbname: birdnet_default_db
|
||||||
|
url: '%env(resolve:DATABASE_DEFAULT_URL)%'
|
||||||
|
# wrapper_class: AppBundle\Connections\ConnectionDefault
|
||||||
|
observations:
|
||||||
|
dbname: birdnet_observations_db
|
||||||
|
url: '%env(resolve:DATABASE_OBSERVATIONS_URL)%'
|
||||||
|
wrapper_class: App\AppBundle\Connections\ConnectionObservations
|
||||||
|
|
||||||
# IMPORTANT: You MUST configure your server version,
|
# IMPORTANT: You MUST configure your server version,
|
||||||
# either here or in the DATABASE_URL env var (see .env file)
|
# either here or in the DATABASE_URL env var (see .env file)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
mailer:
|
||||||
|
dsn: '%env(MAILER_DSN)%'
|
|
@ -0,0 +1,43 @@
|
||||||
|
security:
|
||||||
|
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||||
|
password_hashers:
|
||||||
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||||
|
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||||
|
providers:
|
||||||
|
# used to reload user from session & other features (e.g. switch_user)
|
||||||
|
app_user_provider:
|
||||||
|
entity:
|
||||||
|
class: App\Entity\User
|
||||||
|
property: username
|
||||||
|
firewalls:
|
||||||
|
dev:
|
||||||
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
security: false
|
||||||
|
main:
|
||||||
|
lazy: true
|
||||||
|
provider: app_user_provider
|
||||||
|
|
||||||
|
# activate different ways to authenticate
|
||||||
|
# https://symfony.com/doc/current/security.html#the-firewall
|
||||||
|
|
||||||
|
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||||
|
# switch_user: true
|
||||||
|
|
||||||
|
# Easy way to control access for large sections of your site
|
||||||
|
# Note: Only the *first* access control that matches will be used
|
||||||
|
access_control:
|
||||||
|
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||||
|
# - { path: ^/profile, roles: ROLE_USER }
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
security:
|
||||||
|
password_hashers:
|
||||||
|
# By default, password hashers are resource intensive and take time. This is
|
||||||
|
# important to generate secure password hashes. In tests however, secure hashes
|
||||||
|
# are not important, waste resources and increase test times. The following
|
||||||
|
# reduces the work factor to the lowest possible values.
|
||||||
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||||
|
algorithm: auto
|
||||||
|
cost: 4 # Lowest possible value for bcrypt
|
||||||
|
time_cost: 3 # Lowest possible value for argon
|
||||||
|
memory_cost: 10 # Lowest possible value for argon
|
|
@ -0,0 +1,13 @@
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
email_validation_mode: html5
|
||||||
|
|
||||||
|
# Enables validator auto-mapping support.
|
||||||
|
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||||
|
#auto_mapping:
|
||||||
|
# App\Entity\: []
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
not_compromised_password: false
|
|
@ -0,0 +1,17 @@
|
||||||
|
when@dev:
|
||||||
|
web_profiler:
|
||||||
|
toolbar: true
|
||||||
|
intercept_redirects: false
|
||||||
|
|
||||||
|
framework:
|
||||||
|
profiler:
|
||||||
|
only_exceptions: false
|
||||||
|
collect_serializer_data: true
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
web_profiler:
|
||||||
|
toolbar: false
|
||||||
|
intercept_redirects: false
|
||||||
|
|
||||||
|
framework:
|
||||||
|
profiler: { collect: false }
|
|
@ -0,0 +1,8 @@
|
||||||
|
when@dev:
|
||||||
|
web_profiler_wdt:
|
||||||
|
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||||
|
prefix: /_wdt
|
||||||
|
|
||||||
|
web_profiler_profiler:
|
||||||
|
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||||
|
prefix: /_profiler
|
|
@ -23,5 +23,8 @@ services:
|
||||||
- '../src/Entity/'
|
- '../src/Entity/'
|
||||||
- '../src/Kernel.php'
|
- '../src/Kernel.php'
|
||||||
|
|
||||||
|
# AppBundle\Connections\ExtendedConnection\ConnectionDefault: '@doctrine.dbal.default_connection'
|
||||||
|
App\AppBundle\Connections\ConnectionObservations: '@doctrine.dbal.observations_connection'
|
||||||
|
|
||||||
# add more service definitions when explicit configuration is needed
|
# add more service definitions when explicit configuration is needed
|
||||||
# please note that last definitions always *replace* previous ones
|
# please note that last definitions always *replace* previous ones
|
||||||
|
|
|
@ -6,3 +6,9 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "5432"
|
- "5432"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
###> symfony/mailer ###
|
||||||
|
mailer:
|
||||||
|
image: schickling/mailcatcher
|
||||||
|
ports: [1025, 1080]
|
||||||
|
###< symfony/mailer ###
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20220818163807 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
||||||
|
, password VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON user (username)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20220818165607 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, username, roles, password FROM user');
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
||||||
|
, password VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('INSERT INTO user (id, username, roles, password) SELECT id, username, roles, password FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON user (username)');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON user (email)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, username, roles, password FROM user');
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
||||||
|
, password VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('INSERT INTO user (id, username, roles, password) SELECT id, username, roles, password FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON user (username)');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
namespace App\AppBundle\Connections;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
|
class ConnectionObservations extends Connection
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference: https://stackoverflow.com/questions/46235336/autowire-specific-dbal-connection-when-using-multiple-of-them
|
|
@ -4,22 +4,21 @@ namespace App\Controller;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Doctrine\DBAL\Connection;
|
use App\Entity\User;
|
||||||
|
|
||||||
class AuthController extends AbstractController
|
class AuthController extends AbstractController
|
||||||
{
|
{
|
||||||
private Connection $connection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/auth", name="auth")
|
* @Route("/auth", name="auth")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/auth", name="auth_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/auth", name="auth_i18n")
|
||||||
*/
|
*/
|
||||||
public function index(Connection $connection)
|
public function index()
|
||||||
{
|
{
|
||||||
return $this->redirectToRoute("login");
|
return $this->redirectToRoute("login");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/auth/login", name="login")
|
* @Route("/auth/login", name="login")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/auth/login", name="login_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/auth/login", name="login_i18n")
|
||||||
|
@ -30,4 +29,17 @@ class AuthController extends AbstractController
|
||||||
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/auth/register", name="register")
|
||||||
|
* @Route("/{_locale<%app.supported_locales%>}/auth/register", name="register_i18n")
|
||||||
|
*/
|
||||||
|
public function register(UserPasswordHasherInterface $passwordHasher)
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$plaintextPassword = "";
|
||||||
|
$hashedPassword = $passwordHasher->hashPassword($user, $plaintextPassword);
|
||||||
|
$user->setPassword($hashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,18 +6,23 @@ use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
|
use App\AppBundle\Connections\ConnectionObservations;
|
||||||
|
|
||||||
class HomeController extends AbstractController
|
class HomeController extends AbstractController
|
||||||
{
|
{
|
||||||
private Connection $connection;
|
private Connection $connection;
|
||||||
|
|
||||||
|
public function __construct(ConnectionObservations $connection)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("", name="home")
|
* @Route("", name="home")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/", name="home_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/", name="home_i18n")
|
||||||
*/
|
*/
|
||||||
public function index(Connection $connection)
|
public function index()
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
|
||||||
return $this->render('index.html.twig', [
|
return $this->render('index.html.twig', [
|
||||||
"stats" => $this->get_stats(),
|
"stats" => $this->get_stats(),
|
||||||
"charts" => $this->last_chart_generated(),
|
"charts" => $this->last_chart_generated(),
|
||||||
|
|
|
@ -6,12 +6,17 @@ use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Doctrine\DBAL\Connection;
|
use App\AppBundle\Connections\ConnectionObservations;
|
||||||
|
|
||||||
class RecordsController extends AbstractController
|
class RecordsController extends AbstractController
|
||||||
{
|
{
|
||||||
private Connection $connection;
|
private ConnectionObservations $connection;
|
||||||
|
|
||||||
|
public function __construct(ConnectionObservations $connection)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/records", name="records")
|
* @Route("/records", name="records")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/records/{date}", name="records_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/records/{date}", name="records_i18n")
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Form\RegistrationFormType;
|
||||||
|
use App\Security\EmailVerifier;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||||
|
|
||||||
|
class RegistrationController extends AbstractController
|
||||||
|
{
|
||||||
|
private EmailVerifier $emailVerifier;
|
||||||
|
|
||||||
|
public function __construct(EmailVerifier $emailVerifier)
|
||||||
|
{
|
||||||
|
$this->emailVerifier = $emailVerifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/auth/register", name="register")
|
||||||
|
* @Route("/{_locale<%app.supported_locales%>}/auth/register", name="register_i18n")
|
||||||
|
*/
|
||||||
|
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$form = $this->createForm(RegistrationFormType::class, $user);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
// encode the plain password
|
||||||
|
$user->setPassword(
|
||||||
|
$userPasswordHasher->hashPassword(
|
||||||
|
$user,
|
||||||
|
$form->get('plainPassword')->getData()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$entityManager->persist($user);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
// generate a signed url and email it to the user
|
||||||
|
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
|
||||||
|
(new TemplatedEmail())
|
||||||
|
->from(new Address('mailer@1ib.re', 'BirdNET-stream Mail Bot'))
|
||||||
|
->to($user->getEmail())
|
||||||
|
->subject('Please Confirm your Email')
|
||||||
|
->htmlTemplate('registration/confirmation_email.html.twig')
|
||||||
|
);
|
||||||
|
// do anything else you need here, like send an email
|
||||||
|
|
||||||
|
return $this->redirectToRoute('login_i18n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('registration/register.html.twig', [
|
||||||
|
'registrationForm' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/auth/verify/email", name="verify_email")
|
||||||
|
* @Route("/{_locale<%app.supported_locales%>}/auth/verify/email", name="verify_email_i18n")
|
||||||
|
*/
|
||||||
|
public function verifyUserEmail(Request $request, TranslatorInterface $translator): Response
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||||
|
|
||||||
|
// validate email confirmation link, sets User::isVerified=true and persists
|
||||||
|
try {
|
||||||
|
$this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
|
||||||
|
} catch (VerifyEmailExceptionInterface $exception) {
|
||||||
|
$this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle'));
|
||||||
|
|
||||||
|
return $this->redirectToRoute('register_i18n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO Change the redirect on success and handle or remove the flash message in your templates
|
||||||
|
$this->addFlash('success', 'Your email address has been verified.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('login_i18n');
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ class ServicesController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/services/status", name="services_status")
|
* @Route("/services/status", name="services_status")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/services/status", name="service_status_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/services/status", name="services_status_i18n")
|
||||||
*/
|
*/
|
||||||
public function service_status() {
|
public function service_status() {
|
||||||
$status = array_map(function($service) {
|
$status = array_map(function($service) {
|
||||||
|
|
|
@ -5,17 +5,22 @@ use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Doctrine\DBAL\Connection;
|
use App\AppBundle\Connections\ConnectionObservations;
|
||||||
|
|
||||||
class StatsController extends AbstractController
|
class StatsController extends AbstractController
|
||||||
{
|
{
|
||||||
private Connection $connection;
|
private ConnectionObservations $connection;
|
||||||
|
|
||||||
|
public function __construct(ConnectionObservations $connection)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/stats", name="stats")
|
* @Route("/stats", name="stats")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/stats", name="stats_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/stats", name="stats_i18n")
|
||||||
*/
|
*/
|
||||||
public function index(Connection $connection)
|
public function index()
|
||||||
{
|
{
|
||||||
return $this->render("stats/index.html.twig");
|
return $this->render("stats/index.html.twig");
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,30 @@ use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Doctrine\DBAL\Connection;
|
use App\AppBundle\Connections\ConnectionObservations;
|
||||||
|
|
||||||
class TodayController extends AbstractController
|
class TodayController extends AbstractController
|
||||||
{
|
{ private ConnectionObservations $connection;
|
||||||
|
|
||||||
private Connection $connection;
|
public function __construct(ConnectionObservations $connection)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/today", name="today")
|
* @Route("/today", name="today")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/today", name="today_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/today", name="today_i18n")
|
||||||
*/
|
*/
|
||||||
public function today(Connection $connection)
|
public function today(ConnectionObservations $connection)
|
||||||
{
|
{
|
||||||
return $this->redirectToRoute("today_species");
|
return $this->redirectToRoute("today_species_i18n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/today/species", name="today_species")
|
* @Route("/today/species", name="today_species")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/today/species", name="today_species_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/today/species", name="today_species_i18n")
|
||||||
*/
|
*/
|
||||||
public function today_species_page(Connection $connection)
|
public function today_species_page(ConnectionObservations $connection)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$date = date('Y-m-d');
|
$date = date('Y-m-d');
|
||||||
|
@ -39,9 +42,8 @@ class TodayController extends AbstractController
|
||||||
* @Route("/today/species/{id}", name="today_species_id")
|
* @Route("/today/species/{id}", name="today_species_id")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/today/species/{id}", name="today_species_id_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/today/species/{id}", name="today_species_id_i18n")
|
||||||
*/
|
*/
|
||||||
public function today_species_by_id(Connection $connection, $id)
|
public function today_species_by_id($id)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
|
||||||
$date = date('Y-m-d');
|
$date = date('Y-m-d');
|
||||||
return $this->render('today/species.html.twig', [
|
return $this->render('today/species.html.twig', [
|
||||||
"date" => $date,
|
"date" => $date,
|
||||||
|
@ -54,7 +56,7 @@ class TodayController extends AbstractController
|
||||||
* @Route("/today/{date}", name="today_date")
|
* @Route("/today/{date}", name="today_date")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/today/{date}", name="today_date_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/today/{date}", name="today_date_i18n")
|
||||||
*/
|
*/
|
||||||
public function today_date(Connection $connection, $date)
|
public function today_date($date="2022-08-13")
|
||||||
{
|
{
|
||||||
return $this->redirectToRoute('today_species_date', array('date' => $date));
|
return $this->redirectToRoute('today_species_date', array('date' => $date));
|
||||||
}
|
}
|
||||||
|
@ -63,9 +65,8 @@ class TodayController extends AbstractController
|
||||||
* @Route("/today/{date}/species", name="today_species_date")
|
* @Route("/today/{date}/species", name="today_species_date")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/today/{date}/species", name="today_species_date_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/today/{date}/species", name="today_species_date_i18n")
|
||||||
*/
|
*/
|
||||||
public function today_species_by_date(Connection $connection, $date)
|
public function today_species_by_date($date="2022-08-13")
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
|
||||||
return $this->render('today/index.html.twig', [
|
return $this->render('today/index.html.twig', [
|
||||||
"date" => $date,
|
"date" => $date,
|
||||||
"results" => $this->recorded_species_by_date($date)
|
"results" => $this->recorded_species_by_date($date)
|
||||||
|
@ -76,9 +77,8 @@ class TodayController extends AbstractController
|
||||||
* @Route("/today/{date}/species/{id}", name="today_species_id_and_date")
|
* @Route("/today/{date}/species/{id}", name="today_species_id_and_date")
|
||||||
* @Route("/{_locale<%app.supported_locales%>}/today/{date}/species/{id}", name="today_species_id_and_date_i18n")
|
* @Route("/{_locale<%app.supported_locales%>}/today/{date}/species/{id}", name="today_species_id_and_date_i18n")
|
||||||
*/
|
*/
|
||||||
public function today_species_by_id_and_date(Connection $connection, $date, $id)
|
public function today_species_by_id_and_date($id, $date="2022-08-13")
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
|
||||||
return $this->render('today/species.html.twig', [
|
return $this->render('today/species.html.twig', [
|
||||||
"date" => $date,
|
"date" => $date,
|
||||||
"results" => $this->recorded_species_by_id_and_date($id, $date)
|
"results" => $this->recorded_species_by_id_and_date($id, $date)
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||||
|
#[UniqueEntity(fields: ['username'], message: 'There is already an account with this username')]
|
||||||
|
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 180, unique: true)]
|
||||||
|
private ?string $username = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private array $roles = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The hashed password
|
||||||
|
*/
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?string $password = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, unique: true)]
|
||||||
|
private ?string $email = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'boolean')]
|
||||||
|
private $isVerified = false;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsername(): ?string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUsername(string $username): self
|
||||||
|
{
|
||||||
|
$this->username = $username;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visual identifier that represents this user.
|
||||||
|
*
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function getUserIdentifier(): string
|
||||||
|
{
|
||||||
|
return (string) $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function getRoles(): array
|
||||||
|
{
|
||||||
|
$roles = $this->roles;
|
||||||
|
// guarantee every user at least has ROLE_USER
|
||||||
|
$roles[] = 'ROLE_USER';
|
||||||
|
|
||||||
|
return array_unique($roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRoles(array $roles): self
|
||||||
|
{
|
||||||
|
$this->roles = $roles;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see PasswordAuthenticatedUserInterface
|
||||||
|
*/
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(string $password): self
|
||||||
|
{
|
||||||
|
$this->password = $password;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function eraseCredentials()
|
||||||
|
{
|
||||||
|
// If you store any temporary, sensitive data on the user, clear it here
|
||||||
|
// $this->plainPassword = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmail(): ?string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEmail(string $email): self
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isVerified(): bool
|
||||||
|
{
|
||||||
|
return $this->isVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsVerified(bool $isVerified): self
|
||||||
|
{
|
||||||
|
$this->isVerified = $isVerified;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
|
||||||
|
class RegistrationFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('username')
|
||||||
|
->add('agreeTerms', CheckboxType::class, [
|
||||||
|
'mapped' => false,
|
||||||
|
'constraints' => [
|
||||||
|
new IsTrue([
|
||||||
|
'message' => 'You should agree to our terms.',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('plainPassword', PasswordType::class, [
|
||||||
|
// instead of being set onto the object directly,
|
||||||
|
// this is read and encoded in the controller
|
||||||
|
'mapped' => false,
|
||||||
|
'attr' => ['autocomplete' => 'new-password'],
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Please enter a password',
|
||||||
|
]),
|
||||||
|
new Length([
|
||||||
|
'min' => 6,
|
||||||
|
'minMessage' => 'Your password should be at least {{ limit }} characters',
|
||||||
|
// max length allowed by Symfony for security reasons
|
||||||
|
'max' => 4096,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => User::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<User>
|
||||||
|
*
|
||||||
|
* @method User|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method User|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method User[] findAll()
|
||||||
|
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(User $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(User $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->remove($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to upgrade (rehash) the user's password automatically over time.
|
||||||
|
*/
|
||||||
|
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||||||
|
{
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setPassword($newHashedPassword);
|
||||||
|
|
||||||
|
$this->add($user, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return User[] Returns an array of User objects
|
||||||
|
// */
|
||||||
|
// public function findByExampleField($value): array
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('u')
|
||||||
|
// ->andWhere('u.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->orderBy('u.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function findOneBySomeField($value): ?User
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('u')
|
||||||
|
// ->andWhere('u.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||||
|
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
|
||||||
|
|
||||||
|
class EmailVerifier
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private VerifyEmailHelperInterface $verifyEmailHelper,
|
||||||
|
private MailerInterface $mailer,
|
||||||
|
private EntityManagerInterface $entityManager
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void
|
||||||
|
{
|
||||||
|
$signatureComponents = $this->verifyEmailHelper->generateSignature(
|
||||||
|
$verifyEmailRouteName,
|
||||||
|
$user->getId(),
|
||||||
|
$user->getEmail()
|
||||||
|
);
|
||||||
|
|
||||||
|
$context = $email->getContext();
|
||||||
|
$context['signedUrl'] = $signatureComponents->getSignedUrl();
|
||||||
|
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
|
||||||
|
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
|
||||||
|
|
||||||
|
$email->context($context);
|
||||||
|
|
||||||
|
$this->mailer->send($email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws VerifyEmailExceptionInterface
|
||||||
|
*/
|
||||||
|
public function handleEmailConfirmation(Request $request, UserInterface $user): void
|
||||||
|
{
|
||||||
|
$this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail());
|
||||||
|
|
||||||
|
$user->setIsVerified(true);
|
||||||
|
|
||||||
|
$this->entityManager->persist($user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,6 +90,18 @@
|
||||||
"src/Kernel.php"
|
"src/Kernel.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/mailer": {
|
||||||
|
"version": "6.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "4.3",
|
||||||
|
"ref": "97a61eabb351d7f6cb7702039bcfe07fe9d7e03c"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/mailer.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/maker-bundle": {
|
"symfony/maker-bundle": {
|
||||||
"version": "1.45",
|
"version": "1.45",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -112,6 +124,18 @@
|
||||||
"config/routes.yaml"
|
"config/routes.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/security-bundle": {
|
||||||
|
"version": "6.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.0",
|
||||||
|
"ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/security.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/translation": {
|
"symfony/translation": {
|
||||||
"version": "6.1",
|
"version": "6.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -138,6 +162,31 @@
|
||||||
"templates/base.html.twig"
|
"templates/base.html.twig"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/validator": {
|
||||||
|
"version": "6.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/validator.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/web-profiler-bundle": {
|
||||||
|
"version": "6.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.1",
|
||||||
|
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/web_profiler.yaml",
|
||||||
|
"config/routes/web_profiler.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/webpack-encore-bundle": {
|
"symfony/webpack-encore-bundle": {
|
||||||
"version": "1.15",
|
"version": "1.15",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -157,6 +206,9 @@
|
||||||
"webpack.config.js"
|
"webpack.config.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfonycasts/verify-email-bundle": {
|
||||||
|
"version": "v1.11.0"
|
||||||
|
},
|
||||||
"twig/extra-bundle": {
|
"twig/extra-bundle": {
|
||||||
"version": "v3.4.0"
|
"version": "v3.4.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,51 +8,42 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'home',
|
route: 'home',
|
||||||
url: '/',
|
|
||||||
text: 'Home'|trans
|
text: 'Home'|trans
|
||||||
} %}
|
} %}
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'about',
|
route: 'about',
|
||||||
url: '/about',
|
|
||||||
text: 'About'|trans
|
text: 'About'|trans
|
||||||
} %}
|
} %}
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'today',
|
route: 'today',
|
||||||
url: '/today',
|
|
||||||
text: "Today's Detections"|trans
|
text: "Today's Detections"|trans
|
||||||
} %}
|
} %}
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'spectro',
|
route: 'spectro',
|
||||||
url: '/spectro',
|
|
||||||
text: 'Live Spectrogram'|trans
|
text: 'Live Spectrogram'|trans
|
||||||
} %}
|
} %}
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'stats',
|
route: 'stats',
|
||||||
url: '/stats',
|
|
||||||
text: 'Statistics'|trans
|
text: 'Statistics'|trans
|
||||||
} %}
|
} %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'records',
|
route: 'records',
|
||||||
url: '/records',
|
|
||||||
text: 'Recordings'|trans
|
text: 'Recordings'|trans
|
||||||
} %}
|
} %}
|
||||||
<ul class="dropdown-content">
|
<ul class="dropdown-content">
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'records_best',
|
route: 'records_best',
|
||||||
url: '/records/best',
|
|
||||||
text: 'Best Recordings'|trans
|
text: 'Best Recordings'|trans
|
||||||
} %}
|
} %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'logs',
|
route: 'logs',
|
||||||
url: '/logs',
|
|
||||||
text: 'View Logs'|trans
|
text: 'View Logs'|trans
|
||||||
} %}
|
} %}
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'services_status',
|
route: 'services_status',
|
||||||
url: '/services/status',
|
|
||||||
text: 'Status'|trans
|
text: 'Status'|trans
|
||||||
} %}
|
} %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<h1>Hi! Please confirm your email!</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please confirm your email address by clicking the following link: <br><br>
|
||||||
|
<a href="{{ signedUrl }}">Confirm my Email</a>.
|
||||||
|
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Cheers!
|
||||||
|
</p>
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Register{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% for flash_error in app.flashes('verify_email_error') %}
|
||||||
|
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<h1>Register</h1>
|
||||||
|
|
||||||
|
{{ form_start(registrationForm) }}
|
||||||
|
{{ form_row(registrationForm.username) }}
|
||||||
|
{{ form_row(registrationForm.plainPassword, {
|
||||||
|
label: 'Password'
|
||||||
|
}) }}
|
||||||
|
{{ form_row(registrationForm.agreeTerms) }}
|
||||||
|
|
||||||
|
<button type="submit" class="btn">Register</button>
|
||||||
|
{{ form_end(registrationForm) }}
|
||||||
|
{% endblock %}
|
|
@ -3,10 +3,7 @@
|
||||||
<a class="language-item"
|
<a class="language-item"
|
||||||
href="{{
|
href="{{
|
||||||
path(
|
path(
|
||||||
route,
|
route, app.request.attributes.get('_route_params')|merge({'_locale': locale})
|
||||||
{
|
|
||||||
_locale: locale
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}}">{{ name }}
|
}}">{{ name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<li>
|
<li>
|
||||||
<a class="{{ app.request.get('_route') matches '{' ~ route ~ '_*}' ? 'active' }}"
|
<a class="{{ app.request.get('_route') matches '{' ~ route ~ '_*}' ? 'active' }}"
|
||||||
href="{{ path(route) }}">
|
href="{{ path(route ~ "_i18n", { _locale: app.request.locale }) }}">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue