Compare commits
5 Commits
v2026.2.1-
...
v2026.2.1-
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a8799bb7f | |||
| 6c443d8e86 | |||
| 8795fedda9 | |||
| 588fb57299 | |||
| eb345e17ca |
@@ -2,11 +2,12 @@ Changelog
|
||||
=========
|
||||
|
||||
|
||||
v2026.2.1 (2026-04-15)
|
||||
----------------------
|
||||
(unreleased)
|
||||
------------
|
||||
|
||||
New
|
||||
~~~
|
||||
- Add notification email when a user is registered #4. [Lang]
|
||||
- Add Contact page with email sending behaviour #4. [Lang]
|
||||
- Add timer for the acceptance of the challenge #4. [Lang]
|
||||
- Registered users have avatars next to the timer #4. [Lang]
|
||||
@@ -20,6 +21,7 @@ New
|
||||
|
||||
Changes
|
||||
~~~~~~~
|
||||
- Add notification on activation too #4. [Lang]
|
||||
- Change the shareable battle - add avatars to it - even on the og tags
|
||||
#4. [Lang]
|
||||
- Change text #4. [Lang]
|
||||
@@ -71,6 +73,8 @@ Changes
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Another attempt to fix the email assets #4. [Lang]
|
||||
- The images does not shows in emails #4. [Lang]
|
||||
- Missing font-awesome icons on bare-metal environment #4. [Lang]
|
||||
- Quickfix for email sending #4. [Lang]
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
@@ -41,6 +42,12 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
#[AsController]
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(env: 'APP_CONTACT_MAIL_ADDRESS')]
|
||||
private readonly string $appContactMailAddress,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/login', name: 'MineSeekerBundle_login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
@@ -92,6 +99,11 @@ class SecurityController extends AbstractController
|
||||
UrlGeneratorInterface::ABSOLUTE_URL,
|
||||
);
|
||||
|
||||
/** Ensure HTTPS scheme in production */
|
||||
if ($this->getParameter('kernel.environment') === 'prod') {
|
||||
$activationUrl = str_replace('http://', 'https://', $activationUrl);
|
||||
}
|
||||
|
||||
$mailer->send(
|
||||
new TemplatedEmail()
|
||||
->from('noreply@mineseeker.hu')
|
||||
@@ -104,6 +116,19 @@ class SecurityController extends AbstractController
|
||||
])
|
||||
);
|
||||
|
||||
/** Send admin notification about new user registration */
|
||||
$mailer->send(
|
||||
new TemplatedEmail()
|
||||
->from('noreply@mineseeker.hu')
|
||||
->to($this->appContactMailAddress)
|
||||
->subject('🎉 New User Registration: ' . $user->getUsername())
|
||||
->htmlTemplate('emails/user_registration_notification.html.twig')
|
||||
->context([
|
||||
'user' => $user,
|
||||
'registeredAt' => new DateTime(),
|
||||
])
|
||||
);
|
||||
|
||||
$this->addFlash('verify_email', $user->getEmail());
|
||||
|
||||
return $this->redirectToRoute('MineSeekerBundle_register');
|
||||
@@ -143,6 +168,11 @@ class SecurityController extends AbstractController
|
||||
UrlGeneratorInterface::ABSOLUTE_URL,
|
||||
);
|
||||
|
||||
/** Ensure HTTPS scheme in production */
|
||||
if ($this->getParameter('kernel.environment') === 'prod') {
|
||||
$resetUrl = str_replace('http://', 'https://', $resetUrl);
|
||||
}
|
||||
|
||||
$mailer->send(
|
||||
new TemplatedEmail()
|
||||
->from('noreply@mineseeker.hu')
|
||||
@@ -199,7 +229,7 @@ class SecurityController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/activate/{token}', name: 'MineSeekerBundle_activate')]
|
||||
public function activate(string $token, EntityManagerInterface $em): Response
|
||||
public function activate(string $token, EntityManagerInterface $em, MailerInterface $mailer): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->findOneBy(['verificationToken' => $token]);
|
||||
|
||||
@@ -211,6 +241,19 @@ class SecurityController extends AbstractController
|
||||
$user->setIsVerified(true)->setVerificationToken(null);
|
||||
$em->flush();
|
||||
|
||||
/** Send admin notification about account activation */
|
||||
$mailer->send(
|
||||
new TemplatedEmail()
|
||||
->from('noreply@mineseeker.hu')
|
||||
->to($this->appContactMailAddress)
|
||||
->subject('✅ User Account Activated: ' . $user->getUsername())
|
||||
->htmlTemplate('emails/user_activation_notification.html.twig')
|
||||
->context([
|
||||
'user' => $user,
|
||||
'activatedAt' => new DateTime(),
|
||||
])
|
||||
);
|
||||
|
||||
$this->addFlash('success', 'Your account is now active. Welcome, ' . $user->getUsername() . '!');
|
||||
|
||||
return $this->redirectToRoute('MineSeekerBundle_login');
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
{% block title %} - Battle Report{% endblock %}
|
||||
|
||||
{% block metas %}
|
||||
{%- set shareUrl = url('MineSeekerBundle_battle_share', { uuid: game.uuid }) -%}
|
||||
{%- set _ogImage = url('MineSeekerBundle_og_battle', { uuid: game.uuid }) -%}
|
||||
{%- set shareUrl = url('MineSeekerBundle_battle_share', { uuid: game.uuid }) | replace({'http://': 'https://'}) -%}
|
||||
{%- set _ogImage = url('MineSeekerBundle_og_battle', { uuid: game.uuid }) | replace({'http://': 'https://'}) -%}
|
||||
<meta property="og:url" content="{{ shareUrl }}"/>
|
||||
<meta property="og:type" content="article"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block metas %}
|
||||
{%- set _ogImage = app.request.getSchemeAndHttpHost() ~ asset('images/mine-1600x627.png') -%}
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_homepage') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_homepage') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
<meta property="og:locale" content="en_US"/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block metas %}
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_gamePlay') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_gamePlay') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:title" content="Your friend challenges YOU!"/>
|
||||
<meta property="og:description" content="Do you accept the challenge?"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block metas %}
|
||||
{%- set _ogImage = app.request.getSchemeAndHttpHost() ~ asset('images/mine-1600x627.png') -%}
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_contact') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_contact') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
<meta property="og:title" content="Contact · MineSeeker"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block metas %}
|
||||
{%- set _ogImage = app.request.getSchemeAndHttpHost() ~ asset('images/mine-1600x627.png') -%}
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_privacy') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_privacy') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
<meta property="og:title" content="Privacy Policy · MineSeeker"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block metas %}
|
||||
{%- set _ogImage = app.request.getSchemeAndHttpHost() ~ asset('images/mine-1600x627.png') -%}
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_terms') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_terms') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
<meta property="og:title" content="Terms of Use · MineSeeker"/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block metas %}
|
||||
{%- set _ogImage = app.request.getSchemeAndHttpHost() ~ asset('images/mine-1600x627.png') -%}
|
||||
<meta name="robots" content="noindex,nofollow"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_profile') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_profile') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="profile"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
<meta property="og:title" content="{{ app.user.username }} · MineSeeker"/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block metas %}
|
||||
{%- set _ogImage = app.request.getSchemeAndHttpHost() ~ asset('images/mine-1600x627.png') -%}
|
||||
<meta name="robots" content="noindex,nofollow"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_profile_security') }}"/>
|
||||
<meta property="og:url" content="{{ url('MineSeekerBundle_profile_security') | replace({'http://': 'https://'}) }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="MineSeeker"/>
|
||||
<meta property="og:title" content="Security Settings · MineSeeker"/>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="logo">
|
||||
<img src="{{ absolute_url(asset('images/mine-logo-txt.png')) }}" alt="MineSeeker"/>
|
||||
<img src="{{ absolute_url(asset('images/mine-logo-txt.png')) | replace({'http://': 'https://'}) }}" alt="MineSeeker"/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>One step to go</h1>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="logo">
|
||||
<img src="{{ absolute_url(asset('images/mine-logo-txt.png')) }}" alt="MineSeeker"/>
|
||||
<img src="{{ absolute_url(asset('images/mine-logo-txt.png')) | replace({'http://': 'https://'}) }}" alt="MineSeeker"/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>Reset your password</h1>
|
||||
|
||||
92
templates/emails/user_activation_notification.html.twig
Normal file
92
templates/emails/user_activation_notification.html.twig
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>User Account Activated</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
.content {
|
||||
background: #f9f9f9;
|
||||
padding: 30px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
.field {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.field-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.field-value {
|
||||
background: white;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #667eea;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>✅ User Account Activated</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="field">
|
||||
<div class="field-label">Username</div>
|
||||
<div class="field-value">
|
||||
<strong>{{ user.username }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field-label">Email</div>
|
||||
<div class="field-value">
|
||||
<a href="mailto:{{ user.email }}">{{ user.email }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field-label">Details</div>
|
||||
<div class="field-value">
|
||||
<strong>Activated:</strong> {{ activatedAt|date('Y-m-d H:i:s') }}<br>
|
||||
<strong>Status:</strong> ✓ Email Verified - Account Active<br>
|
||||
<strong>Email Verified:</strong> Yes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
A user has successfully verified their email and activated their account on MineSeeker.<br>
|
||||
They can now play games immediately.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
92
templates/emails/user_registration_notification.html.twig
Normal file
92
templates/emails/user_registration_notification.html.twig
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>New User Registration</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
.content {
|
||||
background: #f9f9f9;
|
||||
padding: 30px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
.field {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.field-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.field-value {
|
||||
background: white;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #667eea;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>👤 New User Registration</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="field">
|
||||
<div class="field-label">Username</div>
|
||||
<div class="field-value">
|
||||
<strong>{{ user.username }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field-label">Email</div>
|
||||
<div class="field-value">
|
||||
<a href="mailto:{{ user.email }}">{{ user.email }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field-label">Details</div>
|
||||
<div class="field-value">
|
||||
<strong>Registered:</strong> {{ registeredAt|date('Y-m-d H:i:s') }}<br>
|
||||
<strong>Status:</strong> {% if user.isVerified %}✓ Verified{% else %}⏳ Awaiting Email Verification{% endif %}<br>
|
||||
<strong>Email Verified:</strong> {% if user.isVerified %}Yes{% else %}No - activation link sent{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
A new user has registered on MineSeeker.<br>
|
||||
User must verify their email before account is fully activated.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user