E-Mail template first steps

This commit is contained in:
Daniel Schick 2024-12-14 18:56:06 +01:00
parent fc6c6179b8
commit 02947ce6e5
6 changed files with 220 additions and 23 deletions

View File

@ -0,0 +1,15 @@
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; box-sizing: border-box; width: 100%; min-width: 100%;" width="100%">
<tbody>
<tr>
<td align="left" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; padding-bottom: 16px;" valign="top">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<tbody>
<tr>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; border-radius: 4px; text-align: center; background-color: #0867ec;" valign="top" align="center" bgcolor="#0867ec"> <a href="https://www.bremen-calling.de" target="_blank" style="border: solid 2px #0867ec; border-radius: 4px; box-sizing: border-box; cursor: pointer; display: inline-block; font-size: 16px; font-weight: bold; margin: 0; padding: 12px 24px; text-decoration: none; text-transform: capitalize; background-color: #0867ec; border-color: #0867ec; color: #ffffff;">Call To Action</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,144 @@
<!doctype html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Bremen calling Benachrichtigung</title>
<style media="all" type="text/css">
@media all {
.btn-primary table td:hover {
background-color: #ec0867 !important;
}
.btn-primary a:hover {
background-color: #ec0867 !important;
border-color: #ec0867 !important;
}
}
@media only screen and (max-width: 640px) {
.main p,
.main td,
.main span {
font-size: 16px !important;
}
.wrapper {
padding: 8px !important;
}
.content {
padding: 0 !important;
}
.container {
padding: 0 !important;
padding-top: 8px !important;
width: 100% !important;
}
.main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
.btn table {
max-width: 100% !important;
width: 100% !important;
}
.btn a {
font-size: 16px !important;
max-width: 100% !important;
width: 100% !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
}
</style>
</head>
<body style="font-family: Helvetica, sans-serif; -webkit-font-smoothing: antialiased; font-size: 16px; line-height: 1.3; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f4f5f6; margin: 0; padding: 0;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f4f5f6; width: 100%;" width="100%" bgcolor="#f4f5f6">
<tr>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; max-width: 600px; padding: 0; padding-top: 24px; width: 600px; margin: 0 auto;" width="600" valign="top">
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 600px; padding: 0;">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Benachrichtung von Bremen calling!</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background: #ffffff; border: 1px solid #eaebed; border-radius: 16px; width: 100%;" width="100%">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; box-sizing: border-box; padding: 24px;" valign="top">
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">Hallo,</p>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">Sie erhalten eine oder mehrere Benachrichtigungen von Bremen calling:</p>
<!--[[NOTIFICATION_ELEMENTS]]-->
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">Wenn Sie diese E-Mails nicht länger erhalten wollen, loggen Sie sich bitte in der Bremen Calling App ein. Im Bereich "Passwort ändern"
können Sie auch die Benachrichtungen anpassen.
</p>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 24px; text-align: center; width: 100%;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block" style="font-family: Helvetica, sans-serif; vertical-align: top; color: #9a9ea6; font-size: 16px; text-align: center;" valign="top" align="center">
<span class="apple-link" style="color: #9a9ea6; font-size: 16px; text-align: center;">Bremer Schiffsmeldedienst GbR - Hafenkopf II / Überseetor 20 - 28217 Bremen / Germany</span>
</td>
</tr>
<tr>
<td class="content-block powered-by" style="font-family: Helvetica, sans-serif; vertical-align: top; color: #9a9ea6; font-size: 16px; text-align: center;" valign="top" align="center">
Kontaktieren Sie uns unter <a href="mailto:bremencalling@bsmd.de" style="color: #9a9ea6; font-size: 16px; text-align: center;">bremencalling@bsmd.de</a>.<br />
<a href="https://www.bsmd.de" style="color: #9a9ea6; font-size: 16px; text-align: center;">www.bsmd.de</a><br />
Tel.: +49 421 38 48 27
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER --></div>
</td>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -8,8 +8,8 @@ from .. import local_db
from ..services.auth_guard import check_jwt
from BreCal.database.update_database import evaluate_shipcall_state
from BreCal.database.sql_utils import get_notification_for_shipcall_and_type
from BreCal.database.sql_queries import create_sql_query_shipcall_get, create_sql_query_shipcall_post, create_sql_query_shipcall_put, create_sql_query_history_post, create_sql_query_history_put, SQLQuery
from BreCal.database.sql_utils import get_notification_for_shipcall_and_type, get_ship_data_for_id
from BreCal.database.sql_queries import create_sql_query_shipcall_get
from marshmallow import Schema, fields, ValidationError
from BreCal.validators.validation_error import create_validation_error_response
@ -76,6 +76,7 @@ def PostShipcalls(schemaModel):
commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_shipcall_post(schemaModel) # create_sql_query_shipcall_post(schemaModel)
query = "INSERT INTO shipcall ("
isNotFirst = False
for key in schemaModel.keys():
@ -134,15 +135,26 @@ def PostShipcalls(schemaModel):
# new_id = commands.execute_scalar(lquery)
new_id = commands.execute_scalar("select last_insert_id()")
shipdata = get_ship_data_for_id(schemaModel["ship_id"])
message = {shipdata['name']}
if "type_value" in schemaModel:
match schemaModel["type_value"]:
case 1:
message += " [ARRIVAL]"
case 2:
message += " [DEPARTURE]"
case 3:
message += " [SHIFTING]"
# add participant assignments if we have a list of participants
if 'participants' in schemaModel:
# pquery = SQLQuery.get_shipcall_post_update_shipcall_participant_map()
pquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id, type) VALUES (?shipcall_id?, ?participant_id?, ?type?)"
nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type) VALUES (?shipcall_id?, ?participant_id?, 0, 1)" # type = 1 is assignment
nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 1, ?message?)" # type = 1 is assignment
for participant_assignment in schemaModel["participants"]:
commands.execute(pquery, param={"shipcall_id" : new_id, "participant_id" : participant_assignment["participant_id"], "type" : participant_assignment["type"]})
commands.execute(nquery, param={"shipcall_id" : new_id, "participant_id" : participant_assignment["participant_id"]})
commands.execute(nquery, param={"shipcall_id" : new_id, "participant_id" : participant_assignment["participant_id"], "message" : message})
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database
# evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=new_id) # new_id (last insert id) refers to the shipcall id
@ -189,8 +201,7 @@ def PutShipcalls(schemaModel):
# test if object to update is found
sentinel = object()
# query = SQLQuery.get_shipcall_by_id()
# theshipcall = commands.query_single_or_default(query, sentinel, param={"id" : schemaModel["id"]})
theshipcall = commands.query_single_or_default("SELECT * FROM shipcall where id = ?id?", sentinel, param={"id" : schemaModel["id"]})
if theshipcall is sentinel:
pooledConnection.close()
@ -230,6 +241,17 @@ def PutShipcalls(schemaModel):
affected_rows = commands.execute(query, param=schemaModel)
shipdata = get_ship_data_for_id(schemaModel["ship_id"])
message = {shipdata['name']}
if "type_value" in schemaModel:
match schemaModel["type_value"]:
case 1:
message += " [ARRIVAL]"
case 2:
message += " [DEPARTURE]"
case 3:
message += " [SHIFTING]"
# pquery = SQLQuery.get_shipcall_participant_map_by_shipcall_id()
pquery = "SELECT id, participant_id, type FROM shipcall_participant_map where shipcall_id = ?id?"
pdata = commands.query(pquery,param={"id" : schemaModel["id"]}) # existing list of assignments
@ -256,8 +278,8 @@ def PutShipcalls(schemaModel):
found_notification = True
break
if not found_notification:
nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type) VALUES (?shipcall_id?, ?participant_id?, 0, 1)" # type = 1 is assignment
commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_assignment["participant_id"]})
nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 1, ?message?)" # type = 1 is assignment
commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_assignment["participant_id"], "message" : message})
# loop across existing pdata entries, deleting those not present in participant list
for elem in pdata:
@ -278,8 +300,8 @@ def PutShipcalls(schemaModel):
commands.execute(nquery, param={"nid" : existing_notification["id"]})
else:
# create un-assignment notification
nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type) VALUES (?shipcall_id?, ?participant_id?, 0, 5)"
commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : elem["participant_id"]})
nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 5, ?message?)"
commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : elem["participant_id"], "message" : message})
break
# save history data

View File

@ -4,8 +4,7 @@ import logging
import json
import os
import sys
import schemas.defs as defs
from BreCal.schemas import defs
config_path = None
def initPool(instancePath, connection_filename="connection_data_devel.json"):
@ -14,7 +13,7 @@ def initPool(instancePath, connection_filename="connection_data_devel.json"):
if(config_path == None):
config_path = os.path.join(instancePath,f'../../../secure/{connection_filename}') #connection_data_devel.json');
config_path = "E:/temp/connection_data.json"
config_path = "C:/temp/connection_data_devel.json"
print (config_path)
if not os.path.exists(config_path):
@ -35,9 +34,13 @@ def initPool(instancePath, connection_filename="connection_data_devel.json"):
credentials_file = "email_credentials_devel.json"
credentials_path = os.path.join(instancePath,f'../../../secure/{credentials_file}')
credentials_path = "C:/temp/email_credentials_devel.json"
if not os.path.exists(credentials_path):
print ('cannot find ' + os.path.abspath(credentials_path))
sys.exit(1)
f = open(credentials_path);
defs.email_credentials = json.load(f)
f.close()

View File

@ -552,6 +552,9 @@ class User:
created: datetime
modified: datetime
def __hash__(self):
return hash(id)
@dataclass
class Ship:
id: int

View File

@ -61,7 +61,7 @@ def UpdateNotifications():
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
query = "SELECT * FROM notification WHERE level = 0 AND created < DATE(NOW() - INTERVAL 10 MINUTE)"
query = "SELECT * FROM notification WHERE level = 0 AND created < TIMESTAMP(NOW() - INTERVAL 10 MINUTE)"
data = commands.query(query, model=model.Notification)
for notification in data:
commands.execute("UPDATE notification SET level = 1 WHERE id = ?id?", param={"id":notification.id})
@ -75,7 +75,7 @@ def SendEmails(email_dict):
This function sends emails to all users in the emaildict
"""
try:
conn = smtplib.SMTP_SSL(defs.email_credentials["server"], defs.email_credentials["port"])
conn = smtplib.SMTP(defs.email_credentials["server"], defs.email_credentials["port"])
conn.set_debuglevel(1)
conn.ehlo()
conn.starttls()
@ -91,12 +91,13 @@ def SendEmails(email_dict):
# TODO: pretty-print and format message according to template
msg.set_content(message)
conn.sendmail(user.user_email, user.user_email, msg.as_string())
conn.sendmail(defs.email_credentials["sender"], user.user_email, msg.as_string())
except Exception as ex:
logging.error(ex)
finally:
conn.quit()
if conn is not None:
conn.quit()
def SendNotifications():
@ -134,22 +135,24 @@ def SendNotifications():
for user in users:
# send notification to user
if user.notify_email:
if user not in email_dict:
email_dict[user] = ""
match notification.type:
case 1: # assignment
email_dict[user] += "\n"
email_dict[user] += "You have been assigned to a new shipcall: " + notification.message
email_dict[user] += "You have been assigned to a new shipcall: " + str(notification.message or "")
case 2: # next 24 hours
email_dict[user] += "\n"
email_dict[user] += "A shipcall is scheduled for the next 24 hours: " + notification.message
email_dict[user] += "A shipcall is scheduled for the next 24 hours: " + str(notification.message or "")
case 3: # Time conflict
email_dict[user] += "\n"
email_dict[user] += "There is a time conflict in a shipcall: " + notification.message
email_dict[user] += "There is a time conflict in a shipcall: " + str(notification.message or "")
case 4: # Time conflict resolved
email_dict[user] += "\n"
email_dict[user] += "A time conflict in a shipcall has been resolved: " + notification.message
email_dict[user] += "A time conflict in a shipcall has been resolved: " + str(notification.message or "")
case 5: # unassigned
email_dict[user] += "\n"
email_dict[user] += "You have been unassigned from a shipcall: " + notification.message
email_dict[user] += "You have been unassigned from a shipcall: " + str(notification.message or "")
if user.notify_whatsapp:
# TBD
pass
@ -165,6 +168,9 @@ def SendNotifications():
except Exception as ex:
logging.error(ex)
finally:
if pooledConnection is not None:
pooledConnection.close()
def add_function_to_schedule__update_shipcalls(interval_in_minutes:int, options:dict={'past_days':2}):
kwargs_ = {"options":options}
@ -207,6 +213,10 @@ def eval_next_24_hrs():
except Exception as ex:
logging.error(ex)
finally:
if pooledConnection is not None:
pooledConnection.close()
return
def setup_schedule(update_shipcalls_interval_in_minutes:int=60):