From cd112bd683dcb9017e5f84c0ed3e4974a52e5571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 11 Mar 2019 13:36:42 +0400 Subject: [PATCH] Refactor/upgrade backend and frontend parts (#2) * :recycle: Refactor and simplify backend code * :recycle: Refactor frontend state, integrate typesafe-vuex accessors into state files * :recycle: Use new state accessors and standardize layout * :lock: Upgrade and fix npm security audit * :wrench: Update local re-generation scripts * :loud_sound: Log startup exceptions to detect errors early * :pencil2: Fix password reset token content * :fire: Remove unneeded Dockerfile directives * :fire: Remove unnecessary print * :fire: Remove unnecessary code, upgrade dependencies in backend * :pencil2: Fix typos in docstrings and comments * :building_construction: Improve user Depends utilities to simplify and remove code * :fire: Remove deprecated SQLAlchemy parameter --- dev-fsfp-back.sh | 17 ++ dev-fsfp.sh | 2 + {{cookiecutter.project_slug}}/README.md | 8 +- .../backend/app/app/api/api_v1/api.py | 10 +- .../app/app/api/api_v1/endpoints/token.py | 12 +- .../app/app/api/api_v1/endpoints/user.py | 61 ++--- .../app/app/api/api_v1/endpoints/utils.py | 17 +- .../backend/app/app/api/utils/security.py | 19 +- .../backend/app/app/backend_pre_start.py | 8 +- .../backend/app/app/celeryworker_pre_start.py | 8 +- .../backend/app/app/crud/__init__.py | 1 + .../backend/app/app/crud/user.py | 14 +- .../backend/app/app/db/init_db.py | 8 +- .../backend/app/app/db/session.py | 2 +- .../app/app/tests/api/api_v1/test_user.py | 16 +- .../backend/app/app/tests/crud/test_user.py | 32 +-- .../backend/app/app/tests_pre_start.py | 12 +- .../backend/app/app/utils.py | 2 +- .../backend/app/app/worker.py | 1 - .../backend/app/backend-live.sh | 2 - .../backend/backend.dockerfile | 2 +- .../backend/celeryworker.dockerfile | 2 +- .../backend/tests.dockerfile | 2 +- .../frontend/package-lock.json | 212 +++++++++++------- .../frontend/package.json | 2 +- .../frontend/src/App.vue | 3 +- .../src/components/NotificationsManager.vue | 4 +- .../src/store/admin/accessors/commit.ts | 9 - .../src/store/admin/accessors/dispatch.ts | 10 - .../src/store/admin/accessors/index.ts | 3 - .../src/store/admin/accessors/read.ts | 9 - .../frontend/src/store/admin/actions.ts | 17 +- .../frontend/src/store/admin/getters.ts | 7 + .../frontend/src/store/admin/mutations.ts | 7 + .../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 | 39 ++-- .../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 | 6 +- .../frontend/src/views/main/Main.vue | 11 +- .../frontend/src/views/main/Start.vue | 3 +- .../frontend/src/views/main/admin/Admin.vue | 2 +- .../src/views/main/admin/AdminUsers.vue | 3 +- .../src/views/main/admin/CreateUser.vue | 15 +- .../src/views/main/admin/EditUser.vue | 96 ++++++-- .../src/views/main/profile/UserProfile.vue | 2 +- .../views/main/profile/UserProfileEdit.vue | 41 +++- .../main/profile/UserProfileEditPassword.vue | 12 +- 54 files changed, 492 insertions(+), 371 deletions(-) create mode 100644 dev-fsfp-back.sh delete mode 100644 {{cookiecutter.project_slug}}/backend/app/backend-live.sh 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/dev-fsfp-back.sh b/dev-fsfp-back.sh new file mode 100644 index 0000000..4301707 --- /dev/null +++ b/dev-fsfp-back.sh @@ -0,0 +1,17 @@ +#! /usr/bin/env bash + +# Run this script from outside the project, to integrate a dev-fsfp 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-fsfp/ -type d -name __pycache__ -exec rm -r {} \+ +fi + +rm -rf ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/* + +rsync -a --exclude=node_modules ./dev-fsfp/* ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/ + +rsync -a ./dev-fsfp/{.env,.gitignore,.gitlab-ci.yml} ./full-stack-fastapi-postgresql/\{\{cookiecutter.project_slug\}\}/ diff --git a/dev-fsfp.sh b/dev-fsfp.sh index 93c0417..bb3554d 100644 --- a/dev-fsfp.sh +++ b/dev-fsfp.sh @@ -1,5 +1,7 @@ #! /usr/bin/env bash +# Run this script from outside the project, to generate a dev-fsfp project + # Exit in case of error set -e diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 5fdc479..8591a26 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -69,7 +69,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: @@ -91,16 +91,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. 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 81b645d..8673a99 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,10 +1,8 @@ from fastapi import APIRouter -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 token, user, utils api_router = APIRouter() -api_router.include_router(token_router) -api_router.include_router(user_router) -api_router.include_router(utils_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/token.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py index 402005a..2640f1c 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 @@ -4,12 +4,12 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session +from app import crud from app.api.utils.db import get_db from app.api.utils.security import get_current_user from app.core import config from app.core.jwt import create_access_token from app.core.security import get_password_hash -from app.crud import user as crud_user from app.db_models.user import User as DBUser from app.models.msg import Msg from app.models.token import Token @@ -30,12 +30,12 @@ def login_access_token( """ OAuth2 compatible token login, get an access token for future requests """ - user = crud_user.authenticate( + user = crud.user.authenticate( db, email=form_data.username, password=form_data.password ) if not user: raise HTTPException(status_code=400, detail="Incorrect email or password") - elif not crud_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 { @@ -59,7 +59,7 @@ def recover_password(email: str, db: Session = Depends(get_db)): """ Password Recovery """ - user = crud_user.get_by_email(db, email=email) + user = crud.user.get_by_email(db, email=email) if not user: raise HTTPException( @@ -81,13 +81,13 @@ def reset_password(token: str, new_password: str, db: Session = Depends(get_db)) email = verify_password_reset_token(token) if not email: raise HTTPException(status_code=400, detail="Invalid token") - user = crud_user.get_by_email(db, email=email) + user = crud.user.get_by_email(db, email=email) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system.", ) - elif not crud_user.is_active(user): + elif not crud.user.is_active(user): raise HTTPException(status_code=400, detail="Inactive user") hashed_password = get_password_hash(new_password) user.hashed_password = hashed_password 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 31668dc..a9430ba 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 @@ -5,10 +5,10 @@ from fastapi.encoders import jsonable_encoder from pydantic.types import EmailStr from sqlalchemy.orm import Session +from app import crud from app.api.utils.db import get_db -from app.api.utils.security import get_current_user +from app.api.utils.security import get_current_active_superuser, get_current_active_user from app.core import config -from app.crud import user as crud_user from app.db_models.user import User as DBUser from app.models.user import User, UserInCreate, UserInDB, UserInUpdate from app.utils import send_new_account_email @@ -21,18 +21,12 @@ def read_users( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_superuser), ): """ Retrieve users """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not crud_user.is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) - users = crud_user.get_multi(db, skip=skip, limit=limit) + users = crud.user.get_multi(db, skip=skip, limit=limit) return users @@ -41,24 +35,18 @@ def create_user( *, db: Session = Depends(get_db), user_in: UserInCreate, - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_superuser), ): """ Create new user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not crud_user.is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) - user = crud_user.get_by_email(db, email=user_in.email) + user = crud.user.get_by_email(db, email=user_in.email) if user: raise HTTPException( status_code=400, detail="The user with this username already exists in the system.", ) - user = crud_user.create(db, user_in=user_in) + user = crud.user.create(db, user_in=user_in) if config.EMAILS_ENABLED and user_in.email: send_new_account_email( email_to=user_in.email, username=user_in.email, password=user_in.password @@ -73,13 +61,11 @@ def update_user_me( password: str = Body(None), full_name: str = Body(None), email: EmailStr = Body(None), - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_user), ): """ Update own user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") current_user_data = jsonable_encoder(current_user) user_in = UserInUpdate(**current_user_data) if password is not None: @@ -88,19 +74,18 @@ def update_user_me( user_in.full_name = full_name if email is not None: user_in.email = email - user = crud_user.update(db, user=current_user, user_in=user_in) + user = crud.user.update(db, user=current_user, user_in=user_in) return user @router.get("/users/me", tags=["users"], response_model=User) def read_user_me( - db: Session = Depends(get_db), current_user: DBUser = Depends(get_current_user) + db: Session = Depends(get_db), + current_user: DBUser = Depends(get_current_active_user), ): """ Get current user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") return current_user @@ -120,32 +105,30 @@ def create_user_open( status_code=403, detail="Open user resgistration is forbidden on this server", ) - user = crud_user.get_by_email(db, email=email) + user = crud.user.get_by_email(db, email=email) if user: raise HTTPException( status_code=400, detail="The user with this username already exists in the system", ) user_in = UserInCreate(password=password, email=email, full_name=full_name) - user = crud_user.create(db, user_in=user_in) + user = crud.user.create(db, user_in=user_in) return user @router.get("/users/{user_id}", tags=["users"], response_model=User) def read_user_by_id( user_id: int, - current_user: DBUser = Depends(get_current_user), + current_user: DBUser = Depends(get_current_active_user), db: Session = Depends(get_db), ): """ Get a specific user by username (email) """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - user = crud_user.get(db, user_id=user_id) + user = crud.user.get(db, user_id=user_id) if user == current_user: return user - if not crud_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" ) @@ -158,23 +141,17 @@ def update_user( db: Session = Depends(get_db), user_id: int, user_in: UserInUpdate, - current_user: UserInDB = Depends(get_current_user), + current_user: UserInDB = Depends(get_current_active_superuser), ): """ Update a user """ - if not crud_user.is_active(current_user): - raise HTTPException(status_code=400, detail="Inactive user") - elif not crud_user.is_superuser(current_user): - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) - user = crud_user.get(db, user_id=user_id) + user = crud.user.get(db, user_id=user_id) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system", ) - user = crud_user.update(db, user=user, user_in=user_in) + user = crud.user.update(db, user=user, 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 874ef5b..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,9 +1,8 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from pydantic.types import EmailStr -from app.api.utils.security import get_current_user +from app.api.utils.security import get_current_active_superuser from app.core.celery_app import celery_app -from app.crud import user as crud_user from app.models.msg import Msg from app.models.user import UserInDB from app.utils import send_test_email @@ -12,22 +11,22 @@ router = APIRouter() @router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201) -def 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 crud_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 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 crud_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/security.py b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py index e8d16c3..0e761f7 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py @@ -5,10 +5,11 @@ from jwt import PyJWTError from sqlalchemy.orm import Session from starlette.status import HTTP_403_FORBIDDEN +from app import crud from app.api.utils.db import get_db from app.core import config from app.core.jwt import ALGORITHM -from app.crud import user as crud_user +from app.db_models.user import User from app.models.token import TokenPayload reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token") @@ -24,7 +25,21 @@ def get_current_user( raise HTTPException( status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials" ) - user = crud_user.get(db, user_id=token_data.user_id) + user = crud.user.get(db, user_id=token_data.user_id) if not user: raise HTTPException(status_code=404, detail="User not found") return user + + +def get_current_active_user(current_user: User = 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: User = 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/backend_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py index 41b6b63..e0bfa48 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py @@ -18,8 +18,12 @@ wait_seconds = 1 after=after_log(logger, logging.WARN), ) def init(): - # Try to create session to check if DB is awake - db_session.execute("SELECT 1") + try: + # Try to create session to check if DB is awake + db_session.execute("SELECT 1") + 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 41b6b63..e0bfa48 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py @@ -18,8 +18,12 @@ wait_seconds = 1 after=after_log(logger, logging.WARN), ) def init(): - # Try to create session to check if DB is awake - db_session.execute("SELECT 1") + try: + # Try to create session to check if DB is awake + db_session.execute("SELECT 1") + except Exception as e: + logger.error(e) + raise e def main(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py index e69de29..f9b61db 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 diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py index 938aa6c..294ae9a 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import List, Optional from fastapi.encoders import jsonable_encoder @@ -7,20 +7,20 @@ from app.db_models.user import User from app.models.user import UserInCreate, UserInUpdate -def get(db_session, *, user_id: int) -> Union[User, None]: +def get(db_session, *, user_id: int) -> Optional[User]: return db_session.query(User).filter(User.id == user_id).first() -def get_by_email(db_session, *, email: str) -> Union[User, None]: +def get_by_email(db_session, *, email: str) -> Optional[User]: return db_session.query(User).filter(User.email == email).first() -def authenticate(db_session, *, email: str, password: str) -> Union[User, bool]: +def authenticate(db_session, *, email: str, password: str) -> Optional[User]: user = get_by_email(db_session, email=email) if not user: - return False + return None if not verify_password(password, user.hashed_password): - return False + return None return user @@ -32,7 +32,7 @@ def is_superuser(user) -> bool: return user.is_superuser -def get_multi(db_session, *, skip=0, limit=100) -> Union[List[User], List[None]]: +def get_multi(db_session, *, skip=0, limit=100) -> List[Optional[User]]: return db_session.query(User).offset(skip).limit(limit).all() 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 26e9039..4b9d825 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py @@ -1,19 +1,19 @@ +from app import crud from app.core import config -from app.crud import user as crud_user from app.models.user import UserInCreate def init_db(db_session): # Tables should be created with Alembic migrations # But if you don't want to use migrations, create - # the tables uncommenting the next line + # the tables un-commenting the next line # Base.metadata.create_all(bind=engine) - user = crud_user.get_by_email(db_session, email=config.FIRST_SUPERUSER) + user = crud.user.get_by_email(db_session, email=config.FIRST_SUPERUSER) if not user: user_in = UserInCreate( email=config.FIRST_SUPERUSER, password=config.FIRST_SUPERUSER_PASSWORD, is_superuser=True, ) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/session.py b/{{cookiecutter.project_slug}}/backend/app/app/db/session.py index 352738e..63752d1 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/session.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/session.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import scoped_session, sessionmaker from app.core import config -engine = create_engine(config.SQLALCHEMY_DATABASE_URI, convert_unicode=True) +engine = create_engine(config.SQLALCHEMY_DATABASE_URI) db_session = scoped_session( sessionmaker(autocommit=False, autoflush=False, bind=engine) ) 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 d2ba9fd..cf7b8be 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 import user as crud_user from app.db.session import db_session from app.models.user import UserInCreate from app.tests.utils.user import user_authentication_headers @@ -32,7 +32,7 @@ def test_create_user_new_email(superuser_token_headers): ) assert 200 <= r.status_code < 300 created_user = r.json() - user = crud_user.get_by_email(db_session, email=username) + user = crud.user.get_by_email(db_session, email=username) assert user.email == created_user["email"] @@ -41,7 +41,7 @@ def test_get_existing_user(superuser_token_headers): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) user_id = user.id r = requests.get( f"{server_api}{config.API_V1_STR}/users/{user_id}", @@ -49,7 +49,7 @@ def test_get_existing_user(superuser_token_headers): ) assert 200 <= r.status_code < 300 api_user = r.json() - user = crud_user.get_by_email(db_session, email=username) + user = crud.user.get_by_email(db_session, email=username) assert user.email == api_user["email"] @@ -59,7 +59,7 @@ def test_create_user_existing_username(superuser_token_headers): # username = email password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) data = {"email": username, "password": password} r = requests.post( f"{server_api}{config.API_V1_STR}/users/", @@ -76,7 +76,7 @@ def test_create_user_by_normal_user(): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) user_token_headers = user_authentication_headers(server_api, username, password) data = {"email": username, "password": password} r = requests.post( @@ -90,12 +90,12 @@ def test_retrieve_users(superuser_token_headers): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) username2 = random_lower_string() password2 = random_lower_string() user_in2 = UserInCreate(email=username2, password=password2) - user2 = crud_user.create(db_session, user_in=user_in2) + user2 = crud.user.create(db_session, user_in=user_in2) 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_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py index b70e0a8..175e4f4 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,6 +1,6 @@ from fastapi.encoders import jsonable_encoder -from app.crud import user as crud_user +from app import crud from app.db.session import db_session from app.models.user import UserInCreate from app.tests.utils.utils import random_lower_string @@ -10,7 +10,7 @@ def test_create_user(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) assert user.email == email assert hasattr(user, "hashed_password") @@ -19,8 +19,8 @@ def test_authenticate_user(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password) - user = crud_user.create(db_session, user_in=user_in) - authenticated_user = crud_user.authenticate( + user = crud.user.create(db_session, user_in=user_in) + authenticated_user = crud.user.authenticate( db_session, email=email, password=password ) assert authenticated_user @@ -30,16 +30,16 @@ def test_authenticate_user(): def test_not_authenticate_user(): email = random_lower_string() password = random_lower_string() - user = crud_user.authenticate(db_session, email=email, password=password) - assert user is False + user = crud.user.authenticate(db_session, email=email, password=password) + assert user is None def test_check_if_user_is_active(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password) - user = crud_user.create(db_session, user_in=user_in) - is_active = crud_user.is_active(user) + user = crud.user.create(db_session, user_in=user_in) + is_active = crud.user.is_active(user) assert is_active is True @@ -48,9 +48,9 @@ def test_check_if_user_is_active_inactive(): password = random_lower_string() user_in = UserInCreate(email=email, password=password, disabled=True) print(user_in) - user = crud_user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, user_in=user_in) print(user) - is_active = crud_user.is_active(user) + is_active = crud.user.is_active(user) print(is_active) assert is_active @@ -59,8 +59,8 @@ def test_check_if_user_is_superuser(): email = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=email, password=password, is_superuser=True) - user = crud_user.create(db_session, user_in=user_in) - is_superuser = crud_user.is_superuser(user) + user = crud.user.create(db_session, user_in=user_in) + is_superuser = crud.user.is_superuser(user) assert is_superuser is True @@ -68,8 +68,8 @@ def test_check_if_user_is_superuser_normal_user(): username = random_lower_string() password = random_lower_string() user_in = UserInCreate(email=username, password=password) - user = crud_user.create(db_session, user_in=user_in) - is_superuser = crud_user.is_superuser(user) + user = crud.user.create(db_session, user_in=user_in) + is_superuser = crud.user.is_superuser(user) assert is_superuser is False @@ -77,7 +77,7 @@ def test_get_user(): password = random_lower_string() username = random_lower_string() user_in = UserInCreate(email=username, password=password, is_superuser=True) - user = crud_user.create(db_session, user_in=user_in) - user_2 = crud_user.get(db_session, user_id=user.id) + user = crud.user.create(db_session, user_in=user_in) + user_2 = crud.user.get(db_session, user_id=user.id) assert user.email == user_2.email assert jsonable_encoder(user) == jsonable_encoder(user_2) 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 6618668..7a2a78b 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py @@ -19,10 +19,14 @@ wait_seconds = 1 after=after_log(logger, logging.WARN), ) def init(): - # Try to create session to check if DB is awake - db_session.execute("SELECT 1") - # Wait for API to be awake, run one simple tests to authenticate - test_get_access_token() + try: + # Try to create session to check if DB is awake + db_session.execute("SELECT 1") + # 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(): diff --git a/{{cookiecutter.project_slug}}/backend/app/app/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/utils.py index 0518912..ffd8dc9 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/utils.py @@ -71,7 +71,7 @@ def send_reset_password_email(email_to: str, email: str, token: str): def send_new_account_email(email_to: str, username: str, password: str): project_name = config.PROJECT_NAME - subject = f"{project_name} - New acccount for user {username}" + 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 = config.SERVER_HOST diff --git a/{{cookiecutter.project_slug}}/backend/app/app/worker.py b/{{cookiecutter.project_slug}}/backend/app/app/worker.py index 2a4a089..82bc5a1 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/worker.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/worker.py @@ -8,5 +8,4 @@ client_sentry = Client(config.SENTRY_DSN) @celery_app.task(acks_late=True) def test_celery(word: str): - print("test task") return f"test task return {word}" 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/backend.dockerfile b/{{cookiecutter.project_slug}}/backend/backend.dockerfile index 1d102cd..1aa82ab 100644 --- a/{{cookiecutter.project_slug}}/backend/backend.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/backend.dockerfile @@ -1,6 +1,6 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.6 -RUN pip install celery==4.2.1 passlib[bcrypt] tenacity requests pydantic emails "fastapi>=0.6.0" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy +RUN pip install celery==4.2.1 passlib[bcrypt] tenacity requests emails "fastapi>=0.7.1" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile index af29ac4..087bf58 100644 --- a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile @@ -1,6 +1,6 @@ FROM python:3.6 -RUN pip install raven celery==4.2.1 passlib[bcrypt] tenacity requests "fastapi>=0.6.0" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy +RUN pip install raven celery==4.2.1 passlib[bcrypt] tenacity requests "fastapi>=0.7.1" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy # 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 d2b849f..5a9158f 100644 --- a/{{cookiecutter.project_slug}}/backend/tests.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/tests.dockerfile @@ -1,6 +1,6 @@ FROM python:3.6 -RUN pip install requests pytest tenacity passlib[bcrypt] pydantic "fastapi>=0.6.0" psycopg2-binary SQLAlchemy +RUN pip install requests pytest tenacity passlib[bcrypt] "fastapi>=0.7.1" psycopg2-binary SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/frontend/package-lock.json b/{{cookiecutter.project_slug}}/frontend/package-lock.json index b1bd42c..f1934df 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,14 +5343,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5302,20 +5363,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5432,8 +5490,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5445,7 +5502,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5460,7 +5516,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5468,14 +5523,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5494,7 +5547,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5575,8 +5627,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5588,7 +5639,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5710,7 +5760,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5951,9 +6000,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", @@ -8321,9 +8370,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": { @@ -8394,9 +8443,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", @@ -9310,12 +9359,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" @@ -9404,9 +9454,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": { @@ -10891,9 +10941,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" @@ -13049,9 +13099,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", 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 @@