mirror of
https://github.com/simple-login/app.git
synced 2024-09-29 21:21:29 +02:00
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.
|
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.
|
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.
|
- (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:
|
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
|
```json
|
||||||
{
|
{
|
||||||
"aliases": [
|
"aliases": [
|
||||||
{
|
{
|
||||||
"creation_date": "2020-02-04 16:23:02+00:00",
|
"creation_date": "2020-04-06 17:57:14+00:00",
|
||||||
"creation_timestamp": 1580833382,
|
"creation_timestamp": 1586195834,
|
||||||
"email": "e3@.alo@sl.local",
|
"email": "prefix1.cat@sl.local",
|
||||||
"id": 4,
|
"enabled": true,
|
||||||
"nb_block": 0,
|
"id": 3,
|
||||||
"nb_forward": 0,
|
"latest_activity": {
|
||||||
"nb_reply": 0,
|
"action": "forward",
|
||||||
"enabled": true,
|
"contact": {
|
||||||
"note": "This is a note"
|
"email": "c1@example.com",
|
||||||
|
"name": null,
|
||||||
|
"reverse_alias": "\"c1 at example.com\" <re1@SL>"
|
||||||
},
|
},
|
||||||
{
|
"timestamp": 1586195834
|
||||||
"creation_date": "2020-02-04 16:23:02+00:00",
|
},
|
||||||
"creation_timestamp": 1580833382,
|
"nb_block": 0,
|
||||||
"email": "e2@.meo@sl.local",
|
"nb_forward": 1,
|
||||||
"id": 3,
|
"nb_reply": 0,
|
||||||
"nb_block": 0,
|
"note": null
|
||||||
"nb_forward": 0,
|
},
|
||||||
"nb_reply": 0,
|
{
|
||||||
"enabled": false,
|
"creation_date": "2020-04-06 17:57:14+00:00",
|
||||||
"note": null
|
"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 dataclasses import dataclass
|
||||||
|
|
||||||
from sqlalchemy import or_
|
from arrow import Arrow
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy import or_, func, case
|
||||||
|
|
||||||
from app.config import PAGE_LIMIT
|
from app.config import PAGE_LIMIT
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
|
from app.models import Alias, Contact, EmailLog, Mailbox
|
||||||
from app.models import Alias, Mailbox, Contact, EmailLog
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -17,6 +16,9 @@ class AliasInfo:
|
|||||||
nb_blocked: int
|
nb_blocked: int
|
||||||
nb_reply: int
|
nb_reply: int
|
||||||
|
|
||||||
|
latest_email_log: EmailLog = None
|
||||||
|
latest_contact: Contact = None
|
||||||
|
|
||||||
|
|
||||||
def serialize_alias_info(alias_info: AliasInfo) -> dict:
|
def serialize_alias_info(alias_info: AliasInfo) -> dict:
|
||||||
return {
|
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:
|
def serialize_contact(contact: Contact) -> dict:
|
||||||
res = {
|
res = {
|
||||||
"id": contact.id,
|
"id": contact.id,
|
||||||
@ -74,6 +106,40 @@ def get_alias_infos_with_pagination(user, page_id=0, query=None) -> [AliasInfo]:
|
|||||||
return ret
|
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:
|
def get_alias_info(alias: Alias) -> AliasInfo:
|
||||||
q = (
|
q = (
|
||||||
db.session.query(Contact, EmailLog)
|
db.session.query(Contact, EmailLog)
|
||||||
@ -94,6 +160,38 @@ def get_alias_info(alias: Alias) -> AliasInfo:
|
|||||||
return alias_info
|
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]:
|
def get_alias_contacts(alias, page_id: int) -> [dict]:
|
||||||
q = (
|
q = (
|
||||||
Contact.query.filter_by(alias_id=alias.id)
|
Contact.query.filter_by(alias_id=alias.id)
|
||||||
|
@ -11,6 +11,8 @@ from app.api.serializer import (
|
|||||||
get_alias_infos_with_pagination,
|
get_alias_infos_with_pagination,
|
||||||
get_alias_info,
|
get_alias_info,
|
||||||
get_alias_contacts,
|
get_alias_contacts,
|
||||||
|
get_alias_infos_with_pagination_v2,
|
||||||
|
serialize_alias_info_v2,
|
||||||
)
|
)
|
||||||
from app.config import EMAIL_DOMAIN
|
from app.config import EMAIL_DOMAIN
|
||||||
from app.dashboard.views.alias_log import get_alias_log
|
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"])
|
@api_bp.route("/aliases/<int:alias_id>", methods=["DELETE"])
|
||||||
@cross_origin()
|
@cross_origin()
|
||||||
@verify_api_key
|
@verify_api_key
|
||||||
@ -127,7 +180,7 @@ def get_alias_activities(alias_id):
|
|||||||
- from
|
- from
|
||||||
- to
|
- to
|
||||||
- timestamp
|
- timestamp
|
||||||
- action: forward|reply|block
|
- action: forward|reply|block|bounced
|
||||||
- reverse_alias
|
- reverse_alias
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -848,6 +848,17 @@ class EmailLog(db.Model, ModelMixin):
|
|||||||
|
|
||||||
contact = db.relationship(Contact)
|
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):
|
class Subscription(db.Model, ModelMixin):
|
||||||
# Come from Paddle
|
# Come from Paddle
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
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
|
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):
|
def test_delete_alias(flask_client):
|
||||||
user = User.create(
|
user = User.create(
|
||||||
email="a@b.c", password="password", name="Test User", activated=True
|
email="a@b.c", password="password", name="Test User", activated=True
|
||||||
|
Loading…
Reference in New Issue
Block a user