diff --git a/packages/notifications-library/src/notifications_library/_email_render.py b/packages/notifications-library/src/notifications_library/_email_render.py index 57f6864a294..a462778a234 100644 --- a/packages/notifications-library/src/notifications_library/_email_render.py +++ b/packages/notifications-library/src/notifications_library/_email_render.py @@ -11,7 +11,7 @@ class EmailPartsTuple(NamedTuple): - suject: str + subject: str text_content: str html_content: str | None @@ -58,5 +58,5 @@ def render_email_parts( html_content = None return EmailPartsTuple( - suject=subject, text_content=text_content, html_content=html_content + subject=subject, text_content=text_content, html_content=html_content ) diff --git a/packages/notifications-library/src/notifications_library/_models.py b/packages/notifications-library/src/notifications_library/_models.py index 6a4a3d77b30..6b52d8d4007 100644 --- a/packages/notifications-library/src/notifications_library/_models.py +++ b/packages/notifications-library/src/notifications_library/_models.py @@ -16,15 +16,23 @@ class JinjaTemplateDbGet: @dataclass(frozen=True) class UserData: + user_name: str first_name: str last_name: str email: str +@dataclass(frozen=True) +class SharerData: + user_name: str + message: str + + @dataclass(frozen=True) class ProductUIData: logo_url: str strong_color: str + project_alias: str @dataclass(frozen=True) diff --git a/packages/notifications-library/src/notifications_library/_repository.py b/packages/notifications-library/src/notifications_library/_repository.py index a202d1a7422..bb211fa82db 100644 --- a/packages/notifications-library/src/notifications_library/_repository.py +++ b/packages/notifications-library/src/notifications_library/_repository.py @@ -25,6 +25,7 @@ class UsersRepo(_BaseRepo): async def get_user_data(self, user_id: UserID) -> UserData: query = sa.select( # NOTE: careful! privacy applies here! + users.c.name, users.c.first_name, users.c.last_name, users.c.email, @@ -38,7 +39,10 @@ async def get_user_data(self, user_id: UserID) -> UserData: raise ValueError(msg) return UserData( - first_name=row.first_name, last_name=row.last_name, email=row.email + user_name=row.name, + first_name=row.first_name, + last_name=row.last_name, + email=row.email, ) diff --git a/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.html index 761028a0a5e..bc328c4cc01 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} {% include 'on_change_email.email.subject.txt' %} {% endblock %} {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

You have requested to change the email address linked to your {{ product.display_name }} account. Please follow the on-screen information in the link below: diff --git a/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.txt index 6e5f56388de..8f837cee41d 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_change_email.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, You have requested to change the email address linked to your {{ product.display_name }} account. Please follow the on-screen information in the link below: diff --git a/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.html index 15065a34bc6..7b88926ef60 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} {% include 'on_new_code.email.subject.txt' %} {% endblock %} {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

Please find below the Two-factor Authentication sign-in code for your {{ host }} account.

diff --git a/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.txt index c312ce6ef83..07c54d9f01f 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_new_code.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, Please find below the Two-factor Authentication sign-in code for your {{ host }} account. diff --git a/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.html index 2e6105f7f3f..1b5b5998a9a 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} {% include 'on_new_invitation.email.subject.txt' %} {% endblock %} {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

Thank you for your interest in {{ product.display_name }}. We are pleased to provide you with a one-time invitation link to register on the platform.

diff --git a/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.txt index a9182019602..3cd0ded7186 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_new_invitation.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, Thank you for your interest in {{ product.display_name }}. We are pleased to provide you with a one-time invitation link to register on the platform. diff --git a/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.html index 8ce5e627cc5..c7ffbee377c 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.html @@ -4,7 +4,7 @@ {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

We are delighted to confirm the successful processing of your payment of {{ payment.price_dollars }} USD for the purchase of {{ payment.osparc_credits }} credits. The credits have been added to your {{ product.display_name }} account, and you are all set to utilize them.

For more details you can view or download your receipt.

diff --git a/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.txt index 6b4b58a3097..4e0609d2018 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_payed.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, We are delighted to confirm the successful processing of your payment of {{ payment.price_dollars }} USD for the purchase of {{ payment.osparc_credits }} credits. The credits have been added to your {{ product.display_name }} account, and you are all set to utilize them. diff --git a/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.html index d3f1217e7ea..9c64a687e12 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} {% include 'on_registered.email.subject.txt' %} {% endblock %} {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

Thank you for your interest in {{ product.display_name }}. You have successfully registered for {{ host }}.

Please activate your account via the link below:

diff --git a/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.txt index b8e6ad9ec97..31d29aeae73 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_registered.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, Thank you for your interest in {{ product.display_name }}. You have successfully registered for {{ host }}. diff --git a/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.html index 687eb1671e0..12d73ee939d 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} {% include 'on_reset_password.email.subject.txt' %} {% endblock %} {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

A request to reset your {{ host }} password has been made.

{% if success %} diff --git a/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.txt index 35726a0fbca..f19749194e8 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_reset_password.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, A request to reset your {{ host }} password has been made. {% if success %} diff --git a/packages/notifications-library/src/notifications_library/templates/on_share_project.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_share_project.email.content.html new file mode 100644 index 00000000000..2bfd9404271 --- /dev/null +++ b/packages/notifications-library/src/notifications_library/templates/on_share_project.email.content.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block title %} {% include 'on_share_project.email.subject.txt' %} {% endblock %} +{% block content %} +

Dear {{ user.first_name or user.user_name }},

+ +

Great news! {{ sharer.user_name }} has shared a {{ product.ui.project_alias }} with you on {{ product.display_name }}.

+ +

To view the {{ product.ui.project_alias }} and accept the sharing, follow below:

+ +

+{% if sharer.message %} +

+

{{ sharer.message }}

+ +
+{% else %} + +{% endif %} +

+ +

Please don't hesitate to contact us at {{ product.support_email }} if you need further help.

+ +

Best Regards,

+

The {{ product.display_name }} Team

+{% endblock %} diff --git a/packages/notifications-library/src/notifications_library/templates/on_share_project.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_share_project.email.content.txt new file mode 100644 index 00000000000..2fae91408f5 --- /dev/null +++ b/packages/notifications-library/src/notifications_library/templates/on_share_project.email.content.txt @@ -0,0 +1,13 @@ +Dear {{ user.first_name or user.user_name }}, + +Great news! {{ sharer.user_name }} has shared a {{ product.ui.project_alias }} with you on {{ product.display_name }}. + +To view the {{ product.ui.project_alias }} and accept the sharing, follow below: + +{{ sharer.message }} +{{ accept_link }} + +Please don't hesitate to contact us at {{ product.support_email }} if you need further help. + +Best Regards, +The {{ product.display_name }} Team diff --git a/packages/notifications-library/src/notifications_library/templates/on_share_project.email.subject.txt b/packages/notifications-library/src/notifications_library/templates/on_share_project.email.subject.txt new file mode 100644 index 00000000000..0a7f2157a39 --- /dev/null +++ b/packages/notifications-library/src/notifications_library/templates/on_share_project.email.subject.txt @@ -0,0 +1 @@ +A {{ product.ui.project_alias }} was shared with you on {{ host }} diff --git a/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.html b/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.html index c3a593fdd8a..49cee1f4906 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.html +++ b/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} {% include 'on_unregister.email.subject.txt' %} {% endblock %} {% block content %} -

Dear {{ user.first_name }},

+

Dear {{ user.first_name or user.user_name }},

We have received your account closure request, and we want to say thank you for being a part of our platform. While we're sad to see you go, we respect your decision. diff --git a/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.txt b/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.txt index d955504700c..0b45d4afc66 100644 --- a/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.txt +++ b/packages/notifications-library/src/notifications_library/templates/on_unregister.email.content.txt @@ -1,4 +1,4 @@ -Dear {{ user.first_name }}, +Dear {{ user.first_name or user.user_name }}, We have received your account closure request, and we want to say thank you for being a part of our platform. While we're sad to see you go, we respect your decision. diff --git a/packages/notifications-library/tests/conftest.py b/packages/notifications-library/tests/conftest.py index 55891dc77d0..7969423c738 100644 --- a/packages/notifications-library/tests/conftest.py +++ b/packages/notifications-library/tests/conftest.py @@ -9,8 +9,14 @@ import notifications_library import pytest +from faker import Faker from models_library.products import ProductName -from notifications_library._models import ProductData, ProductUIData, UserData +from notifications_library._models import ( + ProductData, + ProductUIData, + SharerData, + UserData, +) from notifications_library.payments import PaymentData from pydantic import EmailStr from pytest_simcore.helpers.typing_env import EnvVarsDict @@ -61,6 +67,7 @@ def product_data( "https://raw.githubusercontent.com/ITISFoundation/osparc-simcore/refs/heads/master/services/static-webserver/client/source/resource/osparc/osparc-white.svg", ), strong_color=vendor.get("ui", {}).get("strong_color", "rgb(131, 0, 191)"), + project_alias=vendor.get("ui", {}).get("project_alias", "project"), ) return ProductData( # type: ignore @@ -75,15 +82,24 @@ def product_data( @pytest.fixture def user_data( - user_email: EmailStr, user_first_name: str, user_last_name: str + user_name: str, user_email: EmailStr, user_first_name: str, user_last_name: str ) -> UserData: return UserData( + user_name=user_name, first_name=user_first_name, last_name=user_last_name, email=user_email, ) +@pytest.fixture +def sharer_data(user_name: str, faker: Faker) -> SharerData: + return SharerData( + user_name=user_name, + message=faker.random_element(elements=(faker.sentence(), "")), + ) + + @pytest.fixture def payment_data(successful_transaction: dict[str, Any]) -> PaymentData: return PaymentData( diff --git a/packages/notifications-library/tests/email/test_email_events.py b/packages/notifications-library/tests/email/test_email_events.py index 9f82a1d8bf7..cacbfc6578a 100644 --- a/packages/notifications-library/tests/email/test_email_events.py +++ b/packages/notifications-library/tests/email/test_email_events.py @@ -21,6 +21,7 @@ import functools import json +from dataclasses import asdict from pathlib import Path from typing import Any from unittest.mock import AsyncMock, MagicMock @@ -41,7 +42,7 @@ get_user_address, render_email_parts, ) -from notifications_library._models import ProductData, UserData +from notifications_library._models import ProductData, SharerData, UserData from notifications_library._render import ( create_render_environment_from_notifications_library, ) @@ -135,6 +136,16 @@ def event_extra_data( # noqa: PLR0911 "reason": faker.sentence(), "link": f"{host_url}?reset-password={code}", } + case "on_share_project": + return { + "host": host_url, + "resource_alias": "Project", + "sharer": SharerData( + user_name=faker.name(), + message=faker.random_element(elements=(faker.sentence(), "")), + ), + "accept_link": f"{host_url}?code={code}", + } case "on_unregister": return { "host": host_url, @@ -169,6 +180,7 @@ def event_attachments(event_name: str, faker: Faker) -> list[tuple[bytes, str]]: "on_payed", "on_registered", "on_reset_password", + "on_share_project", "on_unregister", ], ) @@ -177,6 +189,7 @@ async def test_email_event( smtp_mock_or_none: MagicMock | None, user_data: UserData, user_email: EmailStr, + sharer_data: SharerData | None, product_data: ProductData, product_name: ProductName, event_name: str, @@ -187,6 +200,8 @@ async def test_email_event( assert user_data.email == user_email assert product_data.product_name == product_name + event_extra_data = event_extra_data | (asdict(sharer_data) if sharer_data else {}) + parts = render_email_parts( env=create_render_environment_from_notifications_library( undefined=StrictUndefined @@ -207,7 +222,7 @@ async def test_email_event( msg = compose_email( from_, to, - subject=parts.suject, + subject=parts.subject, content_text=parts.text_content, content_html=parts.html_content, ) @@ -278,7 +293,7 @@ async def test_email_with_reply_to( msg = compose_email( from_, to, - subject=parts.suject, + subject=parts.subject, content_text=parts.text_content, content_html=parts.html_content, reply_to=reply_to, diff --git a/packages/notifications-library/tests/test__templates.py b/packages/notifications-library/tests/test__templates.py index 19dab150d7e..b4e746b54ff 100644 --- a/packages/notifications-library/tests/test__templates.py +++ b/packages/notifications-library/tests/test__templates.py @@ -18,6 +18,7 @@ "on_payed", "on_registered", "on_reset_password", + "on_share_project", "on_unregister", ], ) diff --git a/packages/postgres-database/src/simcore_postgres_database/models/products.py b/packages/postgres-database/src/simcore_postgres_database/models/products.py index 6e5fee576e6..c7668a6953a 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/products.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/products.py @@ -32,6 +32,7 @@ class VendorUI(TypedDict, total=True): logo_url: str # vendor logo url strong_color: str # vendor main color + project_alias: str # project alias for the product (e.g. "project" or "study") class Vendor(TypedDict, total=False): diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/faker_factories.py b/packages/pytest-simcore/src/pytest_simcore/helpers/faker_factories.py index 0031be3d1d7..04cde49a9ff 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/faker_factories.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/faker_factories.py @@ -244,7 +244,7 @@ def random_product( "host_regex": r"[a-zA-Z0-9]+\.com", "support_email": f"support@{suffix}.io", "product_owners_email": fake.random_element( - elements=[f"product-onwers@{suffix}.io", None] + elements=[f"product-owners@{suffix}.io", None] ), "twilio_messaging_sid": fake.random_element( elements=(None, f"{fake.uuid4()}"[:34]) @@ -260,6 +260,7 @@ def random_product( ui=VendorUI( logo_url=fake.url(), strong_color=fake.color(), + project_alias=fake.random_element(elements=["project", "study"]), ), ), "registration_email_template": registration_email_template, diff --git a/services/web/server/src/simcore_service_webserver/products/_models.py b/services/web/server/src/simcore_service_webserver/products/_models.py index 41031cded06..06e80d7b83a 100644 --- a/services/web/server/src/simcore_service_webserver/products/_models.py +++ b/services/web/server/src/simcore_service_webserver/products/_models.py @@ -214,7 +214,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None: and isinstance(c.server_default.arg, str) # type: ignore[union-attr] }, }, - # Example of data in the dabase with a url set with blanks + # Example of data in the database with a url set with blanks { "name": "tis", "display_name": "TI PT", @@ -245,6 +245,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None: "ui": { "logo_url": "https://acme.com/logo", "strong_color": "#123456", + "project_alias": "study", }, }, "issues": [ diff --git a/services/web/server/tests/unit/isolated/products/test_products_model.py b/services/web/server/tests/unit/isolated/products/test_products_model.py index efde35506a3..25411aa0e04 100644 --- a/services/web/server/tests/unit/isolated/products/test_products_model.py +++ b/services/web/server/tests/unit/isolated/products/test_products_model.py @@ -63,6 +63,7 @@ def test_product_to_static(): "ui": { "logo_url": "https://acme.com/logo", "strong_color": "#123456", + "project_alias": "study", }, }, "issues": [