diff --git a/README.md b/README.md index 4c92115f..d0f13a5f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/api/base.py b/app/api/base.py index 5e7ca419..36d9bd50 100644 --- a/app/api/base.py +++ b/app/api/base.py @@ -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 diff --git a/app/api/serializer.py b/app/api/serializer.py index 94a874d3..f02c4dc8 100644 --- a/app/api/serializer.py +++ b/app/api/serializer.py @@ -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: diff --git a/app/api/views/alias.py b/app/api/views/alias.py index 62e6182b..e89b2895 100644 --- a/app/api/views/alias.py +++ b/app/api/views/alias.py @@ -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/", 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) diff --git a/app/api/views/auth_mfa.py b/app/api/views/auth_mfa.py index c53a0b9c..38de5426 100644 --- a/app/api/views/auth_mfa.py +++ b/app/api/views/auth_mfa.py @@ -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 diff --git a/app/auth/views/register.py b/app/auth/views/register.py index fd9acfe0..e4caa022 100644 --- a/app/auth/views/register.py +++ b/app/auth/views/register.py @@ -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 diff --git a/app/auth/views/reset_password.py b/app/auth/views/reset_password.py index 77635a1a..1e7ba046 100644 --- a/app/auth/views/reset_password.py +++ b/app/auth/views/reset_password.py @@ -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 diff --git a/app/config.py b/app/config.py index b0fbad06..158c1331 100644 --- a/app/config.py +++ b/app/config.py @@ -2,8 +2,6 @@ import os import random import string import subprocess -import tempfile -from uuid import uuid4 from dotenv import load_dotenv diff --git a/app/dashboard/templates/dashboard/index.html b/app/dashboard/templates/dashboard/index.html index cfe967c2..7fdd74f6 100644 --- a/app/dashboard/templates/dashboard/index.html +++ b/app/dashboard/templates/dashboard/index.html @@ -63,8 +63,19 @@
+ +
@@ -77,7 +88,7 @@ {% set alias = alias_info.alias %}
-
+
@@ -179,7 +190,8 @@
@@ -241,6 +255,7 @@