Merge pull request #135 from simple-login/alias-pagination
Alias pagination, support sorting
This commit is contained in:
commit
51676f02b5
|
@ -1009,7 +1009,8 @@ Update alias note. In the future, the endpoint will support other updates (e.g.
|
|||
Input:
|
||||
- `Authentication` header that contains the api key
|
||||
- `alias_id` in url.
|
||||
- `note` in request body
|
||||
- (optional) `note` in request body
|
||||
- (optional) `mailbox_id` in request body
|
||||
|
||||
Output:
|
||||
If success, return 200
|
||||
|
|
|
@ -3,6 +3,7 @@ from functools import wraps
|
|||
import arrow
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
from flask_login import current_user
|
||||
|
||||
from app.extensions import db
|
||||
from app.models import ApiKey
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|||
|
||||
from arrow import Arrow
|
||||
from sqlalchemy import or_, func, case
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.config import PAGE_LIMIT
|
||||
from app.extensions import db
|
||||
|
@ -11,6 +12,7 @@ from app.models import Alias, Contact, EmailLog, Mailbox
|
|||
@dataclass
|
||||
class AliasInfo:
|
||||
alias: Alias
|
||||
mailbox: Mailbox
|
||||
|
||||
nb_forward: int
|
||||
nb_blocked: int
|
||||
|
@ -89,6 +91,7 @@ def get_alias_infos_with_pagination(user, page_id=0, query=None) -> [AliasInfo]:
|
|||
ret = []
|
||||
q = (
|
||||
db.session.query(Alias)
|
||||
.options(joinedload(Alias.mailbox))
|
||||
.filter(Alias.user_id == user.id)
|
||||
.order_by(Alias.created_at.desc())
|
||||
)
|
||||
|
@ -106,7 +109,9 @@ def get_alias_infos_with_pagination(user, page_id=0, query=None) -> [AliasInfo]:
|
|||
return ret
|
||||
|
||||
|
||||
def get_alias_infos_with_pagination_v2(user, page_id=0, query=None) -> [AliasInfo]:
|
||||
def get_alias_infos_with_pagination_v2(
|
||||
user, page_id=0, query=None, sort=None
|
||||
) -> [AliasInfo]:
|
||||
ret = []
|
||||
latest_activity = func.max(
|
||||
case(
|
||||
|
@ -119,12 +124,11 @@ def get_alias_infos_with_pagination_v2(user, page_id=0, query=None) -> [AliasInf
|
|||
).label("latest")
|
||||
|
||||
q = (
|
||||
db.session.query(Alias, latest_activity)
|
||||
db.session.query(Alias, Mailbox, latest_activity)
|
||||
.join(Contact, Alias.id == Contact.alias_id, isouter=True)
|
||||
.join(EmailLog, Contact.id == EmailLog.contact_id, isouter=True)
|
||||
.filter(Alias.user_id == user.id)
|
||||
.group_by(Alias.id)
|
||||
.order_by(latest_activity.desc())
|
||||
.filter(Alias.mailbox_id == Mailbox.id)
|
||||
)
|
||||
|
||||
if query:
|
||||
|
@ -132,10 +136,18 @@ def get_alias_infos_with_pagination_v2(user, page_id=0, query=None) -> [AliasInf
|
|||
or_(Alias.email.ilike(f"%{query}%"), Alias.note.ilike(f"%{query}%"))
|
||||
)
|
||||
|
||||
if sort == "old2new":
|
||||
q = q.order_by(Alias.created_at)
|
||||
else:
|
||||
# default sorting
|
||||
q = q.order_by(latest_activity.desc())
|
||||
|
||||
q = q.group_by(Alias.id, Mailbox.id)
|
||||
|
||||
q = q.limit(PAGE_LIMIT).offset(page_id * PAGE_LIMIT)
|
||||
|
||||
for alias, latest_activity in q:
|
||||
ret.append(get_alias_info_v2(alias))
|
||||
for alias, mailbox, latest_activity in q:
|
||||
ret.append(get_alias_info_v2(alias, mailbox))
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -147,7 +159,9 @@ def get_alias_info(alias: Alias) -> AliasInfo:
|
|||
.filter(EmailLog.contact_id == Contact.id)
|
||||
)
|
||||
|
||||
alias_info = AliasInfo(alias=alias, nb_blocked=0, nb_forward=0, nb_reply=0,)
|
||||
alias_info = AliasInfo(
|
||||
alias=alias, nb_blocked=0, nb_forward=0, nb_reply=0, mailbox=alias.mailbox
|
||||
)
|
||||
|
||||
for _, el in q:
|
||||
if el.is_reply:
|
||||
|
@ -160,7 +174,7 @@ def get_alias_info(alias: Alias) -> AliasInfo:
|
|||
return alias_info
|
||||
|
||||
|
||||
def get_alias_info_v2(alias: Alias) -> AliasInfo:
|
||||
def get_alias_info_v2(alias: Alias, mailbox) -> AliasInfo:
|
||||
q = (
|
||||
db.session.query(Contact, EmailLog)
|
||||
.filter(Contact.alias_id == alias.id)
|
||||
|
@ -171,7 +185,9 @@ def get_alias_info_v2(alias: Alias) -> AliasInfo:
|
|||
latest_email_log = None
|
||||
latest_contact = None
|
||||
|
||||
alias_info = AliasInfo(alias=alias, nb_blocked=0, nb_forward=0, nb_reply=0,)
|
||||
alias_info = AliasInfo(
|
||||
alias=alias, nb_blocked=0, nb_forward=0, nb_reply=0, mailbox=mailbox
|
||||
)
|
||||
|
||||
for contact, email_log in q:
|
||||
if email_log.is_reply:
|
||||
|
|
|
@ -19,7 +19,7 @@ from app.dashboard.views.alias_log import get_alias_log
|
|||
from app.email_utils import parseaddr_unicode
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import Alias, Contact
|
||||
from app.models import Alias, Contact, Mailbox
|
||||
from app.utils import random_string
|
||||
|
||||
|
||||
|
@ -234,8 +234,6 @@ def update_alias(alias_id):
|
|||
note: in body
|
||||
Output:
|
||||
200
|
||||
|
||||
|
||||
"""
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
|
@ -247,11 +245,25 @@ def update_alias(alias_id):
|
|||
if alias.user_id != user.id:
|
||||
return jsonify(error="Forbidden"), 403
|
||||
|
||||
new_note = data.get("note")
|
||||
alias.note = new_note
|
||||
db.session.commit()
|
||||
changed = False
|
||||
if "note" in data:
|
||||
new_note = data.get("note")
|
||||
alias.note = new_note
|
||||
changed = True
|
||||
|
||||
return jsonify(note=new_note), 200
|
||||
if "mailbox_id" in data:
|
||||
mailbox_id = int(data.get("mailbox_id"))
|
||||
mailbox = Mailbox.get(mailbox_id)
|
||||
if not mailbox or mailbox.user_id != user.id or not mailbox.verified:
|
||||
return jsonify(error="Forbidden"), 400
|
||||
|
||||
alias.mailbox_id = mailbox_id
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(ok=True), 200
|
||||
|
||||
|
||||
@api_bp.route("/aliases/<int:alias_id>", methods=["GET"])
|
||||
|
@ -374,8 +386,6 @@ def delete_contact(contact_id):
|
|||
contact_id: in url
|
||||
Output:
|
||||
200
|
||||
|
||||
|
||||
"""
|
||||
user = g.user
|
||||
contact = Contact.get(contact_id)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pyotp
|
||||
from flask import jsonify, request
|
||||
from flask_cors import cross_origin
|
||||
from itsdangerous import Signer, BadSignature
|
||||
from itsdangerous import Signer
|
||||
|
||||
from app.api.base import api_bp
|
||||
from app.config import FLASK_SECRET
|
||||
|
|
|
@ -6,11 +6,11 @@ from wtforms import StringField, validators
|
|||
from app import email_utils, config
|
||||
from app.auth.base import auth_bp
|
||||
from app.auth.views.login_utils import get_referral
|
||||
from app.config import URL, DISABLE_REGISTRATION
|
||||
from app.config import URL
|
||||
from app.email_utils import can_be_used_as_personal_email, email_already_used
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import User, ActivationCode, Referral
|
||||
from app.models import User, ActivationCode
|
||||
from app.utils import random_string, encode_url
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import arrow
|
||||
from flask import request, flash, render_template, redirect, url_for
|
||||
from flask_login import login_user
|
||||
from flask_wtf import FlaskForm
|
||||
|
|
|
@ -2,8 +2,6 @@ import os
|
|||
import random
|
||||
import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
from uuid import uuid4
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
|
|
@ -63,8 +63,19 @@
|
|||
<div class="col-lg-6 pt-1">
|
||||
<div class="float-right">
|
||||
<form method="get" class="form-inline">
|
||||
<select name="sort"
|
||||
onchange="this.form.submit()"
|
||||
class="form-control custom-select mr-3">
|
||||
<option value="" {% if sort == "" %} selected {% endif %}>
|
||||
Sort by most recent activity
|
||||
</option>
|
||||
<option value="old2new" {% if sort == "old2new" %} selected {% endif %}>
|
||||
Oldest Alias to Newest
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<input type="search" name="query" placeholder="Enter to search for alias"
|
||||
class="form-control shadow text-right"
|
||||
class="form-control shadow"
|
||||
style="max-width: 15em"
|
||||
value="{{ query }}">
|
||||
</form>
|
||||
|
@ -77,7 +88,7 @@
|
|||
{% set alias = alias_info.alias %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card p-4 shadow-sm {% if alias_info.highlight %} highlight-row {% endif %} ">
|
||||
<div class="card p-4 shadow-sm {% if alias_info.alias.id == highlight_alias_id %} highlight-row {% endif %} ">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
|
@ -179,7 +190,8 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{{ url_for('dashboard.alias_contact_manager', alias_id=alias.id) }}"
|
||||
{% if alias_info.show_intro_test_send_email %}
|
||||
id="send-email-{{ alias.id }}"
|
||||
{% if loop.index ==1 %}
|
||||
data-intro="Not only alias can receive emails, it can <em>send</em> emails too! <br><br>
|
||||
You can add a new <em>contact</em> to for your alias here. <br><br>
|
||||
To send an email to your contact, SimpleLogin will create a <em>special</em> email address. <br><br>
|
||||
|
@ -210,10 +222,11 @@
|
|||
<div class="small-text mt-2">Current mailbox</div>
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<select class="form-control form-control-sm custom-select" name="mailbox">
|
||||
<select id="mailbox-{{ alias.id }}"
|
||||
class="form-control form-control-sm custom-select" name="mailbox">
|
||||
{% for mailbox in mailboxes %}
|
||||
<option value="{{ mailbox }}" {% if mailbox == alias_info.mailbox.email %} selected {% endif %}>
|
||||
{{ mailbox }}
|
||||
<option value="{{ mailbox.id }}" {% if mailbox.id == alias_info.mailbox.id %} selected {% endif %}>
|
||||
{{ mailbox.email }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -223,9 +236,10 @@
|
|||
<input type="hidden" name="form-name" value="set-mailbox">
|
||||
<input type="hidden" name="alias-id" value="{{ alias.id }}">
|
||||
|
||||
<button class="btn btn-sm btn-outline-info w-100">
|
||||
<a data-alias="{{ alias.id }}"
|
||||
class="save-mailbox btn btn-sm btn-outline-info w-100">
|
||||
Update
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -241,6 +255,7 @@
|
|||
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<textarea
|
||||
id="note-{{ alias.id }}"
|
||||
name="note"
|
||||
class="form-control"
|
||||
rows="2"
|
||||
|
@ -251,9 +266,10 @@
|
|||
<input type="hidden" name="form-name" value="set-note">
|
||||
<input type="hidden" name="alias-id" value="{{ alias.id }}">
|
||||
|
||||
<button class="btn btn-sm btn-outline-success w-100">
|
||||
<a data-alias="{{ alias.id }}"
|
||||
class="save-note btn btn-sm btn-outline-success w-100">
|
||||
Save
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -279,6 +295,23 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav aria-label="Alias navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item {% if page == 0 %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('dashboard.index', page=page-1, query=query, sort=sort) }}">Previous</a>
|
||||
</li>
|
||||
<li class="page-item {% if last_page %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('dashboard.index', page=page+1, query=query, sort=sort) }}">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if client_users %}
|
||||
<div class="page-header row">
|
||||
|
@ -393,7 +426,6 @@
|
|||
let aliasId = $(this).data("alias");
|
||||
let alias = $(this).parent().find(".alias").val();
|
||||
|
||||
|
||||
try {
|
||||
let res = await fetch(`/api/aliases/${aliasId}/toggle`, {
|
||||
method: "POST",
|
||||
|
@ -424,6 +456,69 @@
|
|||
var oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
})
|
||||
|
||||
$(".save-note").on("click", async function () {
|
||||
let aliasId = $(this).data("alias");
|
||||
let note = $(`#note-${aliasId}`).val();
|
||||
|
||||
try {
|
||||
let res = await fetch(`/api/aliases/${aliasId}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
note: note,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
toastr.success(`Saved`);
|
||||
} else {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
// reset to the original value
|
||||
var oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
} catch (e) {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
// reset to the original value
|
||||
var oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
$(".save-mailbox").on("click", async function () {
|
||||
let aliasId = $(this).data("alias");
|
||||
let mailbox_id = $(`#mailbox-${aliasId}`).val();
|
||||
|
||||
try {
|
||||
let res = await fetch(`/api/aliases/${aliasId}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mailbox_id: mailbox_id,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
toastr.success(`Mailbox Updated`);
|
||||
} else {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
// reset to the original value
|
||||
var oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
} catch (e) {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
// reset to the original value
|
||||
var oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import re
|
||||
from email.utils import parseaddr
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
|
|
|
@ -3,9 +3,9 @@ from flask_login import login_required, current_user
|
|||
|
||||
from app.config import PADDLE_MONTHLY_PRODUCT_ID, PADDLE_YEARLY_PRODUCT_ID
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import Subscription, PlanEnum
|
||||
from app.extensions import db
|
||||
from app.paddle_utils import cancel_subscription, change_plan
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ def custom_alias():
|
|||
)
|
||||
)
|
||||
|
||||
mailboxes = current_user.mailboxes()
|
||||
mailboxes = [mb.email for mb in current_user.mailboxes()]
|
||||
|
||||
if request.method == "POST":
|
||||
alias_prefix = request.form.get("prefix")
|
||||
|
|
|
@ -1,48 +1,32 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from arrow import Arrow
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app import email_utils
|
||||
from app.api.serializer import get_alias_infos_with_pagination_v2
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import (
|
||||
Alias,
|
||||
ClientUser,
|
||||
Contact,
|
||||
EmailLog,
|
||||
DeletedAlias,
|
||||
AliasGeneratorEnum,
|
||||
Mailbox,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AliasInfo:
|
||||
alias: Alias
|
||||
mailbox: Mailbox
|
||||
|
||||
nb_forward: int
|
||||
nb_blocked: int
|
||||
nb_reply: int
|
||||
|
||||
latest_activity: Arrow
|
||||
latest_email_log: EmailLog = None
|
||||
latest_contact: Contact = None
|
||||
|
||||
show_intro_test_send_email: bool = False
|
||||
highlight: bool = False
|
||||
|
||||
|
||||
@dashboard_bp.route("/", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def index():
|
||||
query = request.args.get("query") or ""
|
||||
sort = request.args.get("sort") or ""
|
||||
|
||||
page = 0
|
||||
if request.args.get("page"):
|
||||
page = int(request.args.get("page"))
|
||||
|
||||
highlight_alias_id = None
|
||||
if request.args.get("highlight_alias_id"):
|
||||
highlight_alias_id = int(request.args.get("highlight_alias_id"))
|
||||
|
@ -187,80 +171,12 @@ def index():
|
|||
return render_template(
|
||||
"dashboard/index.html",
|
||||
client_users=client_users,
|
||||
alias_infos=get_alias_infos(current_user, query, highlight_alias_id),
|
||||
alias_infos=get_alias_infos_with_pagination_v2(current_user, page, query, sort),
|
||||
highlight_alias_id=highlight_alias_id,
|
||||
query=query,
|
||||
AliasGeneratorEnum=AliasGeneratorEnum,
|
||||
mailboxes=mailboxes,
|
||||
show_intro=show_intro,
|
||||
page=page,
|
||||
sort=sort,
|
||||
)
|
||||
|
||||
|
||||
def get_alias_infos(user, query=None, highlight_alias_id=None) -> [AliasInfo]:
|
||||
if query:
|
||||
query = query.strip().lower()
|
||||
|
||||
aliases = {} # dict of alias email and AliasInfo
|
||||
|
||||
q = (
|
||||
db.session.query(Alias, Contact, EmailLog, Mailbox)
|
||||
.join(Contact, Alias.id == Contact.alias_id, isouter=True)
|
||||
.join(EmailLog, Contact.id == EmailLog.contact_id, isouter=True)
|
||||
.join(Mailbox, Alias.mailbox_id == Mailbox.id, isouter=True)
|
||||
.filter(Alias.user_id == user.id)
|
||||
.order_by(Alias.created_at.desc())
|
||||
)
|
||||
|
||||
if query:
|
||||
q = q.filter(
|
||||
or_(Alias.email.ilike(f"%{query}%"), Alias.note.ilike(f"%{query}%"))
|
||||
)
|
||||
|
||||
for alias, contact, email_log, mailbox in q:
|
||||
if alias.email not in aliases:
|
||||
aliases[alias.email] = AliasInfo(
|
||||
alias=alias,
|
||||
mailbox=mailbox,
|
||||
nb_blocked=0,
|
||||
nb_forward=0,
|
||||
nb_reply=0,
|
||||
highlight=alias.id == highlight_alias_id,
|
||||
latest_activity=alias.created_at,
|
||||
)
|
||||
|
||||
alias_info = aliases[alias.email]
|
||||
if not email_log:
|
||||
continue
|
||||
|
||||
if email_log.created_at > alias_info.latest_activity:
|
||||
alias_info.latest_activity = email_log.created_at
|
||||
alias_info.latest_email_log = email_log
|
||||
alias_info.latest_contact = contact
|
||||
|
||||
if email_log.is_reply:
|
||||
alias_info.nb_reply += 1
|
||||
elif email_log.blocked:
|
||||
alias_info.nb_blocked += 1
|
||||
else:
|
||||
alias_info.nb_forward += 1
|
||||
|
||||
ret = list(aliases.values())
|
||||
ret = sorted(ret, key=lambda a: a.latest_activity, reverse=True)
|
||||
|
||||
# make sure the highlighted alias is the first element
|
||||
highlight_index = None
|
||||
for ix, alias in enumerate(ret):
|
||||
if alias.highlight:
|
||||
highlight_index = ix
|
||||
break
|
||||
|
||||
if highlight_index:
|
||||
ret.insert(0, ret.pop(highlight_index))
|
||||
|
||||
# only show intro on the first enabled alias
|
||||
for alias in ret:
|
||||
if alias.alias.enabled:
|
||||
alias.show_intro_test_send_email = True
|
||||
break
|
||||
|
||||
return ret
|
||||
|
|
|
@ -3,13 +3,7 @@ from flask_login import login_required, current_user
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, validators
|
||||
|
||||
from app.config import (
|
||||
PADDLE_VENDOR_ID,
|
||||
PADDLE_MONTHLY_PRODUCT_ID,
|
||||
PADDLE_YEARLY_PRODUCT_ID,
|
||||
URL,
|
||||
ADMIN_EMAIL,
|
||||
)
|
||||
from app.config import ADMIN_EMAIL
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import send_email
|
||||
from app.extensions import db
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from itsdangerous import Signer, BadSignature
|
||||
from itsdangerous import Signer
|
||||
from wtforms import validators
|
||||
from wtforms.fields.html5 import EmailField
|
||||
|
||||
|
@ -9,7 +9,6 @@ from app.config import EMAIL_DOMAIN, ALIAS_DOMAINS, MAILBOX_SECRET, URL
|
|||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import (
|
||||
can_be_used_as_personal_email,
|
||||
email_already_used,
|
||||
mailbox_already_used,
|
||||
render,
|
||||
send_email,
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
from smtplib import SMTPRecipientsRefused
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from itsdangerous import Signer, BadSignature
|
||||
from itsdangerous import Signer
|
||||
from wtforms import validators
|
||||
from wtforms.fields.html5 import EmailField
|
||||
|
||||
from app.config import MAILBOX_SECRET
|
||||
from app.config import URL
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import can_be_used_as_personal_email, email_already_used
|
||||
from app.email_utils import can_be_used_as_personal_email
|
||||
from app.email_utils import mailbox_already_used, render, send_email
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import Alias, DeletedAlias
|
||||
from app.models import Mailbox
|
||||
from app.pgp_utils import PGPException, load_public_key
|
||||
from smtplib import SMTPRecipientsRefused
|
||||
|
||||
|
||||
class ChangeEmailForm(FlaskForm):
|
||||
|
|
|
@ -4,7 +4,7 @@ from flask_login import login_required, current_user
|
|||
from app.dashboard.base import dashboard_bp
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import EmailLog, Referral
|
||||
from app.models import Referral
|
||||
from app.utils import random_string
|
||||
|
||||
|
||||
|
|
|
@ -3,10 +3,8 @@ from flask_login import current_user, login_required
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, validators
|
||||
|
||||
from app import email_utils
|
||||
from app.developer.base import developer_bp
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import Client
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import enum
|
||||
import random
|
||||
import uuid
|
||||
from email.utils import parseaddr, formataddr
|
||||
from email.utils import formataddr
|
||||
from typing import List
|
||||
|
||||
import arrow
|
||||
import bcrypt
|
||||
|
@ -357,12 +358,12 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||
def verified_custom_domains(self):
|
||||
return CustomDomain.query.filter_by(user_id=self.id, verified=True).all()
|
||||
|
||||
def mailboxes(self) -> [str]:
|
||||
"""list of mailbox emails that user own"""
|
||||
def mailboxes(self) -> List["Mailbox"]:
|
||||
"""list of mailbox that user own"""
|
||||
mailboxes = []
|
||||
|
||||
for mailbox in Mailbox.query.filter_by(user_id=self.id, verified=True):
|
||||
mailboxes.append(mailbox.email)
|
||||
mailboxes.append(mailbox)
|
||||
|
||||
return mailboxes
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ from app.oauth_models import (
|
|||
SUPPORTED_OPENID_FLOWS_STR,
|
||||
response_types_to_str,
|
||||
)
|
||||
from app.utils import random_string, encode_url, convert_to_id, random_word
|
||||
from app.utils import random_string, encode_url, random_word
|
||||
|
||||
|
||||
@oauth_bp.route("/authorize", methods=["GET", "POST"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from io import BytesIO
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
import boto3
|
||||
import requests
|
||||
|
|
|
@ -160,11 +160,12 @@ def fake_data():
|
|||
m1 = Mailbox.create(user_id=user.id, email="m1@cd.ef", verified=True)
|
||||
db.session.commit()
|
||||
|
||||
user.default_mailbox_id = m1.id
|
||||
|
||||
Alias.create_new(user, "e1@", mailbox_id=m1.id)
|
||||
for i in range(10):
|
||||
Alias.create_new(user, f"e{i}@", mailbox_id=m1.id)
|
||||
for i in range(30):
|
||||
if i % 2 == 0:
|
||||
Alias.create_new(user, f"e{i}@", mailbox_id=m1.id)
|
||||
else:
|
||||
Alias.create_new(user, f"e{i}@")
|
||||
|
||||
CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True)
|
||||
CustomDomain.create(
|
||||
|
|
|
@ -6,7 +6,7 @@ from flask import url_for
|
|||
|
||||
from app.config import PAGE_LIMIT
|
||||
from app.extensions import db
|
||||
from app.models import User, ApiKey, Alias, Contact, EmailLog
|
||||
from app.models import User, ApiKey, Alias, Contact, EmailLog, Mailbox
|
||||
|
||||
|
||||
def test_get_aliases_error_without_pagination(flask_client):
|
||||
|
@ -292,7 +292,38 @@ def test_update_alias(flask_client):
|
|||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.json == {"note": "test note"}
|
||||
|
||||
|
||||
def test_update_alias_mailbox(flask_client):
|
||||
user = User.create(
|
||||
email="a@b.c", password="password", name="Test User", activated=True
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
mb = Mailbox.create(user_id=user.id, email="ab@cd.com", verified=True)
|
||||
|
||||
# create api_key
|
||||
api_key = ApiKey.create(user.id, "for test")
|
||||
db.session.commit()
|
||||
|
||||
alias = Alias.create_new_random(user)
|
||||
db.session.commit()
|
||||
|
||||
r = flask_client.put(
|
||||
url_for("api.update_alias", alias_id=alias.id),
|
||||
headers={"Authentication": api_key.code},
|
||||
json={"mailbox_id": mb.id},
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
|
||||
# fail when update with non-existing mailbox
|
||||
r = flask_client.put(
|
||||
url_for("api.update_alias", alias_id=alias.id),
|
||||
headers={"Authentication": api_key.code},
|
||||
json={"mailbox_id": -1},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_alias_contacts(flask_client):
|
||||
|
|
Loading…
Reference in New Issue