Browse Source

Refactor/upgrade backend and frontend parts (#2)

* ♻️ Refactor and simplify backend code

* ♻️ Refactor frontend state, integrate typesafe-vuex accessors into state files

* ♻️ Use new state accessors and standardize layout

* 🔒 Upgrade and fix npm security audit

* 🔧 Update local re-generation scripts

* 🔊 Log startup exceptions to detect errors early

* ✏️ Fix password reset token content

* 🔥 Remove unneeded Dockerfile directives

* 🔥 Remove unnecessary print

* 🔥 Remove unnecessary code, upgrade dependencies in backend

* ✏️ Fix typos in docstrings and comments

* 🏗️ Improve user Depends utilities to simplify and remove code

* 🔥 Remove deprecated SQLAlchemy parameter
pull/13907/head
Sebastián Ramírez 6 years ago
committed by GitHub
parent
commit
cd112bd683
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      dev-fsfp-back.sh
  2. 2
      dev-fsfp.sh
  3. 8
      {{cookiecutter.project_slug}}/README.md
  4. 10
      {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py
  5. 12
      {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py
  6. 61
      {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py
  7. 17
      {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py
  8. 19
      {{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py
  9. 8
      {{cookiecutter.project_slug}}/backend/app/app/backend_pre_start.py
  10. 8
      {{cookiecutter.project_slug}}/backend/app/app/celeryworker_pre_start.py
  11. 1
      {{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py
  12. 14
      {{cookiecutter.project_slug}}/backend/app/app/crud/user.py
  13. 8
      {{cookiecutter.project_slug}}/backend/app/app/db/init_db.py
  14. 2
      {{cookiecutter.project_slug}}/backend/app/app/db/session.py
  15. 16
      {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py
  16. 32
      {{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py
  17. 12
      {{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py
  18. 2
      {{cookiecutter.project_slug}}/backend/app/app/utils.py
  19. 1
      {{cookiecutter.project_slug}}/backend/app/app/worker.py
  20. 2
      {{cookiecutter.project_slug}}/backend/app/backend-live.sh
  21. 2
      {{cookiecutter.project_slug}}/backend/backend.dockerfile
  22. 2
      {{cookiecutter.project_slug}}/backend/celeryworker.dockerfile
  23. 2
      {{cookiecutter.project_slug}}/backend/tests.dockerfile
  24. 212
      {{cookiecutter.project_slug}}/frontend/package-lock.json
  25. 2
      {{cookiecutter.project_slug}}/frontend/package.json
  26. 3
      {{cookiecutter.project_slug}}/frontend/src/App.vue
  27. 4
      {{cookiecutter.project_slug}}/frontend/src/components/NotificationsManager.vue
  28. 9
      {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/commit.ts
  29. 10
      {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/dispatch.ts
  30. 3
      {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/index.ts
  31. 9
      {{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/read.ts
  32. 17
      {{cookiecutter.project_slug}}/frontend/src/store/admin/actions.ts
  33. 7
      {{cookiecutter.project_slug}}/frontend/src/store/admin/getters.ts
  34. 7
      {{cookiecutter.project_slug}}/frontend/src/store/admin/mutations.ts
  35. 15
      {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/commit.ts
  36. 20
      {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/dispatch.ts
  37. 3
      {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/index.ts
  38. 15
      {{cookiecutter.project_slug}}/frontend/src/store/main/accessors/read.ts
  39. 39
      {{cookiecutter.project_slug}}/frontend/src/store/main/actions.ts
  40. 13
      {{cookiecutter.project_slug}}/frontend/src/store/main/getters.ts
  41. 13
      {{cookiecutter.project_slug}}/frontend/src/store/main/mutations.ts
  42. 3
      {{cookiecutter.project_slug}}/frontend/src/views/Login.vue
  43. 2
      {{cookiecutter.project_slug}}/frontend/src/views/PasswordRecovery.vue
  44. 8
      {{cookiecutter.project_slug}}/frontend/src/views/ResetPassword.vue
  45. 6
      {{cookiecutter.project_slug}}/frontend/src/views/main/Dashboard.vue
  46. 11
      {{cookiecutter.project_slug}}/frontend/src/views/main/Main.vue
  47. 3
      {{cookiecutter.project_slug}}/frontend/src/views/main/Start.vue
  48. 2
      {{cookiecutter.project_slug}}/frontend/src/views/main/admin/Admin.vue
  49. 3
      {{cookiecutter.project_slug}}/frontend/src/views/main/admin/AdminUsers.vue
  50. 15
      {{cookiecutter.project_slug}}/frontend/src/views/main/admin/CreateUser.vue
  51. 96
      {{cookiecutter.project_slug}}/frontend/src/views/main/admin/EditUser.vue
  52. 2
      {{cookiecutter.project_slug}}/frontend/src/views/main/profile/UserProfile.vue
  53. 41
      {{cookiecutter.project_slug}}/frontend/src/views/main/profile/UserProfileEdit.vue
  54. 12
      {{cookiecutter.project_slug}}/frontend/src/views/main/profile/UserProfileEditPassword.vue

17
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\}\}/

2
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

8
{{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.

10
{{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)

12
{{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

61
{{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

17
{{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"}

19
{{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

8
{{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():

8
{{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():

1
{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py

@ -0,0 +1 @@
from . import user

14
{{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()

8
{{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)

2
{{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)
)

16
{{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

32
{{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)

12
{{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():

2
{{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

1
{{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}"

2
{{cookiecutter.project_slug}}/backend/app/backend-live.sh

@ -1,2 +0,0 @@
#! /usr/bin/env bash
uvicorn app.main:app --host 0.0.0.0 --port 80 --debug

2
{{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:

2
{{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:

2
{{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:

212
{{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",

2
{{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",

3
{{cookiecutter.project_slug}}/frontend/src/App.vue

@ -21,8 +21,9 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { dispatchCheckLoggedIn, readIsLoggedIn, commitAddNotification } from '@/store/main/accessors';
import NotificationsManager from '@/components/NotificationsManager.vue';
import { readIsLoggedIn } from '@/store/main/getters';
import { dispatchCheckLoggedIn } from '@/store/main/actions';
@Component({
components: {

4
{{cookiecutter.project_slug}}/frontend/src/components/NotificationsManager.vue

@ -8,8 +8,10 @@
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
import { readFirstNotification, dispatchRemoveNotification, commitRemoveNotification } from '@/store/main/accessors';
import { AppNotification } from '@/store/main/state';
import { commitRemoveNotification } from '@/store/main/mutations';
import { readFirstNotification } from '@/store/main/getters';
import { dispatchRemoveNotification } from '@/store/main/actions';
@Component
export default class NotificationsManager extends Vue {

9
{{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/commit.ts

@ -1,9 +0,0 @@
import { getStoreAccessors } from 'typesafe-vuex';
import { State } from '@/store/state';
import { mutations } from '../mutations';
import { AdminState } from '../state';
const {commit} = getStoreAccessors<AdminState, State>('');
export const commitSetUser = commit(mutations.setUser);
export const commitSetUsers = commit(mutations.setUsers);

10
{{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/dispatch.ts

@ -1,10 +0,0 @@
import { getStoreAccessors } from 'typesafe-vuex';
import { AdminState } from '../state';
import { State } from '@/store/state';
import { actions } from '../actions';
const {dispatch} = getStoreAccessors<AdminState, State>('');
export const dispatchCreateUser = dispatch(actions.actionCreateUser);
export const dispatchGetUsers = dispatch(actions.actionGetUsers);
export const dispatchUpdateUser = dispatch(actions.actionUpdateUser);

3
{{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/index.ts

@ -1,3 +0,0 @@
export * from './commit';
export * from './dispatch';
export * from './read';

9
{{cookiecutter.project_slug}}/frontend/src/store/admin/accessors/read.ts

@ -1,9 +0,0 @@
import { getStoreAccessors } from 'typesafe-vuex';
import { AdminState } from '../state';
import { State } from '@/store/state';
import { getters } from '../getters';
const { read } = getStoreAccessors<AdminState, State>('');
export const readAdminOneUser = read(getters.adminOneUser);
export const readAdminUsers = read(getters.adminUsers);

17
{{cookiecutter.project_slug}}/frontend/src/store/admin/actions.ts

@ -1,13 +1,12 @@
import { api } from '@/api';
import { ActionContext } from 'vuex';
import {
commitSetUsers,
commitSetUser,
} from './accessors/commit';
import { IUserProfileCreate, IUserProfileUpdate } from '@/interfaces';
import { State } from '../state';
import { AdminState } from './state';
import { dispatchCheckApiError, commitAddNotification, commitRemoveNotification } from '../main/accessors';
import { getStoreAccessors } from 'typesafe-vuex';
import { commitSetUsers, commitSetUser } from './mutations';
import { dispatchCheckApiError } from '../main/actions';
import { commitAddNotification, commitRemoveNotification } from '../main/mutations';
type MainContext = ActionContext<AdminState, State>;
@ -32,7 +31,7 @@ export const actions = {
]))[0];
commitSetUser(context, response.data);
commitRemoveNotification(context, loadingNotification);
commitAddNotification(context, {content: 'User successfully updated', color: 'success'});
commitAddNotification(context, { content: 'User successfully updated', color: 'success' });
} catch (error) {
await dispatchCheckApiError(context, error);
}
@ -53,3 +52,9 @@ export const actions = {
}
},
};
const { dispatch } = getStoreAccessors<AdminState, State>('');
export const dispatchCreateUser = dispatch(actions.actionCreateUser);
export const dispatchGetUsers = dispatch(actions.actionGetUsers);
export const dispatchUpdateUser = dispatch(actions.actionUpdateUser);

7
{{cookiecutter.project_slug}}/frontend/src/store/admin/getters.ts

@ -1,4 +1,6 @@
import { AdminState } from './state';
import { getStoreAccessors } from 'typesafe-vuex';
import { State } from '../state';
export const getters = {
adminUsers: (state: AdminState) => state.users,
@ -9,3 +11,8 @@ export const getters = {
}
},
};
const { read } = getStoreAccessors<AdminState, State>('');
export const readAdminOneUser = read(getters.adminOneUser);
export const readAdminUsers = read(getters.adminUsers);

7
{{cookiecutter.project_slug}}/frontend/src/store/admin/mutations.ts

@ -1,5 +1,7 @@
import { IUserProfile } from '@/interfaces';
import { AdminState } from './state';
import { getStoreAccessors } from 'typesafe-vuex';
import { State } from '../state';
export const mutations = {
setUsers(state: AdminState, payload: IUserProfile[]) {
@ -11,3 +13,8 @@ export const mutations = {
state.users = users;
},
};
const { commit } = getStoreAccessors<AdminState, State>('');
export const commitSetUser = commit(mutations.setUser);
export const commitSetUsers = commit(mutations.setUsers);

15
{{cookiecutter.project_slug}}/frontend/src/store/main/accessors/commit.ts

@ -1,15 +0,0 @@
import { getStoreAccessors } from 'typesafe-vuex';
import { MainState } from '../state';
import { State } from '@/store/state';
import { mutations } from '../mutations';
const {commit} = getStoreAccessors<MainState | any, State>('');
export const commitSetDashboardMiniDrawer = commit(mutations.setDashboardMiniDrawer);
export const commitSetDashboardShowDrawer = commit(mutations.setDashboardShowDrawer);
export const commitSetLoggedIn = commit(mutations.setLoggedIn);
export const commitSetLogInError = commit(mutations.setLogInError);
export const commitSetToken = commit(mutations.setToken);
export const commitSetUserProfile = commit(mutations.setUserProfile);
export const commitAddNotification = commit(mutations.addNotification);
export const commitRemoveNotification = commit(mutations.removeNotification);

20
{{cookiecutter.project_slug}}/frontend/src/store/main/accessors/dispatch.ts

@ -1,20 +0,0 @@
import { getStoreAccessors } from 'typesafe-vuex';
import { MainState } from '../state';
import { State } from '@/store/state';
import { actions } from '../actions';
const {dispatch} = getStoreAccessors<MainState | any, State>('');
export const dispatchCheckApiError = dispatch(actions.actionCheckApiError);
export const dispatchCheckLoggedIn = dispatch(actions.actionCheckLoggedIn);
export const dispatchGetUserProfile = dispatch(actions.actionGetUserProfile);
export const dispatchLogIn = dispatch(actions.actionLogIn);
export const dispatchLogOut = dispatch(actions.actionLogOut);
export const dispatchUserLogOut = dispatch(actions.actionUserLogOut);
export const dispatchRemoveLogIn = dispatch(actions.actionRemoveLogIn);
export const dispatchRouteLoggedIn = dispatch(actions.actionRouteLoggedIn);
export const dispatchRouteLogOut = dispatch(actions.actionRouteLogOut);
export const dispatchUpdateUserProfile = dispatch(actions.actionUpdateUserProfile);
export const dispatchRemoveNotification = dispatch(actions.removeNotification);
export const dispatchPasswordRecovery = dispatch(actions.passwordRecovery);
export const dispatchResetPassword = dispatch(actions.resetPassword);

3
{{cookiecutter.project_slug}}/frontend/src/store/main/accessors/index.ts

@ -1,3 +0,0 @@
export * from './commit';
export * from './dispatch';
export * from './read';

15
{{cookiecutter.project_slug}}/frontend/src/store/main/accessors/read.ts

@ -1,15 +0,0 @@
import { getStoreAccessors } from 'typesafe-vuex';
import { MainState } from '../state';
import { State } from '@/store/state';
import { getters } from '../getters';
const {read} = getStoreAccessors<MainState, State>('');
export const readDashboardMiniDrawer = read(getters.dashboardMiniDrawer);
export const readDashboardShowDrawer = read(getters.dashboardShowDrawer);
export const readHasAdminAccess = read(getters.hasAdminAccess);
export const readIsLoggedIn = read(getters.isLoggedIn);
export const readLoginError = read(getters.loginError);
export const readToken = read(getters.token);
export const readUserProfile = read(getters.userProfile);
export const readFirstNotification = read(getters.firstNotification);

39
{{cookiecutter.project_slug}}/frontend/src/store/main/actions.ts

@ -1,24 +1,19 @@
import { api } from '@/api';
import { saveLocalToken, getLocalToken, removeLocalToken } from '@/utils';
import router from '@/router';
import { getLocalToken, removeLocalToken, saveLocalToken } from '@/utils';
import { AxiosError } from 'axios';
import { getStoreAccessors } from 'typesafe-vuex';
import { ActionContext } from 'vuex';
import { State } from '../state';
import {
commitSetToken,
commitAddNotification,
commitRemoveNotification,
commitSetLoggedIn,
commitSetLogInError,
dispatchGetUserProfile,
dispatchRouteLoggedIn,
dispatchLogOut,
commitSetToken,
commitSetUserProfile,
dispatchCheckApiError,
dispatchRemoveLogIn,
dispatchRouteLogOut,
commitRemoveNotification,
commitAddNotification,
} from './accessors';
import { AxiosError } from 'axios';
import { State } from '../state';
import { MainState, AppNotification } from './state';
} from './mutations';
import { AppNotification, MainState } from './state';
type MainContext = ActionContext<MainState, State>;
@ -160,3 +155,19 @@ export const actions = {
}
},
};
const { dispatch } = getStoreAccessors<MainState | any, State>('');
export const dispatchCheckApiError = dispatch(actions.actionCheckApiError);
export const dispatchCheckLoggedIn = dispatch(actions.actionCheckLoggedIn);
export const dispatchGetUserProfile = dispatch(actions.actionGetUserProfile);
export const dispatchLogIn = dispatch(actions.actionLogIn);
export const dispatchLogOut = dispatch(actions.actionLogOut);
export const dispatchUserLogOut = dispatch(actions.actionUserLogOut);
export const dispatchRemoveLogIn = dispatch(actions.actionRemoveLogIn);
export const dispatchRouteLoggedIn = dispatch(actions.actionRouteLoggedIn);
export const dispatchRouteLogOut = dispatch(actions.actionRouteLogOut);
export const dispatchUpdateUserProfile = dispatch(actions.actionUpdateUserProfile);
export const dispatchRemoveNotification = dispatch(actions.removeNotification);
export const dispatchPasswordRecovery = dispatch(actions.passwordRecovery);
export const dispatchResetPassword = dispatch(actions.resetPassword);

13
{{cookiecutter.project_slug}}/frontend/src/store/main/getters.ts

@ -1,4 +1,6 @@
import { MainState } from './state';
import { getStoreAccessors } from 'typesafe-vuex';
import { State } from '../state';
export const getters = {
hasAdminAccess: (state: MainState) => {
@ -14,3 +16,14 @@ export const getters = {
isLoggedIn: (state: MainState) => state.isLoggedIn,
firstNotification: (state: MainState) => state.notifications.length > 0 && state.notifications[0],
};
const {read} = getStoreAccessors<MainState, State>('');
export const readDashboardMiniDrawer = read(getters.dashboardMiniDrawer);
export const readDashboardShowDrawer = read(getters.dashboardShowDrawer);
export const readHasAdminAccess = read(getters.hasAdminAccess);
export const readIsLoggedIn = read(getters.isLoggedIn);
export const readLoginError = read(getters.loginError);
export const readToken = read(getters.token);
export const readUserProfile = read(getters.userProfile);
export const readFirstNotification = read(getters.firstNotification);

13
{{cookiecutter.project_slug}}/frontend/src/store/main/mutations.ts

@ -1,5 +1,7 @@
import { IUserProfile } from '@/interfaces';
import { MainState, AppNotification } from './state';
import { getStoreAccessors } from 'typesafe-vuex';
import { State } from '../state';
export const mutations = {
@ -28,3 +30,14 @@ export const mutations = {
state.notifications = state.notifications.filter((notification) => notification !== payload);
},
};
const {commit} = getStoreAccessors<MainState | any, State>('');
export const commitSetDashboardMiniDrawer = commit(mutations.setDashboardMiniDrawer);
export const commitSetDashboardShowDrawer = commit(mutations.setDashboardShowDrawer);
export const commitSetLoggedIn = commit(mutations.setLoggedIn);
export const commitSetLogInError = commit(mutations.setLogInError);
export const commitSetToken = commit(mutations.setToken);
export const commitSetUserProfile = commit(mutations.setUserProfile);
export const commitAddNotification = commit(mutations.addNotification);
export const commitRemoveNotification = commit(mutations.removeNotification);

3
{{cookiecutter.project_slug}}/frontend/src/views/Login.vue

@ -35,7 +35,8 @@
import { Component, Vue } from 'vue-property-decorator';
import { api } from '@/api';
import { appName } from '@/env';
import { readLoginError, dispatchLogIn } from '@/store/main/accessors';
import { readLoginError } from '@/store/main/getters';
import { dispatchLogIn } from '@/store/main/actions';
@Component
export default class Login extends Vue {

2
{{cookiecutter.project_slug}}/frontend/src/views/PasswordRecovery.vue

@ -30,7 +30,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { appName } from '@/env';
import { dispatchLogIn, dispatchPasswordRecovery } from '@/store/main/accessors';
import { dispatchPasswordRecovery } from '@/store/main/actions';
@Component
export default class Login extends Vue {

8
{{cookiecutter.project_slug}}/frontend/src/views/ResetPassword.vue

@ -33,13 +33,9 @@
import { Component, Vue } from 'vue-property-decorator';
import { Store } from 'vuex';
import { IUserProfileUpdate } from '@/interfaces';
import {
dispatchUpdateUserProfile,
readUserProfile,
dispatchResetPassword,
commitAddNotification,
} from '@/store/main/accessors';
import { appName } from '@/env';
import { commitAddNotification } from '@/store/main/mutations';
import { dispatchResetPassword } from '@/store/main/actions';
@Component
export default class UserProfileEdit extends Vue {

6
{{cookiecutter.project_slug}}/frontend/src/views/main/Dashboard.vue

@ -6,10 +6,12 @@
</v-card-title>
<v-card-text>
<div class="headline font-weight-light ma-5">Welcome {{greetedUser}}</div>
</v-card-text>
<v-card-actions>
<v-btn to="/main/profile/view">View Profile</v-btn>
<v-btn to="/main/profile/edit">Edit Profile</v-btn>
<v-btn to="/main/profile/password">Change Password</v-btn>
</v-card-text>
</v-card-actions>
</v-card>
</v-container>
</template>
@ -17,7 +19,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Store } from 'vuex';
import { readUserProfile } from '@/store/main/accessors';
import { readUserProfile } from '@/store/main/getters';
@Component
export default class Dashboard extends Vue {

11
{{cookiecutter.project_slug}}/frontend/src/views/main/Main.vue

@ -121,14 +121,9 @@
import { Vue, Component } from 'vue-property-decorator';
import { appName } from '@/env';
import {
commitSetDashboardShowDrawer,
readDashboardShowDrawer,
commitSetDashboardMiniDrawer,
readDashboardMiniDrawer,
dispatchUserLogOut,
readHasAdminAccess,
} from '@/store/main/accessors';
import { readDashboardMiniDrawer, readDashboardShowDrawer, readHasAdminAccess } from '@/store/main/getters';
import { commitSetDashboardShowDrawer, commitSetDashboardMiniDrawer } from '@/store/main/mutations';
import { dispatchUserLogOut } from '@/store/main/actions';
const routeGuardMain = async (to, from, next) => {
if (to.path === '/main') {

3
{{cookiecutter.project_slug}}/frontend/src/views/main/Start.vue

@ -4,8 +4,9 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { dispatchCheckLoggedIn, readIsLoggedIn } from '@/store/main/accessors';
import { store } from '@/store';
import { dispatchCheckLoggedIn } from '@/store/main/actions';
import { readIsLoggedIn } from '@/store/main/getters';
const startRouteGuard = async (to, from, next) => {
await dispatchCheckLoggedIn(store);

2
{{cookiecutter.project_slug}}/frontend/src/views/main/admin/Admin.vue

@ -5,7 +5,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { store } from '@/store';
import { readHasAdminAccess } from '@/store/main/accessors';
import { readHasAdminAccess } from '@/store/main/getters';
const routeGuardAdmin = async (to, from, next) => {
if (!readHasAdminAccess(store)) {

3
{{cookiecutter.project_slug}}/frontend/src/views/main/admin/AdminUsers.vue

@ -31,7 +31,8 @@
import { Component, Vue } from 'vue-property-decorator';
import { Store } from 'vuex';
import { IUserProfile } from '@/interfaces';
import { readAdminUsers, dispatchGetUsers } from '@/store/admin/accessors';
import { readAdminUsers } from '@/store/admin/getters';
import { dispatchGetUsers } from '@/store/admin/actions';
@Component
export default class AdminUsers extends Vue {

15
{{cookiecutter.project_slug}}/frontend/src/views/main/admin/CreateUser.vue

@ -21,14 +21,17 @@
</v-text-field>
</v-flex>
</v-layout>
<v-btn @click="submit" :disabled="!valid">
Save
</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn @click="cancel">Cancel</v-btn>
</v-form>
</template>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="cancel">Cancel</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn @click="submit" :disabled="!valid">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
@ -40,7 +43,7 @@ import {
IUserProfileUpdate,
IUserProfileCreate,
} from '@/interfaces';
import { dispatchGetUsers, dispatchCreateUser } from '@/store/admin/accessors';
import { dispatchGetUsers, dispatchCreateUser } from '@/store/admin/actions';
@Component
export default class CreateUser extends Vue {

96
{{cookiecutter.project_slug}}/frontend/src/views/main/admin/EditUser.vue

@ -8,37 +8,92 @@
<template>
<div class="my-3">
<div class="subheading secondary--text text--lighten-2">Username</div>
<div class="title primary--text text--darken-2" v-if="user">{{user.email}}</div>
<div class="title primary--text text--darken-2" v-else>-----</div>
<div
class="title primary--text text--darken-2"
v-if="user"
>{{user.email}}</div>
<div
class="title primary--text text--darken-2"
v-else
>-----</div>
</div>
<v-form v-model="valid" ref="form" lazy-validation>
<v-text-field label="Full Name" v-model="fullName" required></v-text-field>
<v-text-field label="E-mail" type="email" v-model="email" v-validate="'required|email'" data-vv-name="email" :error-messages="errors.collect('email')" required></v-text-field>
<v-form
v-model="valid"
ref="form"
lazy-validation
>
<v-text-field
label="Full Name"
v-model="fullName"
required
></v-text-field>
<v-text-field
label="E-mail"
type="email"
v-model="email"
v-validate="'required|email'"
data-vv-name="email"
:error-messages="errors.collect('email')"
required
></v-text-field>
<div class="subheading secondary--text text--lighten-2">User is superuser <span v-if="isSuperuser">(currently is a superuser)</span><span v-else>(currently is not a superuser)</span></div>
<v-checkbox label="Is Superuser" v-model="isSuperuser"></v-checkbox>
<v-checkbox
label="Is Superuser"
v-model="isSuperuser"
></v-checkbox>
<div class="subheading secondary--text text--lighten-2">User is active <span v-if="isActive">(currently active)</span><span v-else>(currently not active)</span></div>
<v-checkbox label="Is Active" v-model="isActive"></v-checkbox>
<v-checkbox
label="Is Active"
v-model="isActive"
></v-checkbox>
<v-layout align-center>
<v-flex shrink>
<v-checkbox v-model="setPassword" class="mr-2"></v-checkbox>
<v-checkbox
v-model="setPassword"
class="mr-2"
></v-checkbox>
</v-flex>
<v-flex>
<v-text-field :disabled="!setPassword" type="password" ref="password" label="Set Password" data-vv-name="password" data-vv-delay="100" v-validate="{required: setPassword}" v-model="password1" :error-messages="errors.first('password')">
<v-text-field
:disabled="!setPassword"
type="password"
ref="password"
label="Set Password"
data-vv-name="password"
data-vv-delay="100"
v-validate="{required: setPassword}"
v-model="password1"
:error-messages="errors.first('password')"
>
</v-text-field>
<v-text-field v-show="setPassword" type="password" label="Confirm Password" data-vv-name="password_confirmation" data-vv-delay="100" data-vv-as="password" v-validate="{required: setPassword, confirmed: 'password'}" v-model="password2" :error-messages="errors.first('password_confirmation')">
<v-text-field
v-show="setPassword"
type="password"
label="Confirm Password"
data-vv-name="password_confirmation"
data-vv-delay="100"
data-vv-as="password"
v-validate="{required: setPassword, confirmed: 'password'}"
v-model="password2"
:error-messages="errors.first('password_confirmation')"
>
</v-text-field>
</v-flex>
</v-layout>
<v-btn @click="submit" :disabled="!valid">
Save
</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn @click="cancel">Cancel</v-btn>
</v-form>
</template>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="cancel">Cancel</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn
@click="submit"
:disabled="!valid"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
@ -46,11 +101,8 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { IUserProfile, IUserProfileUpdate } from '@/interfaces';
import {
dispatchGetUsers,
dispatchUpdateUser,
readAdminOneUser,
} from '@/store/admin/accessors';
import { dispatchGetUsers, dispatchUpdateUser } from '@/store/admin/actions';
import { readAdminOneUser } from '@/store/admin/getters';
@Component
export default class EditUser extends Vue {

2
{{cookiecutter.project_slug}}/frontend/src/views/main/profile/UserProfile.vue

@ -27,7 +27,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Store } from 'vuex';
import { readUserProfile } from '@/store/main/accessors';
import { readUserProfile } from '@/store/main/getters';
@Component
export default class UserProfile extends Vue {

41
{{cookiecutter.project_slug}}/frontend/src/views/main/profile/UserProfileEdit.vue

@ -6,17 +6,39 @@
</v-card-title>
<v-card-text>
<template>
<v-form v-model="valid" ref="form" lazy-validation>
<v-text-field label="Full Name" v-model="fullName" required></v-text-field>
<v-text-field label="E-mail" type="email" v-model="email" v-validate="'required|email'" data-vv-name="email" :error-messages="errors.collect('email')" required></v-text-field>
<v-btn @click="submit" :disabled="!valid">
Save
</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn @click="cancel">Cancel</v-btn>
<v-form
v-model="valid"
ref="form"
lazy-validation
>
<v-text-field
label="Full Name"
v-model="fullName"
required
></v-text-field>
<v-text-field
label="E-mail"
type="email"
v-model="email"
v-validate="'required|email'"
data-vv-name="email"
:error-messages="errors.collect('email')"
required
></v-text-field>
</v-form>
</template>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="cancel">Cancel</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn
@click="submit"
:disabled="!valid"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
@ -25,7 +47,8 @@
import { Component, Vue } from 'vue-property-decorator';
import { Store } from 'vuex';
import { IUserProfileUpdate } from '@/interfaces';
import { dispatchUpdateUserProfile, readUserProfile } from '@/store/main/accessors';
import { readUserProfile } from '@/store/main/getters';
import { dispatchUpdateUserProfile } from '@/store/main/actions';
@Component
export default class UserProfileEdit extends Vue {

12
{{cookiecutter.project_slug}}/frontend/src/views/main/profile/UserProfileEditPassword.vue

@ -34,12 +34,15 @@
v-model="password2"
:error-messages="errors.first('password_confirmation')">
</v-text-field>
<v-btn @click="cancel">Cancel</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn @click="submit" :disabled="!valid">Save</v-btn>
</v-form>
</template>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="cancel">Cancel</v-btn>
<v-btn @click="reset">Reset</v-btn>
<v-btn @click="submit" :disabled="!valid">Save</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
@ -48,7 +51,8 @@
import { Component, Vue } from 'vue-property-decorator';
import { Store } from 'vuex';
import { IUserProfileUpdate } from '@/interfaces';
import { dispatchUpdateUserProfile, readUserProfile } from '@/store/main/accessors';
import { readUserProfile } from '@/store/main/getters';
import { dispatchUpdateUserProfile } from '@/store/main/actions';
@Component
export default class UserProfileEdit extends Vue {

Loading…
Cancel
Save