create alias contact manager page
This commit is contained in:
parent
881525dfc2
commit
231fc6fd88
|
@ -8,4 +8,5 @@ from .views import (
|
|||
unsubscribe,
|
||||
api_key,
|
||||
custom_domain,
|
||||
alias_contact_manager,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
{% extends 'default.html' %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
|
||||
{% block title %}
|
||||
Alias Contact Manager
|
||||
{% endblock %}
|
||||
|
||||
{% block default_content %}
|
||||
<div class="page-header row">
|
||||
<h3 class="page-title col">
|
||||
{{ alias }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-primary" role="alert">
|
||||
<p>
|
||||
To send an email from your alias, just send the email to a special email address that we call <em>reverse-alias</em>
|
||||
and SimpleLogin will send it from the alias.
|
||||
</p>
|
||||
<p>
|
||||
Make sure you send the email from your personal email address ({{ current_user.email }}).
|
||||
This special email address can <em>only</em> be used by you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
{{ new_contact_form.csrf_token }}
|
||||
|
||||
<label class="form-label">Where do you want to send email to?</label>
|
||||
|
||||
{{ new_contact_form.email(class="form-control", placeholder="First Last <email@example.com>") }}
|
||||
{{ render_field_errors(new_contact_form.email) }}
|
||||
<button class="btn btn-primary mt-2">Create reverse-alias</button>
|
||||
</form>
|
||||
|
||||
<div class="row">
|
||||
{% for forward_email in forward_emails %}
|
||||
<div class="col-6">
|
||||
<div class="my-2 p-2 card {% if forward_email.id == forward_email_id %} highlight-row {% endif %}">
|
||||
<div>
|
||||
<span>
|
||||
<a href="{{ 'mailto:' + forward_email.website_send_to() }}"
|
||||
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() }}">
|
||||
Copy reverse-alias
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
→ {{ forward_email.website_from or forward_email.website_email }}
|
||||
</div>
|
||||
|
||||
<div class="mb-2 text-muted small-text">
|
||||
Created {{ forward_email.created_at | dt }} <br>
|
||||
|
||||
{% if forward_email.last_reply() %}
|
||||
{% set email_log = forward_email.last_reply() %}
|
||||
Last email sent {{ email_log.created_at | dt }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" name="forward-email-id" value="{{ forward_email.id }}">
|
||||
<span class="card-link btn btn-link float-right delete-forward-email">
|
||||
Delete
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
$(".delete-forward-email").on("click", function (e) {
|
||||
notie.confirm({
|
||||
text: "Activity history associated with this reverse-alias will be deleted, " +
|
||||
" please confirm",
|
||||
cancelCallback: () => {
|
||||
// nothing to do
|
||||
},
|
||||
submitCallback: () => {
|
||||
$(this).closest("form").submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,146 @@
|
|||
import re
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, validators, ValidationError
|
||||
|
||||
from app.config import EMAIL_DOMAIN
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import get_email_part
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import GenEmail, ForwardEmail
|
||||
from app.utils import random_string
|
||||
|
||||
|
||||
def email_validator():
|
||||
"""validate email address. Handle both only email and email with name:
|
||||
- ab@cd.com
|
||||
- AB CD <ab@cd.com>
|
||||
|
||||
"""
|
||||
message = "Invalid email format. Email must be either email@example.com or *First Last <email@example.com>*"
|
||||
|
||||
def _check(form, field):
|
||||
email = field.data
|
||||
email_part = email
|
||||
|
||||
if "<" in email and ">" in email:
|
||||
if email.find("<") + 1 < email.find(">"):
|
||||
email_part = email[email.find("<") + 1 : email.find(">")].strip()
|
||||
|
||||
if re.match(r"^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$", email_part):
|
||||
return
|
||||
|
||||
raise ValidationError(message)
|
||||
|
||||
return _check
|
||||
|
||||
|
||||
class NewContactForm(FlaskForm):
|
||||
email = StringField(
|
||||
"Email", validators=[validators.DataRequired(), email_validator()]
|
||||
)
|
||||
|
||||
|
||||
@dashboard_bp.route("/alias_contact_manager/<alias>/", methods=["GET", "POST"])
|
||||
@dashboard_bp.route(
|
||||
"/alias_contact_manager/<alias>/<int:forward_email_id>", methods=["GET", "POST"]
|
||||
)
|
||||
@login_required
|
||||
def alias_contact_manager(alias, forward_email_id=None):
|
||||
gen_email = GenEmail.get_by(email=alias)
|
||||
|
||||
# sanity check
|
||||
if not gen_email:
|
||||
flash("You do not have access to this page", "warning")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
if gen_email.user_id != current_user.id:
|
||||
flash("You do not have access to this page", "warning")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
new_contact_form = NewContactForm()
|
||||
|
||||
if request.method == "POST":
|
||||
if request.form.get("form-name") == "create":
|
||||
if new_contact_form.validate():
|
||||
contact_email = new_contact_form.email.data
|
||||
|
||||
# generate a reply_email, make sure it is unique
|
||||
# not use while to avoid infinite loop
|
||||
for _ in range(1000):
|
||||
reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
|
||||
if not ForwardEmail.get_by(reply_email=reply_email):
|
||||
break
|
||||
|
||||
website_email = get_email_part(contact_email)
|
||||
|
||||
# already been added
|
||||
if ForwardEmail.get_by(
|
||||
gen_email_id=gen_email.id, website_email=website_email
|
||||
):
|
||||
flash(f"{website_email} is already added", "error")
|
||||
return redirect(
|
||||
url_for("dashboard.alias_contact_manager", alias=alias)
|
||||
)
|
||||
|
||||
forward_email = ForwardEmail.create(
|
||||
gen_email_id=gen_email.id,
|
||||
website_email=website_email,
|
||||
website_from=contact_email,
|
||||
reply_email=reply_email,
|
||||
)
|
||||
|
||||
LOG.d("create reverse-alias for %s", contact_email)
|
||||
db.session.commit()
|
||||
flash(
|
||||
f"Reverse alias for {contact_email} is created successfully",
|
||||
"success",
|
||||
)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"dashboard.alias_contact_manager",
|
||||
alias=alias,
|
||||
forward_email_id=forward_email.id,
|
||||
)
|
||||
)
|
||||
elif request.form.get("form-name") == "delete":
|
||||
forward_email_id = request.form.get("forward-email-id")
|
||||
forward_email = ForwardEmail.get(forward_email_id)
|
||||
|
||||
if not forward_email:
|
||||
flash("Unknown error. Refresh the page", "warning")
|
||||
return redirect(url_for("dashboard.alias_contact_manager", alias=alias))
|
||||
elif forward_email.gen_email_id != gen_email.id:
|
||||
flash("You cannot delete reverse-alias", "warning")
|
||||
return redirect(url_for("dashboard.alias_contact_manager", alias=alias))
|
||||
|
||||
contact_name = forward_email.website_from
|
||||
ForwardEmail.delete(forward_email_id)
|
||||
db.session.commit()
|
||||
|
||||
flash(
|
||||
f"Reverse-alias for {contact_name} has been deleted successfully",
|
||||
"success",
|
||||
)
|
||||
|
||||
return redirect(url_for("dashboard.alias_contact_manager", alias=alias))
|
||||
|
||||
# make sure highlighted forward_email is at array start
|
||||
forward_emails = gen_email.forward_emails
|
||||
|
||||
if forward_email_id:
|
||||
forward_emails = sorted(
|
||||
forward_emails, key=lambda fe: fe.id == forward_email_id, reverse=True
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"dashboard/alias_contact_manager.html",
|
||||
forward_emails=forward_emails,
|
||||
alias=gen_email.email,
|
||||
new_contact_form=new_contact_form,
|
||||
forward_email_id=forward_email_id,
|
||||
)
|
|
@ -5,11 +5,12 @@ import arrow
|
|||
import bcrypt
|
||||
from flask import url_for
|
||||
from flask_login import UserMixin
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy import text, desc
|
||||
from sqlalchemy_utils import ArrowType
|
||||
|
||||
from app import s3
|
||||
from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN, URL, AVATAR_URL_EXPIRATION
|
||||
from app.email_utils import get_email_name
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.oauth_models import Scope
|
||||
|
@ -540,7 +541,25 @@ class ForwardEmail(db.Model, ModelMixin):
|
|||
# it has the prefix "reply+" to distinguish with other email
|
||||
reply_email = db.Column(db.String(128), nullable=False)
|
||||
|
||||
gen_email = db.relationship(GenEmail)
|
||||
gen_email = db.relationship(GenEmail, backref="forward_emails")
|
||||
|
||||
def website_send_to(self):
|
||||
"""return the email address with name.
|
||||
to use when user wants to send an email from the alias"""
|
||||
if self.website_from:
|
||||
name = get_email_name(self.website_from)
|
||||
if name:
|
||||
return name + " " + self.website_email + " " + f"<{self.reply_email}>"
|
||||
|
||||
return self.reply_email
|
||||
|
||||
def last_reply(self) -> "ForwardEmailLog":
|
||||
"""return the most recent reply"""
|
||||
return (
|
||||
ForwardEmailLog.query.filter_by(forward_id=self.id, is_reply=True)
|
||||
.order_by(desc(ForwardEmailLog.created_at))
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
class ForwardEmailLog(db.Model, ModelMixin):
|
||||
|
|
Loading…
Reference in New Issue