Skip to content

Commit

Permalink
feat: added mail_parser, notifier and notify command
Browse files Browse the repository at this point in the history
  • Loading branch information
c0rydoras committed Jul 13, 2023
1 parent 9d70627 commit 9a00f76
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 84 deletions.
17 changes: 17 additions & 0 deletions api/outdated/email-templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<html>

<head>
<style>
/* ignore */
{% block style %}
{% endblock %}
/* ignore */
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/uikit.min.css" />
</head>

<body>
{% block content %}{% endblock %}
</body>

</html>
28 changes: 28 additions & 0 deletions api/outdated/email-templates/notification.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block style %}
th,
td {
width: 25% text-align: center !important
}
{% endblock %}

{% block content %}
Project: {{ project.name }}
<br />
Repo: <a href='{{ project.repo }}'>{{ project.repo }}</a>
<br />
{% if is_outdated %}
Your project is using some packages that are EOL. This means that they are no longer stable or supported and may have
security vulnerabilities or compatibility problems. You should update them to the next LTS version immediately to fix
these issues. Here is a list of the packages:
{% else %}
Your project is using some packages that will be EOL soon. You should consider updating them to the next LTS version as
soon as possible to avoid potential issues. Here is a list of the packages that need updating:
{% endif %}
<br />
{% for vd in versioned_dependencies %}
<br />
{{ vd.dependency }}: current LTS version <span class='uk-text-{{ "danger" if vd.is_EOL else "warning" }}'>{{
vd.version }}</span>, EOL {{vd.since_in_string}} days
{% endfor %}
{% endblock %}
50 changes: 50 additions & 0 deletions api/outdated/email_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import re
from typing import Any, Union

from django.core.mail import EmailMultiAlternatives
from django.core.mail.message import EmailMessage
from django.utils.html import strip_tags
from jinja2 import Environment, FileSystemLoader, Template


class EmailParser:
"""Parse mail from template."""

def __init__(
self,
mail_kwargs: dict[str, Any],
template: Union[str, Template],
context: dict[str, Any],
) -> None:
self.mail_kwargs = mail_kwargs
self.template = template
self.context = context

@staticmethod
def _template_to_text(parsed_template: str) -> str:
"""Parse the parsed templates html to raw text."""

cleaned_template = parsed_template.replace("\n", "").replace("<br />", "\n")

text = strip_tags(cleaned_template)

return re.sub(
r"[/][*] ignore [*][/].*[/][*] ignore [*][/]",
"",
text,
flags=re.DOTALL,
).strip()

def parse_mail(self) -> EmailMessage:
"""Parse a MailMessage from the template."""

env = Environment(loader=FileSystemLoader("outdated/email-templates"))
template = env.get_template(self.template)
if not template:
raise ValueError("Mail template does not exist!")
body = template.render(**self.context)
message = EmailMultiAlternatives(
body=self._template_to_text(body), **self.mail_kwargs
)
message.attach_alternative(body, "text/html")
return message
13 changes: 13 additions & 0 deletions api/outdated/outdated/management/commands/notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.core.management.base import BaseCommand

from outdated.outdated.models import Project
from outdated.outdated.notifier import Notifier


class Command(BaseCommand):
help = "Sends email notification to maintainers when project outdated/warning"

def handle(self, *args, **options):
for project in Project.objects.all():
Notifier(project).send()
print("Finished")
23 changes: 23 additions & 0 deletions api/outdated/outdated/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ def __str__(self):
def version(self):
return f"{self.release_version.version}.{self.patch_version}"

@property
def end_of_life(self):
return self.release_version.end_of_life

@property
def dependency(self):
return self.release_version.dependency

@property
def status(self):
return self.release_version.status

@property
def is_EOL(self):
return self.status == STATUS_OPTIONS["outdated"]

@property
def since_in_string(self):
values = [(self.end_of_life, "in"), (date.today(), "since")]
if self.is_EOL:
values = values[: -1 if self.is_EOL else 1]
return f"{values[0][1]} {(values[0][0] - values[1][0]).days}"


class Project(UUIDModel):
name = models.CharField(max_length=100, db_index=True)
Expand Down
44 changes: 44 additions & 0 deletions api/outdated/outdated/notifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from datetime import date

from outdated.email_parser import EmailParser
from outdated.outdated import models


class Notifier:
"""Check project and send an informative mail if the status is outdated/warning."""

def __init__(self, project: models.Project) -> None:
self.project = project

def send(self):
# maybe sync project before checking
if not self.project.maintainers:
return
if self.project.status in ["UP_TO_DATE", "UNDEFINED"]:
return

dependencies = [
version
for version in self.project.versioned_dependencies.all()
if version.status in ["OUTDATED", "WARNING"]
]
is_outdated = self.project.status == "OUTDATED"

subject = f"{'Your project is out of date!' if is_outdated else 'Your project will be out of date soon!'}"
context = dict(
versioned_dependencies=dependencies,
project=self.project,
is_outdated=is_outdated,
today=date.today(),
)
email_kwargs = dict(
subject=subject,
to=[self.project.maintainers.get(is_primary=True).user.email],
cc=[
maintainer.user.email
for maintainer in self.project.maintainers.filter(is_primary=False)
if is_outdated
],
)
email = EmailParser(email_kwargs, "notification.html", context).parse_mail()
return email.send()
4 changes: 2 additions & 2 deletions api/outdated/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ def default(default_dev=env.NOTSET, default_prod=env.NOTSET):
SERVER_EMAIL = env.str("SERVER_EMAIL", EMAIL_HOST_USER)
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD", "")
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", False)

from_name = env.str("MAILING_FROM_NAME", "outdated")
from_mail = env.str("MAILING_FROM_MAIL", "[email protected]")
MAILING = {"from_email": from_mail, "from_name": from_name}
MAILING_SENDER = f"{from_name} <{from_mail}>"
DEFAULT_FROM_EMAIL = f"{from_name} <{from_mail}>"

# Syncproject settings
RELEVANT_DEPENDENCIES = [
Expand Down
Loading

0 comments on commit 9a00f76

Please sign in to comment.