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:
- `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.
- (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:
@ -929,6 +930,7 @@ If success, 200 with the list of contacts, for example:
{
"contacts": [
{
"id": 1,
"contact": "marketing@example.com",
"creation_date": "2020-02-21 11:35:00+00:00",
"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>"
},
{
"id": 2,
"contact": "newsletter@example.com",
"creation_date": "2020-02-21 11:35:00+00:00",
"creation_timestamp": 1582284900,
@ -966,6 +969,7 @@ Return 409 if contact is already added.
```
{
"id": 1,
"contact": "First Last <first@example.com>",
"creation_date": "2020-03-14 11:52:41+00:00",
"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
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 PAGE_LIMIT
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.log import LOG
from app.models import EmailLog
from app.models import Alias, Contact
from app.models import EmailLog
from app.utils import random_string
@api_bp.route("/aliases")
@api_bp.route("/aliases", methods=["GET", "POST"])
@cross_origin()
@verify_api_key
def get_aliases():
@ -43,7 +46,14 @@ def get_aliases():
except (ValueError, TypeError):
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 (
jsonify(
@ -202,6 +212,7 @@ def update_alias(alias_id):
def serialize_contact(fe: Contact) -> dict:
res = {
"id": fe.id,
"creation_date": fe.created_at.format(),
"creation_timestamp": fe.created_at.timestamp,
"last_email_sent_date": None,
@ -319,3 +330,28 @@ def create_contact_route(alias_id):
db.session.commit()
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(
"dashboard/index.html",
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,
query=query,
AliasGeneratorEnum=AliasGeneratorEnum,
@ -184,9 +184,56 @@ def index():
)
def get_alias_info(
user, query=None, highlight_alias_id=None, page_id=None
) -> [AliasInfo]:
def get_alias_info(alias: Alias) -> AliasInfo:
q = (
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:
query = query.strip().lower()
@ -206,10 +253,6 @@ def get_alias_info(
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:
if ge.email not in aliases:
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
def test_error_without_pagination(flask_client):
def test_get_aliases_error_without_pagination(flask_client):
user = User.create(
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"]
def test_success_with_pagination(flask_client):
def test_get_aliases_with_pagination(flask_client):
user = User.create(
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
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):
user = User.create(
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>"},
)
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}