Skip to content

Commit

Permalink
feat: Add localized email templates for password recovery and team in…
Browse files Browse the repository at this point in the history
…formation in greek (M2-8109) (#1652)

* feat: Add localized email templates for password recovery and team information in greek

* refactor: Update get_tz_utc_offset function to handle timezone input and exceptions

* refactor: Enhance get_tz_utc_offset function to support dependency injection and improve timezone handling + revert deps change

* refactor: ruff format

* feat: Update password recovery service to use language parameter for localized email subject

* fix: fixing dupe subject property

* fix: fixing missing french template

* fix: ruff format

* fix: correct content language handling in password recovery

* fix: using now infrastructure.http to retrieve language

* fix: revert incorrect change

---------

Co-authored-by: Ramir Mesquita <[email protected]>
  • Loading branch information
ramirlm and Ramir Mesquita authored Nov 13, 2024
1 parent 8f7f719 commit 3d49920
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/apps/mailing/static/templates/blocks/team_info_el.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<tr>
<td style="padding-top: 24px; padding-bottom: 1%">– Η Ομάδα MindLogger</td>
</tr>
52 changes: 52 additions & 0 deletions src/apps/mailing/static/templates/footers/footer_info_el.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<table
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%; text-align: center"
>
<tr>
<td>
<table
border="0"
cellpadding="0"
cellspacing="0"
style="
width: 100%;
background: #eff4fa;
padding: 26px;
border-radius: 8px;
text-align: center;
"
>
<tr>
<td colspan="2" style="padding-bottom: 14px; font-size: 14px">
Κατεβάστε την εφαρμογή MindLogger για κινητές συσκευές:
</td>
</tr>
<tr>
<td width="50%" align="right" style="padding-right: 4px">
<a href="https://apps.apple.com/br/app/mindlogger/id1299242097"><img src="https://media.mindlogger.org/apple-store.png" style="height: 44px" alt="Λήψη εφαρμογής για iOS"/></a>
</td>
<td width="50%" align="left" style="padding-left: 4px">
<a href="https://play.google.com/store/apps/details?id=lab.childmindinstitute.data&amp;pli=1"><img src="https://media.mindlogger.org/google-play.png" style="height: 44px" alt="Λήψη εφαρμογής για Android"/></a>
</td>
</tr>
<tr>
<td colspan="2" style="padding-top: 23px; font-size: 14px">
Χρειάζεστε βοήθεια; <a href="https://mindlogger.atlassian.net/servicedesk/customer/portals" style="color: black">Κέντρο βοήθειας</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan="2" style="padding-top: 32px">
<img src="https://media.mindlogger.org/cmi-logo.png" style="width: 106px" alt="Child Mind Institute"/>
</td>
</tr>
<tr>
<td colspan="2" style="color: #51606f; padding-top: 12px; font-size: 12px">
Το Child Mind Institute είναι ο δημιουργός του MindLogger, ωστόσο δεν είναι υπεύθυνο για το περιεχόμενο που δημιουργείται από εξωτερικούς χρήστες.
</td>
</tr>
</table>
29 changes: 29 additions & 0 deletions src/apps/mailing/static/templates/reset_password_el.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<html style="max-width: 450px; line-height: 2.1rem; font-size: 10px" lang="el">
<body>
{% include 'header.html' %}
<table style="padding: 0; font-size: 1.4rem">
<tr>
<td style="padding-bottom: 16px">
Λάβαμε ένα αίτημα για επαναφορά του κωδικού πρόσβασής σας.
</td>
</tr>
<tr>
<td style="padding-bottom: 16px">
<a href="{{url}}">Κάντε κλικ στον σύνδεσμο αυτόν</a> για επαναφορά του κωδικού πρόσβασής σας τώρα. Ο σύνδεσμος θα λήξει σε 15 λεπτά.
</td>
</tr>
<tr>
<td style="padding-bottom: 16px">
Εάν δεν πραγματοποιήσατε εσείς το αίτημα αυτό, μπορείτε να αγνοήσετε το συγκεκριμένο email.
</td>
</tr>
<tr>
<td>
<i>Μετά την ενημέρωση του κωδικού πρόσβασής σας, δεν θα βλέπετε τα προηγούμενα δεδομένα σας στην εφαρμογή. Αλλά μην ανησυχείτε, τα έχουμε ακόμα.</i>
</td>
</tr>
{% include 'blocks/team_info_el.html' %}
</table>
{% include 'footers/footer_info_el.html' %}
</body>
</html>
31 changes: 31 additions & 0 deletions src/apps/mailing/static/templates/reset_password_fr.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<html style="max-width: 450px; line-height: 2.1rem; font-size: 10px" lang="fr">
<body>
{% include 'header.html' %}
<table style="padding: 0; font-size: 1.4rem">
<tr>
<td style="padding-bottom: 16px">
Nous avons reçu une demande de réinitialisation de votre mot de passe.
</td>
</tr>
<tr>
<td style="padding-bottom: 16px">
<a href="{{url}}">Cliquez sur ce lien</a> pour réinitialiser votre mot de passe maintenant. Le lien expirera dans 15 minutes.
</td>
</tr>
<tr>
<td style="padding-bottom: 16px">
Si vous n'avez pas fait cette demande, vous pouvez ignorer cet e-mail.
</td>
</tr>
<tr>
<td>
<i>
Après avoir mis à jour votre mot de passe, vous ne verrez plus vos données passées dans l'application. Mais ne vous inquiétez pas, nous les avons toujours.
</i>
</td>
</tr>
{% include 'blocks/team_info_fr.html' %}
</table>
{% include 'footers/footer_info_fr.html' %}
</body>
</html>
4 changes: 3 additions & 1 deletion src/apps/users/api/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from infrastructure.cache import CacheNotFound, PasswordRecoveryHealthCheckNotValid
from infrastructure.database import atomic
from infrastructure.database.deps import get_session
from infrastructure.http import get_language
from infrastructure.http.deps import get_mindlogger_content_source


Expand Down Expand Up @@ -64,6 +65,7 @@ async def password_recovery(
request: Request,
schema: PasswordRecoveryRequest = Body(...),
session=Depends(get_session),
language: str = Depends(get_language),
) -> EmptyResponse:
"""General endpoint for sending password recovery email
and stored info in Redis.
Expand All @@ -72,7 +74,7 @@ async def password_recovery(
async with atomic(session):
try:
content_source = await get_mindlogger_content_source(request)
await PasswordRecoveryService(session).send_password_recovery(schema, content_source)
await PasswordRecoveryService(session).send_password_recovery(schema, content_source, language)
except UserNotFound:
pass # mute error in terms of user enumeration vulnerability

Expand Down
3 changes: 2 additions & 1 deletion src/apps/users/services/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ async def send_password_recovery(
self,
schema: PasswordRecoveryRequest,
content_source: MindloggerContentSource,
language: str,
) -> PublicUser:
user: User = await UsersCRUD(self.session).get_by_email(schema.email)

Expand Down Expand Up @@ -93,7 +94,7 @@ async def send_password_recovery(
subject="Password reset",
body=service.get_localized_html_template(
template_name="reset_password",
language="en",
language=language,
email=user.email_encrypted,
expiration_minutes=exp,
url=url,
Expand Down
11 changes: 10 additions & 1 deletion src/infrastructure/tests/unit/http/test_deps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import datetime

import pytest
import pytz
from fastapi import HTTPException, Request

from infrastructure.http import get_local_tz, get_tz_utc_offset
Expand Down Expand Up @@ -53,4 +56,10 @@ def test_get_local_tz__exception(headers: list, required: bool, details: str):
),
)
def test_get_tz_utc_offset(timezone: str | None, offset: int):
assert get_tz_utc_offset()(timezone) == offset
offset_without_dst = offset
if timezone is not None and offset is not None:
tz = pytz.timezone(timezone)
now = datetime.datetime.now().astimezone(tz)
now_dst_delta = now.dst()
offset_without_dst = offset + int(now_dst_delta.total_seconds() if now_dst_delta else 0)
assert get_tz_utc_offset()(timezone) == offset_without_dst

0 comments on commit 3d49920

Please sign in to comment.