diff --git a/README.md b/README.md index a1b60f29..46b741a4 100644 --- a/README.md +++ b/README.md @@ -737,6 +737,43 @@ Output: The `api_key` is used in all subsequent requests. It's empty if MFA is enabled. If user hasn't enabled MFA, `mfa_key` is empty. +#### GET /api/aliases + +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. + +Output: +If success, 200 with the list of aliases, for 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 + }, + { + "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 + } + ] +} +``` + + ### Database migration The database migration is handled by `alembic` diff --git a/app/api/__init__.py b/app/api/__init__.py index 430672cb..62021b60 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -5,4 +5,5 @@ from .views import ( user_info, auth_login, auth_mfa, + alias, ) diff --git a/app/api/views/alias.py b/app/api/views/alias.py new file mode 100644 index 00000000..76a51a5e --- /dev/null +++ b/app/api/views/alias.py @@ -0,0 +1,58 @@ +from flask import g +from flask import jsonify, request +from flask_cors import cross_origin + +from app.api.base import api_bp, verify_api_key +from app.config import MAX_NB_EMAIL_FREE_PLAN +from app.dashboard.views.custom_alias import verify_prefix_suffix +from app.dashboard.views.index import get_alias_info, AliasInfo +from app.extensions import db +from app.log import LOG +from app.models import GenEmail, AliasUsedOn +from app.utils import convert_to_id + + +@api_bp.route("/aliases") +@cross_origin() +@verify_api_key +def get_aliases(): + """ + Get aliases + Input: + page_id: in query + Output: + - aliases: list of alias: + - id + - email + - creation_date + - creation_timestamp + - nb_forward + - nb_block + - nb_reply + + """ + 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 + + aliases: [AliasInfo] = get_alias_info(user.id, page_id=page_id) + + return ( + jsonify( + aliases=[ + { + "id": alias.id, + "email": alias.gen_email.email, + "creation_date": alias.gen_email.created_at.format(), + "creation_timestamp": alias.gen_email.created_at.timestamp, + "nb_forward": alias.nb_forward, + "nb_block": alias.nb_blocked, + "nb_reply": alias.nb_reply, + } + for alias in aliases + ] + ), + 200, + ) diff --git a/app/config.py b/app/config.py index 6c0f4d36..4fa15910 100644 --- a/app/config.py +++ b/app/config.py @@ -170,3 +170,6 @@ FLASK_PROFILER_PASSWORD = os.environ.get("FLASK_PROFILER_PASSWORD") # Job names JOB_ONBOARDING_1 = "onboarding-1" + +# for pagination +PAGE_LIMIT = 20 diff --git a/app/dashboard/views/index.py b/app/dashboard/views/index.py index 4ca67d74..e16a6909 100644 --- a/app/dashboard/views/index.py +++ b/app/dashboard/views/index.py @@ -4,6 +4,7 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload from app import email_utils +from app.config import PAGE_LIMIT from app.dashboard.base import dashboard_bp from app.extensions import db from app.log import LOG @@ -18,6 +19,7 @@ from app.models import ( class AliasInfo: + id: int gen_email: GenEmail nb_forward: int nb_blocked: int @@ -143,7 +145,9 @@ def index(): ) -def get_alias_info(user_id, query=None, highlight_gen_email_id=None) -> [AliasInfo]: +def get_alias_info( + user_id, query=None, highlight_gen_email_id=None, page_id=None +) -> [AliasInfo]: if query: query = query.strip().lower() @@ -162,9 +166,14 @@ def get_alias_info(user_id, query=None, highlight_gen_email_id=None) -> [AliasIn if query: q = q.filter(GenEmail.email.contains(query)) + # pagination activated + if page_id is not None: + q = q.limit(PAGE_LIMIT).offset(page_id * PAGE_LIMIT) + for ge, fe, fel in q: if ge.email not in aliases: aliases[ge.email] = AliasInfo( + id=ge.id, gen_email=ge, nb_blocked=0, nb_forward=0, diff --git a/tests/api/test_alias.py b/tests/api/test_alias.py new file mode 100644 index 00000000..9138c5c2 --- /dev/null +++ b/tests/api/test_alias.py @@ -0,0 +1,56 @@ +from flask import url_for + +from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN, PAGE_LIMIT +from app.extensions import db +from app.models import User, ApiKey, GenEmail +from app.utils import random_word + + +def test_error_without_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() + + r = flask_client.get( + url_for("api.get_aliases"), headers={"Authentication": api_key.code}, + ) + + assert r.status_code == 400 + assert r.json["error"] + + +def test_success_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 + for _ in range(PAGE_LIMIT + 1): + GenEmail.create_new_random(user.id) + db.session.commit() + + # get aliases on the 1st page, should return PAGE_LIMIT aliases + 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"]) == PAGE_LIMIT + + # get aliases on the 2nd page, should return 2 aliases + # as the total number of aliases is PAGE_LIMIT +2 + # 1 alias is created when user is created + r = flask_client.get( + url_for("api.get_aliases", page_id=1), headers={"Authentication": api_key.code}, + ) + assert r.status_code == 200 + assert len(r.json["aliases"]) == 2