From fd0fed74a4124f065b8439dada9354ee9abe17da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:22:58 +0400 Subject: [PATCH 01/12] :arrow_up: Upgrade backend dependencies in Dockerfiles --- .../backend/backend.dockerfile | 12 ++---------- .../backend/celeryworker.dockerfile | 2 +- .../backend/tests.dockerfile | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/{{cookiecutter.project_slug}}/backend/backend.dockerfile b/{{cookiecutter.project_slug}}/backend/backend.dockerfile index dfaf933..c6a4086 100644 --- a/{{cookiecutter.project_slug}}/backend/backend.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/backend.dockerfile @@ -1,12 +1,11 @@ -FROM python:3.6 +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.6 # Dependencies for Couchbase RUN wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | apt-key add - RUN echo "deb http://packages.couchbase.com/ubuntu stretch stretch/main" > /etc/apt/sources.list.d/couchbase.list RUN apt-get update && apt-get install -y libcouchbase-dev build-essential -RUN pip install --upgrade pip -RUN pip install celery==4.2.1 passlib[bcrypt] tenacity requests pydantic couchbase emails fastapi>=0.1.13 uvicorn gunicorn pyjwt python-multipart email_validator jinja2 +RUN pip install celery==4.2.1 passlib[bcrypt] tenacity requests couchbase emails "fastapi>=0.7.1" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 # For development, Jupyter remote kernel, Hydrogen # Using inside the container: @@ -16,10 +15,3 @@ RUN bash -c "if [ $env == 'dev' ] ; then pip install jupyter ; fi" EXPOSE 8888 COPY ./app /app -WORKDIR /app/ - -ENV PYTHONPATH=/app - -EXPOSE 80 - -CMD ["bash", "/app/backend-start.sh"] diff --git a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile index 09d5a7b..7c4076e 100644 --- a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile @@ -5,7 +5,7 @@ RUN wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | apt-key add - RUN echo "deb http://packages.couchbase.com/ubuntu stretch stretch/main" > /etc/apt/sources.list.d/couchbase.list RUN apt-get update && apt-get install -y libcouchbase-dev build-essential -RUN pip install raven celery==4.2.1 passlib[bcrypt] tenacity requests fastapi>=0.1.13 pydantic couchbase emails pyjwt email_validator jinja2 +RUN pip install raven celery==4.2.1 passlib[bcrypt] tenacity requests "fastapi>=0.7.1" couchbase emails pyjwt email_validator jinja2 # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/backend/tests.dockerfile b/{{cookiecutter.project_slug}}/backend/tests.dockerfile index 117219d..15aeeab 100644 --- a/{{cookiecutter.project_slug}}/backend/tests.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/tests.dockerfile @@ -4,7 +4,7 @@ RUN wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | apt-key add - RUN echo "deb http://packages.couchbase.com/ubuntu stretch stretch/main" > /etc/apt/sources.list.d/couchbase.list RUN apt-get update && apt-get install -y libcouchbase-dev build-essential -RUN pip install requests pytest tenacity passlib[bcrypt] couchbase pydantic fastapi>=0.1.13 +RUN pip install requests pytest tenacity passlib[bcrypt] couchbase "fastapi>=0.7.1" # For development, Jupyter remote kernel, Hydrogen # Using inside the container: From 86ad729a2625f6c4d302b0b65a3444b5a231c3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:26:48 +0400 Subject: [PATCH 02/12] :memo: Update generated docs to use latest integrated /start-reload.sh --- {{cookiecutter.project_slug}}/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 839caec..7517eb8 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -81,7 +81,7 @@ The changes to those files only affect the local development environment, not th For example, the directory with the backend code is mounted as a Docker "host volume" (in the file `docker-compose.dev.volumes.yml`), mapping the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast. -There is also a commented out `command` override (in the file `docker-compose.dev.command.yml`), if you want to enable it, uncomment it. It makes the backend container run a process that does "nothing", but keeps the process running. That allows you to get inside your living container and run commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detectes changes. +There is also a commented out `command` override (in the file `docker-compose.dev.command.yml`), if you want to enable it, uncomment it. It makes the backend container run a process that does "nothing", but keeps the process running. That allows you to get inside your living container and run commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes. To get inside the container with a `bash` session you can start the stack with: @@ -103,16 +103,16 @@ root@7f2607af31c3:/app# that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory. -There is also a script `backend-live.sh` to run the debug live reloading server. You can run that script from inside the container with: +There is also a script `/start-reload.sh` to run the debug live reloading server. You can run that script from inside the container with: ```bash -bash ./backend-live.sh +bash /start-reload.sh ``` ...it will look like: ```bash -root@7f2607af31c3:/app# bash ./backend-live.sh +root@7f2607af31c3:/app# bash /start-reload.sh ``` and then hit enter. That runs the debugging server that auto reloads when it detects code changes. From c0b0eb7419e3684250b7f4e15fbf55a62054e491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:27:28 +0400 Subject: [PATCH 03/12] :wrench: Add scripts for project generator development --- dev-fsfcb-back.sh | 17 +++++++++++++++++ dev-fsfcb-config.yml | 2 ++ dev-fsfcb.sh | 10 ++++++++++ 3 files changed, 29 insertions(+) create mode 100644 dev-fsfcb-back.sh create mode 100644 dev-fsfcb-config.yml create mode 100644 dev-fsfcb.sh diff --git a/dev-fsfcb-back.sh b/dev-fsfcb-back.sh new file mode 100644 index 0000000..a1254df --- /dev/null +++ b/dev-fsfcb-back.sh @@ -0,0 +1,17 @@ +#! /usr/bin/env bash + +# Run this script from outside the project, to integrate a dev-fsfcb project with changes and review modifications + +# Exit in case of error +set -e + +if [ $(uname -s) = "Linux" ]; then + echo "Remove __pycache__ files" + sudo find ./dev-fsfcb/ -type d -name __pycache__ -exec rm -r {} \+ +fi + +rm -rf ./full-stack-fastapi-couchbase/\{\{cookiecutter.project_slug\}\}/* + +rsync -a --exclude=node_modules ./dev-fsfcb/* ./full-stack-fastapi-couchbase/\{\{cookiecutter.project_slug\}\}/ + +rsync -a ./dev-fsfcb/{.env,.gitignore,.gitlab-ci.yml} ./full-stack-fastapi-couchbase/\{\{cookiecutter.project_slug\}\}/ diff --git a/dev-fsfcb-config.yml b/dev-fsfcb-config.yml new file mode 100644 index 0000000..9290046 --- /dev/null +++ b/dev-fsfcb-config.yml @@ -0,0 +1,2 @@ +default_context: + "project_name": "Dev FSFCB" diff --git a/dev-fsfcb.sh b/dev-fsfcb.sh new file mode 100644 index 0000000..7f294ea --- /dev/null +++ b/dev-fsfcb.sh @@ -0,0 +1,10 @@ +#! /usr/bin/env bash + +# Run this script from outside the project, to generate a dev-fsfcb project + +# Exit in case of error +set -e + +rm -rf ./dev-fsfcb + +cookiecutter --config-file ./full-stack-fastapi-couchbase/dev-fsfcb-config.yml --no-input -f ./full-stack-fastapi-couchbase From c5f5c41b4bafaf8e4cb9589a3384d88a7ddacee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:29:11 +0400 Subject: [PATCH 04/12] :heavy_plus_sign: Add backend development dependencies --- {{cookiecutter.project_slug}}/backend/app/Pipfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/{{cookiecutter.project_slug}}/backend/app/Pipfile b/{{cookiecutter.project_slug}}/backend/app/Pipfile index 8e81505..4a6a12e 100644 --- a/{{cookiecutter.project_slug}}/backend/app/Pipfile +++ b/{{cookiecutter.project_slug}}/backend/app/Pipfile @@ -11,6 +11,7 @@ isort = "*" autoflake = "*" flake8 = "*" pytest = "*" +vulture = "*" [packages] fastapi = "*" @@ -26,6 +27,7 @@ pydantic = "*" couchbase = "*" emails = "*" raven = "*" +jinja2 = "*" [requires] python_version = "3.6" From 85ab7c514d7ea28f8a3897ae24d2582c0b6b20cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:29:30 +0400 Subject: [PATCH 05/12] :fire: Remove scripts provided by base image --- .../backend/app/backend-live.sh | 2 -- .../backend/app/backend-start.sh | 27 ------------------- 2 files changed, 29 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/backend/app/backend-live.sh delete mode 100644 {{cookiecutter.project_slug}}/backend/app/backend-start.sh diff --git a/{{cookiecutter.project_slug}}/backend/app/backend-live.sh b/{{cookiecutter.project_slug}}/backend/app/backend-live.sh deleted file mode 100644 index c092307..0000000 --- a/{{cookiecutter.project_slug}}/backend/app/backend-live.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -uvicorn app.main:app --host 0.0.0.0 --port 80 --debug diff --git a/{{cookiecutter.project_slug}}/backend/app/backend-start.sh b/{{cookiecutter.project_slug}}/backend/app/backend-start.sh deleted file mode 100644 index 7a7893f..0000000 --- a/{{cookiecutter.project_slug}}/backend/app/backend-start.sh +++ /dev/null @@ -1,27 +0,0 @@ -#! /usr/bin/env bash - -set -e - -# Let the DB start -python /app/app/backend_pre_start.py - - -LOG_LEVEL=info -# Uncomment to squeeze performance in exchange of logs -# LOG_LEVEL=warning - -# Get CPU cores -CORES=$(nproc --all) -# Read env var WORKERS_PER_CORE with default of 2 -WORKERS_PER_CORE_PERCENT=${WORKERS_PER_CORE_PERCENT:-200} -# Compute DEFAULT_WEB_CONCURRENCY as CPU cores * workers per core -DEFAULT_WEB_CONCURRENCY=$(( ($CORES * $WORKERS_PER_CORE_PERCENT) / 100 )) -# Minimum default of workers is 1 -if [ "$DEFAULT_WEB_CONCURRENCY" -lt 1 ]; then - DEFAULT_WEB_CONCURRENCY=1 -fi -# Read WEB_CONCURRENCY env var, with default of computed value -WEB_CONCURRENCY=${WEB_CONCURRENCY:-$DEFAULT_WEB_CONCURRENCY} -echo "Using these many workers: $WEB_CONCURRENCY" - -gunicorn -k uvicorn.workers.UvicornWorker --log-level $LOG_LEVEL app.main:app --bind 0.0.0.0:80 From e68326fb6e3c66e4777f44a1dd4a0950123ea2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:30:08 +0400 Subject: [PATCH 06/12] :loud_sound: Add logs to starting scripts --- .../backend/app/app/backend_pre_start.py | 6 ++++- .../backend/app/app/celeryworker_pre_start.py | 16 ++++++++---- .../backend/app/app/tests_pre_start.py | 25 ++++++++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py index c7e170f..e6b6bb8 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py @@ -18,7 +18,11 @@ after=after_log(logger, logging.WARN), ) def init(): - init_db() + try: + init_db() + except Exception as e: + logger.error(e) + raise e def main(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py index 20fba64..f11db65 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py @@ -2,6 +2,8 @@ from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed +from app.db.database import get_default_bucket + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -16,11 +18,15 @@ after=after_log(logger, logging.WARN), ) def init(): - # Check Couchbase is awake - from app.db.database import get_default_bucket - - bucket = get_default_bucket() - logger.info(f"Database bucket connection established with bucket object: {bucket}") + try: + # Check Couchbase is awake + bucket = get_default_bucket() + logger.info( + f"Database bucket connection established with bucket object: {bucket}" + ) + except Exception as e: + logger.error(e) + raise e def main(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py index 481b24f..e623af8 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py @@ -2,6 +2,7 @@ from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed +from app.db.database import get_default_bucket from app.tests.api.api_v1.test_token import test_get_access_token logging.basicConfig(level=logging.INFO) @@ -18,18 +19,18 @@ after=after_log(logger, logging.WARN), ) def init(): - init_tests() - - -def init_tests(): - # Check Couchbase is awake - from app.db.database import get_default_bucket # noqa - - bucket = get_default_bucket() - logger.info(f"Database bucket connection established with bucket object: {bucket}") - - # Wait for API to be awake, run one simple tests to authenticate - test_get_access_token() + try: + # Check Couchbase is awake + bucket = get_default_bucket() + logger.info( + f"Database bucket connection established with bucket object: {bucket}" + ) + + # Wait for API to be awake, run one simple tests to authenticate + test_get_access_token() + except Exception as e: + logger.error(e) + raise e def main(): From 6d1c020b523ddf52c7587edc09f1d9ae4163b52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:35:35 +0400 Subject: [PATCH 07/12] :building_construction: Upgrade backend structure, names, dependencies --- .../backend/app/app/api/api_v1/api.py | 13 +-- .../app/app/api/api_v1/endpoints/role.py | 16 +-- .../app/app/api/api_v1/endpoints/token.py | 40 +++---- .../app/app/api/api_v1/endpoints/user.py | 110 +++++++----------- .../app/app/api/api_v1/endpoints/utils.py | 16 +-- .../backend/app/app/api/utils/__init__.py | 0 .../backend/app/app/api/utils/security.py | 43 +++++++ .../backend/app/app/core/jwt.py | 29 +---- .../backend/app/app/core/security.py | 4 +- .../backend/app/app/crud/__init__.py | 1 + .../backend/app/app/crud/user.py | 100 ++++++++-------- .../backend/app/app/crud/utils.py | 4 +- .../backend/app/app/db/init_db.py | 77 ++++++------ .../backend/app/app/main.py | 10 +- .../app/app/tests/api/api_v1/test_user.py | 16 +-- .../app/app/tests/crud/test_get_ids.py | 4 +- .../backend/app/app/tests/crud/test_user.py | 40 +++---- .../backend/app/app/utils.py | 74 +++++------- .../backend/app/app/worker.py | 4 +- 19 files changed, 267 insertions(+), 334 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/api/utils/__init__.py create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py index 5ec46a5..6803319 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py @@ -1,12 +1,9 @@ from fastapi import APIRouter -from app.api.api_v1.endpoints.role import router as roles_router -from app.api.api_v1.endpoints.token import router as token_router -from app.api.api_v1.endpoints.user import router as user_router -from app.api.api_v1.endpoints.utils import router as utils_router +from app.api.api_v1.endpoints import role, token, user, utils api_router = APIRouter() -api_router.include_router(roles_router) -api_router.include_router(token_router) -api_router.include_router(user_router) -api_router.include_router(utils_router) +api_router.include_router(role.router) +api_router.include_router(token.router) +api_router.include_router(user.router) +api_router.include_router(utils.router) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/role.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/role.py index 18118b1..e2dcfea 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/role.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/role.py @@ -1,9 +1,7 @@ from fastapi import APIRouter, Depends -from starlette.exceptions import HTTPException -from app.core.jwt import get_current_user -from app.crud.user import check_if_user_is_active, check_if_user_is_superuser -from app.crud.utils import ensure_enums_to_strs +from app import crud +from app.api.utils.security import get_current_active_superuser from app.models.role import RoleEnum, Roles from app.models.user import UserInDB @@ -11,15 +9,9 @@ @router.get("/roles/", response_model=Roles) -def route_roles_get(current_user: UserInDB = Depends(get_current_user)): +def read_roles(current_user: UserInDB = Depends(get_current_active_superuser)): """ Retrieve roles """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not (check_if_user_is_superuser(current_user)): - raise HTTPException( - status_code=400, detail="The current user does not have enogh privileges" - ) - roles = ensure_enums_to_strs(RoleEnum) + roles = crud.utils.ensure_enums_to_strs(RoleEnum) return {"roles": roles} diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py index 26a1725..6df86bc 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py @@ -1,18 +1,12 @@ from datetime import timedelta -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm -from starlette.exceptions import HTTPException +from app import crud +from app.api.utils.security import get_current_user from app.core import config -from app.core.jwt import create_access_token, get_current_user -from app.crud.user import ( - authenticate_user, - check_if_user_is_active, - check_if_user_is_superuser, - get_user, - update_user, -) +from app.core.jwt import create_access_token from app.db.database import get_default_bucket from app.models.msg import Msg from app.models.token import Token @@ -27,27 +21,29 @@ @router.post("/login/access-token", response_model=Token, tags=["login"]) -def route_login_access_token(form_data: OAuth2PasswordRequestForm = Depends()): +def login(form_data: OAuth2PasswordRequestForm = Depends()): """ OAuth2 compatible token login, get an access token for future requests """ bucket = get_default_bucket() - user = authenticate_user(bucket, form_data.username, form_data.password) + user = crud.user.authenticate( + bucket, username=form_data.username, password=form_data.password + ) if not user: raise HTTPException(status_code=400, detail="Incorrect email or password") - elif not check_if_user_is_active(user): + elif not crud.user.is_active(user): raise HTTPException(status_code=400, detail="Inactive user") access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES) return { "access_token": create_access_token( - data={"username": form_data.username}, expires_delta=access_token_expires + data={"username": user.username}, expires_delta=access_token_expires ), "token_type": "bearer", } @router.post("/login/test-token", tags=["login"], response_model=User) -def route_test_token(current_user: UserInDB = Depends(get_current_user)): +def test_token(current_user: UserInDB = Depends(get_current_user)): """ Test access token """ @@ -55,19 +51,19 @@ def route_test_token(current_user: UserInDB = Depends(get_current_user)): @router.post("/password-recovery/{username}", tags=["login"], response_model=Msg) -def route_recover_password(username: str): +def recover_password(username: str): """ Password Recovery """ bucket = get_default_bucket() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system.", ) - password_reset_token = generate_password_reset_token(username) + password_reset_token = generate_password_reset_token(username=username) send_reset_password_email( email_to=user.email, username=username, token=password_reset_token ) @@ -75,7 +71,7 @@ def route_recover_password(username: str): @router.post("/reset-password/", tags=["login"], response_model=Msg) -def route_reset_password(token: str, new_password: str): +def reset_password(token: str, new_password: str): """ Reset password """ @@ -83,14 +79,14 @@ def route_reset_password(token: str, new_password: str): if not username: raise HTTPException(status_code=400, detail="Invalid token") bucket = get_default_bucket() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system.", ) - elif not check_if_user_is_active(user): + elif not crud.user.is_active(user): raise HTTPException(status_code=400, detail="Inactive user") user_in = UserInUpdate(name=username, password=new_password) - user = update_user(bucket, user_in) + user = crud.user.update(bucket, user_in=user_in) return {"msg": "Password updated successfully"} diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py index 2f75859..ccbc308 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py @@ -1,20 +1,11 @@ from typing import List -from fastapi import APIRouter, Body, Depends +from fastapi import APIRouter, Body, Depends, HTTPException from pydantic.types import EmailStr -from starlette.exceptions import HTTPException +from app import crud +from app.api.utils.security import get_current_active_superuser, get_current_active_user from app.core import config -from app.core.jwt import get_current_user -from app.crud.user import ( - check_if_user_is_active, - check_if_user_is_superuser, - get_user, - get_users, - search_users, - update_user, - upsert_user, -) from app.db.database import get_default_bucket from app.models.user import User, UserInCreate, UserInDB, UserInUpdate from app.utils import send_new_account_email @@ -23,68 +14,55 @@ @router.get("/users/", tags=["users"], response_model=List[User]) -def route_users_get( - skip: int = 0, limit: int = 100, current_user: UserInDB = Depends(get_current_user) +def read_users( + skip: int = 0, + limit: int = 100, + current_user: UserInDB = Depends(get_current_active_superuser), ): """ Retrieve users """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not check_if_user_is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) bucket = get_default_bucket() - users = get_users(bucket, skip=skip, limit=limit) + users = crud.user.get_multi(bucket, skip=skip, limit=limit) return users @router.get("/users/search/", tags=["users"], response_model=List[User]) -def route_search_users( +def search_users( q: str, skip: int = 0, limit: int = 100, - current_user: UserInDB = Depends(get_current_user), + current_user: UserInDB = Depends(get_current_active_superuser), ): """ - Search users, use Bleve Query String syntax: http://blevesearch.com/docs/Query-String-Query/ + Search users, use Bleve Query String syntax: + http://blevesearch.com/docs/Query-String-Query/ - For typeahead sufix with `*`. For example, a query with: `email:johnd*` will match users with - email `johndoe@example.com`, `johndid@example.net`, etc. + For typeahead suffix with `*`. For example, a query with: `email:johnd*` will match + users with email `johndoe@example.com`, `johndid@example.net`, etc. """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not check_if_user_is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) bucket = get_default_bucket() - users = search_users(bucket=bucket, query_string=q, skip=skip, limit=limit) + users = crud.user.search(bucket=bucket, query_string=q, skip=skip, limit=limit) return users @router.post("/users/", tags=["users"], response_model=User) -def route_users_post( - *, user_in: UserInCreate, current_user: UserInDB = Depends(get_current_user) +def create_user( + *, + user_in: UserInCreate, + current_user: UserInDB = Depends(get_current_active_superuser), ): """ Create new user """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not check_if_user_is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) bucket = get_default_bucket() - user = get_user(bucket, user_in.username) + user = crud.user.get(bucket, username=user_in.username) if user: raise HTTPException( status_code=400, detail="The user with this username already exists in the system.", ) - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) if config.EMAILS_ENABLED and user_in.email: send_new_account_email( email_to=user_in.email, username=user_in.username, password=user_in.password @@ -93,18 +71,16 @@ def route_users_post( @router.put("/users/me", tags=["users"], response_model=User) -def route_users_me_put( +def update_user_me( *, password: str = Body(None), full_name: str = Body(None), email: EmailStr = Body(None), - current_user: UserInDB = Depends(get_current_user), + current_user: UserInDB = Depends(get_current_active_user), ): """ Update own user """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") user_in = UserInUpdate(**current_user.dict()) if password is not None: user_in.password = password @@ -113,22 +89,20 @@ def route_users_me_put( if email is not None: user_in.email = email bucket = get_default_bucket() - user = update_user(bucket, user_in) + user = crud.user.update(bucket, user_in=user_in) return user @router.get("/users/me", tags=["users"], response_model=User) -def route_users_me_get(current_user: UserInDB = Depends(get_current_user)): +def read_user_me(current_user: UserInDB = Depends(get_current_active_user)): """ Get current user """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") return current_user @router.post("/users/open", tags=["users"], response_model=User) -def route_users_post_open( +def create_user_open( *, username: str = Body(...), password: str = Body(...), @@ -144,7 +118,7 @@ def route_users_post_open( detail="Open user resgistration is forbidden on this server", ) bucket = get_default_bucket() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) if user: raise HTTPException( status_code=400, @@ -153,24 +127,24 @@ def route_users_post_open( user_in = UserInCreate( username=username, password=password, email=email, full_name=full_name ) - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + if config.EMAILS_ENABLED and user_in.email: + send_new_account_email( + email_to=user_in.email, username=user_in.username, password=user_in.password + ) return user @router.get("/users/{username}", tags=["users"], response_model=User) -def route_users_id_get( - username: str, current_user: UserInDB = Depends(get_current_user) -): +def read_user(username: str, current_user: UserInDB = Depends(get_current_active_user)): """ Get a specific user by username (email) """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") bucket = get_default_bucket() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) if user == current_user: return user - if not check_if_user_is_superuser(current_user): + if not crud.user.is_superuser(current_user): raise HTTPException( status_code=400, detail="The user doesn't have enough privileges" ) @@ -178,28 +152,22 @@ def route_users_id_get( @router.put("/users/{username}", tags=["users"], response_model=User) -def route_users_put( +def update_user( *, username: str, user_in: UserInUpdate, - current_user: UserInDB = Depends(get_current_user), + current_user: UserInDB = Depends(get_current_active_superuser), ): """ Update a user """ - if not check_if_user_is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not check_if_user_is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) bucket = get_default_bucket() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system", ) - user = update_user(bucket, user_in) + user = crud.user.update(bucket, user_in=user_in) return user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py index 34a869b..18995ce 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py @@ -1,10 +1,8 @@ from fastapi import APIRouter, Depends from pydantic.types import EmailStr -from starlette.exceptions import HTTPException +from app.api.utils.security import get_current_active_superuser from app.core.celery_app import celery_app -from app.core.jwt import get_current_user -from app.crud.user import check_if_user_is_superuser from app.models.msg import Msg from app.models.user import UserInDB from app.utils import send_test_email @@ -13,24 +11,22 @@ @router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201) -def route_test_celery(msg: Msg, current_user: UserInDB = Depends(get_current_user)): +def test_celery( + msg: Msg, current_user: UserInDB = Depends(get_current_active_superuser) +): """ Test Celery worker """ - if not check_if_user_is_superuser(current_user): - raise HTTPException(status_code=400, detail="Not a superuser") celery_app.send_task("app.worker.test_celery", args=[msg.msg]) return {"msg": "Word received"} @router.post("/test-email/", tags=["utils"], response_model=Msg, status_code=201) -def route_test_email( - email_to: EmailStr, current_user: UserInDB = Depends(get_current_user) +def test_email( + email_to: EmailStr, current_user: UserInDB = Depends(get_current_active_superuser) ): """ Test emails """ - if not check_if_user_is_superuser(current_user): - raise HTTPException(status_code=400, detail="Not a superuser") send_test_email(email_to=email_to) return {"msg": "Test email sent"} diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py new file mode 100644 index 0000000..4d59abd --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py @@ -0,0 +1,43 @@ +import jwt +from fastapi import HTTPException, Security +from fastapi.security import OAuth2PasswordBearer +from jwt import PyJWTError +from starlette.status import HTTP_403_FORBIDDEN + +from app import crud +from app.core import config +from app.core.jwt import ALGORITHM +from app.db.database import get_default_bucket +from app.models.token import TokenPayload +from app.models.user import UserInDB + +reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token") + + +def get_current_user(token: str = Security(reusable_oauth2)): + try: + payload = jwt.decode(token, config.SECRET_KEY, algorithms=[ALGORITHM]) + token_data = TokenPayload(**payload) + except PyJWTError: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials" + ) + bucket = get_default_bucket() + user = crud.user.get(bucket, username=token_data.username) + if not user: + raise HTTPException(status_code=404, detail="User not found") + return user + + +def get_current_active_user(current_user: UserInDB = Security(get_current_user)): + if not crud.user.is_active(current_user): + raise HTTPException(status_code=400, detail="Inactive user") + return current_user + + +def get_current_active_superuser(current_user: UserInDB = Security(get_current_user)): + if not crud.user.is_superuser(current_user): + raise HTTPException( + status_code=400, detail="The user doesn't have enough privileges" + ) + return current_user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/core/jwt.py b/{{cookiecutter.project_slug}}/backend/app/app/core/jwt.py index 0acd756..4dc2835 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/core/jwt.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/core/jwt.py @@ -1,37 +1,12 @@ from datetime import datetime, timedelta import jwt -from fastapi import Security -from fastapi.security import OAuth2PasswordBearer -from jwt import PyJWTError -from starlette.exceptions import HTTPException -from starlette.status import HTTP_403_FORBIDDEN -from app.core.config import SECRET_KEY -from app.crud.user import get_user -from app.db.database import get_default_bucket -from app.models.token import TokenPayload +from app.core import config ALGORITHM = "HS256" access_token_jwt_subject = "access" -reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token") - - -def get_current_user(token: str = Security(reusable_oauth2)): - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - token_data = TokenPayload(**payload) - except PyJWTError: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials" - ) - bucket = get_default_bucket() - user = get_user(bucket, username=token_data.username) - if not user: - raise HTTPException(status_code=404, detail="User not found") - return user - def create_access_token(*, data: dict, expires_delta: timedelta = None): to_encode = data.copy() @@ -40,5 +15,5 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None): else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire, "sub": access_token_jwt_subject}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt diff --git a/{{cookiecutter.project_slug}}/backend/app/app/core/security.py b/{{cookiecutter.project_slug}}/backend/app/app/core/security.py index df7b656..8d2a49f 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/core/security.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/core/security.py @@ -3,9 +3,9 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -def verify_password(plain_password, hashed_password): +def verify_password(plain_password: str, hashed_password: str): return pwd_context.verify(plain_password, hashed_password) -def get_password_hash(password): +def get_password_hash(password: str): return pwd_context.hash(password) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py index e69de29..f3b5b99 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py @@ -0,0 +1 @@ +from . import user, utils diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py index 02bb547..e9af3ff 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py @@ -3,64 +3,54 @@ from couchbase.n1ql import CONSISTENCY_REQUEST, N1QLQuery from fastapi.encoders import jsonable_encoder -from app.core.config import ( - COUCHBASE_BUCKET_NAME, - COUCHBASE_DURABILITY_TIMEOUT_SECS, - COUCHBASE_SYNC_GATEWAY_DATABASE, - COUCHBASE_SYNC_GATEWAY_HOST, - COUCHBASE_SYNC_GATEWAY_PORT, -) +from app.core import config from app.core.security import get_password_hash, verify_password -from app.crud.utils import ( - ensure_enums_to_strs, - generate_new_id, - get_all_documents_by_type, - get_doc, - get_docs, - results_to_model, - search_results_by_type, -) from app.models.config import USERPROFILE_DOC_TYPE from app.models.role import RoleEnum from app.models.user import UserInCreate, UserInDB, UserInUpdate, UserSyncIn +from . import utils + full_text_index_name = "users" -def get_user_doc_id(username): +def get_doc_id(username): return f"userprofile::{username}" -def get_user(bucket: Bucket, username: str): - doc_id = get_user_doc_id(username) - return get_doc(bucket=bucket, doc_id=doc_id, doc_model=UserInDB) +def get(bucket: Bucket, *, username: str): + doc_id = get_doc_id(username) + return utils.get_doc(bucket=bucket, doc_id=doc_id, doc_model=UserInDB) -def get_user_by_email(bucket: Bucket, email: str): - query_str = f"SELECT *, META().id as doc_id FROM {COUCHBASE_BUCKET_NAME} WHERE type = $type AND email = $email;" +def get_by_email(bucket: Bucket, *, email: str): + query_str = f"SELECT *, META().id as doc_id FROM {config.COUCHBASE_BUCKET_NAME} WHERE type = $type AND email = $email;" q = N1QLQuery( - query_str, bucket=COUCHBASE_BUCKET_NAME, type=USERPROFILE_DOC_TYPE, email=email + query_str, + bucket=config.COUCHBASE_BUCKET_NAME, + type=USERPROFILE_DOC_TYPE, + email=email, ) q.consistency = CONSISTENCY_REQUEST doc_results = bucket.n1ql_query(q) - users = results_to_model(doc_results, doc_model=UserInDB) + users = utils.results_to_model(doc_results, doc_model=UserInDB) if not users: return None return users[0] -def insert_sync_gateway_user(user: UserSyncIn): +def insert_sync_gateway(user: UserSyncIn): name = user.name - url = f"http://{COUCHBASE_SYNC_GATEWAY_HOST}:{COUCHBASE_SYNC_GATEWAY_PORT}/{COUCHBASE_SYNC_GATEWAY_DATABASE}/_user/{name}" + url = f"http://{config.COUCHBASE_SYNC_GATEWAY_HOST}:{config.COUCHBASE_SYNC_GATEWAY_PORT}/{config.COUCHBASE_SYNC_GATEWAY_DATABASE}/_user/{name}" data = jsonable_encoder(user) response = requests.put(url, json=data) return response.status_code == 200 or response.status_code == 201 -def update_sync_gateway_user(user: UserSyncIn): +def update_sync_gateway(user: UserSyncIn): name = user.name - url = f"http://{COUCHBASE_SYNC_GATEWAY_HOST}:{COUCHBASE_SYNC_GATEWAY_PORT}/{COUCHBASE_SYNC_GATEWAY_DATABASE}/_user/{name}" + url = f"http://{config.COUCHBASE_SYNC_GATEWAY_HOST}:{config.COUCHBASE_SYNC_GATEWAY_PORT}/{config.COUCHBASE_SYNC_GATEWAY_DATABASE}/_user/{name}" if user.password: data = jsonable_encoder(user) else: @@ -69,22 +59,22 @@ def update_sync_gateway_user(user: UserSyncIn): return response.status_code == 200 or response.status_code == 201 -def upsert_user_in_db(bucket: Bucket, user_in: UserInCreate, persist_to=0): - user_doc_id = get_user_doc_id(user_in.username) +def upsert_in_db(bucket: Bucket, *, user_in: UserInCreate, persist_to=0): + user_doc_id = get_doc_id(user_in.username) passwordhash = get_password_hash(user_in.password) user = UserInDB(**user_in.dict(), hashed_password=passwordhash) doc_data = jsonable_encoder(user) with bucket.durability( - persist_to=persist_to, timeout=COUCHBASE_DURABILITY_TIMEOUT_SECS + persist_to=persist_to, timeout=config.COUCHBASE_DURABILITY_TIMEOUT_SECS ): bucket.upsert(user_doc_id, doc_data) return user -def update_user_in_db(bucket: Bucket, user_in: UserInUpdate, persist_to=0): - user_doc_id = get_user_doc_id(user_in.username) - stored_user = get_user(bucket, username=user_in.username) +def update_in_db(bucket: Bucket, *, user_in: UserInUpdate, persist_to=0): + user_doc_id = get_doc_id(user_in.username) + stored_user = get(bucket, username=user_in.username) for field in stored_user.fields: if field in user_in.fields: value_in = getattr(user_in, field) @@ -95,49 +85,51 @@ def update_user_in_db(bucket: Bucket, user_in: UserInUpdate, persist_to=0): stored_user.hashed_password = passwordhash data = jsonable_encoder(stored_user) with bucket.durability( - persist_to=persist_to, timeout=COUCHBASE_DURABILITY_TIMEOUT_SECS + persist_to=persist_to, timeout=config.COUCHBASE_DURABILITY_TIMEOUT_SECS ): bucket.upsert(user_doc_id, data) return stored_user -def upsert_user(bucket: Bucket, user_in: UserInCreate, persist_to=0): - user = upsert_user_in_db(bucket, user_in, persist_to=persist_to) +def upsert(bucket: Bucket, *, user_in: UserInCreate, persist_to=0): + user = upsert_in_db(bucket, user_in=user_in, persist_to=persist_to) user_in_sync = UserSyncIn(**user_in.dict(), name=user_in.username) - assert insert_sync_gateway_user(user_in_sync) + assert insert_sync_gateway(user_in_sync) return user -def update_user(bucket: Bucket, user_in: UserInUpdate, persist_to=0): - user = update_user_in_db(bucket, user_in, persist_to=persist_to) +def update(bucket: Bucket, *, user_in: UserInUpdate, persist_to=0): + user = update_in_db(bucket, user_in=user_in, persist_to=persist_to) user_in_sync_data = user.dict() user_in_sync_data.update({"name": user.username}) if user_in.password: user_in_sync_data.update({"password": user_in.password}) user_in_sync = UserSyncIn(**user_in_sync_data) - assert update_sync_gateway_user(user_in_sync) + assert update_sync_gateway(user_in_sync) return user -def authenticate_user(bucket: Bucket, name: str, password: str): - user = get_user(bucket, name) +def authenticate(bucket: Bucket, *, username: str, password: str): + user = get(bucket, username=username) if not user: - return False + return None if not verify_password(password, user.hashed_password): - return False + return None return user -def check_if_user_is_active(user: UserInDB): +def is_active(user: UserInDB): return not user.disabled -def check_if_user_is_superuser(user: UserInDB): - return RoleEnum.superuser.value in ensure_enums_to_strs(user.admin_roles) +def is_superuser(user: UserInDB): + return RoleEnum.superuser.value in utils.ensure_enums_to_strs( + user.admin_roles or [] + ) -def get_users(bucket: Bucket, *, skip=0, limit=100): - users = get_docs( +def get_multi(bucket: Bucket, *, skip=0, limit=100): + users = utils.get_docs( bucket=bucket, doc_type=USERPROFILE_DOC_TYPE, doc_model=UserInDB, @@ -147,8 +139,8 @@ def get_users(bucket: Bucket, *, skip=0, limit=100): return users -def search_user_docs(bucket: Bucket, *, query_string: str, skip=0, limit=100): - users = search_docs( +def search_docs(bucket: Bucket, *, query_string: str, skip=0, limit=100): + users = utils.search_docs( bucket=bucket, query_string=query_string, index_name=full_text_index_name, @@ -159,8 +151,8 @@ def search_user_docs(bucket: Bucket, *, query_string: str, skip=0, limit=100): return users -def search_users(bucket: Bucket, *, query_string: str, skip=0, limit=100): - users = search_results_by_type( +def search(bucket: Bucket, *, query_string: str, skip=0, limit=100): + users = utils.search_results_by_type( bucket=bucket, query_string=query_string, index_name=full_text_index_name, diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/utils.py index 3d78634..74d18a8 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/utils.py @@ -170,9 +170,7 @@ def search_docs( ) if not keys: return [] - doc_results = get_documents_by_keys( - bucket=bucket, keys=keys, doc_model=doc_model - ) + doc_results = get_documents_by_keys(bucket=bucket, keys=keys, doc_model=doc_model) return doc_results diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py index d41cbb8..750921a 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py @@ -1,20 +1,7 @@ import logging -from app.core.config import ( - COUCHBASE_BUCKET_NAME, - COUCHBASE_FULL_TEXT_INDEX_DEFINITIONS_DIR, - COUCHBASE_FULL_TEXT_PORT, - COUCHBASE_HOST, - COUCHBASE_MEMORY_QUOTA_MB, - COUCHBASE_PASSWORD, - COUCHBASE_PORT, - COUCHBASE_SYNC_GATEWAY_PASSWORD, - COUCHBASE_SYNC_GATEWAY_USER, - COUCHBASE_USER, - FIRST_SUPERUSER, - FIRST_SUPERUSER_PASSWORD, -) -from app.crud.user import upsert_user +from app import crud +from app.core import config from app.db.couchbase_utils import ( config_couchbase, ensure_create_bucket, @@ -32,13 +19,15 @@ def init_db(): - cluster_url = get_cluster_http_url(host=COUCHBASE_HOST, port=COUCHBASE_PORT) + cluster_url = get_cluster_http_url( + host=config.COUCHBASE_HOST, port=config.COUCHBASE_PORT + ) logging.info("before config_couchbase") config_couchbase( - username=COUCHBASE_USER, - password=COUCHBASE_PASSWORD, - host=COUCHBASE_HOST, - port=COUCHBASE_PORT, + username=config.COUCHBASE_USER, + password=config.COUCHBASE_PASSWORD, + host=config.COUCHBASE_HOST, + port=config.COUCHBASE_PORT, ) logging.info("after config_couchbase") # COUCHBASE_USER="Administrator" @@ -46,19 +35,19 @@ def init_db(): logging.info("before ensure_create_bucket") ensure_create_bucket( cluster_url=cluster_url, - username=COUCHBASE_USER, - password=COUCHBASE_PASSWORD, - bucket_name=COUCHBASE_BUCKET_NAME, - ram_quota_mb=COUCHBASE_MEMORY_QUOTA_MB, + username=config.COUCHBASE_USER, + password=config.COUCHBASE_PASSWORD, + bucket_name=config.COUCHBASE_BUCKET_NAME, + ram_quota_mb=config.COUCHBASE_MEMORY_QUOTA_MB, ) logging.info("after ensure_create_bucket") logging.info("before get_bucket") bucket = get_bucket( - COUCHBASE_USER, - COUCHBASE_PASSWORD, - COUCHBASE_BUCKET_NAME, - host=COUCHBASE_HOST, - port=COUCHBASE_PORT, + config.COUCHBASE_USER, + config.COUCHBASE_PASSWORD, + config.COUCHBASE_BUCKET_NAME, + host=config.COUCHBASE_HOST, + port=config.COUCHBASE_PORT, ) logging.info("after get_bucket") logging.info("before ensure_create_primary_index") @@ -69,29 +58,29 @@ def init_db(): logging.info("after ensure_create_type_index") logging.info("before ensure_create_full_text_indexes") ensure_create_full_text_indexes( - index_dir=COUCHBASE_FULL_TEXT_INDEX_DEFINITIONS_DIR, - username=COUCHBASE_USER, - password=COUCHBASE_PASSWORD, - host=COUCHBASE_HOST, - port=COUCHBASE_FULL_TEXT_PORT, + index_dir=config.COUCHBASE_FULL_TEXT_INDEX_DEFINITIONS_DIR, + username=config.COUCHBASE_USER, + password=config.COUCHBASE_PASSWORD, + host=config.COUCHBASE_HOST, + port=config.COUCHBASE_FULL_TEXT_PORT, ) logging.info("after ensure_create_full_text_indexes") logging.info("before ensure_create_couchbase_app_user sync") ensure_create_couchbase_user( cluster_url=cluster_url, - username=COUCHBASE_USER, - password=COUCHBASE_PASSWORD, - new_user_id=COUCHBASE_SYNC_GATEWAY_USER, - new_user_password=COUCHBASE_SYNC_GATEWAY_PASSWORD, + username=config.COUCHBASE_USER, + password=config.COUCHBASE_PASSWORD, + new_user_id=config.COUCHBASE_SYNC_GATEWAY_USER, + new_user_password=config.COUCHBASE_SYNC_GATEWAY_PASSWORD, ) logging.info("after ensure_create_couchbase_app_user sync") logging.info("before upsert_user first superuser") - in_user = UserInCreate( - username=FIRST_SUPERUSER, - password=FIRST_SUPERUSER_PASSWORD, - email=FIRST_SUPERUSER, + user_in = UserInCreate( + username=config.FIRST_SUPERUSER, + password=config.FIRST_SUPERUSER_PASSWORD, + email=config.FIRST_SUPERUSER, admin_roles=[RoleEnum.superuser], - admin_channels=[FIRST_SUPERUSER], + admin_channels=[config.FIRST_SUPERUSER], ) - upsert_user(bucket, in_user, persist_to=1) + crud.user.upsert(bucket, user_in=user_in, persist_to=1) logging.info("after upsert_user first superuser") diff --git a/{{cookiecutter.project_slug}}/backend/app/app/main.py b/{{cookiecutter.project_slug}}/backend/app/app/main.py index 328dabd..7514703 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/main.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/main.py @@ -2,16 +2,16 @@ from starlette.middleware.cors import CORSMiddleware from app.api.api_v1.api import api_router -from app.core.config import API_V1_STR, BACKEND_CORS_ORIGINS, PROJECT_NAME +from app.core import config -app = FastAPI(title=PROJECT_NAME, openapi_url="/api/v1/openapi.json") +app = FastAPI(title=config.PROJECT_NAME, openapi_url="/api/v1/openapi.json") # CORS origins = [] # Set all CORS enabled origins -if BACKEND_CORS_ORIGINS: - origins_raw = BACKEND_CORS_ORIGINS.split(",") +if config.BACKEND_CORS_ORIGINS: + origins_raw = config.BACKEND_CORS_ORIGINS.split(",") for origin in origins_raw: use_origin = origin.strip() origins.append(use_origin) @@ -23,4 +23,4 @@ allow_headers=["*"], ), -app.include_router(api_router, prefix=API_V1_STR) +app.include_router(api_router, prefix=config.API_V1_STR) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py index 94ae472..8dae7d9 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py @@ -1,7 +1,7 @@ import requests +from app import crud from app.core import config -from app.crud.user import get_user, upsert_user from app.db.database import get_default_bucket from app.models.user import UserInCreate from app.tests.utils.user import user_authentication_headers @@ -33,7 +33,7 @@ def test_create_user_new_email(superuser_token_headers): assert 200 <= r.status_code < 300 created_user = r.json() bucket = get_default_bucket() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) assert user.username == created_user["username"] @@ -43,14 +43,14 @@ def test_get_existing_user(superuser_token_headers): password = random_lower_string() user_in = UserInCreate(username=username, email=username, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) r = requests.get( f"{server_api}{config.API_V1_STR}/users/{username}", headers=superuser_token_headers, ) assert 200 <= r.status_code < 300 api_user = r.json() - user = get_user(bucket, username) + user = crud.user.get(bucket, username=username) assert user.username == api_user["username"] @@ -61,7 +61,7 @@ def test_create_user_existing_username(superuser_token_headers): password = random_lower_string() user_in = UserInCreate(username=username, email=username, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) data = {"username": username, "password": password} r = requests.post( f"{server_api}{config.API_V1_STR}/users/", @@ -79,7 +79,7 @@ def test_create_user_by_normal_user(): password = random_lower_string() user_in = UserInCreate(username=username, email=username, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) user_token_headers = user_authentication_headers(server_api, username, password) data = {"username": username, "password": password} r = requests.post( @@ -94,12 +94,12 @@ def test_retrieve_users(superuser_token_headers): password = random_lower_string() user_in = UserInCreate(username=username, email=username, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) username2 = random_lower_string() password2 = random_lower_string() user_in2 = UserInCreate(username=username2, email=username2, password=password2) - user2 = upsert_user(bucket, user_in, persist_to=1) + user2 = crud.user.upsert(bucket, user_in=user_in, persist_to=1) r = requests.get( f"{server_api}{config.API_V1_STR}/users/", headers=superuser_token_headers diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_get_ids.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_get_ids.py index 038e838..933d0ae 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_get_ids.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_get_ids.py @@ -1,7 +1,7 @@ -from app.crud.user import get_user_doc_id +from app import crud def test_get_user_id(): username = "johndoe@example.com" - user_id = get_user_doc_id(username) + user_id = crud.user.get_doc_id(username) assert user_id == "userprofile::johndoe@example.com" diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py index 27c0e1b..454a80a 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py @@ -1,12 +1,6 @@ from fastapi.encoders import jsonable_encoder -from app.crud.user import ( - authenticate_user, - check_if_user_is_active, - check_if_user_is_superuser, - get_user, - upsert_user, -) +from app import crud from app.db.database import get_default_bucket from app.models.role import RoleEnum from app.models.user import UserInCreate @@ -18,7 +12,7 @@ def test_create_user(): password = random_lower_string() user_in = UserInCreate(username=email, email=email, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) assert hasattr(user, "username") assert user.username == email assert hasattr(user, "hashed_password") @@ -31,8 +25,10 @@ def test_authenticate_user(): password = random_lower_string() user_in = UserInCreate(username=email, email=email, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) - authenticated_user = authenticate_user(bucket, email, password) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + authenticated_user = crud.user.authenticate( + bucket, username=user_in.username, password=password + ) assert authenticated_user assert user.username == authenticated_user.username @@ -41,8 +37,8 @@ def test_not_authenticate_user(): email = random_lower_string() password = random_lower_string() bucket = get_default_bucket() - user = authenticate_user(bucket, email, password) - assert user is False + user = crud.user.authenticate(bucket, username=email, password=password) + assert user is None def test_check_if_user_is_active(): @@ -50,8 +46,8 @@ def test_check_if_user_is_active(): password = random_lower_string() user_in = UserInCreate(username=email, email=email, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) - is_active = check_if_user_is_active(user) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + is_active = crud.user.is_active(user) assert is_active is True @@ -62,8 +58,8 @@ def test_check_if_user_is_active_inactive(): username=email, email=email, password=password, disabled=True ) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) - is_active = check_if_user_is_active(user) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + is_active = crud.user.is_active(user) assert is_active is False @@ -74,8 +70,8 @@ def test_check_if_user_is_superuser(): username=email, email=email, password=password, admin_roles=[RoleEnum.superuser] ) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) - is_superuser = check_if_user_is_superuser(user) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + is_superuser = crud.user.is_superuser(user) assert is_superuser is True @@ -84,8 +80,8 @@ def test_check_if_user_is_superuser_normal_user(): password = random_lower_string() user_in = UserInCreate(username=username, email=username, password=password) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) - is_superuser = check_if_user_is_superuser(user) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + is_superuser = crud.user.is_superuser(user) assert is_superuser is False @@ -99,7 +95,7 @@ def test_get_user(): admin_roles=[RoleEnum.superuser], ) bucket = get_default_bucket() - user = upsert_user(bucket, user_in, persist_to=1) - user_2 = get_user(bucket, username) + user = crud.user.upsert(bucket, user_in=user_in, persist_to=1) + user_2 = crud.user.get(bucket, username=username) assert user.username == user_2.username assert jsonable_encoder(user) == jsonable_encoder(user_2) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/utils.py index b9347cb..952ac20 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/utils.py @@ -1,96 +1,86 @@ import logging from datetime import datetime, timedelta from pathlib import Path -from typing import Union +from typing import Optional import emails import jwt from emails.template import JinjaTemplate from jwt.exceptions import InvalidTokenError -from app.core.config import ( - EMAIL_RESET_TOKEN_EXPIRE_HOURS, - EMAIL_TEMPLATES_DIR, - EMAILS_ENABLED, - EMAILS_FROM_EMAIL, - EMAILS_FROM_NAME, - PROJECT_NAME, - SECRET_KEY, - SERVER_HOST, - SMTP_HOST, - SMTP_PASSWORD, - SMTP_PORT, - SMTP_TLS, - SMTP_USER, -) +from app.core import config -password_reset_jwt_subject = "preset" +password_reset_jwt_subject = "reset" def send_email(email_to: str, subject_template="", html_template="", environment={}): - assert EMAILS_ENABLED, "no provided configuration for email variables" + assert config.EMAILS_ENABLED, "no provided configuration for email variables" message = emails.Message( subject=JinjaTemplate(subject_template), html=JinjaTemplate(html_template), - mail_from=(EMAILS_FROM_NAME, EMAILS_FROM_EMAIL), + mail_from=(config.EMAILS_FROM_NAME, config.EMAILS_FROM_EMAIL), ) - smtp_options = {"host": SMTP_HOST, "port": SMTP_PORT} - if SMTP_TLS: + smtp_options = {"host": config.SMTP_HOST, "port": config.SMTP_PORT} + if config.SMTP_TLS: smtp_options["tls"] = True - if SMTP_USER: - smtp_options["user"] = SMTP_USER - if SMTP_PASSWORD: - smtp_options["password"] = SMTP_PASSWORD + if config.SMTP_USER: + smtp_options["user"] = config.SMTP_USER + if config.SMTP_PASSWORD: + smtp_options["password"] = config.SMTP_PASSWORD response = message.send(to=email_to, render=environment, smtp=smtp_options) logging.info(f"send email result: {response}") def send_test_email(email_to: str): - subject = f"{PROJECT_NAME} - Test email" - with open(Path(EMAIL_TEMPLATES_DIR) / "test_email.html") as f: + project_name = config.PROJECT_NAME + subject = f"{project_name} - Test email" + with open(Path(config.EMAIL_TEMPLATES_DIR) / "test_email.html") as f: template_str = f.read() send_email( email_to=email_to, subject_template=subject, html_template=template_str, - environment={"project_name": PROJECT_NAME, "email": email_to}, + environment={"project_name": config.PROJECT_NAME, "email": email_to}, ) def send_reset_password_email(email_to: str, username: str, token: str): - subject = f"{PROJECT_NAME} - Password recovery for user {username}" - with open(Path(EMAIL_TEMPLATES_DIR) / "reset_password.html") as f: + project_name = config.PROJECT_NAME + subject = f"{project_name} - Password recovery for user {username}" + with open(Path(config.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f: template_str = f.read() if hasattr(token, "decode"): use_token = token.decode() else: use_token = token - link = f"{SERVER_HOST}/reset-password?token={use_token}" + server_host = config.SERVER_HOST + link = f"{server_host}/reset-password?token={use_token}" send_email( email_to=email_to, subject_template=subject, html_template=template_str, environment={ - "project_name": PROJECT_NAME, + "project_name": config.PROJECT_NAME, "username": username, "email": email_to, - "valid_hours": EMAIL_RESET_TOKEN_EXPIRE_HOURS, + "valid_hours": config.EMAIL_RESET_TOKEN_EXPIRE_HOURS, "link": link, }, ) def send_new_account_email(email_to: str, username: str, password: str): - subject = f"{PROJECT_NAME} - New acccount for user {username}" - with open(Path(EMAIL_TEMPLATES_DIR) / "new_account.html") as f: + project_name = config.PROJECT_NAME + subject = f"{project_name} - New account for user {username}" + with open(Path(config.EMAIL_TEMPLATES_DIR) / "new_account.html") as f: template_str = f.read() - link = f"{SERVER_HOST}" + link = config.SERVER_HOST send_email( email_to=email_to, subject_template=subject, html_template=template_str, environment={ - "project_name": PROJECT_NAME, + "project_name": config.PROJECT_NAME, "username": username, "password": password, "email": email_to, @@ -100,7 +90,7 @@ def send_new_account_email(email_to: str, username: str, password: str): def generate_password_reset_token(username): - delta = timedelta(hours=EMAIL_RESET_TOKEN_EXPIRE_HOURS) + delta = timedelta(hours=config.EMAIL_RESET_TOKEN_EXPIRE_HOURS) now = datetime.utcnow() expires = now + delta exp = expires.timestamp() @@ -111,16 +101,16 @@ def generate_password_reset_token(username): "sub": password_reset_jwt_subject, "username": username, }, - SECRET_KEY, + config.SECRET_KEY, algorithm="HS256", ) return encoded_jwt -def verify_password_reset_token(token) -> Union[str, bool]: +def verify_password_reset_token(token) -> Optional[str]: try: - decoded_token = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) + decoded_token = jwt.decode(token, config.SECRET_KEY, algorithms=["HS256"]) assert decoded_token["sub"] == password_reset_jwt_subject return decoded_token["username"] except InvalidTokenError: - return False + return None diff --git a/{{cookiecutter.project_slug}}/backend/app/app/worker.py b/{{cookiecutter.project_slug}}/backend/app/app/worker.py index ff5000e..82bc5a1 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/worker.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/worker.py @@ -1,9 +1,9 @@ from raven import Client +from app.core import config from app.core.celery_app import celery_app -from app.core.config import SENTRY_DSN -client_sentry = Client(SENTRY_DSN) +client_sentry = Client(config.SENTRY_DSN) @celery_app.task(acks_late=True) From a28ea10cae1b6870d5a15136208ddec556d64c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:36:22 +0400 Subject: [PATCH 08/12] :art: Upgrade linting script to include dead code detection --- {{cookiecutter.project_slug}}/backend/app/scripts/lint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh b/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh index a7c6da9..08bb841 100644 --- a/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh +++ b/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh @@ -5,3 +5,4 @@ set -x autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place app --exclude=__init__.py isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --apply app black app +vulture app From 6713d9c19c88bd9075e06f1a62161d750f86a758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 12:37:25 +0400 Subject: [PATCH 09/12] :arrow_up: Upgrade frontend dependencies --- .../frontend/package-lock.json | 212 ++++++++++++------ .../frontend/package.json | 2 +- 2 files changed, 146 insertions(+), 68 deletions(-) diff --git a/{{cookiecutter.project_slug}}/frontend/package-lock.json b/{{cookiecutter.project_slug}}/frontend/package-lock.json index a5ae7db..3cda519 100644 --- a/{{cookiecutter.project_slug}}/frontend/package-lock.json +++ b/{{cookiecutter.project_slug}}/frontend/package-lock.json @@ -858,24 +858,12 @@ "integrity": "sha512-ePl4l+7dLLmCucIwgQHAgjiepY++qcI6nb8eAwGNkB6OxmTe3Z9rQU3rSpomqu42PCCnlThZbOoxsf+qylJsLA==", "dev": true }, - "@types/node": { - "version": "10.12.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.20.tgz", - "integrity": "sha512-9spv6SklidqxevvZyOUGjZVz4QRXGu2dNaLyXIFzFYZW0AGDykzPRIUFJXTlQXyfzAucddwTcGtJNim8zqSOPA==", - "dev": true - }, "@types/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz", "integrity": "sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==", "dev": true }, - "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", - "dev": true - }, "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -1034,18 +1022,95 @@ } }, "@vue/cli-plugin-unit-jest": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-3.3.0.tgz", - "integrity": "sha512-Y/WkrO95vdvjVjeNO1vZRQUAxlZ6ngdgAzvMzCeEaujbRG4b8M6W7ePSAe8C9yfoVcJtbnoHcBv2er31sPwtyQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-3.5.0.tgz", + "integrity": "sha512-JFKiuLil1ayzTZCYk1DgoUFYb0F3nfbdVH3C7CN39EOfNgvEMvtavgS2Pb6MU+xx1f2J71bwVHQYY0HIx8zWJw==", "dev": true, "requires": { - "@vue/cli-shared-utils": "^3.3.0", + "@vue/cli-shared-utils": "^3.5.0", "babel-jest": "^23.6.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "jest": "^23.6.0", "jest-serializer-vue": "^2.0.2", - "jest-transform-stub": "^1.0.0", - "vue-jest": "^3.0.2" + "jest-transform-stub": "^2.0.0", + "vue-jest": "^3.0.3" + }, + "dependencies": { + "@vue/cli-shared-utils": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-3.5.0.tgz", + "integrity": "sha512-+EIwVMTjdfRQVEtcIhpRjNsPB2ZlopiUktlPpx6oLQdlJXwBWkFQVwuXdXHtPYxB5Kzs3VPyUfhHxnPIbNw1+Q==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "execa": "^1.0.0", + "joi": "^14.3.0", + "launch-editor": "^2.2.1", + "lru-cache": "^5.1.1", + "node-ipc": "^9.1.1", + "opn": "^5.3.0", + "ora": "^3.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.7", + "semver": "^5.5.0", + "string.prototype.padstart": "^3.0.0" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cli-spinners": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.0.0.tgz", + "integrity": "sha512-yiEBmhaKPPeBj7wWm4GEdtPZK940p9pl3EANIrnJ3JnvWyrPjcFcsEq6qRUuQ7fzB0+Y82ld3p6B34xo95foWw==", + "dev": true + }, + "ora": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.2.0.tgz", + "integrity": "sha512-XHMZA5WieCbtg+tu0uPF8CjvwQdNzKCX6BVh3N6GFsEXH40mTk5dsw/ya1lBTUGJslcEFJFQ8cBhOgkkZXQtMA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.0.0", + "wcwidth": "^1.0.1" + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "@vue/cli-service": { @@ -1467,9 +1532,9 @@ }, "dependencies": { "acorn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.6.tgz", - "integrity": "sha512-5M3G/A4uBSMIlfJ+h9W125vJvPFH/zirISsW5qfxF5YzEvXJCtolLoQvM5yZft0DvMcUrPGKPOlgEu55I6iUtA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } @@ -3806,15 +3871,15 @@ } }, "cssom": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", - "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", "dev": true }, "cssstyle": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", - "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", + "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", "dev": true, "requires": { "cssom": "0.3.x" @@ -4266,15 +4331,13 @@ } }, "editorconfig": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.2.tgz", - "integrity": "sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ==", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", "dev": true, "requires": { - "@types/node": "^10.11.7", - "@types/semver": "^5.5.0", "commander": "^2.19.0", - "lru-cache": "^4.1.3", + "lru-cache": "^4.1.5", "semver": "^5.6.0", "sigmund": "^1.0.1" }, @@ -4433,9 +4496,9 @@ "dev": true }, "escodegen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", - "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", "dev": true, "requires": { "esprima": "^3.1.3", @@ -5010,9 +5073,9 @@ } }, "find-babel-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz", - "integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", + "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", "dev": true, "requires": { "json5": "^0.5.1", @@ -5280,12 +5343,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5300,17 +5365,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5427,7 +5495,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5439,6 +5508,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5453,6 +5523,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5460,12 +5531,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5484,6 +5557,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5564,7 +5638,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5576,6 +5651,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5697,6 +5773,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5937,9 +6014,9 @@ "dev": true }, "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { "async": "^2.5.0", @@ -8307,9 +8384,9 @@ } }, "jest-transform-stub": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jest-transform-stub/-/jest-transform-stub-1.0.0.tgz", - "integrity": "sha512-7eilMk4sxi2Fiy223I+BYTS5wJQEGEBqR3D8dy5A6RWmMTnmjipw2ImGDfXzEUBieebyrnitzkJfpNOJSFklLQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jest-transform-stub/-/jest-transform-stub-2.0.0.tgz", + "integrity": "sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg==", "dev": true }, "jest-util": { @@ -8380,9 +8457,9 @@ } }, "js-beautify": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.8.9.tgz", - "integrity": "sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.9.0.tgz", + "integrity": "sha512-P0skmY4IDjfLiVrx+GLDeme8w5G0R1IGXgccVU5HP2VM3lRblH7qN2LTea5vZAxrDjpZBD0Jv+ahpjwVcbz/rw==", "dev": true, "requires": { "config-chain": "^1.1.12", @@ -9296,12 +9373,13 @@ } }, "node-notifier": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.3.0.tgz", - "integrity": "sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", "dev": true, "requires": { "growly": "^1.3.0", + "is-wsl": "^1.1.0", "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" @@ -9390,9 +9468,9 @@ "dev": true }, "nwsapi": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", - "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.1.tgz", + "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==", "dev": true }, "oauth-sign": { @@ -10877,9 +10955,9 @@ } }, "realpath-native": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.2.tgz", - "integrity": "sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", "dev": true, "requires": { "util.promisify": "^1.0.0" @@ -13035,9 +13113,9 @@ "dev": true }, "vue-jest": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.2.tgz", - "integrity": "sha512-5XIQ1xQFW0ZnWxHWM7adVA2IqbDsdw1vhgZfGFX4oWd75J38KIS3YT41PtiE7lpMLmNM6+VJ0uprT2mhHjUgkA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.4.tgz", + "integrity": "sha512-PY9Rwt4OyaVlA+KDJJ0614CbEvNOkffDI9g9moLQC/2DDoo0YrqZm7dHi13Q10uoK5Nt5WCYFdeAheOExPah0w==", "dev": true, "requires": { "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", diff --git a/{{cookiecutter.project_slug}}/frontend/package.json b/{{cookiecutter.project_slug}}/frontend/package.json index 3a792b7..f1f5232 100644 --- a/{{cookiecutter.project_slug}}/frontend/package.json +++ b/{{cookiecutter.project_slug}}/frontend/package.json @@ -26,7 +26,7 @@ "@vue/cli-plugin-babel": "^3.3.0", "@vue/cli-plugin-pwa": "^3.3.0", "@vue/cli-plugin-typescript": "^3.3.0", - "@vue/cli-plugin-unit-jest": "^3.3.0", + "@vue/cli-plugin-unit-jest": "^3.5.0", "@vue/cli-service": "^3.3.1", "@vue/test-utils": "^1.0.0-beta.28", "babel-core": "7.0.0-bridge.0", From 7f652be2baafbdc76e6530c49f89d48aec24c8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 13:14:32 +0400 Subject: [PATCH 10/12] :building_construction: Upgrade vuex integration and standardize layout --- .../frontend/src/App.vue | 3 +- .../frontend/src/api.ts | 4 +- .../src/components/NotificationsManager.vue | 4 +- .../frontend/src/interfaces/index.ts | 10 +- .../frontend/src/router.ts | 2 +- .../src/store/admin/accessors/commit.ts | 10 -- .../src/store/admin/accessors/dispatch.ts | 11 -- .../src/store/admin/accessors/index.ts | 3 - .../src/store/admin/accessors/read.ts | 10 -- .../frontend/src/store/admin/actions.ts | 21 ++-- .../frontend/src/store/admin/getters.ts | 12 +- .../frontend/src/store/admin/mutations.ts | 10 +- .../src/store/main/accessors/commit.ts | 15 --- .../src/store/main/accessors/dispatch.ts | 20 ---- .../src/store/main/accessors/index.ts | 3 - .../frontend/src/store/main/accessors/read.ts | 15 --- .../frontend/src/store/main/actions.ts | 40 ++++--- .../frontend/src/store/main/getters.ts | 13 ++ .../frontend/src/store/main/mutations.ts | 13 ++ .../frontend/src/views/Login.vue | 3 +- .../frontend/src/views/PasswordRecovery.vue | 2 +- .../frontend/src/views/ResetPassword.vue | 8 +- .../frontend/src/views/main/Dashboard.vue | 14 ++- .../frontend/src/views/main/Main.vue | 11 +- .../frontend/src/views/main/Start.vue | 3 +- .../frontend/src/views/main/admin/Admin.vue | 6 +- .../src/views/main/admin/AdminUsers.vue | 21 ++-- .../src/views/main/admin/CreateUser.vue | 28 +++-- .../src/views/main/admin/EditUser.vue | 113 +++++++++++++----- .../src/views/main/profile/UserProfile.vue | 32 +++-- .../views/main/profile/UserProfileEdit.vue | 57 ++++++--- .../main/profile/UserProfileEditPassword.vue | 33 +++-- 32 files changed, 318 insertions(+), 232 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/commit.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/dispatch.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/index.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/read.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/commit.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/dispatch.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/index.ts delete mode 100644 {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/read.ts diff --git a/{{cookiecutter.project_slug}}/frontend/src/App.vue b/{{cookiecutter.project_slug}}/frontend/src/App.vue index 01c2c2a..795a97c 100644 --- a/{{cookiecutter.project_slug}}/frontend/src/App.vue +++ b/{{cookiecutter.project_slug}}/frontend/src/App.vue @@ -21,8 +21,9 @@