rename ForwardEmail to Contact

This commit is contained in:
Son NK 2020-03-17 10:56:59 +01:00
parent cc6e8a00a5
commit 17974de746
9 changed files with 103 additions and 116 deletions

View File

@ -13,7 +13,7 @@ from app.dashboard.views.index import get_alias_info, AliasInfo
from app.extensions import db
from app.log import LOG
from app.models import ForwardEmailLog
from app.models import GenEmail, ForwardEmail
from app.models import GenEmail, Contact
from app.utils import random_string
@ -199,7 +199,7 @@ def update_alias(alias_id):
return jsonify(note=new_note), 200
def serialize_forward_email(fe: ForwardEmail) -> dict:
def serialize_contact(fe: Contact) -> dict:
res = {
"creation_date": fe.created_at.format(),
@ -220,15 +220,15 @@ def serialize_forward_email(fe: ForwardEmail) -> dict:
def get_alias_contacts(gen_email, page_id: int) -> [dict]:
q = (
ForwardEmail.query.filter_by(gen_email_id=gen_email.id)
.order_by(ForwardEmail.id.desc())
Contact.query.filter_by(gen_email_id=gen_email.id)
.order_by(Contact.id.desc())
.limit(PAGE_LIMIT)
.offset(page_id * PAGE_LIMIT)
)
res = []
for fe in q.all():
res.append(serialize_forward_email(fe))
res.append(serialize_contact(fe))
return res
@ -299,16 +299,16 @@ def create_contact_route(alias_id):
reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
for _ in range(1000):
reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
if not ForwardEmail.get_by(reply_email=reply_email):
if not Contact.get_by(reply_email=reply_email):
break
_, website_email = parseaddr(contact_email)
# already been added
if ForwardEmail.get_by(gen_email_id=gen_email.id, website_email=website_email):
if Contact.get_by(gen_email_id=gen_email.id, website_email=website_email):
return jsonify(error="Contact already added"), 409
forward_email = ForwardEmail.create(
contact = Contact.create(
gen_email_id=gen_email.id,
website_email=website_email,
website_from=contact_email,
@ -318,4 +318,4 @@ def create_contact_route(alias_id):
LOG.d("create reverse-alias for %s %s", contact_email, gen_email)
db.session.commit()
return jsonify(**serialize_forward_email(forward_email)), 201
return jsonify(**serialize_contact(contact)), 201

View File

@ -47,33 +47,33 @@
</form>
<div class="row">
{% for forward_email in forward_emails %}
{% for contact in contacts %}
<div class="col-md-6">
<div class="my-2 p-2 card {% if forward_email.id == forward_email_id %} highlight-row {% endif %}">
<div class="my-2 p-2 card {% if contact.id == contact_id %} highlight-row {% endif %}">
<div>
<span>
<a href="{{ 'mailto:' + forward_email.website_send_to() }}"
<a href="{{ 'mailto:' + contact.website_send_to() }}"
data-toggle="tooltip"
title="You can click on this to open your email client. Or use the copy button 👉"
class="font-weight-bold">*************************</a>
<span class="clipboard btn btn-sm btn-success copy-btn" data-toggle="tooltip"
title="Copy to clipboard"
data-clipboard-text="{{ forward_email.website_send_to() }}">
data-clipboard-text="{{ contact.website_send_to() }}">
Copy reverse-alias
</span>
</span>
</div>
<div>
<i class="fe fe-mail"></i> ➡ {{ forward_email.website_from or forward_email.website_email }}
<i class="fe fe-mail"></i> ➡ {{ contact.website_from or contact.website_email }}
</div>
<div class="mb-2 text-muted small-text">
Created {{ forward_email.created_at | dt }} <br>
Created {{ contact.created_at | dt }} <br>
{% if forward_email.last_reply() %}
{% set email_log = forward_email.last_reply() %}
{% if contact.last_reply() %}
{% set email_log = contact.last_reply() %}
Last email sent {{ email_log.created_at | dt }}
{% endif %}
</div>
@ -81,7 +81,7 @@
<div>
<form method="post">
<input type="hidden" name="form-name" value="delete">
<input type="hidden" name="forward-email-id" value="{{ forward_email.id }}">
<input type="hidden" name="contact-id" value="{{ contact.id }}">
<span class="card-link btn btn-link float-right delete-forward-email">
Delete
</span>

View File

@ -10,7 +10,7 @@ from app.config import EMAIL_DOMAIN
from app.dashboard.base import dashboard_bp
from app.extensions import db
from app.log import LOG
from app.models import GenEmail, ForwardEmail
from app.models import GenEmail, Contact
from app.utils import random_string
@ -47,10 +47,10 @@ class NewContactForm(FlaskForm):
@dashboard_bp.route("/alias_contact_manager/<alias_id>/", methods=["GET", "POST"])
@dashboard_bp.route(
"/alias_contact_manager/<alias_id>/<int:forward_email_id>", methods=["GET", "POST"]
"/alias_contact_manager/<alias_id>/<contact_id>", methods=["GET", "POST"]
)
@login_required
def alias_contact_manager(alias_id, forward_email_id=None):
def alias_contact_manager(alias_id, contact_id=None):
gen_email = GenEmail.get(alias_id)
# sanity check
@ -71,15 +71,16 @@ def alias_contact_manager(alias_id, forward_email_id=None):
# generate a reply_email, make sure it is unique
# not use while to avoid infinite loop
reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
for _ in range(1000):
reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
if not ForwardEmail.get_by(reply_email=reply_email):
if not Contact.get_by(reply_email=reply_email):
break
_, website_email = parseaddr(contact_email)
# already been added
if ForwardEmail.get_by(
if Contact.get_by(
gen_email_id=gen_email.id, website_email=website_email
):
flash(f"{website_email} is already added", "error")
@ -87,7 +88,7 @@ def alias_contact_manager(alias_id, forward_email_id=None):
url_for("dashboard.alias_contact_manager", alias_id=alias_id)
)
forward_email = ForwardEmail.create(
contact = Contact.create(
gen_email_id=gen_email.id,
website_email=website_email,
website_from=contact_email,
@ -102,26 +103,26 @@ def alias_contact_manager(alias_id, forward_email_id=None):
url_for(
"dashboard.alias_contact_manager",
alias_id=alias_id,
forward_email_id=forward_email.id,
contact_id=contact.id,
)
)
elif request.form.get("form-name") == "delete":
forward_email_id = request.form.get("forward-email-id")
forward_email = ForwardEmail.get(forward_email_id)
contact_id = request.form.get("contact-id")
contact = Contact.get(contact_id)
if not forward_email:
if not contact:
flash("Unknown error. Refresh the page", "warning")
return redirect(
url_for("dashboard.alias_contact_manager", alias_id=alias_id)
)
elif forward_email.gen_email_id != gen_email.id:
elif contact.gen_email_id != gen_email.id:
flash("You cannot delete reverse-alias", "warning")
return redirect(
url_for("dashboard.alias_contact_manager", alias_id=alias_id)
)
contact_name = forward_email.website_from
ForwardEmail.delete(forward_email_id)
contact_name = contact.website_from
Contact.delete(contact_id)
db.session.commit()
flash(f"Reverse-alias for {contact_name} has been deleted", "success")
@ -130,19 +131,17 @@ def alias_contact_manager(alias_id, forward_email_id=None):
url_for("dashboard.alias_contact_manager", alias_id=alias_id)
)
# make sure highlighted forward_email is at array start
forward_emails = gen_email.forward_emails
# make sure highlighted contact is at array start
contacts = gen_email.contacts
if forward_email_id:
forward_emails = sorted(
forward_emails, key=lambda fe: fe.id == forward_email_id, reverse=True
)
if contact_id:
contacts = sorted(contacts, key=lambda fe: fe.id == contact_id, reverse=True)
return render_template(
"dashboard/alias_contact_manager.html",
forward_emails=forward_emails,
contacts=contacts,
alias=gen_email.email,
gen_email=gen_email,
new_contact_form=new_contact_form,
forward_email_id=forward_email_id,
contact_id=contact_id,
)

View File

@ -5,7 +5,7 @@ from flask_login import login_required, current_user
from app.config import PAGE_LIMIT
from app.dashboard.base import dashboard_bp
from app.extensions import db
from app.models import GenEmail, ForwardEmailLog, ForwardEmail
from app.models import GenEmail, ForwardEmailLog, Contact
class AliasLog:
@ -42,9 +42,9 @@ def alias_log(alias_id, page_id):
logs = get_alias_log(gen_email, page_id)
base = (
db.session.query(ForwardEmail, ForwardEmailLog)
.filter(ForwardEmail.id == ForwardEmailLog.forward_id)
.filter(ForwardEmail.gen_email_id == gen_email.id)
db.session.query(Contact, ForwardEmailLog)
.filter(Contact.id == ForwardEmailLog.forward_id)
.filter(Contact.gen_email_id == gen_email.id)
)
total = base.count()
email_forwarded = (
@ -66,9 +66,9 @@ def get_alias_log(gen_email: GenEmail, page_id=0):
mailbox = gen_email.mailbox_email()
q = (
db.session.query(ForwardEmail, ForwardEmailLog)
.filter(ForwardEmail.id == ForwardEmailLog.forward_id)
.filter(ForwardEmail.gen_email_id == gen_email.id)
db.session.query(Contact, ForwardEmailLog)
.filter(Contact.id == ForwardEmailLog.forward_id)
.filter(Contact.gen_email_id == gen_email.id)
.order_by(ForwardEmailLog.id.desc())
.limit(PAGE_LIMIT)
.offset(page_id * PAGE_LIMIT)

View File

@ -12,7 +12,7 @@ from app.log import LOG
from app.models import (
GenEmail,
ClientUser,
ForwardEmail,
Contact,
ForwardEmailLog,
DeletedAlias,
AliasGeneratorEnum,
@ -202,11 +202,9 @@ def get_alias_info(
aliases = {} # dict of alias and AliasInfo
q = (
db.session.query(GenEmail, ForwardEmail, ForwardEmailLog, Mailbox)
.join(ForwardEmail, GenEmail.id == ForwardEmail.gen_email_id, isouter=True)
.join(
ForwardEmailLog, ForwardEmail.id == ForwardEmailLog.forward_id, isouter=True
)
db.session.query(GenEmail, Contact, ForwardEmailLog, Mailbox)
.join(Contact, GenEmail.id == Contact.gen_email_id, isouter=True)
.join(ForwardEmailLog, Contact.id == ForwardEmailLog.forward_id, isouter=True)
.join(Mailbox, GenEmail.mailbox_id == Mailbox.id, isouter=True)
.filter(GenEmail.user_id == user.id)
.order_by(GenEmail.created_at.desc())

View File

@ -698,7 +698,7 @@ class ClientUser(db.Model, ModelMixin):
return res
class ForwardEmail(db.Model, ModelMixin):
class Contact(db.Model, ModelMixin):
"""
Store configuration of sender (website-email) and alias.
"""
@ -724,7 +724,7 @@ class ForwardEmail(db.Model, ModelMixin):
# it has the prefix "reply+" to distinguish with other email
reply_email = db.Column(db.String(512), nullable=False)
gen_email = db.relationship(GenEmail, backref="forward_emails")
gen_email = db.relationship(GenEmail, backref="contacts")
def website_send_to(self):
"""return the email address with name.
@ -755,7 +755,7 @@ class ForwardEmail(db.Model, ModelMixin):
class ForwardEmailLog(db.Model, ModelMixin):
forward_id = db.Column(
db.ForeignKey(ForwardEmail.id, ondelete="cascade"), nullable=False
db.ForeignKey(Contact.id, ondelete="cascade"), nullable=False
)
# whether this is a reply
@ -774,7 +774,7 @@ class ForwardEmailLog(db.Model, ModelMixin):
)
refused_email = db.relationship("RefusedEmail")
forward = db.relationship(ForwardEmail)
forward = db.relationship(Contact)
class Subscription(db.Model, ModelMixin):

View File

@ -12,7 +12,7 @@ from app.models import (
User,
GenEmail,
ForwardEmailLog,
ForwardEmail,
Contact,
CustomDomain,
Client,
ManualSubscription,
@ -119,9 +119,9 @@ def stats():
LOG.d("total number alias %s", nb_gen_email)
# nb mails forwarded
q = db.session.query(ForwardEmailLog, ForwardEmail, GenEmail, User).filter(
ForwardEmailLog.forward_id == ForwardEmail.id,
ForwardEmail.gen_email_id == GenEmail.id,
q = db.session.query(ForwardEmailLog, Contact, GenEmail, User).filter(
ForwardEmailLog.forward_id == Contact.id,
Contact.gen_email_id == GenEmail.id,
GenEmail.user_id == User.id,
)
for ie in IGNORED_EMAILS:

View File

@ -70,7 +70,7 @@ from app.extensions import db
from app.log import LOG
from app.models import (
GenEmail,
ForwardEmail,
Contact,
ForwardEmailLog,
CustomDomain,
Directory,
@ -207,21 +207,17 @@ def try_auto_create_catch_all_domain(alias: str) -> Optional[GenEmail]:
return gen_email
def get_or_create_forward_email(
website_from_header: str, gen_email: GenEmail
) -> ForwardEmail:
def get_or_create_contact(website_from_header: str, gen_email: GenEmail) -> Contact:
"""
website_from_header can be the full-form email, i.e. "First Last <email@example.com>"
"""
_, website_email = parseaddr(website_from_header)
forward_email = ForwardEmail.get_by(
gen_email_id=gen_email.id, website_email=website_email
)
if forward_email:
contact = Contact.get_by(gen_email_id=gen_email.id, website_email=website_email)
if contact:
# update the website_from if needed
if forward_email.website_from != website_from_header:
LOG.d("Update From header for %s", forward_email)
forward_email.website_from = website_from_header
if contact.website_from != website_from_header:
LOG.d("Update From header for %s", contact)
contact.website_from = website_from_header
db.session.commit()
else:
LOG.debug(
@ -234,12 +230,12 @@ def get_or_create_forward_email(
# not use while loop to avoid infinite loop
reply_email = f"reply+{random_string(30)}@{EMAIL_DOMAIN}"
for _ in range(1000):
if not ForwardEmail.get_by(reply_email=reply_email):
if not Contact.get_by(reply_email=reply_email):
# found!
break
reply_email = f"reply+{random_string(30)}@{EMAIL_DOMAIN}"
forward_email = ForwardEmail.create(
contact = Contact.create(
gen_email_id=gen_email.id,
website_email=website_email,
website_from=website_from_header,
@ -247,7 +243,7 @@ def get_or_create_forward_email(
)
db.session.commit()
return forward_email
return contact
def should_append_alias(msg, alias):
@ -317,8 +313,8 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
LOG.d("Encrypt message using mailbox %s", mailbox)
msg = prepare_pgp_message(msg, mailbox.pgp_finger_print)
forward_email = get_or_create_forward_email(msg["From"], gen_email)
forward_log = ForwardEmailLog.create(forward_id=forward_email.id)
contact = get_or_create_contact(msg["From"], gen_email)
forward_log = ForwardEmailLog.create(forward_id=contact.id)
if gen_email.enabled:
# add custom header
@ -338,7 +334,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
+ (" - " if website_name else "")
+ website_email.replace("@", " at ")
)
from_header = formataddr((new_website_name, forward_email.reply_email))
from_header = formataddr((new_website_name, contact.reply_email))
add_or_replace_header(msg, "From", from_header)
LOG.d("new from header:%s", from_header)
@ -373,7 +369,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
# encode message raw directly instead
msg_raw = msg.as_string().encode()
smtp.sendmail(
forward_email.reply_email,
contact.reply_email,
mailbox_email,
msg_raw,
envelope.mail_options,
@ -395,12 +391,12 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
LOG.warning(f"Reply email {reply_email} has wrong domain")
return "550 wrong reply email"
forward_email = ForwardEmail.get_by(reply_email=reply_email)
if not forward_email:
contact = Contact.get_by(reply_email=reply_email)
if not contact:
LOG.warning(f"No such forward-email with {reply_email} as reply-email")
return "550 wrong reply email"
alias: str = forward_email.gen_email.email
alias: str = contact.gen_email.email
alias_domain = alias[alias.find("@") + 1 :]
# alias must end with one of the ALIAS_DOMAINS or custom-domain
@ -408,7 +404,7 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
if not CustomDomain.get_by(domain=alias_domain):
return "550 alias unknown by SimpleLogin"
gen_email = forward_email.gen_email
gen_email = contact.gen_email
user = gen_email.user
mailbox_email = gen_email.mailbox_email()
@ -420,12 +416,12 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
LOG.error(
"Bounce when sending to alias %s from %s, user %s",
alias,
forward_email.website_from,
contact.website_from,
gen_email.user,
)
handle_bounce(
alias, envelope, forward_email, gen_email, msg, smtp, user, mailbox_email
alias, envelope, contact, gen_email, msg, smtp, user, mailbox_email
)
return "550 ignored"
@ -485,10 +481,10 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
# remove sender header if present as this could reveal user real email
delete_header(msg, "Sender")
add_or_replace_header(msg, "To", forward_email.website_email)
add_or_replace_header(msg, "To", contact.website_email)
# add List-Unsubscribe header
unsubscribe_link = f"{URL}/dashboard/unsubscribe/{forward_email.gen_email_id}"
unsubscribe_link = f"{URL}/dashboard/unsubscribe/{contact.gen_email_id}"
add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>")
add_or_replace_header(msg, "List-Unsubscribe-Post", "List-Unsubscribe=One-Click")
@ -498,7 +494,7 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
LOG.d(
"send email from %s to %s, mail_options:%s,rcpt_options:%s",
alias,
forward_email.website_email,
contact.website_email,
envelope.mail_options,
envelope.rcpt_options,
)
@ -514,29 +510,23 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
msg_raw = msg.as_string().encode()
smtp.sendmail(
alias,
forward_email.website_email,
contact.website_email,
msg_raw,
envelope.mail_options,
envelope.rcpt_options,
)
ForwardEmailLog.create(forward_id=forward_email.id, is_reply=True)
ForwardEmailLog.create(forward_id=contact.id, is_reply=True)
db.session.commit()
return "250 Message accepted for delivery"
def handle_bounce(
alias, envelope, forward_email, gen_email, msg, smtp, user, mailbox_email
):
fel: ForwardEmailLog = ForwardEmailLog.create(
forward_id=forward_email.id, bounced=True
)
def handle_bounce(alias, envelope, contact, gen_email, msg, smtp, user, mailbox_email):
fel: ForwardEmailLog = ForwardEmailLog.create(forward_id=contact.id, bounced=True)
db.session.commit()
nb_bounced = ForwardEmailLog.filter_by(
forward_id=forward_email.id, bounced=True
).count()
nb_bounced = ForwardEmailLog.filter_by(forward_id=contact.id, bounced=True).count()
disable_alias_link = f"{URL}/dashboard/unsubscribe/{gen_email.id}"
# Store the bounced email
@ -569,19 +559,19 @@ def handle_bounce(
LOG.d(
"Inform user %s about bounced email sent by %s to alias %s",
user,
forward_email.website_from,
contact.website_from,
alias,
)
send_email(
# use user mail here as only user is authenticated to see the refused email
user.email,
f"Email from {forward_email.website_from} to {alias} cannot be delivered to your inbox",
f"Email from {contact.website_from} to {alias} cannot be delivered to your inbox",
render(
"transactional/bounced-email.txt",
name=user.name,
alias=alias,
website_from=forward_email.website_from,
website_email=forward_email.website_email,
website_from=contact.website_from,
website_email=contact.website_email,
disable_alias_link=disable_alias_link,
refused_email_url=refused_email_url,
mailbox_email=mailbox_email,
@ -590,8 +580,8 @@ def handle_bounce(
"transactional/bounced-email.html",
name=user.name,
alias=alias,
website_from=forward_email.website_from,
website_email=forward_email.website_email,
website_from=contact.website_from,
website_email=contact.website_email,
disable_alias_link=disable_alias_link,
refused_email_url=refused_email_url,
mailbox_email=mailbox_email,
@ -604,7 +594,7 @@ def handle_bounce(
LOG.d(
"Bounce happens again with alias %s from %s. Disable alias now ",
alias,
forward_email.website_from,
contact.website_from,
)
gen_email.enabled = False
db.session.commit()
@ -612,13 +602,13 @@ def handle_bounce(
send_email(
# use user mail here as only user is authenticated to see the refused email
user.email,
f"Alias {alias} has been disabled due to second undelivered email from {forward_email.website_from}",
f"Alias {alias} has been disabled due to second undelivered email from {contact.website_from}",
render(
"transactional/automatic-disable-alias.txt",
name=user.name,
alias=alias,
website_from=forward_email.website_from,
website_email=forward_email.website_email,
website_from=contact.website_from,
website_email=contact.website_email,
refused_email_url=refused_email_url,
mailbox_email=mailbox_email,
),
@ -626,8 +616,8 @@ def handle_bounce(
"transactional/automatic-disable-alias.html",
name=user.name,
alias=alias,
website_from=forward_email.website_from,
website_email=forward_email.website_email,
website_from=contact.website_from,
website_email=contact.website_email,
refused_email_url=refused_email_url,
mailbox_email=mailbox_email,
),

View File

@ -4,7 +4,7 @@ from flask import url_for
from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN, PAGE_LIMIT
from app.extensions import db
from app.models import User, ApiKey, GenEmail, ForwardEmail, ForwardEmailLog
from app.models import User, ApiKey, GenEmail, Contact, ForwardEmailLog
from app.utils import random_word
@ -128,7 +128,7 @@ def test_alias_activities(flask_client):
db.session.commit()
# create some alias log
forward_email = ForwardEmail.create(
contact = Contact.create(
website_email="marketing@example.com",
reply_email="reply@a.b",
gen_email_id=gen_email.id,
@ -136,10 +136,10 @@ def test_alias_activities(flask_client):
db.session.commit()
for _ in range(int(PAGE_LIMIT / 2)):
ForwardEmailLog.create(forward_id=forward_email.id, is_reply=True)
ForwardEmailLog.create(forward_id=contact.id, is_reply=True)
for _ in range(int(PAGE_LIMIT / 2) + 2):
ForwardEmailLog.create(forward_id=forward_email.id, blocked=True)
ForwardEmailLog.create(forward_id=contact.id, blocked=True)
r = flask_client.get(
url_for("api.get_alias_activities", alias_id=gen_email.id, page_id=0),
@ -200,14 +200,14 @@ def test_alias_contacts(flask_client):
# create some alias log
for i in range(PAGE_LIMIT + 1):
forward_email = ForwardEmail.create(
contact = Contact.create(
website_email=f"marketing-{i}@example.com",
reply_email=f"reply-{i}@a.b",
gen_email_id=gen_email.id,
)
db.session.commit()
ForwardEmailLog.create(forward_id=forward_email.id, is_reply=True)
ForwardEmailLog.create(forward_id=contact.id, is_reply=True)
db.session.commit()
r = flask_client.get(