mirror of
https://github.com/simple-login/app.git
synced 2024-09-30 05:31:30 +02:00
commit
6cec75a066
22
README.md
22
README.md
@ -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`
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user