Merge pull request #116 from simple-login/misc

Api Improvements
This commit is contained in:
Son Nguyen Kim 2020-03-17 20:19:46 +01:00 committed by GitHub
commit 6cec75a066
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 176 additions and 14 deletions

View File

@ -802,6 +802,7 @@ Get user aliases.
Input: Input:
- `Authentication` header that contains the api key - `Authentication` header that contains the api key
- `page_id` used for the pagination. The endpoint returns maximum 20 aliases for each page. `page_id` starts at 0. - `page_id` used for the pagination. The endpoint returns maximum 20 aliases for each page. `page_id` starts at 0.
- (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, for example:
@ -929,6 +930,7 @@ If success, 200 with the list of contacts, for example:
{ {
"contacts": [ "contacts": [
{ {
"id": 1,
"contact": "marketing@example.com", "contact": "marketing@example.com",
"creation_date": "2020-02-21 11:35:00+00:00", "creation_date": "2020-02-21 11:35:00+00:00",
"creation_timestamp": 1582284900, "creation_timestamp": 1582284900,
@ -937,6 +939,7 @@ If success, 200 with the list of contacts, for example:
"reverse_alias": "marketing at example.com <reply+bzvpazcdedcgcpztehxzgjgzmxskqa@sl.co>" "reverse_alias": "marketing at example.com <reply+bzvpazcdedcgcpztehxzgjgzmxskqa@sl.co>"
}, },
{ {
"id": 2,
"contact": "newsletter@example.com", "contact": "newsletter@example.com",
"creation_date": "2020-02-21 11:35:00+00:00", "creation_date": "2020-02-21 11:35:00+00:00",
"creation_timestamp": 1582284900, "creation_timestamp": 1582284900,
@ -966,6 +969,7 @@ Return 409 if contact is already added.
``` ```
{ {
"id": 1,
"contact": "First Last <first@example.com>", "contact": "First Last <first@example.com>",
"creation_date": "2020-03-14 11:52:41+00:00", "creation_date": "2020-03-14 11:52:41+00:00",
"creation_timestamp": 1584186761, "creation_timestamp": 1584186761,
@ -975,6 +979,24 @@ Return 409 if contact is already added.
} }
``` ```
#### DELETE /api/contacts/:contact_id
Delete a contact
Input:
- `Authentication` header that contains the api key
- `contact_id` in url.
Output:
If success, 200.
```json
{
"deleted": true
}
```
### Database migration ### Database migration
The database migration is handled by `alembic` The database migration is handled by `alembic`

View File

@ -9,15 +9,18 @@ from app.api.base import api_bp, verify_api_key
from app.config import EMAIL_DOMAIN from app.config import EMAIL_DOMAIN
from app.config import PAGE_LIMIT from app.config import PAGE_LIMIT
from app.dashboard.views.alias_log import get_alias_log from app.dashboard.views.alias_log import get_alias_log
from app.dashboard.views.index import get_alias_info, AliasInfo from app.dashboard.views.index import (
AliasInfo,
get_alias_infos_with_pagination,
)
from app.extensions import db from app.extensions import db
from app.log import LOG from app.log import LOG
from app.models import EmailLog
from app.models import Alias, Contact from app.models import Alias, Contact
from app.models import EmailLog
from app.utils import random_string from app.utils import random_string
@api_bp.route("/aliases") @api_bp.route("/aliases", methods=["GET", "POST"])
@cross_origin() @cross_origin()
@verify_api_key @verify_api_key
def get_aliases(): def get_aliases():
@ -43,7 +46,14 @@ def get_aliases():
except (ValueError, TypeError): except (ValueError, TypeError):
return jsonify(error="page_id must be provided in request query"), 400 return jsonify(error="page_id must be provided in request query"), 400
alias_infos: [AliasInfo] = get_alias_info(user, page_id=page_id) query = None
data = request.get_json(silent=True)
if data:
query = data.get("query")
alias_infos: [AliasInfo] = get_alias_infos_with_pagination(
user, page_id=page_id, query=query
)
return ( return (
jsonify( jsonify(
@ -202,6 +212,7 @@ def update_alias(alias_id):
def serialize_contact(fe: Contact) -> dict: def serialize_contact(fe: Contact) -> dict:
res = { res = {
"id": fe.id,
"creation_date": fe.created_at.format(), "creation_date": fe.created_at.format(),
"creation_timestamp": fe.created_at.timestamp, "creation_timestamp": fe.created_at.timestamp,
"last_email_sent_date": None, "last_email_sent_date": None,
@ -319,3 +330,28 @@ def create_contact_route(alias_id):
db.session.commit() db.session.commit()
return jsonify(**serialize_contact(contact)), 201 return jsonify(**serialize_contact(contact)), 201
@api_bp.route("/contacts/<int:contact_id>", methods=["DELETE"])
@cross_origin()
@verify_api_key
def delete_contact(contact_id):
"""
Delete contact
Input:
contact_id: in url
Output:
200
"""
user = g.user
contact = Contact.get(contact_id)
if not contact or contact.alias.user_id != user.id:
return jsonify(error="Forbidden"), 403
Contact.delete(contact_id)
db.session.commit()
return jsonify(deleted=True), 200

View File

@ -176,7 +176,7 @@ def index():
return render_template( return render_template(
"dashboard/index.html", "dashboard/index.html",
client_users=client_users, client_users=client_users,
aliases=get_alias_info(current_user, query, highlight_alias_id), aliases=get_alias_infos(current_user, query, highlight_alias_id),
highlight_alias_id=highlight_alias_id, highlight_alias_id=highlight_alias_id,
query=query, query=query,
AliasGeneratorEnum=AliasGeneratorEnum, AliasGeneratorEnum=AliasGeneratorEnum,
@ -184,9 +184,56 @@ def index():
) )
def get_alias_info( def get_alias_info(alias: Alias) -> AliasInfo:
user, query=None, highlight_alias_id=None, page_id=None q = (
) -> [AliasInfo]: db.session.query(Contact, EmailLog)
.filter(Contact.alias_id == alias.id)
.filter(EmailLog.contact_id == Contact.id)
)
alias_info = AliasInfo(
id=alias.id,
alias=alias,
mailbox=alias.mailbox,
note=alias.note,
nb_blocked=0,
nb_forward=0,
nb_reply=0,
)
for _, el in q:
if el.is_reply:
alias_info.nb_reply += 1
elif el.blocked:
alias_info.nb_blocked += 1
else:
alias_info.nb_forward += 1
return alias_info
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)
)
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 in q:
ret.append(get_alias_info(alias))
return ret
def get_alias_infos(user, query=None, highlight_alias_id=None) -> [AliasInfo]:
if query: if query:
query = query.strip().lower() query = query.strip().lower()
@ -206,10 +253,6 @@ def get_alias_info(
or_(Alias.email.ilike(f"%{query}%"), Alias.note.ilike(f"%{query}%")) or_(Alias.email.ilike(f"%{query}%"), Alias.note.ilike(f"%{query}%"))
) )
# pagination activated
if page_id is not None:
q = q.limit(PAGE_LIMIT).offset(page_id * PAGE_LIMIT)
for ge, fe, fel, mb in q: for ge, fe, fel, mb in q:
if ge.email not in aliases: if ge.email not in aliases:
aliases[ge.email] = AliasInfo( aliases[ge.email] = AliasInfo(

View File

@ -8,7 +8,7 @@ from app.models import User, ApiKey, Alias, Contact, EmailLog
from app.utils import random_word from app.utils import random_word
def test_error_without_pagination(flask_client): def test_get_aliases_error_without_pagination(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
) )
@ -26,7 +26,7 @@ def test_error_without_pagination(flask_client):
assert r.json["error"] assert r.json["error"]
def test_success_with_pagination(flask_client): def test_get_aliases_with_pagination(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
) )
@ -70,6 +70,38 @@ def test_success_with_pagination(flask_client):
assert len(r.json["aliases"]) == 2 assert len(r.json["aliases"]) == 2
def test_get_aliases_with_pagination(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()
# create more aliases than PAGE_LIMIT
Alias.create_new(user, "prefix1")
Alias.create_new(user, "prefix2")
db.session.commit()
# get aliases without query, should return 3 aliases as one alias is created when user is created
r = flask_client.get(
url_for("api.get_aliases", page_id=0), headers={"Authentication": api_key.code}
)
assert r.status_code == 200
assert len(r.json["aliases"]) == 3
# get aliases with "prefix1" query, should return 1 alias
r = flask_client.get(
url_for("api.get_aliases", page_id=0),
headers={"Authentication": api_key.code},
json={"query": "prefix1"},
)
assert r.status_code == 200
assert len(r.json["aliases"]) == 1
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
@ -267,3 +299,32 @@ def test_create_contact_route(flask_client):
json={"contact": "First2 Last2 <first@example.com>"}, json={"contact": "First2 Last2 <first@example.com>"},
) )
assert r.status_code == 409 assert r.status_code == 409
def test_delete_contact(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()
alias = Alias.create_new_random(user)
db.session.commit()
contact = Contact.create(
alias_id=alias.id,
website_email="contact@example.com",
reply_email="reply+random@sl.io",
)
db.session.commit()
r = flask_client.delete(
url_for("api.delete_contact", contact_id=contact.id),
headers={"Authentication": api_key.code},
)
assert r.status_code == 200
assert r.json == {"deleted": True}