Add GET /api/v2/aliases
This commit is contained in:
parent
7ed317e334
commit
5d0519ed86
87
README.md
87
README.md
|
@ -831,7 +831,8 @@ Input:
|
|||
|
||||
Output: always return 200, even if email doesn't exist. User need to enter correctly their email.
|
||||
|
||||
#### GET /api/aliases
|
||||
|
||||
#### GET /api/v2/aliases
|
||||
|
||||
Get user aliases.
|
||||
|
||||
|
@ -841,34 +842,70 @@ Input:
|
|||
- (Optional) query: included in request body. Some frameworks might prevent GET request having a non-empty body, in this case this endpoint also supports POST.
|
||||
|
||||
Output:
|
||||
If success, 200 with the list of aliases, for example:
|
||||
If success, 200 with the list of aliases. Each alias has the following fields:
|
||||
|
||||
- id
|
||||
- email
|
||||
- enabled
|
||||
- creation_timestamp
|
||||
- note
|
||||
- nb_block
|
||||
- nb_forward
|
||||
- nb_reply
|
||||
- (optional) latest_activity:
|
||||
- action: forward|reply|block|bounced
|
||||
- timestamp
|
||||
- contact:
|
||||
- email
|
||||
- name
|
||||
- reverse_alias
|
||||
|
||||
Here's an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"aliases": [
|
||||
{
|
||||
"creation_date": "2020-02-04 16:23:02+00:00",
|
||||
"creation_timestamp": 1580833382,
|
||||
"email": "e3@.alo@sl.local",
|
||||
"id": 4,
|
||||
"nb_block": 0,
|
||||
"nb_forward": 0,
|
||||
"nb_reply": 0,
|
||||
"enabled": true,
|
||||
"note": "This is a note"
|
||||
"aliases": [
|
||||
{
|
||||
"creation_date": "2020-04-06 17:57:14+00:00",
|
||||
"creation_timestamp": 1586195834,
|
||||
"email": "prefix1.cat@sl.local",
|
||||
"enabled": true,
|
||||
"id": 3,
|
||||
"latest_activity": {
|
||||
"action": "forward",
|
||||
"contact": {
|
||||
"email": "c1@example.com",
|
||||
"name": null,
|
||||
"reverse_alias": "\"c1 at example.com\" <re1@SL>"
|
||||
},
|
||||
{
|
||||
"creation_date": "2020-02-04 16:23:02+00:00",
|
||||
"creation_timestamp": 1580833382,
|
||||
"email": "e2@.meo@sl.local",
|
||||
"id": 3,
|
||||
"nb_block": 0,
|
||||
"nb_forward": 0,
|
||||
"nb_reply": 0,
|
||||
"enabled": false,
|
||||
"note": null
|
||||
}
|
||||
]
|
||||
"timestamp": 1586195834
|
||||
},
|
||||
"nb_block": 0,
|
||||
"nb_forward": 1,
|
||||
"nb_reply": 0,
|
||||
"note": null
|
||||
},
|
||||
{
|
||||
"creation_date": "2020-04-06 17:57:14+00:00",
|
||||
"creation_timestamp": 1586195834,
|
||||
"email": "prefix0.hey@sl.local",
|
||||
"enabled": true,
|
||||
"id": 2,
|
||||
"latest_activity": {
|
||||
"action": "forward",
|
||||
"contact": {
|
||||
"email": "c0@example.com",
|
||||
"name": null,
|
||||
"reverse_alias": "\"c0 at example.com\" <re0@SL>"
|
||||
},
|
||||
"timestamp": 1586195834
|
||||
},
|
||||
"nb_block": 0,
|
||||
"nb_forward": 1,
|
||||
"nb_reply": 0,
|
||||
"note": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import joinedload
|
||||
from arrow import Arrow
|
||||
from sqlalchemy import or_, func, case
|
||||
|
||||
from app.config import PAGE_LIMIT
|
||||
from app.extensions import db
|
||||
|
||||
from app.models import Alias, Mailbox, Contact, EmailLog
|
||||
from app.models import Alias, Contact, EmailLog, Mailbox
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -17,6 +16,9 @@ class AliasInfo:
|
|||
nb_blocked: int
|
||||
nb_reply: int
|
||||
|
||||
latest_email_log: EmailLog = None
|
||||
latest_contact: Contact = None
|
||||
|
||||
|
||||
def serialize_alias_info(alias_info: AliasInfo) -> dict:
|
||||
return {
|
||||
|
@ -34,6 +36,36 @@ def serialize_alias_info(alias_info: AliasInfo) -> dict:
|
|||
}
|
||||
|
||||
|
||||
def serialize_alias_info_v2(alias_info: AliasInfo) -> dict:
|
||||
res = {
|
||||
# Alias field
|
||||
"id": alias_info.alias.id,
|
||||
"email": alias_info.alias.email,
|
||||
"creation_date": alias_info.alias.created_at.format(),
|
||||
"creation_timestamp": alias_info.alias.created_at.timestamp,
|
||||
"enabled": alias_info.alias.enabled,
|
||||
"note": alias_info.alias.note,
|
||||
# activity
|
||||
"nb_forward": alias_info.nb_forward,
|
||||
"nb_block": alias_info.nb_blocked,
|
||||
"nb_reply": alias_info.nb_reply,
|
||||
}
|
||||
if alias_info.latest_email_log:
|
||||
email_log = alias_info.latest_email_log
|
||||
contact = alias_info.latest_contact
|
||||
# latest activity
|
||||
res["latest_activity"] = {
|
||||
"timestamp": email_log.created_at.timestamp,
|
||||
"action": email_log.get_action(),
|
||||
"contact": {
|
||||
"email": contact.website_email,
|
||||
"name": contact.name,
|
||||
"reverse_alias": contact.website_send_to(),
|
||||
},
|
||||
}
|
||||
return res
|
||||
|
||||
|
||||
def serialize_contact(contact: Contact) -> dict:
|
||||
res = {
|
||||
"id": contact.id,
|
||||
|
@ -74,6 +106,40 @@ 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]:
|
||||
ret = []
|
||||
latest_activity = func.max(
|
||||
case(
|
||||
[
|
||||
(Alias.created_at > EmailLog.created_at, Alias.created_at),
|
||||
(Alias.created_at < EmailLog.created_at, EmailLog.created_at),
|
||||
],
|
||||
else_=Alias.created_at,
|
||||
)
|
||||
).label("latest")
|
||||
|
||||
q = (
|
||||
db.session.query(Alias, 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())
|
||||
)
|
||||
|
||||
if query:
|
||||
q = q.filter(
|
||||
or_(Alias.email.ilike(f"%{query}%"), Alias.note.ilike(f"%{query}%"))
|
||||
)
|
||||
|
||||
q = q.limit(PAGE_LIMIT).offset(page_id * PAGE_LIMIT)
|
||||
|
||||
for alias, latest_activity in q:
|
||||
ret.append(get_alias_info_v2(alias))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def get_alias_info(alias: Alias) -> AliasInfo:
|
||||
q = (
|
||||
db.session.query(Contact, EmailLog)
|
||||
|
@ -94,6 +160,38 @@ def get_alias_info(alias: Alias) -> AliasInfo:
|
|||
return alias_info
|
||||
|
||||
|
||||
def get_alias_info_v2(alias: Alias) -> AliasInfo:
|
||||
q = (
|
||||
db.session.query(Contact, EmailLog)
|
||||
.filter(Contact.alias_id == alias.id)
|
||||
.filter(EmailLog.contact_id == Contact.id)
|
||||
)
|
||||
|
||||
latest_activity: Arrow = alias.created_at
|
||||
latest_email_log = None
|
||||
latest_contact = None
|
||||
|
||||
alias_info = AliasInfo(alias=alias, nb_blocked=0, nb_forward=0, nb_reply=0,)
|
||||
|
||||
for contact, email_log in q:
|
||||
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
|
||||
|
||||
if email_log.created_at > latest_activity:
|
||||
latest_activity = email_log.created_at
|
||||
latest_email_log = email_log
|
||||
latest_contact = contact
|
||||
|
||||
alias_info.latest_contact = latest_contact
|
||||
alias_info.latest_email_log = latest_email_log
|
||||
|
||||
return alias_info
|
||||
|
||||
|
||||
def get_alias_contacts(alias, page_id: int) -> [dict]:
|
||||
q = (
|
||||
Contact.query.filter_by(alias_id=alias.id)
|
||||
|
|
|
@ -11,6 +11,8 @@ from app.api.serializer import (
|
|||
get_alias_infos_with_pagination,
|
||||
get_alias_info,
|
||||
get_alias_contacts,
|
||||
get_alias_infos_with_pagination_v2,
|
||||
serialize_alias_info_v2,
|
||||
)
|
||||
from app.config import EMAIL_DOMAIN
|
||||
from app.dashboard.views.alias_log import get_alias_log
|
||||
|
@ -64,6 +66,57 @@ def get_aliases():
|
|||
)
|
||||
|
||||
|
||||
@api_bp.route("/v2/aliases", methods=["GET", "POST"])
|
||||
@cross_origin()
|
||||
@verify_api_key
|
||||
def get_aliases_v2():
|
||||
"""
|
||||
Get aliases
|
||||
Input:
|
||||
page_id: in query
|
||||
Output:
|
||||
- aliases: list of alias:
|
||||
- id
|
||||
- email
|
||||
- creation_date
|
||||
- creation_timestamp
|
||||
- nb_forward
|
||||
- nb_block
|
||||
- nb_reply
|
||||
- note
|
||||
- (optional) latest_activity:
|
||||
- timestamp
|
||||
- action: forward|reply|block|bounced
|
||||
- contact:
|
||||
- email
|
||||
- name
|
||||
- reverse_alias
|
||||
|
||||
|
||||
"""
|
||||
user = g.user
|
||||
try:
|
||||
page_id = int(request.args.get("page_id"))
|
||||
except (ValueError, TypeError):
|
||||
return jsonify(error="page_id must be provided in request query"), 400
|
||||
|
||||
query = None
|
||||
data = request.get_json(silent=True)
|
||||
if data:
|
||||
query = data.get("query")
|
||||
|
||||
alias_infos: [AliasInfo] = get_alias_infos_with_pagination_v2(
|
||||
user, page_id=page_id, query=query
|
||||
)
|
||||
|
||||
return (
|
||||
jsonify(
|
||||
aliases=[serialize_alias_info_v2(alias_info) for alias_info in alias_infos]
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@api_bp.route("/aliases/<int:alias_id>", methods=["DELETE"])
|
||||
@cross_origin()
|
||||
@verify_api_key
|
||||
|
@ -127,7 +180,7 @@ def get_alias_activities(alias_id):
|
|||
- from
|
||||
- to
|
||||
- timestamp
|
||||
- action: forward|reply|block
|
||||
- action: forward|reply|block|bounced
|
||||
- reverse_alias
|
||||
|
||||
"""
|
||||
|
|
|
@ -848,6 +848,17 @@ class EmailLog(db.Model, ModelMixin):
|
|||
|
||||
contact = db.relationship(Contact)
|
||||
|
||||
def get_action(self) -> str:
|
||||
"""return the action name: forward|reply|block|bounced"""
|
||||
if self.is_reply:
|
||||
return "reply"
|
||||
elif self.bounced:
|
||||
return "bounced"
|
||||
elif self.blocked:
|
||||
return "blocked"
|
||||
else:
|
||||
return "forward"
|
||||
|
||||
|
||||
class Subscription(db.Model, ModelMixin):
|
||||
# Come from Paddle
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import json
|
||||
|
||||
from flask import url_for
|
||||
|
||||
from flask import url_for
|
||||
|
@ -101,6 +103,81 @@ def test_get_aliases_with_pagination(flask_client):
|
|||
assert len(r.json["aliases"]) == 1
|
||||
|
||||
|
||||
def test_get_aliases_v2(flask_client):
|
||||
user = User.create(
|
||||
email="a@b.c", password="password", name="Test User", activated=True
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
# create api_key
|
||||
api_key = ApiKey.create(user.id, "for test")
|
||||
db.session.commit()
|
||||
|
||||
a0 = Alias.create_new(user, "prefix0")
|
||||
a1 = Alias.create_new(user, "prefix1")
|
||||
db.session.commit()
|
||||
|
||||
# add activity for a0
|
||||
c0 = Contact.create(
|
||||
user_id=user.id,
|
||||
alias_id=a0.id,
|
||||
website_email="c0@example.com",
|
||||
reply_email="re0@SL",
|
||||
)
|
||||
db.session.commit()
|
||||
EmailLog.create(contact_id=c0.id, user_id=user.id)
|
||||
db.session.commit()
|
||||
|
||||
# a1 has more recent activity
|
||||
c1 = Contact.create(
|
||||
user_id=user.id,
|
||||
alias_id=a1.id,
|
||||
website_email="c1@example.com",
|
||||
reply_email="re1@SL",
|
||||
)
|
||||
db.session.commit()
|
||||
EmailLog.create(contact_id=c1.id, user_id=user.id)
|
||||
db.session.commit()
|
||||
|
||||
# get aliases v2
|
||||
r = flask_client.get(
|
||||
url_for("api.get_aliases_v2", page_id=0),
|
||||
headers={"Authentication": api_key.code},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
# make sure a1 is returned before a0
|
||||
r0 = r.json["aliases"][0]
|
||||
# r0 will have the following format
|
||||
# {
|
||||
# "creation_date": "2020-04-06 17:52:47+00:00",
|
||||
# "creation_timestamp": 1586195567,
|
||||
# "email": "prefix1.hey@sl.local",
|
||||
# "enabled": true,
|
||||
# "id": 3,
|
||||
# "latest_activity": {
|
||||
# "action": "forward",
|
||||
# "contact": {
|
||||
# "email": "c1@example.com",
|
||||
# "name": null,
|
||||
# "reverse_alias": "\"c1 at example.com\" <re1@SL>"
|
||||
# },
|
||||
# "timestamp": 1586195567
|
||||
# },
|
||||
# "nb_block": 0,
|
||||
# "nb_forward": 1,
|
||||
# "nb_reply": 0,
|
||||
# "note": null
|
||||
# }
|
||||
assert r0["email"].startswith("prefix1")
|
||||
assert r0["latest_activity"]["action"] == "forward"
|
||||
assert "timestamp" in r0["latest_activity"]
|
||||
|
||||
assert r0["latest_activity"]["contact"]["email"] == "c1@example.com"
|
||||
assert "name" in r0["latest_activity"]["contact"]
|
||||
assert "reverse_alias" in r0["latest_activity"]["contact"]
|
||||
|
||||
|
||||
def test_delete_alias(flask_client):
|
||||
user = User.create(
|
||||
email="a@b.c", password="password", name="Test User", activated=True
|
||||
|
|
Loading…
Reference in New Issue