Skip to content

Commit

Permalink
Merge branch 'notifier' into feature/user_notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
jcardonnet authored Feb 26, 2024
2 parents f348928 + 32a8d93 commit 2389770
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 14 deletions.
219 changes: 219 additions & 0 deletions packages/syft/src/syft/service/notification/email_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# stdlib
from typing import TYPE_CHECKING

# relative
from ..context import AuthedServiceContext

if TYPE_CHECKING:
# relative
from .notifications import Notification


class EmailTemplate:
pass


class OnBoardEmailTemplate(EmailTemplate):
@staticmethod
def email_title(notification: "Notification", context: AuthedServiceContext) -> str:
return f"Welcome to {context.node.name} node!"

@staticmethod
def email_body(notification: "Notification", context: AuthedServiceContext) -> str:
user_service = context.node.get_service("userservice")
admin_name = user_service.get_by_verify_key(
user_service.admin_verify_key()
).name

head = (
f"""
<head>
<title>Welcome to {context.node.name}</title>
"""
+ """
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
line-height: 1.6;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background: #fff;
}
h1 {
color: #0056b3;
}
.feature {
background-color: #e7f1ff;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.footer {
text-align: center;
font-size: 14px;
color: #aaa;
}
</style>
</head>
"""
)

body = f"""
<body>
<div class="container">
<h1>Welcome to {context.node.name} node!</h1>
<p>Hello,</p>
<p>We're thrilled to have you on board and
excited to help you get started with our powerful features:</p>
<div class="feature">
<h3>Remote Data Science</h3>
<p>Access and analyze data from anywhere, using our comprehensive suite of data science tools.</p>
</div>
<div class="feature">
<h3>Remote Code Execution</h3>
<p>Execute code remotely on private data, ensuring flexibility and efficiency in your research.</p>
</div>
<!-- Add more features here if needed -->
<p>Explore these features and much more within your account.
If you have any questions or need assistance, don't hesitate to reach out.</p>
<p>Cheers,</p>
<p>{admin_name}</p>
<div class="footer">
This is an automated message, please do not reply directly to this email. <br>
For assistance, please contact our support team.
</div>
</div>
</body>
"""
return f"""<html>{head} {body}</html>"""


class RequestEmailTemplate(EmailTemplate):
@staticmethod
def email_title(notification: "Notification", context: AuthedServiceContext) -> str:
return f"Domain {context.node.name}: New Request!"

@staticmethod
def email_body(notification: "Notification", context: AuthedServiceContext) -> str:
request_obj = notification.linked_obj.resolve_with_context(context=context).ok()

head = """
<head>
<title>Access Request Notification</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
font-size: 24px;
color: #333;
text-align: center;
}
.content {
font-size: 16px;
line-height: 1.6;
}
.request-card {
background-color: #ffffff;
border: 1px solid #ddd;
padding: 15px;
margin-top: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.request-header {
font-size: 18px;
color: #333;
margin-bottom: 10px;
font-weight: bold;
}
.request-content {
font-size: 14px;
line-height: 1.5;
color: #555;
}
.button {
display: block;
width: max-content;
background-color: #007bff;
color: white;
padding: 10px 20px;
text-align: center;
text-color: white;
border-radius: 5px;
text-decoration: none;
font-weight: bold;
margin: 20px auto;
}
.footer {
text-align: center;
font-size: 14px;
color: #aaa;
}
</style>
</head>"""

body = f"""
<body>
<div class="container">
<div class="header">
Request Notification
</div>
<div class="content">
<p>Hello,</p>
<p>A new request has been submitted and requires your attention.
Please review the details below:</p>
<div class="request-card">
<div class="request-header">Request Details</div>
<div class="request-content">
<p><strong>ID:</strong> {request_obj.id}</p>
<p>
<strong>Submitted By:</strong>
{request_obj.requesting_user_name} {request_obj.requesting_user_email or ""}
</p>
<p><strong>Date:</strong> {request_obj.request_time}</p>
<p>
<strong>Changes:</strong>
{",".join([change.__class__.__name__ for change in request_obj.changes])}
</p>
</div>
</div>
<p>If you did not expect this request or have concerns about it,
please contact our support team immediately.</p>
</div>
<div class="footer">
This is an automated message, please do not reply directly to this email. <br>
For assistance, please contact our support team.
</div>
</div>
</body>
"""
return f"""<html>{head} {body}</html>"""
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def send(
self, context: AuthedServiceContext, notification: CreateNotification
) -> Union[Notification, SyftError]:
"""Send a new notification"""

new_notification = notification.to(Notification, context=context)

# Add read permissions to person receiving this message
Expand All @@ -55,7 +54,7 @@ def send(
)
notifier_service = context.node.get_service("notifierservice")

res = notifier_service.dispatch_notification(context.node, new_notification)
res = notifier_service.dispatch_notification(context, new_notification)
if isinstance(res, SyftError):
return res

Expand Down
3 changes: 3 additions & 0 deletions packages/syft/src/syft/service/notification/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import Enum
from typing import List
from typing import Optional
from typing import Type

# relative
from ...client.api import APIRegistry
Expand All @@ -24,6 +25,7 @@
from ...util import options
from ...util.colors import SURFACE
from ..notifier.notifier_enums import NOTIFIERS
from .email_templates import EmailTemplate


@serializable()
Expand Down Expand Up @@ -139,6 +141,7 @@ class Notification(SyftObject):
status: NotificationStatus = NotificationStatus.UNREAD
linked_obj: Optional[LinkedObject]
notifier_types: Optional[List[NOTIFIERS]] = []
email_template: Optional[Type[EmailTemplate]] = None
replies: Optional[List[ReplyNotification]] = []

__attr_searchable__ = [
Expand Down
18 changes: 11 additions & 7 deletions packages/syft/src/syft/service/notifier/notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
from result import Result

# relative
from ...abstract_node import AbstractNode
from ...node.credentials import SyftVerifyKey
from ...serde.serializable import serializable
from ...types.syft_object import SYFT_OBJECT_VERSION_1
from ...types.syft_object import SyftObject
from ..context import AuthedServiceContext
from ..notification.notifications import Notification
from ..response import SyftError
from ..response import SyftSuccess
Expand Down Expand Up @@ -76,18 +76,22 @@ def check_credentials(
password=password,
)

def send(self, node: AbstractNode, notification: Notification) -> Result[Ok, Err]:
def send(
self, context: AuthedServiceContext, notification: Notification
) -> Result[Ok, Err]:
try:
user_service = node.get_service("userservice")
user_service = context.node.get_service("userservice")
sender_email = user_service.get_by_verify_key(
notification.from_user_verify_key
).email
receiver_email = user_service.get_by_verify_key(
notification.to_user_verify_key
).email

subject = notification.subject
body = "Testing email notification!"
subject = notification.email_template.email_title(
notification, context=context
)
body = notification.email_template.email_body(notification, context=context)

if isinstance(receiver_email, str):
receiver_email = [receiver_email]
Expand Down Expand Up @@ -165,13 +169,13 @@ def validate_email_credentials(

def send_notifications(
self,
node: AbstractNode,
context: AuthedServiceContext,
notification: Notification,
) -> Result[Ok, Err]:
notifier_objs: List = self.select_notifiers(notification)

for notifier in notifier_objs:
result = notifier.send(node, notification)
result = notifier.send(context, notification)
if result.err():
return result

Expand Down
10 changes: 6 additions & 4 deletions packages/syft/src/syft/service/notifier/notifier_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,9 @@ def init_notifier(
# This is not a public API.
# This method is used by other services to dispatch notifications internally
def dispatch_notification(
self, node: AbstractNode, notification: Notification
) -> Union[SyftSuccess, SyftError]:
admin_key = node.get_service("userservice").admin_verify_key()
self, context: AuthedServiceContext, notification: Notification
) -> Union[SyftError]:
admin_key = context.node.get_service("userservice").admin_verify_key()
notifier = self.stash.get(admin_key)
if notifier.is_err():
return SyftError(
Expand All @@ -288,7 +288,9 @@ def dispatch_notification(
notifier: NotifierSettings = notifier.ok()
# If notifier is active
if notifier.active:
resp = notifier.send_notifications(node=node, notification=notification)
resp = notifier.send_notifications(
context=context, notification=notification
)
if resp.is_err():
return SyftError(message=resp.err())

Expand Down
2 changes: 1 addition & 1 deletion packages/syft/src/syft/service/notifier/smtp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def send(self, sender: str, receiver: list[str], subject: str, body: str) -> Non
msg["From"] = sender
msg["To"] = ", ".join(receiver)
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
msg.attach(MIMEText(body, "html"))

with smtplib.SMTP(
self.server, self.port, timeout=self.SOCKET_TIMEOUT
Expand Down
2 changes: 2 additions & 0 deletions packages/syft/src/syft/service/request/request_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from ..action.action_permissions import ActionPermission
from ..code.user_code import UserCode
from ..context import AuthedServiceContext
from ..notification.email_templates import RequestEmailTemplate
from ..notification.notification_service import CreateNotification
from ..notification.notification_service import NotificationService
from ..notification.notifications import Notification
Expand Down Expand Up @@ -89,6 +90,7 @@ def submit(
to_user_verify_key=root_verify_key,
linked_obj=link,
notifier_types=[NOTIFIERS.EMAIL],
email_template=RequestEmailTemplate,
)
method = context.node.get_service_method(NotificationService.send)
result = method(context=context, notification=message)
Expand Down
Loading

0 comments on commit 2389770

Please sign in to comment.