From a6ca394f7edcdc74e8e40137260ca64be13c0bde Mon Sep 17 00:00:00 2001 From: Rohul Amin Date: Wed, 21 Jun 2023 00:29:53 +0900 Subject: [PATCH 1/2] HW-Amin --- fastapi-react-project/.prettierignore | 1 + fastapi-react-project/README.md | 159 ++++++++++++++++ fastapi-react-project/backend/Dockerfile | 13 ++ fastapi-react-project/backend/alembic.ini | 82 +++++++++ fastapi-react-project/backend/app/__init__.py | 0 .../app/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 111 bytes .../app/__pycache__/main.cpython-38.pyc | Bin 0 -> 1631 bytes .../app/__pycache__/tasks.cpython-38.pyc | Bin 0 -> 360 bytes fastapi-react-project/backend/app/alembic.ini | 85 +++++++++ .../backend/app/alembic/README | 1 + .../backend/app/alembic/__init__.py | 0 .../alembic/__pycache__/env.cpython-38.pyc | Bin 0 -> 1801 bytes .../backend/app/alembic/env.py | 81 +++++++++ .../backend/app/alembic/script.py.mako | 24 +++ .../91979b40eb38_create_users_table.py | 34 ++++ ...9b40eb38_create_users_table.cpython-38.pyc | Bin 0 -> 1001 bytes ...77025b5f0b_added_todo_table.cpython-38.pyc | Bin 0 -> 1596 bytes .../versions/a377025b5f0b_added_todo_table.py | 55 ++++++ .../backend/app/api/__init__.py | 0 .../api/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 115 bytes .../backend/app/api/api_v1/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 122 bytes .../app/api/api_v1/routers/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 130 bytes .../routers/__pycache__/auth.cpython-38.pyc | Bin 0 -> 1620 bytes .../routers/__pycache__/todos.cpython-38.pyc | Bin 0 -> 1807 bytes .../routers/__pycache__/users.cpython-38.pyc | Bin 0 -> 2176 bytes .../backend/app/api/api_v1/routers/auth.py | 63 +++++++ .../app/api/api_v1/routers/tests/__init__.py | 0 .../app/api/api_v1/routers/tests/test_auth.py | 66 +++++++ .../api/api_v1/routers/tests/test_users.py | 110 ++++++++++++ .../backend/app/api/api_v1/routers/todos.py | 85 +++++++++ .../backend/app/api/api_v1/routers/users.py | 107 +++++++++++ .../backend/app/api/dependencies/__init__.py | 0 .../backend/app/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 116 bytes .../app/core/__pycache__/auth.cpython-38.pyc | Bin 0 -> 2094 bytes .../__pycache__/celery_app.cpython-38.pyc | Bin 0 -> 279 bytes .../core/__pycache__/config.cpython-38.pyc | Bin 0 -> 257 bytes .../core/__pycache__/security.cpython-38.pyc | Bin 0 -> 1237 bytes .../backend/app/core/auth.py | 75 ++++++++ .../backend/app/core/celery_app.py | 5 + .../backend/app/core/config.py | 7 + .../backend/app/core/security.py | 31 ++++ .../backend/app/db/__init__.py | 0 .../db/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 114 bytes .../app/db/__pycache__/crud.cpython-38.pyc | Bin 0 -> 3537 bytes .../app/db/__pycache__/models.cpython-38.pyc | Bin 0 -> 1120 bytes .../app/db/__pycache__/schemas.cpython-38.pyc | Bin 0 -> 2998 bytes .../app/db/__pycache__/session.cpython-38.pyc | Bin 0 -> 588 bytes fastapi-react-project/backend/app/db/crud.py | 119 ++++++++++++ .../backend/app/db/models.py | 28 +++ .../backend/app/db/schemas.py | 73 ++++++++ .../backend/app/db/session.py | 21 +++ .../backend/app/initial_data.py | 26 +++ fastapi-react-project/backend/app/main.py | 51 ++++++ fastapi-react-project/backend/app/tasks.py | 6 + .../backend/app/tests/__init__.py | 0 .../backend/app/tests/test_main.py | 4 + .../backend/app/tests/test_tasks.py | 6 + fastapi-react-project/backend/conftest.py | 169 ++++++++++++++++++ fastapi-react-project/backend/pyproject.toml | 2 + .../backend/requirements.txt | 19 ++ fastapi-react-project/docker-compose.yml | 71 ++++++++ fastapi-react-project/frontend/.dockerignore | 2 + fastapi-react-project/frontend/.eslintrc.js | 51 ++++++ fastapi-react-project/frontend/.prettierrc.js | 5 + fastapi-react-project/frontend/Dockerfile | 16 ++ fastapi-react-project/frontend/README.md | 68 +++++++ fastapi-react-project/frontend/package.json | 62 +++++++ .../frontend/public/favicon.ico | Bin 0 -> 3150 bytes .../frontend/public/index.html | 43 +++++ .../frontend/public/logo192.png | Bin 0 -> 5347 bytes .../frontend/public/logo512.png | Bin 0 -> 9664 bytes .../frontend/public/manifest.json | 25 +++ .../frontend/public/robots.txt | 3 + fastapi-react-project/frontend/run.sh | 18 ++ fastapi-react-project/frontend/src/App.tsx | 6 + fastapi-react-project/frontend/src/Routes.tsx | 59 ++++++ .../frontend/src/__tests__/home.test.tsx | 12 ++ .../frontend/src/__tests__/login.test.tsx | 11 ++ .../frontend/src/admin/Admin.tsx | 37 ++++ .../frontend/src/admin/Todo.tsx | 40 +++++ .../frontend/src/admin/Todo/ToDoCreate.tsx | 18 ++ .../frontend/src/admin/Todo/ToDoEdit.tsx | 19 ++ .../frontend/src/admin/Todo/ToDoList.tsx | 20 +++ .../frontend/src/admin/Todo/index.ts | 3 + .../frontend/src/admin/Users/UserCreate.tsx | 21 +++ .../frontend/src/admin/Users/UserEdit.tsx | 22 +++ .../frontend/src/admin/Users/UserList.tsx | 24 +++ .../frontend/src/admin/Users/index.ts | 3 + .../frontend/src/admin/authProvider.ts | 55 ++++++ .../frontend/src/admin/index.ts | 1 + .../frontend/src/config/index.tsx | 3 + fastapi-react-project/frontend/src/decs.d.ts | 1 + fastapi-react-project/frontend/src/index.css | 13 ++ fastapi-react-project/frontend/src/index.tsx | 12 ++ fastapi-react-project/frontend/src/logo.svg | 7 + .../frontend/src/react-app-env.d.ts | 1 + .../frontend/src/utils/api.ts | 13 ++ .../frontend/src/utils/auth.ts | 118 ++++++++++++ .../frontend/src/utils/index.ts | 2 + .../frontend/src/views/Home.tsx | 69 +++++++ .../frontend/src/views/Login.tsx | 151 ++++++++++++++++ .../frontend/src/views/PrivateRoute.tsx | 25 +++ .../frontend/src/views/Protected.tsx | 5 + .../frontend/src/views/SignUp.tsx | 138 ++++++++++++++ .../frontend/src/views/index.ts | 5 + fastapi-react-project/frontend/tsconfig.json | 19 ++ fastapi-react-project/nginx/nginx.conf | 22 +++ fastapi-react-project/scripts/build.sh | 16 ++ fastapi-react-project/scripts/test.sh | 7 + fastapi-react-project/scripts/test_backend.sh | 6 + 113 files changed, 2956 insertions(+) create mode 100644 fastapi-react-project/.prettierignore create mode 100644 fastapi-react-project/README.md create mode 100644 fastapi-react-project/backend/Dockerfile create mode 100644 fastapi-react-project/backend/alembic.ini create mode 100644 fastapi-react-project/backend/app/__init__.py create mode 100644 fastapi-react-project/backend/app/__pycache__/__init__.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/__pycache__/main.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/__pycache__/tasks.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/alembic.ini create mode 100644 fastapi-react-project/backend/app/alembic/README create mode 100644 fastapi-react-project/backend/app/alembic/__init__.py create mode 100644 fastapi-react-project/backend/app/alembic/__pycache__/env.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/alembic/env.py create mode 100644 fastapi-react-project/backend/app/alembic/script.py.mako create mode 100644 fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py create mode 100644 fastapi-react-project/backend/app/alembic/versions/__pycache__/91979b40eb38_create_users_table.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/alembic/versions/__pycache__/a377025b5f0b_added_todo_table.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py create mode 100644 fastapi-react-project/backend/app/api/__init__.py create mode 100644 fastapi-react-project/backend/app/api/__pycache__/__init__.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/api/api_v1/__init__.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/__pycache__/__init__.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__init__.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/__init__.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/auth.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/todos.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/users.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/auth.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/tests/__init__.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/todos.py create mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/users.py create mode 100644 fastapi-react-project/backend/app/api/dependencies/__init__.py create mode 100644 fastapi-react-project/backend/app/core/__init__.py create mode 100644 fastapi-react-project/backend/app/core/__pycache__/__init__.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/core/__pycache__/auth.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/core/__pycache__/celery_app.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/core/__pycache__/config.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/core/__pycache__/security.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/core/auth.py create mode 100644 fastapi-react-project/backend/app/core/celery_app.py create mode 100644 fastapi-react-project/backend/app/core/config.py create mode 100644 fastapi-react-project/backend/app/core/security.py create mode 100644 fastapi-react-project/backend/app/db/__init__.py create mode 100644 fastapi-react-project/backend/app/db/__pycache__/__init__.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/db/__pycache__/crud.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/db/__pycache__/models.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/db/__pycache__/schemas.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/db/__pycache__/session.cpython-38.pyc create mode 100644 fastapi-react-project/backend/app/db/crud.py create mode 100644 fastapi-react-project/backend/app/db/models.py create mode 100644 fastapi-react-project/backend/app/db/schemas.py create mode 100644 fastapi-react-project/backend/app/db/session.py create mode 100644 fastapi-react-project/backend/app/initial_data.py create mode 100644 fastapi-react-project/backend/app/main.py create mode 100644 fastapi-react-project/backend/app/tasks.py create mode 100644 fastapi-react-project/backend/app/tests/__init__.py create mode 100644 fastapi-react-project/backend/app/tests/test_main.py create mode 100644 fastapi-react-project/backend/app/tests/test_tasks.py create mode 100644 fastapi-react-project/backend/conftest.py create mode 100644 fastapi-react-project/backend/pyproject.toml create mode 100644 fastapi-react-project/backend/requirements.txt create mode 100644 fastapi-react-project/docker-compose.yml create mode 100644 fastapi-react-project/frontend/.dockerignore create mode 100644 fastapi-react-project/frontend/.eslintrc.js create mode 100644 fastapi-react-project/frontend/.prettierrc.js create mode 100644 fastapi-react-project/frontend/Dockerfile create mode 100644 fastapi-react-project/frontend/README.md create mode 100644 fastapi-react-project/frontend/package.json create mode 100644 fastapi-react-project/frontend/public/favicon.ico create mode 100644 fastapi-react-project/frontend/public/index.html create mode 100644 fastapi-react-project/frontend/public/logo192.png create mode 100644 fastapi-react-project/frontend/public/logo512.png create mode 100644 fastapi-react-project/frontend/public/manifest.json create mode 100644 fastapi-react-project/frontend/public/robots.txt create mode 100644 fastapi-react-project/frontend/run.sh create mode 100644 fastapi-react-project/frontend/src/App.tsx create mode 100644 fastapi-react-project/frontend/src/Routes.tsx create mode 100644 fastapi-react-project/frontend/src/__tests__/home.test.tsx create mode 100644 fastapi-react-project/frontend/src/__tests__/login.test.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Admin.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Todo.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Todo/index.ts create mode 100644 fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Users/UserList.tsx create mode 100644 fastapi-react-project/frontend/src/admin/Users/index.ts create mode 100644 fastapi-react-project/frontend/src/admin/authProvider.ts create mode 100644 fastapi-react-project/frontend/src/admin/index.ts create mode 100644 fastapi-react-project/frontend/src/config/index.tsx create mode 100644 fastapi-react-project/frontend/src/decs.d.ts create mode 100644 fastapi-react-project/frontend/src/index.css create mode 100644 fastapi-react-project/frontend/src/index.tsx create mode 100644 fastapi-react-project/frontend/src/logo.svg create mode 100644 fastapi-react-project/frontend/src/react-app-env.d.ts create mode 100644 fastapi-react-project/frontend/src/utils/api.ts create mode 100644 fastapi-react-project/frontend/src/utils/auth.ts create mode 100644 fastapi-react-project/frontend/src/utils/index.ts create mode 100644 fastapi-react-project/frontend/src/views/Home.tsx create mode 100644 fastapi-react-project/frontend/src/views/Login.tsx create mode 100644 fastapi-react-project/frontend/src/views/PrivateRoute.tsx create mode 100644 fastapi-react-project/frontend/src/views/Protected.tsx create mode 100644 fastapi-react-project/frontend/src/views/SignUp.tsx create mode 100644 fastapi-react-project/frontend/src/views/index.ts create mode 100644 fastapi-react-project/frontend/tsconfig.json create mode 100644 fastapi-react-project/nginx/nginx.conf create mode 100644 fastapi-react-project/scripts/build.sh create mode 100644 fastapi-react-project/scripts/test.sh create mode 100644 fastapi-react-project/scripts/test_backend.sh diff --git a/fastapi-react-project/.prettierignore b/fastapi-react-project/.prettierignore new file mode 100644 index 00000000..412c2574 --- /dev/null +++ b/fastapi-react-project/.prettierignore @@ -0,0 +1 @@ +docker-compose.yml \ No newline at end of file diff --git a/fastapi-react-project/README.md b/fastapi-react-project/README.md new file mode 100644 index 00000000..bbd43623 --- /dev/null +++ b/fastapi-react-project/README.md @@ -0,0 +1,159 @@ +# y + +## Features + +- **FastAPI** with Python 3.8 +- **React 16** with Typescript, Redux, and react-router +- Postgres +- SqlAlchemy with Alembic for migrations +- Pytest for backend tests +- Jest for frontend tests +- Perttier/Eslint (with Airbnb style guide) +- Docker compose for easier development +- Nginx as a reverse proxy to allow backend and frontend on the same port + +## Development + +The only dependencies for this project should be docker and docker-compose. + +### Quick Start + +Starting the project with hot-reloading enabled +(the first time it will take a while): + +```bash +docker-compose up -d +``` + +To run the alembic migrations (for the users table): + +```bash +docker-compose run --rm backend alembic upgrade head +``` + +And navigate to http://localhost:8000 + +_Note: If you see an Nginx error at first with a `502: Bad Gateway` page, you may have to wait for webpack to build the development server (the nginx container builds much more quickly)._ + +Auto-generated docs will be at +http://localhost:8000/api/docs + +### Rebuilding containers: + +``` +docker-compose build +``` + +### Restarting containers: + +``` +docker-compose restart +``` + +### Bringing containers down: + +``` +docker-compose down +``` + +### Frontend Development + +Alternatively to running inside docker, it can sometimes be easier +to use npm directly for quicker reloading. To run using npm: + +``` +cd frontend +npm install +npm start +``` + +This should redirect you to http://localhost:3000 + +### Frontend Tests + +``` +cd frontend +npm install +npm test +``` + +## Migrations + +Migrations are run using alembic. To run all migrations: + +``` +docker-compose run --rm backend alembic upgrade head +``` + +To create a new migration: + +``` +alembic revision -m "create users table" +``` + +And fill in `upgrade` and `downgrade` methods. For more information see +[Alembic's official documentation](https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script). + +## Testing + +There is a helper script for both frontend and backend tests: + +``` +./scripts/test.sh +``` + +### Backend Tests + +``` +docker-compose run backend pytest +``` + +any arguments to pytest can also be passed after this command + +### Frontend Tests + +``` +docker-compose run frontend test +``` + +This is the same as running npm test from within the frontend directory + +## Logging + +``` +docker-compose logs +``` + +Or for a specific service: + +``` +docker-compose logs -f name_of_service # frontend|backend|db +``` + +## Project Layout + +``` +backend +└── app + ├── alembic + │ └── versions # where migrations are located + ├── api + │ └── api_v1 + │ └── endpoints + ├── core # config + ├── db # db models + ├── tests # pytest + └── main.py # entrypoint to backend + +frontend +└── public +└── src + ├── components + │ └── Home.tsx + ├── config + │ └── index.tsx # constants + ├── __tests__ + │ └── test_home.tsx + ├── index.tsx # entrypoint + └── App.tsx # handles routing +``` diff --git a/fastapi-react-project/backend/Dockerfile b/fastapi-react-project/backend/Dockerfile new file mode 100644 index 00000000..10aef33a --- /dev/null +++ b/fastapi-react-project/backend/Dockerfile @@ -0,0 +1,13 @@ + +FROM python:3.8 + +RUN mkdir /app +WORKDIR /app + +RUN apt update && \ + apt install -y postgresql-client + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . \ No newline at end of file diff --git a/fastapi-react-project/backend/alembic.ini b/fastapi-react-project/backend/alembic.ini new file mode 100644 index 00000000..53e6f13f --- /dev/null +++ b/fastapi-react-project/backend/alembic.ini @@ -0,0 +1,82 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = app/alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# timezone to use when rendering the date +# within the migration file as well as the filename. +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +timezone = America/Los_Angeles + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path +# version_locations = %(here)s/bar %(here)s/bat alembic/versions + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks=black +# black.type=console_scripts +# black.entrypoint=black +# black.options=-l 79 + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S \ No newline at end of file diff --git a/fastapi-react-project/backend/app/__init__.py b/fastapi-react-project/backend/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6a8e8f8af2397d5bc740f4edbbc34f5ccf156b8 GIT binary patch literal 111 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BmKe3f*p)HAl%-d%69$!@;WHf=eu95(cdP@#gN7D1@mA|zid$MdrBc5E** zQ+f^J2XJVP`~&X%i@9>*#<@a*H?vE!0(GnzzwddU^KGZoa&Ud}=a1}_?>N63WN|eR z`4qPt631Z*WaUc3`HrVnd z0grWmCTxYRzV^;ZyovNJ*1IIE&(>Z$NZGcOb);;tO-tE<{&*Jx)?-^E0u)xx$%wG+ z3yj8cns~_%3VNBPJo zJ_9N9s{E?TQjYk>2vm~QLO`jKG*kHrB*r$fS{ZNkB4&3oik@l5_(3? zum+E@V;(uLsG*JN8~4&7&Kbo_k%NIJny8_DsZs@+vP11>ypk|*#fo7xF+!e{aH@1D zKu)Ss!Y_{4K;GLf`aZF{Nb_l!EoN0QW$S_P@m*kB}%<((u7K8 zCnxuYvtg4%8-&28&(S;Y5HTSL^ANt%hz&^uMkF*w@J0}(Y4nmo+2L^BjI$>1{3|oG zrp*hidsnpUL7kP!MUIG!O!VaGH(x&d?0ItV;OmFFb=}yukF|@_Z(D0tpQXIac+Ft` zkeG~@X#q*1T~U_{)wadAB@aI?Dpqs2kB_}0@8i+MDG6zW=fdYw;^CyzzZ-r?xEDbWEE%ZAC3pAZ2$lO literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/__pycache__/tasks.cpython-38.pyc b/fastapi-react-project/backend/app/__pycache__/tasks.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19250f6cc8f3a13e1e1014b3e67a4000360c9c7a GIT binary patch literal 360 zcmYjMJ5Iwu5ZzgC9AhN~Z~?cLkhuUtNc3rdCXHozrXg!=?QR4?R6xrK08w+LZ7C2} zpkgKgg^}KTR`1Q5y_imO#nFG>;~Uox8UBai;e_E2IH5wYs)IRzBDmD3F?j+?C_(e0 zafIokd4RdW@tG>KFIK1$V&0M^x2l_AIOlPvX&1h>O`uXobKN_PsiUxTS1pL*H>B5$ zQ-+rt5$@EpdeyvI&u;eu`A~$$FN%%W7w>H5LN`q}SgPek z=w57;b{XecIg0x%soLIAwLRPkCmNB�R$~Z+$l1SUA~H*W=RCv3$VG{z;O<5AAVK AcK`qY literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/alembic.ini b/fastapi-react-project/backend/app/alembic.ini new file mode 100644 index 00000000..bfcc3c7f --- /dev/null +++ b/fastapi-react-project/backend/app/alembic.ini @@ -0,0 +1,85 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# timezone to use when rendering the date +# within the migration file as well as the filename. +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path +# version_locations = %(here)s/bar %(here)s/bat alembic/versions + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks=black +# black.type=console_scripts +# black.entrypoint=black +# black.options=-l 79 + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/fastapi-react-project/backend/app/alembic/README b/fastapi-react-project/backend/app/alembic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/fastapi-react-project/backend/app/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/fastapi-react-project/backend/app/alembic/__init__.py b/fastapi-react-project/backend/app/alembic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/alembic/__pycache__/env.cpython-38.pyc b/fastapi-react-project/backend/app/alembic/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cdf51f87f52af8aca4aa77e2aa523303eced031 GIT binary patch literal 1801 zcmZvc&5s*36u|AtWHOl~yW1_TKtjmkQX*9;7bFfV#O}6-N@!KKtw6bqoUxNQcszsc z*)Q#0x=388_!qKA{-wEcK>P~`1kavizl2BQZ@>5a-p}vtV2~gv#UJn3!!AOj!8pLK-;%979EZLi%61-&Bj0aBzccRmek9|(JMLn{!$mSq zc)*iI?-IkfyXcPxti$4;(0GG&Spw}Q>#;txTRdDoxI+3J-@HT^sfcg6mf_cEIQSDR zLI*=!CsQH$exasf2BS_^D9cYQbho*h3B}W?F7gznn+;)E6cSd$J!*L7gxoGI80^Du zoL?+jVur7ry5L~kf4=)>cW?Lgi}dZ`H^ZO~3R6cjZn--84QcRRQ~k9~%kmkOJfDc{ z87z*<)4Buu(n`xuoM`7%gmn+I{7MR1O6i(wY9mcT5056-rD z8D61ZH+-*s1#bBceSyA3hv<96+F)@+_d(!crARJjnp#mPLxdtvis@8>Tb_`-V0@Gi zC~xM%kj6um=7!igcSGk#axCneEGlCOC2(=SpDIRF0W*rc@IHPDM~Z4TznLK*k!Be$ zEuBd2PtCdJgc@?pr5q8mcS_6;qAZ1)d4-K6l4K(q?+c&o=RS~70o#thII?VS#MgZU1)P|DWjIw11T)mRHhT5n5p|r0125* zi_)5dVdR6ZTM*m2d+l$%>BbKx9B56grpnOFg{$7sl}c}-H^h1acHW?-9i;W+ySvg= zd_RRXvketWA{^qtf6?DjC&Bs=%w}$BU7STu*gpJb4wXaSqD63p0l?NJI)_)cgKP+G zVB3p`wcfWb@Ht*~0K(5N0vj*7=YdTCPRI&2UiMaS#>@U)IPYV6_VfQSs_rBDQbCM> zDU)$UwJ3n1h6fjHhYYZ=rtz9lYD|#{zvhTg5DD;WgY+%Nn>e3sm>(sT*_`L6BM9zU z@75UJdsJ#Z6(@DqC6G*lY?w@L#QHEE3`*{IR4T{f#bt9;j>o!mH1 zUte!LfV?5tsut(9wB9#7r#s^j&0YOd`vrDxU~Qf~^ghCFq`Zr#-1 l*}sZ-2bL^;6IM^+m-+T literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/alembic/env.py b/fastapi-react-project/backend/app/alembic/env.py new file mode 100644 index 00000000..5cbeca06 --- /dev/null +++ b/fastapi-react-project/backend/app/alembic/env.py @@ -0,0 +1,81 @@ +import os +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from app.db.models import Base + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_url(): + return os.getenv("DATABASE_URL") + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + Calls to context.execute() here emit the given string to the + script output. + """ + # url = config.get_main_option("sqlalchemy.url") + url = get_url() + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + In this scenario we need to create an Engine + and associate a connection with the context. + """ + configuration = config.get_section(config.config_ini_section) + configuration["sqlalchemy.url"] = get_url() + connectable = engine_from_config( + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/fastapi-react-project/backend/app/alembic/script.py.mako b/fastapi-react-project/backend/app/alembic/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/fastapi-react-project/backend/app/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py b/fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py new file mode 100644 index 00000000..af661b44 --- /dev/null +++ b/fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py @@ -0,0 +1,34 @@ +"""create users table + +Revision ID: 91979b40eb38 +Revises: +Create Date: 2020-03-23 14:53:53.101322 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "91979b40eb38" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "user", + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("email", sa.String(50), nullable=False), + sa.Column("first_name", sa.String(100)), + sa.Column("last_name", sa.String(100)), + sa.Column("address", sa.String(100)), + sa.Column("hashed_password", sa.String(100), nullable=False), + sa.Column("is_active", sa.Boolean, nullable=False), + sa.Column("is_superuser", sa.Boolean, nullable=False), + ) + + +def downgrade(): + op.drop_table("user") diff --git a/fastapi-react-project/backend/app/alembic/versions/__pycache__/91979b40eb38_create_users_table.cpython-38.pyc b/fastapi-react-project/backend/app/alembic/versions/__pycache__/91979b40eb38_create_users_table.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3c20d2cd8a7371e91a58f0d6f1cec4bf719aa2a GIT binary patch literal 1001 zcmZuv&2Q5%6pxd%`B=LRfdFv|#~!xzV+dMJ2+;{?mmL^fa~vE(BCcFH zuSfnRUpb)t1xTEDPCBgu?7SEM{OsTR^4o4Vv~UzZKLd9x>xTiG!$$A|MIE66E4F}u zEl6g^HZf~QII#m(r*UVKEisnOv4dcWqQ0T3tSbVfvfn#kqjtdo_p4LmR%KsdIxAw5 zF?&}I1od9HA}c(NcsexVMe$?G3zhi&p`b*20qQo@&<=OWP^D!L+tY znPyBb=?A{lEuOJdTn{n)S7`I|g%GBsI((N(RneSf{2I`z1HslWl5D@aNWVBT9jNG-#t&7H=sLB^Jv{RZ7ZhR(?Tq=Tze;Z#m_Oy_E{y< z{9Jo4i$ZXgf4Ag)yjrW@M+dAd8xcI4rOCksz5;B#I@s(JUAw8@NaJk3TxxGoo=XP& zF7hcuY7v*%WS87SF|+>vk`K}HPpci=tXsW}i>B2pvM#ZGDvh~kH(7=ti?VTh8+)^Q zfX8$I#bsVc@))5qm9dC?gu1t-JxZZSD8(u_DXuxyN5Mog=h;%;!IRpT>o>2vhK}CC ynPhn~r`Sg>ln#NHJO@RKyiTn4^WU)iqpX01;Li#2t6fyC*CD>^VwG?@{`MaxlK^i3 literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/alembic/versions/__pycache__/a377025b5f0b_added_todo_table.cpython-38.pyc b/fastapi-react-project/backend/app/alembic/versions/__pycache__/a377025b5f0b_added_todo_table.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4eb7b3325075e870c5c11b430a05d02a70da56a GIT binary patch literal 1596 zcmZux&u`l{6c%M!l4UtbQx6?-nb&Q=JRpf1*L7nQ!%}y}iVo?JW;^&I$bPKDKtCLk z&XS!6>=f*{9qnWPC0=*xKd=Ek411*P#6c%PAI0xIeSGHo=pSpfz{2ade}0EQJj?n^ zPgXA*C*R;Bo*;lVuz-M_kklU7M8B7k(!c>{P=@lrh0?&o#|Ia@U#&p}e5l|UKhecaWqU?Nf zI0#quRk*4<(s`>_h?71(;%@|L%?J=_pO(Ou_RN`EPy*+wWSBBCO zZkP%(-VHPJ!Jd=3M{upv3a%GGFvm5)9^)26^~y1kDuhtOFGKU?+6ue=0b8hSph^gL zP{q4eDI%LDa!Y?!!`!=b-B=47RzFl_iMj?iV@RyBITgk;$`l9v<8 zUEFvTVuG0B%%s;)OTR=kjaj0~#Sko62Fi;8I2EF~#O*G|v+nXUf;+I%X+V^&dD(V}ALPUiR}$NFK9HUTd-p=!GJ0R2U}` ziyAG2pT2ta{kI2)xH&wZEZDae{|xIf@V>VjPbS5a&~(T~yXTbaLm+ln9U9?ppRIW;*0?Hady$+|oo~k#s^c5K*2PN0@c~w_N{8n!_}qUlU#J Q27=>-#CIHG?vj7+KOJhI;{X5v literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py b/fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py new file mode 100644 index 00000000..e0c14b27 --- /dev/null +++ b/fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py @@ -0,0 +1,55 @@ +"""Added Todo table + +Revision ID: a377025b5f0b +Revises: 91979b40eb38 +Create Date: 2023-06-18 23:36:55.497350-07:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a377025b5f0b' +down_revision = '91979b40eb38' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('todos', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=True), + sa.Column('is_done', sa.Boolean(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_todos_id'), 'todos', ['id'], unique=False) + op.alter_column('user', 'is_active', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('user', 'is_superuser', + existing_type=sa.BOOLEAN(), + nullable=True) + op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) + op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False) + op.drop_column('user', 'address') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('user', sa.Column('address', sa.VARCHAR(length=100), autoincrement=False, nullable=True)) + op.drop_index(op.f('ix_user_id'), table_name='user') + op.drop_index(op.f('ix_user_email'), table_name='user') + op.alter_column('user', 'is_superuser', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('user', 'is_active', + existing_type=sa.BOOLEAN(), + nullable=False) + op.drop_index(op.f('ix_todos_id'), table_name='todos') + op.drop_table('todos') + # ### end Alembic commands ### diff --git a/fastapi-react-project/backend/app/api/__init__.py b/fastapi-react-project/backend/app/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/api/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4680ad5db2ee26d41dae1d06cb29c0b30521abf GIT binary patch literal 115 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BkKe3;4vlb}; literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/api/api_v1/__init__.py b/fastapi-react-project/backend/app/api/api_v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/api/api_v1/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de65e9cd5f147c03eba78331845b95ae89f9a79d GIT binary patch literal 122 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B=Ke3&ryk0@&Ee@O9{FKt1R6CHy&p^xo0HHt_p8x;= literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__init__.py b/fastapi-react-project/backend/app/api/api_v1/routers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41549911aba0067875df8f6af928950488dd0b63 GIT binary patch literal 130 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B&ryk0@&Ee@O9{FKt1R6CFXpMjVG0FbL2>Hq)$ literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/auth.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/auth.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..232bfef2f3888f84945b53d34839b4fe9fee61a8 GIT binary patch literal 1620 zcmd5+OK%)S5T5RN?CkFBdX3{if(Xky%flv!3lc&x&PK5UYs+5CXfC7K>9#XwUUc^m z+ge{@uKou(z|j}}1pk0zUpXNDLL@L%?X_$q4jgHzzpkz+RabpgHQSv|%Ys$@{*3+F zu&m$ka(%gQxer_Z48SZ#GAkz`At=*~=5}c3PUz%r=;mJN8NHn~azFHq>}1Wn6}F7* zX6-x(10#D`C+~*cd@h_L7Pn6<#|nGlJfHvZCD!V{C4)?v^!}`vYRO21MUlU#ZoTPc~vHpO%7c^6zRc)x%Qc14lPkHr6)F zT5+Kp4|v53CUxi0c)T$@NO+~vve2GXv8p9_YN|BnjAtr_i?_#B#3ms5k|(uD)!~5Z z`54~fg-VlH@u-&Iw|;8m@~*O#v+pv(D6><-oKyQ5QJ!k-`^-Hh$M%%&H^qJC9Xo}?8pjTN zX@Raib%0SiwOR9&9=lVQwOIQ(1;&EW(Vev-DTUyP>SOFhob!Gu`qhlP7rmXGosW>t z%M`WuC65IcXSQx%@DwE_Uq-L}%h&}H zDv(*dhy{*5{rFzAJzCiwKU&{h3x^K`On@btXz=RF>gsT7D;lpq9*&~nH_#))=w6?W;=y5jlRFPEKHNiW$dq|>QK8`xtA8`TfPloIX&VKYPR>M=Ye$Y*&gwz1G*NW ze|fI>5bTZ`%h-=~_5WzNn=46D)`jZF8Suys`}`o4N{SAID!K@B2nz^{2uP%O8R0g< zYY2A$pz>{$-#~a1;i}&AvwD9Pt*(zAezmqbzN)oA_QX2~?;^a1a2EkD8m|%4mnqrAfz=N3* z;iCf|l`ygVqHjBw*ClKdGYW5T2P`?XGquMN$<1#Ev7Xxa=Voxj~hSf=Va~iUP_cmp!18HDgDv-IbVG zK`i8xxWWry9)Rb_Tg;UcJOYIsx_c$X4!$s|nd$BMdiLvYdREWt^^igP_Uae;m&e#2 z_;9^}k9`E)yw4evO!AavJmHy0MCK$;<|Zyjzep>I2eXs4s`=GwI9VXGb|ISny-6 zTv@%~GL+Fzl?6{W30qU`w@^ZD$@+zm4S9!f+km?dd;S;Kluf|3WSek1fZLU~?x$Mv z?u8>e@*W`{0CMkkB)xerd1)c5U_JuRl8+PYb23f2h8jDpOehshX%< z8XFucGa2VbeHnA>ov5NOkK&50qE(FL7!WJdMnkOzg(AO=q)OH5p{nGl#K(>epF!Lc zg4zJ>PhipdXni#;W?ql>eHIJb(94(ivoUOEcWyrS)yrWzm8zeQa|QH*)$&kaYRl%p zjQtgK(}kg6bH3o`Lh^#o#e)CJeiL(cDx^4Ib5zEeZH8TFpQ@reNYgIyA~+Swee1BCbD@=OCA_SEsjh4l(al zbHP~a(0~WfL4XqC+?hL&HJ|Oh$vQuyth-VbgHdW|A?7-Ou-MhxFzNd^bYQS9zK)VB zhfQ?Wh{H1e9qIdSv;#b9; zoi{rZzqgr1#H_5<#}HtsTB`cNwO~q)xDjhu!G<>WSI|uj2H@t*v52k<_4zBHwf^zI6i~|=NoqVljY8=P4Av{oCLo06g>69X@5`foql_7ibB$$fqos4G zr;@G`)tSb_)JRUFYRNiu_(Ej^qfvid#{rAEtT)pyFOucjI`Cbx?iX+fbYrYD$_pwz z>rTet#N)QkWLl7-)+=+PgsvrzS5{L&2M6#Gnx^VQ_|swL7=|q#h#KGZJ0cV=7d+tN s()GP7!Nu!F4L$I?bOY~_tgA-UaMyMS>kNLcA>toe2W%r+d%YF%UsQse=l}o! literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/users.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/users.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0660a6cf48b779bdc93bea6adcbf713ff87d5299 GIT binary patch literal 2176 zcmb7E&2HO95auo^k@}-$`A2rFI7;Fa(7|?lED#iJlcFe4G=*DV_9D>QjV*(u)FtV} zs(h((ZC{}71N1q1>>KR0hoZ00)=g)Ik}L;-i!Q<8?98{rZ)a!ruvRNuFb2PWCx0zj z)^9jj97ULX2ygNOvn**z7FrP#EaHMkwy+~dI8i|q82Wfv6fS)2(2GiRYtgz`kD8*%EX5|4V~Gv1sWwmV zNLRV1TWV#~lAbJmZ!24sCJbbA!k%;G$nqtV6|A&HdH!;wWH!bev+>tg0H8$=ti)G z&}`kDch$>Ym`T-*2eE?Pc{6v<#vDzD17Do~0&mg=ky>LYgbQ1;l#T5P`_cM|kF8TK z`D1HrLyGNDv)qR9sY=^H7`BNISw_ygyYKIhYEK4n3cK1p2;yVav9$+bjUB4kxF;&W zYJhDI)rOm&t73}gxv?q^GHf&b5){a`qpzJ2)iyJzq1YdY2>PsHBv+>~ieH|!^A z16C#2pjOS}t4!YoZB)vk`;qEF$Ib`p_|n!f4`U0q$42Y(?G0YUv^_EqqYl%XfFwN^ zEtnb?8FVA{2qp;!VHNZ?$WeXz>u(9rmltquuAbH7)Y!wH&qe{1`GNQ_fdk=iB>ch| zJH&5$&M%11iCGL?t^}V>y6^UOLNARcy1A$yc60%J612QlL;gd4|PB!G4eIZH|bS~Ufz8l12WHW zSwF-G_QE!aASQ4&P)Cw<#8ps$pgm?|KEX<6qdb*|LkWG|RxhD|`tk7%mbmCfDsQJ+ zBC2>DJcETf(VE@?{eL7bXZ4gWn76a4AggKa+Mxee?xvR&AahDpjg{JY$GFdfB!#}x zShO1JPGi0_CQf4?nPPfAgrpdj8XuZ*Paf?hDoOf-Si9gCm8r3(GfACJ)kmkRk*3BB ziuuysiHd?m*U*F2t(Q>LL6{mw>J7Bo3$pY?FQWtd*-&{V-MsG&)5eA?(m0>PaZ!U) zs~Ni=r&O-S84kdM)nV*mmQwmnG0y>Qe4)Vbn$gt5-eb5%vrv7&z?7VUXfcmhS%Oxvs{+P?=QXYUi!iXlug&en GYxKX~&h_yC literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/auth.py b/fastapi-react-project/backend/app/api/api_v1/routers/auth.py new file mode 100644 index 00000000..05247f58 --- /dev/null +++ b/fastapi-react-project/backend/app/api/api_v1/routers/auth.py @@ -0,0 +1,63 @@ +from fastapi.security import OAuth2PasswordRequestForm +from fastapi import APIRouter, Depends, HTTPException, status +from datetime import timedelta + +from app.db.session import get_db +from app.core import security +from app.core.auth import authenticate_user, sign_up_new_user + +auth_router = r = APIRouter() + + +@r.post("/token") +async def login( + db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends() +): + user = authenticate_user(db, form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + access_token_expires = timedelta( + minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES + ) + if user.is_superuser: + permissions = "admin" + else: + permissions = "user" + access_token = security.create_access_token( + data={"sub": user.email, "permissions": permissions}, + expires_delta=access_token_expires, + ) + + return {"access_token": access_token, "token_type": "bearer"} + + +@r.post("/signup") +async def signup( + db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends() +): + user = sign_up_new_user(db, form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Account already exists", + headers={"WWW-Authenticate": "Bearer"}, + ) + + access_token_expires = timedelta( + minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES + ) + if user.is_superuser: + permissions = "admin" + else: + permissions = "user" + access_token = security.create_access_token( + data={"sub": user.email, "permissions": permissions}, + expires_delta=access_token_expires, + ) + + return {"access_token": access_token, "token_type": "bearer"} diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/tests/__init__.py b/fastapi-react-project/backend/app/api/api_v1/routers/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py b/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py new file mode 100644 index 00000000..3e73790a --- /dev/null +++ b/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py @@ -0,0 +1,66 @@ +from app.core import security + +# Monkey patch function we can use to shave a second off our tests by skipping the password hashing check +def verify_password_mock(first: str, second: str): + return True + + +def test_login(client, test_user, monkeypatch): + # Patch the test to skip password hashing check for speed + monkeypatch.setattr(security, "verify_password", verify_password_mock) + + response = client.post( + "/api/token", + data={"username": test_user.email, "password": "nottheactualpass"}, + ) + assert response.status_code == 200 + + +def test_signup(client, monkeypatch): + def get_password_hash_mock(first: str, second: str): + return True + + monkeypatch.setattr(security, "get_password_hash", get_password_hash_mock) + + response = client.post( + "/api/signup", + data={"username": "some@email.com", "password": "randompassword"}, + ) + assert response.status_code == 200 + + +def test_resignup(client, test_user, monkeypatch): + # Patch the test to skip password hashing check for speed + monkeypatch.setattr(security, "verify_password", verify_password_mock) + + response = client.post( + "/api/signup", + data={ + "username": test_user.email, + "password": "password_hashing_is_skipped_via_monkey_patch", + }, + ) + assert response.status_code == 409 + + +def test_wrong_password( + client, test_db, test_user, test_password, monkeypatch +): + def verify_password_failed_mock(first: str, second: str): + return False + + monkeypatch.setattr( + security, "verify_password", verify_password_failed_mock + ) + + response = client.post( + "/api/token", data={"username": test_user.email, "password": "wrong"} + ) + assert response.status_code == 401 + + +def test_wrong_login(client, test_db, test_user, test_password): + response = client.post( + "/api/token", data={"username": "fakeuser", "password": test_password} + ) + assert response.status_code == 401 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py b/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py new file mode 100644 index 00000000..5f285b03 --- /dev/null +++ b/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py @@ -0,0 +1,110 @@ +from app.db import models + + +def test_get_users(client, test_superuser, superuser_token_headers): + response = client.get("/api/v1/users", headers=superuser_token_headers) + assert response.status_code == 200 + assert response.json() == [ + { + "id": test_superuser.id, + "email": test_superuser.email, + "is_active": test_superuser.is_active, + "is_superuser": test_superuser.is_superuser, + } + ] + + +def test_delete_user(client, test_superuser, test_db, superuser_token_headers): + response = client.delete( + f"/api/v1/users/{test_superuser.id}", headers=superuser_token_headers + ) + assert response.status_code == 200 + assert test_db.query(models.User).all() == [] + + +def test_delete_user_not_found(client, superuser_token_headers): + response = client.delete( + "/api/v1/users/4321", headers=superuser_token_headers + ) + assert response.status_code == 404 + + +def test_edit_user(client, test_superuser, superuser_token_headers): + new_user = { + "email": "newemail@email.com", + "is_active": False, + "is_superuser": True, + "first_name": "Joe", + "last_name": "Smith", + "password": "new_password", + } + + response = client.put( + f"/api/v1/users/{test_superuser.id}", + json=new_user, + headers=superuser_token_headers, + ) + assert response.status_code == 200 + new_user["id"] = test_superuser.id + new_user.pop("password") + assert response.json() == new_user + + +def test_edit_user_not_found(client, test_db, superuser_token_headers): + new_user = { + "email": "newemail@email.com", + "is_active": False, + "is_superuser": False, + "password": "new_password", + } + response = client.put( + "/api/v1/users/1234", json=new_user, headers=superuser_token_headers + ) + assert response.status_code == 404 + + +def test_get_user( + client, + test_user, + superuser_token_headers, +): + response = client.get( + f"/api/v1/users/{test_user.id}", headers=superuser_token_headers + ) + assert response.status_code == 200 + assert response.json() == { + "id": test_user.id, + "email": test_user.email, + "is_active": bool(test_user.is_active), + "is_superuser": test_user.is_superuser, + } + + +def test_user_not_found(client, superuser_token_headers): + response = client.get("/api/v1/users/123", headers=superuser_token_headers) + assert response.status_code == 404 + + +def test_authenticated_user_me(client, user_token_headers): + response = client.get("/api/v1/users/me", headers=user_token_headers) + assert response.status_code == 200 + + +def test_unauthenticated_routes(client): + response = client.get("/api/v1/users/me") + assert response.status_code == 401 + response = client.get("/api/v1/users") + assert response.status_code == 401 + response = client.get("/api/v1/users/123") + assert response.status_code == 401 + response = client.put("/api/v1/users/123") + assert response.status_code == 401 + response = client.delete("/api/v1/users/123") + assert response.status_code == 401 + + +def test_unauthorized_routes(client, user_token_headers): + response = client.get("/api/v1/users", headers=user_token_headers) + assert response.status_code == 403 + response = client.get("/api/v1/users/123", headers=user_token_headers) + assert response.status_code == 403 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/todos.py b/fastapi-react-project/backend/app/api/api_v1/routers/todos.py new file mode 100644 index 00000000..b3790dbb --- /dev/null +++ b/fastapi-react-project/backend/app/api/api_v1/routers/todos.py @@ -0,0 +1,85 @@ +from fastapi import APIRouter, Request, Depends, Response +import typing as t + +from app.db.session import get_db +from app.db.crud import ( + get_todos, + get_todo, + create_todo, + delete_todo, + edit_todo, +) +from app.db.schemas import TodoCreate, TodoEdit, TodoOut + +todos_router = r = APIRouter() + +@r.get( + "/todos", + response_model=t.List[TodoOut], + response_model_exclude_none=True, +) +async def todos_list( + response: Response, + db=Depends(get_db), +): + """ + Get all todos + """ + todos = get_todos(db) + response.headers["Content-Range"] = f"0-9/{len(todos)}" + return todos + + +@r.get( + "/todos/{todo_id}", + response_model=TodoOut, + response_model_exclude_none=True, +) +async def todo_details( + request: Request, + todo_id: int, + db=Depends(get_db), +): + """ + Get any todo details + """ + todo = get_todo(db, todo_id) + return todo + +@r.post("/todos", response_model=TodoOut, response_model_exclude_none=True) +async def todo_create( + request: Request, + todo: TodoCreate, + db=Depends(get_db), +): + """ + Create a new todo + """ + return create_todo(db, todo) + +@r.put( + "/todos/{todo_id}", response_model=TodoOut, response_model_exclude_none=True +) +async def todo_id_edit( + request: Request, + todo_id: int, + todo: TodoEdit, + db=Depends(get_db), +): + """ + Update existing todo + """ + return edit_todo(db, todo_id, todo) + +@r.delete( + "/todos/{todo_id}", response_model=TodoOut, response_model_exclude_none=True +) +async def todo_delete( + request: Request, + todo_id: int, + db=Depends(get_db), +): + """ + Delete existing todo + """ + return delete_todo(db, todo_id) \ No newline at end of file diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/users.py b/fastapi-react-project/backend/app/api/api_v1/routers/users.py new file mode 100644 index 00000000..06167e27 --- /dev/null +++ b/fastapi-react-project/backend/app/api/api_v1/routers/users.py @@ -0,0 +1,107 @@ +from fastapi import APIRouter, Request, Depends, Response, encoders +import typing as t + +from app.db.session import get_db +from app.db.crud import ( + get_users, + get_user, + create_user, + delete_user, + edit_user, +) +from app.db.schemas import UserCreate, UserEdit, User, UserOut +from app.core.auth import get_current_active_user, get_current_active_superuser + +users_router = r = APIRouter() + + +@r.get( + "/users", + response_model=t.List[User], + response_model_exclude_none=True, +) +async def users_list( + response: Response, + db=Depends(get_db), + current_user=Depends(get_current_active_superuser), +): + """ + Get all users + """ + users = get_users(db) + # This is necessary for react-admin to work + response.headers["Content-Range"] = f"0-9/{len(users)}" + return users + + +@r.get("/users/me", response_model=User, response_model_exclude_none=True) +async def user_me(current_user=Depends(get_current_active_user)): + """ + Get own user + """ + return current_user + + +@r.get( + "/users/{user_id}", + response_model=User, + response_model_exclude_none=True, +) +async def user_details( + request: Request, + user_id: int, + db=Depends(get_db), + current_user=Depends(get_current_active_superuser), +): + """ + Get any user details + """ + user = get_user(db, user_id) + return user + # return encoders.jsonable_encoder( + # user, skip_defaults=True, exclude_none=True, + # ) + + +@r.post("/users", response_model=User, response_model_exclude_none=True) +async def user_create( + request: Request, + user: UserCreate, + db=Depends(get_db), + current_user=Depends(get_current_active_superuser), +): + """ + Create a new user + """ + return create_user(db, user) + + +@r.put( + "/users/{user_id}", response_model=User, response_model_exclude_none=True +) +async def user_edit( + request: Request, + user_id: int, + user: UserEdit, + db=Depends(get_db), + current_user=Depends(get_current_active_superuser), +): + """ + Update existing user + """ + return edit_user(db, user_id, user) + + +@r.delete( + "/users/{user_id}", response_model=User, response_model_exclude_none=True +) +async def user_delete( + request: Request, + user_id: int, + db=Depends(get_db), + current_user=Depends(get_current_active_superuser), +): + """ + Delete existing user + """ + return delete_user(db, user_id) diff --git a/fastapi-react-project/backend/app/api/dependencies/__init__.py b/fastapi-react-project/backend/app/api/dependencies/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/core/__init__.py b/fastapi-react-project/backend/app/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/core/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/core/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9629c87dc1ebc473666984a8d0d7f6315dc45d04 GIT binary patch literal 116 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B!Ke3s6a_yU~g%Aw*bAZU1RoP?z+7rdJ1H*cQbyx;q`Yt3ejz>~jt#9su2 z{Dzz9$A!%ou+*xUA--8kGZwPU37yOhU5YX%C0Qk`Sl&&&%n$u62!pH|R;{d( z*0Oq7&l+LF?!B~`wZax9f{E&IW}m{lHEi$G2TaUz{|V&*uRgAXb0D7sDHZC;L-;9`&n;#whs% z5pyAJwBvL$hX@TaWgIoxF!iyJSz<%1AjTCNS9uD8(``uyUJ^^yEMWSgPe1xNT3fxg z)?4}Z?yYe7hH-}58n40YWE~Z2_m-FLF889_%lA$7+Sgx#VsGUe<3R4Dj|$Zu=H*7L zV_8A-RV4F3x`APQLpFd77pCw=;-qT)alDh}F}M2G$*^YA96^v`<6?0rZH`)PtnTJgFHsg*4k-c3Nj>WPSdj54zP9zYnHj`tAoz&@w9mr@Wu?B4 z+yx}BByASU^0qs$^%1ZEnEJVH_s&4x}v3kCy@ za_7JWq%gj0+1>1ci`aM4diM@;7?NX{*cWBT*47|XOLow9E3ZQp1|zX;y?+&7TP2&L zXflqD6|RC+bq)wQf(O|HfJou@r~A^a$}4c%Ju9(kjQ=2pi8f9o&hdZdksTeO2OYi$ zBLl13x+*awre2B{`*Om|lpz)qpcrRCIjB4=Fuq}$Ip*M#$o}C77>Zij?lvyMw(Vjo z%jUrPHU-kAvtzA)4*y(}R9wZzR~XWO&eH~)r)}DB0}>npzQe{j58m!!qiWyOec7nB GtA7J@mhr0q literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/core/__pycache__/celery_app.cpython-38.pyc b/fastapi-react-project/backend/app/core/__pycache__/celery_app.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a27f7d4435296bfe392bd6c74d6da25b3c220722 GIT binary patch literal 279 zcmWIL<>g`kg8b)ADYJm|V-N=!umCv@KwPW=BvKes7;_kM8KW2(8B!Tjm{OQiSbCYF zm{M3%*fJSYnX;JUSyI@888kUw0#!3;GTvfyPR&Uzs*GYQ&o9bOEvgbJN=?Zuw$j&! z&}PQwmih*oj8SY!Mfo6k?!|#BwDz4nb%skz~($vyaKTXyswq&qbMNB}W zZ*f65@j&%aEXnzKX;IuDZSh6g`kg8b)ADQ!UdF^GcFa|Sdvc3eWVeqRGO-n2;Ni4|JElN#HF3~M0%FjwoE{WoCaSU;EatwBj4-N9E zV%G<%&@VI8WWB|dUwn%vAjsd#)j1^I&(YWQ7F&91Noro%E%D$$A4eZ&4_DvFcqHAP zQCyAzp7CLZ@xdWMD;bK|fYySEU*bSh3&1!zzbI8dIX^EgGhMHs@)n0pZhlH>PO2Rv PP;D_t0|yfe6BiQzu5m!Z literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/core/__pycache__/security.cpython-38.pyc b/fastapi-react-project/backend/app/core/__pycache__/security.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8e38a06352280da7856ed6acdf96df4b862322d GIT binary patch literal 1237 zcmZ8g&2HQ_5GE-~`{VudI*HL1Xwf1UU2MQWdn$@Li$!9jSqEMlXn7H2k?|^(RszMX zvxR&Kay5_#@WFe`BbC=43co^6o#C#HpcFWq8O@L%XFi^FyKMra_~*U+%Om7(G(I03 zjGtjvA3;bWX+{LDETcISjH2#kUd{z~I?w#v7k(ayfD*-|FN13;8j8!t1(i+Nx@Mv& z+oC16L|b;mmg=aE?A|chll>1|bkSBlIk@rUwj6?|FR9$Qp=zK4`3T}3%O@WKu`TJ8 zgu8cuicCUk9~~bq%#B4nabB{P+)%C@B{wMt+sDLU1nR643uqLmT; zi|N7lKiqu{u<*2UgU1H&bD`%6MZlYZI@FwuU{=@$LvCqJR&cXVbOPyH<)w_0HFnEq zaWxC6#=uWR_hI?>3jkianG{;>1BQi8&2q0?+U*NvqWj|`%*Bj~fgn_GgA6^}E3-JA zKZMyn#wq#W&|46fxE#!bfjV-+VeWvk#$3}Cn-YzVkm)WgHuyv7^vx2?A((mx1wvSh z0fzL~ArHr4%ss-#phGjd>Rp*?RYk5-P!kRfjP(=vo4|AfG5MS6a81Cd$t_utD^^qe zrJ=WMMQXZBuDtnhZ5a=E#jc&NX182=wU^QrrNsDegPNI!X-a9U9Z#)S8SPkT z`FT-f8ru!MO%vmQeReU&8Aq4qk9i>%nfeJIPK6X`(hhtB)@D7%;d0y?@FD*X!~`?2 literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/core/auth.py b/fastapi-react-project/backend/app/core/auth.py new file mode 100644 index 00000000..0b404b2f --- /dev/null +++ b/fastapi-react-project/backend/app/core/auth.py @@ -0,0 +1,75 @@ +import jwt +from fastapi import Depends, HTTPException, status +from jwt import PyJWTError + +from app.db import models, schemas, session +from app.db.crud import get_user_by_email, create_user +from app.core import security + + +async def get_current_user( + db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme) +): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode( + token, security.SECRET_KEY, algorithms=[security.ALGORITHM] + ) + email: str = payload.get("sub") + if email is None: + raise credentials_exception + permissions: str = payload.get("permissions") + token_data = schemas.TokenData(email=email, permissions=permissions) + except PyJWTError: + raise credentials_exception + user = get_user_by_email(db, token_data.email) + if user is None: + raise credentials_exception + return user + + +async def get_current_active_user( + current_user: models.User = Depends(get_current_user), +): + if not current_user.is_active: + raise HTTPException(status_code=400, detail="Inactive user") + return current_user + + +async def get_current_active_superuser( + current_user: models.User = Depends(get_current_user), +) -> models.User: + if not current_user.is_superuser: + raise HTTPException( + status_code=403, detail="The user doesn't have enough privileges" + ) + return current_user + + +def authenticate_user(db, email: str, password: str): + user = get_user_by_email(db, email) + if not user: + return False + if not security.verify_password(password, user.hashed_password): + return False + return user + + +def sign_up_new_user(db, email: str, password: str): + user = get_user_by_email(db, email) + if user: + return False # User already exists + new_user = create_user( + db, + schemas.UserCreate( + email=email, + password=password, + is_active=True, + is_superuser=False, + ), + ) + return new_user diff --git a/fastapi-react-project/backend/app/core/celery_app.py b/fastapi-react-project/backend/app/core/celery_app.py new file mode 100644 index 00000000..8355ef0d --- /dev/null +++ b/fastapi-react-project/backend/app/core/celery_app.py @@ -0,0 +1,5 @@ +from celery import Celery + +celery_app = Celery("worker", broker="redis://redis:6379/0") + +celery_app.conf.task_routes = {"app.tasks.*": "main-queue"} diff --git a/fastapi-react-project/backend/app/core/config.py b/fastapi-react-project/backend/app/core/config.py new file mode 100644 index 00000000..3a6f2852 --- /dev/null +++ b/fastapi-react-project/backend/app/core/config.py @@ -0,0 +1,7 @@ +import os + +PROJECT_NAME = "fastapi-react-project" + +SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL") + +API_V1_STR = "/api/v1" diff --git a/fastapi-react-project/backend/app/core/security.py b/fastapi-react-project/backend/app/core/security.py new file mode 100644 index 00000000..eb0cffce --- /dev/null +++ b/fastapi-react-project/backend/app/core/security.py @@ -0,0 +1,31 @@ +import jwt +from fastapi.security import OAuth2PasswordBearer +from passlib.context import CryptContext +from datetime import datetime, timedelta + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token") + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +SECRET_KEY = "super_secret" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + + +def create_access_token(*, data: dict, expires_delta: timedelta = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt diff --git a/fastapi-react-project/backend/app/db/__init__.py b/fastapi-react-project/backend/app/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/db/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cf10b685303a0b422034cd89bbfcd9ce588e43f GIT binary patch literal 114 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B;Ke3|i|ee^Xyr>{Bj7hDyR@AXKsB~wWbM5=z%-S2hxTfgqrv9YXy-|u_B^7CVc@dphK z{#*=hp(U4@VQ_=9*l4gaYnWw|(b$ZwhF!LG--?~G%Wdw&Uc)c@jZ8VC=iNBl$dz-< z2<y;mmpdFF*(9>aJ%%+x1%_LaqRJde2ozerN}yY)%%re+O3{>tVPyzs&)PlK1K z&+tj?F~z4Le`K)d3_pVLQN21-KL)wu{3uo)StL0{~?gT%H@d**j;B zpTaqF;AQHk_&nrK^99Jy57wXIXE8plS7+)AkUPWA;hgjQ0_M+hYsDyDeoa0snriHR zsr19$XVtJRqgGS7i40^XDKh1)gh_&_<=5~lNxF>|59366N%e8q2uNn~Q79|zAW42{ z30`>|B#$9y@-^jkl2BA4UNz~M-p|6TdNTeVwcP){@M^LsYoAlo`#4!x^rmqC;wr+gwN>z0D zVtYgRWI9RFqQnO=*gUgYo{3rXi|KC4fv2KaxgwMu(HwFI9XfEwJO=;djum5;I@vO-FJLor9`(86s$RdvRPb1PL5cUAe7VEeWMq09RQ-9QOlo z8WVap)n417O~~|Jo{+Jr>XtZ26vuDbuxf7$f7dJBI$|dNWi)CgP#8I)N(9pb@G}93)bAbx2euN-9BB zMo&YPM>pxTLqQQx6I3Z7A5=~;FGxw{_e<$OgfD<0E)vmEy+pmsBy0toE4SKeAVSI$ zVNHZ6zchS;93hG%=J7S1rlZcxRYe%cQ1fW#goweiNa6jzx##*>=3`b|0e8r?6#qf` zQZ>W1aPF3=D|b#Zaxhh|u3aO|2<=2vH@;NO*0RWBV z%#Y@#v*}8|YjvHjyJa!s$%B7QIxqE@xC(VjyG)IR&#G~UBaTffCbCz@9Vs>JOYsp{ z%H~m3>SWWR%86vyAV>#XN-4zqv|fAmL-dN?J|w0-TBJ>Koi?Y`0ST0~9&RY-X%GWu zM*@)vd6Z%#IXq1;ixO;{?PQBIxqz=<;fOr{Xf@-$l;v5@e&T$zdi&W z7G&)JU6oA&DZEaDr{Q(znz(^kWs{zxX6u`Q7~eu<_-43`nRf}KUz2KQh%68}OXM7p z(MS+-l1j-kxP9cLyzV3CH|QTK??Li^3a&Kj4ESiGY1{-#S}1j68%f?nNwI9lmxcAXaSD+xIOe^db^+p2K$1c+TXYdbW z*LRrhV;AM1Aa5MLp@*=m29f`YUBD;c^8tKv58%^PgxecjiheKLy=#&i2)DB=1|BGh zYou)Ut$v`C5}y(IoXAL|d%&}AAk~uig2yA*E7a}l`ujbIGm<^a&pGXQDN zXbXCF4fW3{uZCMZXh-{jQ{%~#IEd-hyRq034MAz5T)EMXnvWEds`nxzpO+``R$Q#M zM7WrQ)sBedMtZkfQB!J2Ru&$dWq%b9f!hHHJ-w9uAWCqH)8xaB)NdM%3L)W(XJlGMQ=M&$4gw?o^?Wr4e2ZZ}Kw*ddv*-^ZWe20c1eR Ap8x;= literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/db/__pycache__/models.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..643a22ee910ef3ebd8eef94f3505c9dbde7bf86e GIT binary patch literal 1120 zcmah}&2AGh5Z?dYY_drJm7iPTf^wlRfKXLX5kl&LrWapUZai(b*t=VMmy&b2miNe8 z@EktEsdrACm~l#52_$U!%lCQg@p#6YgQCbiY~Ox<72iY8`$6Jvi%@LiFakk%!k3=% znXdvCsE~y!Vv&kjtP+;^WDjJjGL|6^MI;9*XSth?<&X{EdLj|&7f+;+UxaLgIuirb z1E&iS?|ZZS3QzK8iA{H!Mgp&G^16||s%`qN?%)`-P4+uo)yFn_!#kK)3T(dHXsC|s z_pqG#cBDb_u4-y?Qngn;x$|wb!wu{$$obutpxDM?4iUg(zVKKed=^3ku?Vn-mmy0~ zrA|d6zKmE#`od>}>)+hjBavJNY>0e>Yg6QfT|QHaakPhp0!KuL3)4b)~po zmLFkhimR=!OI9PwUud$Wnn%ChDP%F^a# zsT$Es;-j)W>$$wP6lK|whqcsLYfn>gZBPjv6Ny3bO4|IW(x!6`?LhKBbaKMY35c@g z#+)}==z#VNDpT@BSA7CILTdUJG+k`v=}ojd__eXc+tv+nxP{??G%wnv9^!%&hnXT| zeiV%Tsh?pVVRzQQibZj5)0Phi|0xy~Dp(f^i$R=DcLsNU=DXjpmJL zY}_H5xi0XSU40)>KS0=z*SJnuig^-E z4Vqq_%>+K-j-GJRcZ<3?XZb<{&WVzDw>ez1r{w(^0!3s-263&Lo$M z6HrTc;+^m)GIy?~#M|-h$`_4RI-|+dfH4#;{W0k01XL;K{a*pu?B)8&y`m4z(5_7v JPZmyxzX7eJ3labT literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/db/__pycache__/schemas.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/schemas.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2227b781c20cb3df23a36dba7902c488e62ce52 GIT binary patch literal 2998 zcmbVOO>-MX5Z%@8YF8`SmL12j9U#R=0fH)r1I2-IxeOtJ3(5icvX`xmW)e0?S~0VN zGS_hCfA|kk)Lc1n=fsKkMxUjXD|V!sQNNzA?l(Q%y4c!^9en=$_YZOAJI>z(s~_l` zb2R-II^hU+;0)cwbt%^dwZ!|@5uWgWcZ4t9S6<=+2ciyKmp*U+9Eu1yvbYZ15HWCU zaR}TLE#Q{L5%8AS2Hv*&HGp?S8@O$847@A$fcGqJ0^bq)!27a^^IE`n#R2ev#aqDl z#3AsZ#oNF~;u!eY;vG@@!Rg%p2i`dy*EGJ)wEVsha?tHlwfgY@&e8M}bka#&;Uu*S zM|#p1?kVU<0tu&u2iZ?TD+iG4No41QR&H21qKtzZCc4m4@l+GYVU`birjhG3>zDa2 z@`-6d)|0VR6U^$G_<62$nU1odG>t(v6FaeqQmaf;6Q}905R-v~+)C4*C)t40fjnI* zvy-OTXjGJ0nHM9SrYe9l#(!26167BpLUs|lD@W~6X7}I0!)!dZO+0(3`#-`|eKNjO z_*LN_N+xS*kCLelVbQg*LA)xfaghN{VKVZ~HxQ@4w5 z3r*M1MXtq_^A;EO0Vd7QTV5C;g7*qnCw$lun3#4vQZg&0iN=}MzZObVc3yZw zW9B;xH}PyVdB5#0cR6`fjGpH&dfh93%`p=dYRJoyn_M*Y>B9XLFYiI_cIs>UDAK9O zhT?=xo2fe(AZMEI8TVAPnu=cw#!fJ8?$-rgeS#o=iSRyhWGHH<4!E1WdT6__AqS7T&;(U@$ymmnvfy850*3_WT?5G zhIcT!44HXWw7&=#;o*7tCg>GE$x+hE`hBT&S`wMq{-ovQSav(X`W-H#ds8Byg&(e1 z=_+cjw~}+0R!#$31`KK8CCJ{8eaK8ofoWK4zsbt1tN3t?KjHl~k&lPD*36)taNS-` zTnV%H2{&2(E(ShDn}u1+Rn%N>`R5p2T0ZA@T6JEA(!)P0rPSJ5*es*ET`vJ^@?*&2E!#d=j6bX_ge0g*{hM zSMhF<5;l~Cm{w0yaaZkO^~R$<#=sY7^MEJ5)iIWwY1X>-kPbHea^(=>${`+<=bS`O zt2ec=+=TQIWlz0@HI*F~>|9f3wr8tvm>0gHajSVjy$#JqRzJbO8K*OvZM~qTV&I#& z?9=Es^S8;RH+1$pT7NTV`3fI%`odXiI_)GHUy5v0=6%JDOdT02AHA5q0=jPhcGya4 zON333HvibCY$IMV9u?z7kwL|sH!*Ia)9K%~GhVvp8*90d*N)nLJG9@R9klB}!2bhL CV&^>o literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/db/__pycache__/session.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/session.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa66b734900da853f414fd7876231f2a62709121 GIT binary patch literal 588 zcmYjOy>1jS5FUH~?lyZ-L;(^N?KYQ+4k0ci2&5wt9O1{!%4<891$%widx)UWaJ0Mw zl)MX1uq_4R6=-0*0dZr^=$lWY`DVr^lSvL}_QwbLjR1TP$+jh!+(GvYg9J%57{Uk% zm}+Vw77aMku}N4m;8>?7V;KQORQ7VTA#AJ?iBFAX`YB?$iZ;M@WF|)+ffaJRfHMDy z{h=iNL^#DeRjGE_v7&_R%dw7>$z*8Xb3~s^&d?sHIKgVrxMsi%z zzF1sJ&#mCPPlUF^L{!#a!G&A?y1LI-tHEWx|98;o>by^vs;gwZ7xaQKkd#o8Q&%89 zE+;JauQk`=Oqp{>kz?2Y6I1oJn{HjgMWJAKtH3(rVkG+!%1?!L%7xEk*A|cFv-#co zM-N|AhqEWMgW2M!I(htnrI#mmS*VH+@Up`s!xCFzXXts$Sh?2fcDS0qf}tSzXhA87 Kf5*`-P2)dv5sdW! literal 0 HcmV?d00001 diff --git a/fastapi-react-project/backend/app/db/crud.py b/fastapi-react-project/backend/app/db/crud.py new file mode 100644 index 00000000..ae9aa129 --- /dev/null +++ b/fastapi-react-project/backend/app/db/crud.py @@ -0,0 +1,119 @@ +from fastapi import HTTPException, status +from sqlalchemy.orm import Session +import typing as t + +from . import models, schemas +from app.core.security import get_password_hash + + +def get_user(db: Session, user_id: int): + user = db.query(models.User).filter(models.User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + return user + + +def get_user_by_email(db: Session, email: str) -> schemas.UserBase: + return db.query(models.User).filter(models.User.email == email).first() + + +def get_users( + db: Session, skip: int = 0, limit: int = 100 +) -> t.List[schemas.UserOut]: + return db.query(models.User).offset(skip).limit(limit).all() + + +def create_user(db: Session, user: schemas.UserCreate): + hashed_password = get_password_hash(user.password) + db_user = models.User( + first_name=user.first_name, + last_name=user.last_name, + email=user.email, + is_active=user.is_active, + is_superuser=user.is_superuser, + hashed_password=hashed_password, + ) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + + +def delete_user(db: Session, user_id: int): + user = get_user(db, user_id) + if not user: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found") + db.delete(user) + db.commit() + return user + + +def edit_user( + db: Session, user_id: int, user: schemas.UserEdit +) -> schemas.User: + db_user = get_user(db, user_id) + if not db_user: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found") + update_data = user.dict(exclude_unset=True) + + if "password" in update_data: + update_data["hashed_password"] = get_password_hash(user.password) + del update_data["password"] + + for key, value in update_data.items(): + setattr(db_user, key, value) + + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + +def get_todo(db: Session, todo_id: int): + todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first() + if not todo: + raise HTTPException(status_code=404, detail="Todo not found") + return todo + + +def get_todos( + db: Session, skip: int = 0, limit: int = 100 +) -> t.List[schemas.TodoOut]: + return db.query(models.Todo).offset(skip).limit(limit).all() + + +def create_todo(db: Session, todo: schemas.TodoCreate): + db_todo = models.Todo( + title=todo.title, + description=todo.description, + is_done=todo.is_done + ) + db.add(db_todo) + db.commit() + db.refresh(db_todo) + return db_todo + + +def delete_todo(db: Session, todo_id: int): + todo = get_todo(db, todo_id) + if not todo: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Todo not found") + db.delete(todo) + db.commit() + return todo + + +def edit_todo( + db: Session, todo_id: int, todo: schemas.TodoEdit +) -> schemas.Todo: + db_todo = get_todo(db, todo_id) + if not db_todo: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Todo not found") + update_data = todo.dict(exclude_unset=True) + + for key, value in update_data.items(): + setattr(db_todo, key, value) + + db.add(db_todo) + db.commit() + db.refresh(db_todo) + return \ No newline at end of file diff --git a/fastapi-react-project/backend/app/db/models.py b/fastapi-react-project/backend/app/db/models.py new file mode 100644 index 00000000..c98c3480 --- /dev/null +++ b/fastapi-react-project/backend/app/db/models.py @@ -0,0 +1,28 @@ +from sqlalchemy import Boolean, Column, Integer, String, DateTime, ForeignKey +from sqlalchemy.orm import relationship +from .session import Base +import datetime + + +class User(Base): + __tablename__ = "user" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String, unique=True, index=True, nullable=False) + first_name = Column(String) + last_name = Column(String) + hashed_password = Column(String, nullable=False) + is_active = Column(Boolean, default=True) + is_superuser = Column(Boolean, default=False) + +class Todo(Base): + __tablename__ = "todos" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String, nullable=False) + description = Column(String, nullable=True) + is_done = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + # user_id = Column(Integer, ForeignKey("user.id")) + + # user = relationship("User", back_populates="todos") \ No newline at end of file diff --git a/fastapi-react-project/backend/app/db/schemas.py b/fastapi-react-project/backend/app/db/schemas.py new file mode 100644 index 00000000..9da769e0 --- /dev/null +++ b/fastapi-react-project/backend/app/db/schemas.py @@ -0,0 +1,73 @@ +from pydantic import BaseModel +import typing as t + + +class UserBase(BaseModel): + email: str + is_active: bool = True + is_superuser: bool = False + first_name: str = None + last_name: str = None + + +class UserOut(UserBase): + pass + + +class UserCreate(UserBase): + password: str + + class Config: + orm_mode = True + + +class UserEdit(UserBase): + password: t.Optional[str] = None + + class Config: + orm_mode = True + + +class User(UserBase): + id: int + + class Config: + orm_mode = True + + +class Token(BaseModel): + access_token: str + token_type: str + + +class TokenData(BaseModel): + email: str = None + permissions: str = "user" + + +class TodoBase(BaseModel): + title: str + description: t.Optional[str] = None + is_done: bool = True + + +class TodoCreate(TodoBase): + pass + + +class TodoEdit(TodoBase): + pass + + +class TodoOut(TodoBase): + id: int + + class Config: + orm_mode = True + + +class Todo(TodoBase): + id: int + + class Config: + orm_mode = True \ No newline at end of file diff --git a/fastapi-react-project/backend/app/db/session.py b/fastapi-react-project/backend/app/db/session.py new file mode 100644 index 00000000..d7e2f6c5 --- /dev/null +++ b/fastapi-react-project/backend/app/db/session.py @@ -0,0 +1,21 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +from app.core import config + +engine = create_engine( + config.SQLALCHEMY_DATABASE_URI, +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + + +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/fastapi-react-project/backend/app/initial_data.py b/fastapi-react-project/backend/app/initial_data.py new file mode 100644 index 00000000..dc781698 --- /dev/null +++ b/fastapi-react-project/backend/app/initial_data.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +from app.db.session import get_db +from app.db.crud import create_user +from app.db.schemas import UserCreate +from app.db.session import SessionLocal + + +def init() -> None: + db = SessionLocal() + + create_user( + db, + UserCreate( + email="admin@fastapi-react-project.com", + password="password", + is_active=True, + is_superuser=True, + ), + ) + + +if __name__ == "__main__": + print("Creating superuser admin@fastapi-react-project.com") + init() + print("Superuser created") diff --git a/fastapi-react-project/backend/app/main.py b/fastapi-react-project/backend/app/main.py new file mode 100644 index 00000000..b9a512c3 --- /dev/null +++ b/fastapi-react-project/backend/app/main.py @@ -0,0 +1,51 @@ +from fastapi import FastAPI, Depends +from starlette.requests import Request +import uvicorn + +from app.api.api_v1.routers.users import users_router +from app.api.api_v1.routers.auth import auth_router +from app.api.api_v1.routers.todos import todos_router +from app.core import config +from app.db.session import SessionLocal +from app.core.auth import get_current_active_user +from app.core.celery_app import celery_app +from app import tasks + + +app = FastAPI( + title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api" +) + + +@app.middleware("http") +async def db_session_middleware(request: Request, call_next): + request.state.db = SessionLocal() + response = await call_next(request) + request.state.db.close() + return response + + +@app.get("/api/v1") +async def root(): + return {"message": "Hello World"} + + +@app.get("/api/v1/task") +async def example_task(): + celery_app.send_task("app.tasks.example_task", args=["Hello World"]) + + return {"message": "success"} + + +# Routers +app.include_router( + users_router, + prefix="/api/v1", + tags=["users"], + dependencies=[Depends(get_current_active_user)], +) +app.include_router(auth_router, prefix="/api", tags=["auth"]) +app.include_router(todos_router, prefix="/api/v1", tags=["todos"]) + +if __name__ == "__main__": + uvicorn.run("main:app", host="0.0.0.0", reload=True, port=8888) diff --git a/fastapi-react-project/backend/app/tasks.py b/fastapi-react-project/backend/app/tasks.py new file mode 100644 index 00000000..c17d5063 --- /dev/null +++ b/fastapi-react-project/backend/app/tasks.py @@ -0,0 +1,6 @@ +from app.core.celery_app import celery_app + + +@celery_app.task(acks_late=True) +def example_task(word: str) -> str: + return f"test task returns {word}" diff --git a/fastapi-react-project/backend/app/tests/__init__.py b/fastapi-react-project/backend/app/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi-react-project/backend/app/tests/test_main.py b/fastapi-react-project/backend/app/tests/test_main.py new file mode 100644 index 00000000..024cf9e5 --- /dev/null +++ b/fastapi-react-project/backend/app/tests/test_main.py @@ -0,0 +1,4 @@ +def test_read_main(client): + response = client.get("/api/v1") + assert response.status_code == 200 + assert response.json() == {"message": "Hello World"} diff --git a/fastapi-react-project/backend/app/tests/test_tasks.py b/fastapi-react-project/backend/app/tests/test_tasks.py new file mode 100644 index 00000000..7f49f1ca --- /dev/null +++ b/fastapi-react-project/backend/app/tests/test_tasks.py @@ -0,0 +1,6 @@ +from app import tasks + + +def test_example_task(): + task_output = tasks.example_task("Hello World") + assert task_output == "test task returns Hello World" diff --git a/fastapi-react-project/backend/conftest.py b/fastapi-react-project/backend/conftest.py new file mode 100644 index 00000000..ecd831dc --- /dev/null +++ b/fastapi-react-project/backend/conftest.py @@ -0,0 +1,169 @@ +import pytest +from sqlalchemy import create_engine, event +from sqlalchemy.orm import sessionmaker +from sqlalchemy_utils import database_exists, create_database, drop_database +from fastapi.testclient import TestClient +import typing as t + +from app.core import config, security +from app.db.session import Base, get_db +from app.db import models +from app.main import app + + +def get_test_db_url() -> str: + return f"{config.SQLALCHEMY_DATABASE_URI}_test" + + +@pytest.fixture +def test_db(): + """ + Modify the db session to automatically roll back after each test. + This is to avoid tests affecting the database state of other tests. + """ + # Connect to the test database + engine = create_engine( + get_test_db_url(), + ) + + connection = engine.connect() + trans = connection.begin() + + # Run a parent transaction that can roll back all changes + test_session_maker = sessionmaker( + autocommit=False, autoflush=False, bind=engine + ) + test_session = test_session_maker() + test_session.begin_nested() + + @event.listens_for(test_session, "after_transaction_end") + def restart_savepoint(s, transaction): + if transaction.nested and not transaction._parent.nested: + s.expire_all() + s.begin_nested() + + yield test_session + + # Roll back the parent transaction after the test is complete + test_session.close() + trans.rollback() + connection.close() + + +@pytest.fixture(scope="session", autouse=True) +def create_test_db(): + """ + Create a test database and use it for the whole test session. + """ + + test_db_url = get_test_db_url() + + # Create the test database + assert not database_exists( + test_db_url + ), "Test database already exists. Aborting tests." + create_database(test_db_url) + test_engine = create_engine(test_db_url) + Base.metadata.create_all(test_engine) + + # Run the tests + yield + + # Drop the test database + drop_database(test_db_url) + + +@pytest.fixture +def client(test_db): + """ + Get a TestClient instance that reads/write to the test database. + """ + + def get_test_db(): + yield test_db + + app.dependency_overrides[get_db] = get_test_db + + yield TestClient(app) + + +@pytest.fixture +def test_password() -> str: + return "securepassword" + + +def get_password_hash() -> str: + """ + Password hashing can be expensive so a mock will be much faster + """ + return "supersecrethash" + + +@pytest.fixture +def test_user(test_db) -> models.User: + """ + Make a test user in the database + """ + + user = models.User( + email="fake@email.com", + hashed_password=get_password_hash(), + is_active=True, + ) + test_db.add(user) + test_db.commit() + return user + + +@pytest.fixture +def test_superuser(test_db) -> models.User: + """ + Superuser for testing + """ + + user = models.User( + email="fakeadmin@email.com", + hashed_password=get_password_hash(), + is_superuser=True, + ) + test_db.add(user) + test_db.commit() + return user + + +def verify_password_mock(first: str, second: str) -> bool: + return True + + +@pytest.fixture +def user_token_headers( + client: TestClient, test_user, test_password, monkeypatch +) -> t.Dict[str, str]: + monkeypatch.setattr(security, "verify_password", verify_password_mock) + + login_data = { + "username": test_user.email, + "password": test_password, + } + r = client.post("/api/token", data=login_data) + tokens = r.json() + a_token = tokens["access_token"] + headers = {"Authorization": f"Bearer {a_token}"} + return headers + + +@pytest.fixture +def superuser_token_headers( + client: TestClient, test_superuser, test_password, monkeypatch +) -> t.Dict[str, str]: + monkeypatch.setattr(security, "verify_password", verify_password_mock) + + login_data = { + "username": test_superuser.email, + "password": test_password, + } + r = client.post("/api/token", data=login_data) + tokens = r.json() + a_token = tokens["access_token"] + headers = {"Authorization": f"Bearer {a_token}"} + return headers diff --git a/fastapi-react-project/backend/pyproject.toml b/fastapi-react-project/backend/pyproject.toml new file mode 100644 index 00000000..627a23c9 --- /dev/null +++ b/fastapi-react-project/backend/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 80 \ No newline at end of file diff --git a/fastapi-react-project/backend/requirements.txt b/fastapi-react-project/backend/requirements.txt new file mode 100644 index 00000000..7f8389d3 --- /dev/null +++ b/fastapi-react-project/backend/requirements.txt @@ -0,0 +1,19 @@ +alembic==1.4.3 +Authlib==0.14.3 +fastapi==0.65.2 +celery==5.0.0 +redis==3.5.3 +httpx==0.15.5 +ipython==7.31.1 +itsdangerous==1.1.0 +Jinja2==2.11.3 +psycopg2==2.8.6 +pytest==6.1.0 +requests==2.24.0 +SQLAlchemy==1.3.19 +uvicorn==0.12.1 +passlib==1.7.2 +bcrypt==3.2.0 +sqlalchemy-utils==0.36.8 +python-multipart==0.0.5 +pyjwt==1.7.1 \ No newline at end of file diff --git a/fastapi-react-project/docker-compose.yml b/fastapi-react-project/docker-compose.yml new file mode 100644 index 00000000..b1a41e33 --- /dev/null +++ b/fastapi-react-project/docker-compose.yml @@ -0,0 +1,71 @@ +version: '3.7' +services: + nginx: + image: nginx:1.17 + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf + ports: + - 8000:80 + depends_on: + - backend + - frontend + + redis: + image: redis + ports: + - 6379:6379 + + postgres: + image: postgres:12 + restart: always + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - '5432:5432' + volumes: + - db-data:/var/lib/postgresql/data:cached + + worker: + build: + context: backend + dockerfile: Dockerfile + command: celery --app app.tasks worker --loglevel=DEBUG -Q main-queue -c 1 + + flower: + image: mher/flower + command: celery flower --broker=redis://redis:6379/0 --port=5555 + ports: + - 5555:5555 + depends_on: + - "redis" + + backend: + build: + context: backend + dockerfile: Dockerfile + command: python app/main.py + tty: true + volumes: + - ./backend:/app/:cached + - ./.docker/.ipython:/root/.ipython:cached + environment: + PYTHONPATH: . + DATABASE_URL: 'postgresql://postgres:password@postgres:5432/postgres' + depends_on: + - "postgres" + + frontend: + build: + context: frontend + dockerfile: Dockerfile + stdin_open: true + volumes: + - './frontend:/app:cached' + - './frontend/node_modules:/app/node_modules:cached' + environment: + - NODE_ENV=development + + +volumes: + db-data: diff --git a/fastapi-react-project/frontend/.dockerignore b/fastapi-react-project/frontend/.dockerignore new file mode 100644 index 00000000..25c8fdba --- /dev/null +++ b/fastapi-react-project/frontend/.dockerignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/fastapi-react-project/frontend/.eslintrc.js b/fastapi-react-project/frontend/.eslintrc.js new file mode 100644 index 00000000..428e4222 --- /dev/null +++ b/fastapi-react-project/frontend/.eslintrc.js @@ -0,0 +1,51 @@ +let rules = { + 'max-len': ['error', 80, 2, { ignoreUrls: true }], + 'no-console': [0], + 'no-restricted-syntax': 'off', + 'no-continue': 'off', + 'no-underscore-dangle': 'off', + 'import/extensions': 'off', + 'import/no-unresolved': 'off', + 'operator-linebreak': 'off', + 'implicit-arrow-linebreak': 'off', + 'react/destructuring-assignment': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + 'jsx-a11y/no-static-element-interactions': 'off', + 'react/jsx-one-expression-per-line': 'off', + 'react/jsx-filename-extension': [2, { extensions: ['.ts', '.tsx'] }], + 'lines-between-class-members': [ + 'error', + 'always', + { exceptAfterSingleLine: true }, + ], +}; + +module.exports = { + extends: ['airbnb', 'plugin:prettier/recommended', 'prettier/react'], + parser: 'babel-eslint', + rules, + env: { + browser: true, + commonjs: true, + node: true, + jest: true, + es6: true, + }, + plugins: ['react', 'react-hooks', 'jsx-a11y'], + settings: { + ecmascript: 6, + jsx: true, + 'import/resolver': { + node: { + paths: ['src'], + }, + }, + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + react: { + pragma: 'React', + version: '16.8', + }, + }, +}; diff --git a/fastapi-react-project/frontend/.prettierrc.js b/fastapi-react-project/frontend/.prettierrc.js new file mode 100644 index 00000000..158883bf --- /dev/null +++ b/fastapi-react-project/frontend/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + printWidth: 80, + singleQuote: true, + trailingComma: 'es5', +}; diff --git a/fastapi-react-project/frontend/Dockerfile b/fastapi-react-project/frontend/Dockerfile new file mode 100644 index 00000000..d398ff4f --- /dev/null +++ b/fastapi-react-project/frontend/Dockerfile @@ -0,0 +1,16 @@ +FROM node:12 + +ADD package.json /package.json + +ENV NODE_PATH=/node_modules +ENV PATH=$PATH:/node_modules/.bin +RUN npm install + +WORKDIR /app +ADD . /app + +EXPOSE 8000 +EXPOSE 35729 + +ENTRYPOINT ["/bin/bash", "/app/run.sh"] +CMD ["start"] diff --git a/fastapi-react-project/frontend/README.md b/fastapi-react-project/frontend/README.md new file mode 100644 index 00000000..54ef0943 --- /dev/null +++ b/fastapi-react-project/frontend/README.md @@ -0,0 +1,68 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting + +### Analyzing the Bundle Size + +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size + +### Making a Progressive Web App + +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app + +### Advanced Configuration + +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration + +### Deployment + +This section has moved here: https://facebook.github.io/create-react-app/docs/deployment + +### `npm run build` fails to minify + +This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/fastapi-react-project/frontend/package.json b/fastapi-react-project/frontend/package.json new file mode 100644 index 00000000..14dd1f81 --- /dev/null +++ b/fastapi-react-project/frontend/package.json @@ -0,0 +1,62 @@ +{ + "name": "fastapi-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "ra-data-json-server": "^3.5.2", + "ra-data-simple-rest": "^3.3.2", + "react": "^16.13.1", + "react-admin": "^3.5.2", + "react-dom": "^16.13.1", + "react-router-dom": "^5.1.2", + "react-scripts": "3.4.3", + "react-truncate": "^2.4.0", + "standard": "^14.3.3", + "jwt-decode": "^3.0.0", + "@material-ui/lab": "^4.0.0-alpha.54" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "CI=true react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "airbnb" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "typescript": "^4.0.2", + "@testing-library/jest-dom": "^5.11.1", + "@testing-library/react": "^11.0.4", + "@typescript-eslint/eslint-plugin": "^2.24.0", + "@typescript-eslint/parser": "^2.24.0", + "@testing-library/user-event": "^12.0.11", + "@types/jest": "^26.0.3", + "@types/node": "^14.0.1", + "@types/react": "^16.9.19", + "@types/react-dom": "^16.9.5", + "@types/react-router-dom": "^5.1.3", + "@types/jwt-decode": "^2.2.1", + "eslint-config-airbnb": "^18.1.0", + "eslint-config-react-app": "^5.2.1", + "eslint-plugin-flowtype": "^4.6.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.19.0", + "eslint-plugin-react-hooks": "^2.5.1", + "prettier": "^2.0.5", + "react-test-renderer": "^16.13.1" + } +} diff --git a/fastapi-react-project/frontend/public/favicon.ico b/fastapi-react-project/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bcd5dfd67cd0361b78123e95c2dd96031f27f743 GIT binary patch literal 3150 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 MDeX2GIKR{Qf5okjU;qFB literal 0 HcmV?d00001 diff --git a/fastapi-react-project/frontend/public/index.html b/fastapi-react-project/frontend/public/index.html new file mode 100644 index 00000000..aa069f27 --- /dev/null +++ b/fastapi-react-project/frontend/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/fastapi-react-project/frontend/public/logo192.png b/fastapi-react-project/frontend/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/fastapi-react-project/frontend/public/manifest.json b/fastapi-react-project/frontend/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/fastapi-react-project/frontend/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/fastapi-react-project/frontend/public/robots.txt b/fastapi-react-project/frontend/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/fastapi-react-project/frontend/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/fastapi-react-project/frontend/run.sh b/fastapi-react-project/frontend/run.sh new file mode 100644 index 00000000..5d2b8435 --- /dev/null +++ b/fastapi-react-project/frontend/run.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +case $1 in + start) + # The '| cat' is to trick Node that this is an non-TTY terminal + # then react-scripts won't clear the console. + npm start | cat + ;; + build) + npm build + ;; + test) + npm test $@ + ;; + *) + npm "$@" + ;; +esac \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/App.tsx b/fastapi-react-project/frontend/src/App.tsx new file mode 100644 index 00000000..f41354b7 --- /dev/null +++ b/fastapi-react-project/frontend/src/App.tsx @@ -0,0 +1,6 @@ +import React, { FC } from 'react'; +import { Routes } from './Routes'; + +const App: FC = () => ; + +export default App; diff --git a/fastapi-react-project/frontend/src/Routes.tsx b/fastapi-react-project/frontend/src/Routes.tsx new file mode 100644 index 00000000..065ce319 --- /dev/null +++ b/fastapi-react-project/frontend/src/Routes.tsx @@ -0,0 +1,59 @@ +import React, { FC } from 'react'; +import { Switch, Route } from 'react-router-dom'; +import { useHistory } from 'react-router'; +import { makeStyles } from '@material-ui/core/styles'; + +import { Home, Login, SignUp, Protected, PrivateRoute } from './views'; +import { Admin } from './admin'; +import { Todo } from './admin/Todo'; + +import { logout } from './utils/auth'; + +const useStyles = makeStyles((theme) => ({ + app: { + textAlign: 'center', + }, + header: { + backgroundColor: '#282c34', + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + fontSize: 'calc(10px + 2vmin)', + color: 'white', + }, +})); + +export const Routes: FC = () => { + const classes = useStyles(); + const history = useHistory(); + + return ( + + + + + + + + +
+
+ + + { + logout(); + history.push('/'); + return null; + }} + /> + + +
+
+
+ ); +}; diff --git a/fastapi-react-project/frontend/src/__tests__/home.test.tsx b/fastapi-react-project/frontend/src/__tests__/home.test.tsx new file mode 100644 index 00000000..ef03bdef --- /dev/null +++ b/fastapi-react-project/frontend/src/__tests__/home.test.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { Home } from '../views/Home'; + +it('Home renders correctly', () => { + const home = render(); + expect(home.getByText('Admin Dashboard')).toBeInTheDocument(); + expect(home.getByText('Protected Route')).toBeInTheDocument(); + expect(home.getByText('Login')).toBeInTheDocument(); + expect(home.getByText('Sign Up')).toBeInTheDocument(); +}); diff --git a/fastapi-react-project/frontend/src/__tests__/login.test.tsx b/fastapi-react-project/frontend/src/__tests__/login.test.tsx new file mode 100644 index 00000000..1dae198d --- /dev/null +++ b/fastapi-react-project/frontend/src/__tests__/login.test.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { Login } from '../views'; + +it('Login renders correctly', () => { + const login = render(); + expect(login.getByText('Email')).toBeInTheDocument(); + expect(login.getByText('Password')).toBeInTheDocument(); + expect(login.getByText('Login')).toBeInTheDocument(); +}); diff --git a/fastapi-react-project/frontend/src/admin/Admin.tsx b/fastapi-react-project/frontend/src/admin/Admin.tsx new file mode 100644 index 00000000..daa12188 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Admin.tsx @@ -0,0 +1,37 @@ +import React, { FC } from 'react'; +import { fetchUtils, Admin as ReactAdmin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; +import authProvider from './authProvider'; + +import { UserList, UserEdit, UserCreate } from './Users'; + +const httpClient = (url: any, options: any) => { + if (!options) { + options = {}; + } + if (!options.headers) { + options.headers = new Headers({ Accept: 'application/json' }); + } + const token = localStorage.getItem('token'); + options.headers.set('Authorization', `Bearer ${token}`); + return fetchUtils.fetchJson(url, options); +}; + +const dataProvider = simpleRestProvider('api/v1', httpClient); + +export const Admin: FC = () => { + return ( + + {(permissions: 'admin' | 'user') => [ + permissions === 'admin' ? ( + + ) : null, + ]} + + ); +}; diff --git a/fastapi-react-project/frontend/src/admin/Todo.tsx b/fastapi-react-project/frontend/src/admin/Todo.tsx new file mode 100644 index 00000000..36b1a858 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Todo.tsx @@ -0,0 +1,40 @@ +import React, { FC } from 'react'; +import { fetchUtils, Admin as ReactAdmin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; +import authProvider from './authProvider'; + +import ToDoList from './Todo/ToDoList' +import ToDoCreate from './Todo/ToDoCreate' +import ToDoEdit from './Todo/ToDoEdit' + + +const httpClient = (url: any, options: any) => { + if (!options) { + options = {}; + } + if (!options.headers) { + options.headers = new Headers({ Accept: 'application/json' }); + } + const token = localStorage.getItem('token'); + options.headers.set('Authorization', `Bearer ${token}`); + return fetchUtils.fetchJson(url, options); +}; + +const dataProvider = simpleRestProvider('api/v1', httpClient); + +export const Todo: FC = () => { + return ( + + {(permissions: 'admin' | 'user') => [ + permissions === 'admin' ? ( + + ) : null, + ]} + + ); +}; diff --git a/fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx b/fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx new file mode 100644 index 00000000..737ea5a6 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx @@ -0,0 +1,18 @@ +import React, { FC } from 'react'; +import { + Create, + SimpleForm, + TextInput, + BooleanInput +} from 'react-admin'; + +const ToDoCreate: FC = (props) => ( + + + + + + + +); +export default ToDoCreate \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx b/fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx new file mode 100644 index 00000000..62527429 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react'; +import { + Edit, + SimpleForm, + TextInput, + BooleanInput +} from 'react-admin'; + +const ToDoEdit: FC = (props) => ( + + + + + + + + +); +export default ToDoEdit \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx b/fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx new file mode 100644 index 00000000..31e8aecc --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import { + List, + Datagrid, + TextField, + EditButton, +} from 'react-admin'; + +const ToDoList: FC = (props) => ( + + + + + + + + + +); +export default ToDoList \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/admin/Todo/index.ts b/fastapi-react-project/frontend/src/admin/Todo/index.ts new file mode 100644 index 00000000..9c45cae5 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Todo/index.ts @@ -0,0 +1,3 @@ +export * from './ToDoEdit'; +export * from './ToDoList'; +export * from './ToDoCreate'; diff --git a/fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx b/fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx new file mode 100644 index 00000000..466a5182 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import { + Create, + SimpleForm, + TextInput, + PasswordInput, + BooleanInput, +} from 'react-admin'; + +export const UserCreate: FC = (props) => ( + + + + + + + + + + +); diff --git a/fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx b/fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx new file mode 100644 index 00000000..7925d192 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx @@ -0,0 +1,22 @@ +import React, { FC } from 'react'; +import { + Edit, + SimpleForm, + TextInput, + PasswordInput, + BooleanInput, +} from 'react-admin'; + +export const UserEdit: FC = (props) => ( + + + + + + + + + + + +); diff --git a/fastapi-react-project/frontend/src/admin/Users/UserList.tsx b/fastapi-react-project/frontend/src/admin/Users/UserList.tsx new file mode 100644 index 00000000..dce27f7e --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Users/UserList.tsx @@ -0,0 +1,24 @@ +// in src/users.js +import React, { FC } from 'react'; +import { + List, + Datagrid, + TextField, + BooleanField, + EmailField, + EditButton, +} from 'react-admin'; + +export const UserList: FC = (props) => ( + + + + + + + + + + + +); diff --git a/fastapi-react-project/frontend/src/admin/Users/index.ts b/fastapi-react-project/frontend/src/admin/Users/index.ts new file mode 100644 index 00000000..999f7e00 --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/Users/index.ts @@ -0,0 +1,3 @@ +export * from './UserEdit'; +export * from './UserList'; +export * from './UserCreate'; diff --git a/fastapi-react-project/frontend/src/admin/authProvider.ts b/fastapi-react-project/frontend/src/admin/authProvider.ts new file mode 100644 index 00000000..1e0fe3ae --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/authProvider.ts @@ -0,0 +1,55 @@ +import decodeJwt from 'jwt-decode'; + +type loginFormType = { + username: string; + password: string; +}; + +const authProvider = { + login: ({ username, password }: loginFormType) => { + let formData = new FormData(); + formData.append('username', username); + formData.append('password', password); + const request = new Request('/api/token', { + method: 'POST', + body: formData, + }); + return fetch(request) + .then((response) => { + if (response.status < 200 || response.status >= 300) { + throw new Error(response.statusText); + } + return response.json(); + }) + .then(({ access_token }) => { + const decodedToken: any = decodeJwt(access_token); + if (decodedToken.permissions !== 'admin') { + throw new Error('Forbidden'); + } + localStorage.setItem('token', access_token); + localStorage.setItem('permissions', decodedToken.permissions); + }); + }, + logout: () => { + localStorage.removeItem('token'); + localStorage.removeItem('permissions'); + return Promise.resolve(); + }, + checkError: (error: { status: number }) => { + const status = error.status; + if (status === 401 || status === 403) { + localStorage.removeItem('token'); + return Promise.reject(); + } + return Promise.resolve(); + }, + checkAuth: () => + localStorage.getItem('token') ? Promise.resolve() : Promise.reject(), + getPermissions: () => { + const role = localStorage.getItem('permissions'); + return role ? Promise.resolve(role) : Promise.reject(); + // localStorage.getItem('token') ? Promise.resolve() : Promise.reject(), + }, +}; + +export default authProvider; diff --git a/fastapi-react-project/frontend/src/admin/index.ts b/fastapi-react-project/frontend/src/admin/index.ts new file mode 100644 index 00000000..c956a8fd --- /dev/null +++ b/fastapi-react-project/frontend/src/admin/index.ts @@ -0,0 +1 @@ +export * from './Admin'; diff --git a/fastapi-react-project/frontend/src/config/index.tsx b/fastapi-react-project/frontend/src/config/index.tsx new file mode 100644 index 00000000..31dc509f --- /dev/null +++ b/fastapi-react-project/frontend/src/config/index.tsx @@ -0,0 +1,3 @@ +export const BASE_URL: string = 'http://localhost:8000'; +export const BACKEND_URL: string = + 'http://localhost:8000/api/v1'; diff --git a/fastapi-react-project/frontend/src/decs.d.ts b/fastapi-react-project/frontend/src/decs.d.ts new file mode 100644 index 00000000..5557bb84 --- /dev/null +++ b/fastapi-react-project/frontend/src/decs.d.ts @@ -0,0 +1 @@ +declare module 'react-admin'; diff --git a/fastapi-react-project/frontend/src/index.css b/fastapi-react-project/frontend/src/index.css new file mode 100644 index 00000000..ec2585e8 --- /dev/null +++ b/fastapi-react-project/frontend/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/fastapi-react-project/frontend/src/index.tsx b/fastapi-react-project/frontend/src/index.tsx new file mode 100644 index 00000000..9a6816b7 --- /dev/null +++ b/fastapi-react-project/frontend/src/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter as Router } from 'react-router-dom'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/fastapi-react-project/frontend/src/logo.svg b/fastapi-react-project/frontend/src/logo.svg new file mode 100644 index 00000000..6b60c104 --- /dev/null +++ b/fastapi-react-project/frontend/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/fastapi-react-project/frontend/src/react-app-env.d.ts b/fastapi-react-project/frontend/src/react-app-env.d.ts new file mode 100644 index 00000000..6431bc5f --- /dev/null +++ b/fastapi-react-project/frontend/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/fastapi-react-project/frontend/src/utils/api.ts b/fastapi-react-project/frontend/src/utils/api.ts new file mode 100644 index 00000000..6b7e2f0a --- /dev/null +++ b/fastapi-react-project/frontend/src/utils/api.ts @@ -0,0 +1,13 @@ +import { BACKEND_URL } from '../config'; + +export const getMessage = async () => { + const response = await fetch(BACKEND_URL); + + const data = await response.json(); + + if (data.message) { + return data.message; + } + + return Promise.reject('Failed to get message from backend'); +}; diff --git a/fastapi-react-project/frontend/src/utils/auth.ts b/fastapi-react-project/frontend/src/utils/auth.ts new file mode 100644 index 00000000..bbcf39eb --- /dev/null +++ b/fastapi-react-project/frontend/src/utils/auth.ts @@ -0,0 +1,118 @@ +import decodeJwt from 'jwt-decode'; + +export const isAuthenticated = () => { + const permissions = localStorage.getItem('permissions'); + if (!permissions) { + return false; + } + return permissions === 'user' || permissions === 'admin' ? true : false; +}; + +/** + * Login to backend and store JSON web token on success + * + * @param email + * @param password + * @returns JSON data containing access token on success + * @throws Error on http errors or failed attempts + */ +export const login = async (email: string, password: string) => { + // Assert email or password is not empty + if (!(email.length > 0) || !(password.length > 0)) { + throw new Error('Email or password was not provided'); + } + const formData = new FormData(); + // OAuth2 expects form data, not JSON data + formData.append('username', email); + formData.append('password', password); + + const request = new Request('/api/token', { + method: 'POST', + body: formData, + }); + + const response = await fetch(request); + + if (response.status === 500) { + throw new Error('Internal server error'); + } + + const data = await response.json(); + + if (response.status > 400 && response.status < 500) { + if (data.detail) { + throw data.detail; + } + throw data; + } + + if ('access_token' in data) { + const decodedToken: any = decodeJwt(data['access_token']); + localStorage.setItem('token', data['access_token']); + localStorage.setItem('permissions', decodedToken.permissions); + } + + return data; +}; + +/** + * Sign up via backend and store JSON web token on success + * + * @param email + * @param password + * @returns JSON data containing access token on success + * @throws Error on http errors or failed attempts + */ +export const signUp = async ( + email: string, + password: string, + passwordConfirmation: string +) => { + // Assert email or password or password confirmation is not empty + if (!(email.length > 0)) { + throw new Error('Email was not provided'); + } + if (!(password.length > 0)) { + throw new Error('Password was not provided'); + } + if (!(passwordConfirmation.length > 0)) { + throw new Error('Password confirmation was not provided'); + } + + const formData = new FormData(); + // OAuth2 expects form data, not JSON data + formData.append('username', email); + formData.append('password', password); + + const request = new Request('/api/signup', { + method: 'POST', + body: formData, + }); + + const response = await fetch(request); + + if (response.status === 500) { + throw new Error('Internal server error'); + } + + const data = await response.json(); + if (response.status > 400 && response.status < 500) { + if (data.detail) { + throw data.detail; + } + throw data; + } + + if ('access_token' in data) { + const decodedToken: any = decodeJwt(data['access_token']); + localStorage.setItem('token', data['access_token']); + localStorage.setItem('permissions', decodedToken.permissions); + } + + return data; +}; + +export const logout = () => { + localStorage.removeItem('token'); + localStorage.removeItem('permissions'); +}; diff --git a/fastapi-react-project/frontend/src/utils/index.ts b/fastapi-react-project/frontend/src/utils/index.ts new file mode 100644 index 00000000..abb0c9d6 --- /dev/null +++ b/fastapi-react-project/frontend/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './auth'; +export * from './api'; diff --git a/fastapi-react-project/frontend/src/views/Home.tsx b/fastapi-react-project/frontend/src/views/Home.tsx new file mode 100644 index 00000000..50ba5f97 --- /dev/null +++ b/fastapi-react-project/frontend/src/views/Home.tsx @@ -0,0 +1,69 @@ +import React, { FC, useState } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; + +import { getMessage } from '../utils/api'; +import { isAuthenticated } from '../utils/auth'; + +const useStyles = makeStyles((theme) => ({ + link: { + color: '#61dafb', + }, +})); + +export const Home: FC = () => { + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + const classes = useStyles(); + + const queryBackend = async () => { + try { + const message = await getMessage(); + setMessage(message); + } catch (err) { + setError(String(err)); + } + }; + + return ( + <> + {!message && !error && ( + queryBackend()}> + Click to make request to backend + + )} + {message && ( +

+ {message} +

+ )} + {error && ( +

+ Error: {error} +

+ )} + + Admin Dashboard + + + Todo Dashboard + + + Protected Route + + {isAuthenticated() ? ( + + Logout + + ) : ( + <> + + Login + + + Sign Up + + + )} + + ); +}; diff --git a/fastapi-react-project/frontend/src/views/Login.tsx b/fastapi-react-project/frontend/src/views/Login.tsx new file mode 100644 index 00000000..26298c8a --- /dev/null +++ b/fastapi-react-project/frontend/src/views/Login.tsx @@ -0,0 +1,151 @@ +import React, { FC, useState } from 'react'; +import { + Paper, + Grid, + TextField, + Button, + FormControlLabel, + Checkbox, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { Face, Fingerprint } from '@material-ui/icons'; +import { Alert } from '@material-ui/lab'; +import { Redirect } from 'react-router-dom'; +import { useHistory } from 'react-router'; + +import { login, isAuthenticated } from '../utils/auth'; + +const useStyles = makeStyles((theme) => ({ + margin: { + margin: theme.spacing(2), + }, + padding: { + padding: theme.spacing(1), + }, + button: { + textTransform: 'none', + }, + marginTop: { + marginTop: 10, + }, +})); + +export const Login: FC = () => { + const classes = useStyles(); + const history = useHistory(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + + const handleSubmit = async (_: React.MouseEvent) => { + setError(''); + try { + const data = await login(email, password); + + if (data) { + history.push('/'); + } + } catch (err) { + if (err instanceof Error) { + // handle errors thrown from frontend + setError(err.message); + } else { + // handle errors thrown from backend + setError(String(err)); + } + } + }; + + return isAuthenticated() ? ( + + ) : ( + +
+ + + + + + ) => + setEmail(e.currentTarget.value) + } + fullWidth + autoFocus + required + /> + + + + + + + + ) => + setPassword(e.currentTarget.value) + } + fullWidth + required + /> + + +
+ + {error && ( + + {error} + + )} + + + + } + label="Remember me" + /> + + + + + + + {' '} + {' '} +   + + +
+
+ ); +}; diff --git a/fastapi-react-project/frontend/src/views/PrivateRoute.tsx b/fastapi-react-project/frontend/src/views/PrivateRoute.tsx new file mode 100644 index 00000000..285fc5fd --- /dev/null +++ b/fastapi-react-project/frontend/src/views/PrivateRoute.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import { Route, Redirect } from 'react-router-dom'; + +import { isAuthenticated } from '../utils/auth'; + +type PrivateRouteType = { + component: React.ComponentType; + path?: string | string[]; +}; + +export const PrivateRoute: FC = ({ + component, + ...rest +}: any) => ( + + isAuthenticated() === true ? ( + React.createElement(component, props) + ) : ( + + ) + } + /> +); diff --git a/fastapi-react-project/frontend/src/views/Protected.tsx b/fastapi-react-project/frontend/src/views/Protected.tsx new file mode 100644 index 00000000..078414ad --- /dev/null +++ b/fastapi-react-project/frontend/src/views/Protected.tsx @@ -0,0 +1,5 @@ +import React, { FC } from 'react'; + +export const Protected: FC = () => { + return

This component is protected

; +}; diff --git a/fastapi-react-project/frontend/src/views/SignUp.tsx b/fastapi-react-project/frontend/src/views/SignUp.tsx new file mode 100644 index 00000000..0ede11ee --- /dev/null +++ b/fastapi-react-project/frontend/src/views/SignUp.tsx @@ -0,0 +1,138 @@ +import React, { FC, useState } from 'react'; +import { Paper, Grid, TextField, Button } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { Face, Fingerprint } from '@material-ui/icons'; +import { Alert } from '@material-ui/lab'; +import { Redirect } from 'react-router-dom'; +import { useHistory } from 'react-router'; + +import { signUp, isAuthenticated } from '../utils/auth'; + +const useStyles = makeStyles((theme) => ({ + margin: { + margin: theme.spacing(2), + }, + padding: { + padding: theme.spacing(1), + }, + button: { + textTransform: 'none', + }, + marginTop: { + marginTop: 10, + }, +})); + +export const SignUp: FC = () => { + const classes = useStyles(); + const history = useHistory(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [passwordConfirmation, setPasswordConfirmation] = useState(''); + const [error, setError] = useState(''); + + const handleSubmit = async (_: React.MouseEvent) => { + // Password confirmation validation + if (password !== passwordConfirmation) setError('Passwords do not match'); + else { + setError(''); + try { + const data = await signUp(email, password, passwordConfirmation); + + if (data) { + history.push('/'); + } + } catch (err) { + if (err instanceof Error) { + // handle errors thrown from frontend + setError(err.message); + } else { + // handle errors thrown from backend + setError(String(err)); + } + } + } + }; + + return isAuthenticated() ? ( + + ) : ( + +
+ + + + + + ) => + setEmail(e.currentTarget.value) + } + fullWidth + autoFocus + required + /> + + + + + + + + ) => + setPassword(e.currentTarget.value) + } + fullWidth + required + /> + + + + + + + + ) => + setPasswordConfirmation(e.currentTarget.value) + } + fullWidth + required + /> + + +
+ + {error && ( + + {error} + + )} + + + + +
+
+ ); +}; diff --git a/fastapi-react-project/frontend/src/views/index.ts b/fastapi-react-project/frontend/src/views/index.ts new file mode 100644 index 00000000..797586c5 --- /dev/null +++ b/fastapi-react-project/frontend/src/views/index.ts @@ -0,0 +1,5 @@ +export * from './Home'; +export * from './Login'; +export * from './SignUp'; +export * from './Protected'; +export * from './PrivateRoute'; diff --git a/fastapi-react-project/frontend/tsconfig.json b/fastapi-react-project/frontend/tsconfig.json new file mode 100644 index 00000000..4a41017b --- /dev/null +++ b/fastapi-react-project/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src", "decs.d.ts"] +} diff --git a/fastapi-react-project/nginx/nginx.conf b/fastapi-react-project/nginx/nginx.conf new file mode 100644 index 00000000..10a3d32d --- /dev/null +++ b/fastapi-react-project/nginx/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 80; + server_name fastapi-react-project; + + location / { + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://frontend:3000; + + proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /api { + proxy_pass http://backend:8888/api; + } +} diff --git a/fastapi-react-project/scripts/build.sh b/fastapi-react-project/scripts/build.sh new file mode 100644 index 00000000..77a36455 --- /dev/null +++ b/fastapi-react-project/scripts/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Exit in case of error +set -e + +# Build and run containers +docker-compose up -d + +# Hack to wait for postgres container to be up before running alembic migrations +sleep 5; + +# Run migrations +docker-compose run --rm backend alembic upgrade head + +# Create initial data +docker-compose run --rm backend python3 app/initial_data.py \ No newline at end of file diff --git a/fastapi-react-project/scripts/test.sh b/fastapi-react-project/scripts/test.sh new file mode 100644 index 00000000..9f1d0d33 --- /dev/null +++ b/fastapi-react-project/scripts/test.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash + +# Exit in case of error +set -e + +docker-compose run backend pytest +docker-compose run frontend test \ No newline at end of file diff --git a/fastapi-react-project/scripts/test_backend.sh b/fastapi-react-project/scripts/test_backend.sh new file mode 100644 index 00000000..b5fb2c2e --- /dev/null +++ b/fastapi-react-project/scripts/test_backend.sh @@ -0,0 +1,6 @@ +#! /usr/bin/env bash + +# Exit in case of error +set -e + +docker-compose run backend pytest $@ \ No newline at end of file From 78ead5581c9a4cb61fafa9b8a471270f8337a147 Mon Sep 17 00:00:00 2001 From: rohul1392 <45639411+rohul1392@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:19:47 +0900 Subject: [PATCH 2/2] Revert "HW-Amin" This reverts commit a6ca394f7edcdc74e8e40137260ca64be13c0bde. --- fastapi-react-project/.prettierignore | 1 - fastapi-react-project/README.md | 159 ---------------- fastapi-react-project/backend/Dockerfile | 13 -- fastapi-react-project/backend/alembic.ini | 82 --------- fastapi-react-project/backend/app/__init__.py | 0 .../app/__pycache__/__init__.cpython-38.pyc | Bin 111 -> 0 bytes .../app/__pycache__/main.cpython-38.pyc | Bin 1631 -> 0 bytes .../app/__pycache__/tasks.cpython-38.pyc | Bin 360 -> 0 bytes fastapi-react-project/backend/app/alembic.ini | 85 --------- .../backend/app/alembic/README | 1 - .../backend/app/alembic/__init__.py | 0 .../alembic/__pycache__/env.cpython-38.pyc | Bin 1801 -> 0 bytes .../backend/app/alembic/env.py | 81 --------- .../backend/app/alembic/script.py.mako | 24 --- .../91979b40eb38_create_users_table.py | 34 ---- ...9b40eb38_create_users_table.cpython-38.pyc | Bin 1001 -> 0 bytes ...77025b5f0b_added_todo_table.cpython-38.pyc | Bin 1596 -> 0 bytes .../versions/a377025b5f0b_added_todo_table.py | 55 ------ .../backend/app/api/__init__.py | 0 .../api/__pycache__/__init__.cpython-38.pyc | Bin 115 -> 0 bytes .../backend/app/api/api_v1/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 122 -> 0 bytes .../app/api/api_v1/routers/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 130 -> 0 bytes .../routers/__pycache__/auth.cpython-38.pyc | Bin 1620 -> 0 bytes .../routers/__pycache__/todos.cpython-38.pyc | Bin 1807 -> 0 bytes .../routers/__pycache__/users.cpython-38.pyc | Bin 2176 -> 0 bytes .../backend/app/api/api_v1/routers/auth.py | 63 ------- .../app/api/api_v1/routers/tests/__init__.py | 0 .../app/api/api_v1/routers/tests/test_auth.py | 66 ------- .../api/api_v1/routers/tests/test_users.py | 110 ------------ .../backend/app/api/api_v1/routers/todos.py | 85 --------- .../backend/app/api/api_v1/routers/users.py | 107 ----------- .../backend/app/api/dependencies/__init__.py | 0 .../backend/app/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-38.pyc | Bin 116 -> 0 bytes .../app/core/__pycache__/auth.cpython-38.pyc | Bin 2094 -> 0 bytes .../__pycache__/celery_app.cpython-38.pyc | Bin 279 -> 0 bytes .../core/__pycache__/config.cpython-38.pyc | Bin 257 -> 0 bytes .../core/__pycache__/security.cpython-38.pyc | Bin 1237 -> 0 bytes .../backend/app/core/auth.py | 75 -------- .../backend/app/core/celery_app.py | 5 - .../backend/app/core/config.py | 7 - .../backend/app/core/security.py | 31 ---- .../backend/app/db/__init__.py | 0 .../db/__pycache__/__init__.cpython-38.pyc | Bin 114 -> 0 bytes .../app/db/__pycache__/crud.cpython-38.pyc | Bin 3537 -> 0 bytes .../app/db/__pycache__/models.cpython-38.pyc | Bin 1120 -> 0 bytes .../app/db/__pycache__/schemas.cpython-38.pyc | Bin 2998 -> 0 bytes .../app/db/__pycache__/session.cpython-38.pyc | Bin 588 -> 0 bytes fastapi-react-project/backend/app/db/crud.py | 119 ------------ .../backend/app/db/models.py | 28 --- .../backend/app/db/schemas.py | 73 -------- .../backend/app/db/session.py | 21 --- .../backend/app/initial_data.py | 26 --- fastapi-react-project/backend/app/main.py | 51 ------ fastapi-react-project/backend/app/tasks.py | 6 - .../backend/app/tests/__init__.py | 0 .../backend/app/tests/test_main.py | 4 - .../backend/app/tests/test_tasks.py | 6 - fastapi-react-project/backend/conftest.py | 169 ------------------ fastapi-react-project/backend/pyproject.toml | 2 - .../backend/requirements.txt | 19 -- fastapi-react-project/docker-compose.yml | 71 -------- fastapi-react-project/frontend/.dockerignore | 2 - fastapi-react-project/frontend/.eslintrc.js | 51 ------ fastapi-react-project/frontend/.prettierrc.js | 5 - fastapi-react-project/frontend/Dockerfile | 16 -- fastapi-react-project/frontend/README.md | 68 ------- fastapi-react-project/frontend/package.json | 62 ------- .../frontend/public/favicon.ico | Bin 3150 -> 0 bytes .../frontend/public/index.html | 43 ----- .../frontend/public/logo192.png | Bin 5347 -> 0 bytes .../frontend/public/logo512.png | Bin 9664 -> 0 bytes .../frontend/public/manifest.json | 25 --- .../frontend/public/robots.txt | 3 - fastapi-react-project/frontend/run.sh | 18 -- fastapi-react-project/frontend/src/App.tsx | 6 - fastapi-react-project/frontend/src/Routes.tsx | 59 ------ .../frontend/src/__tests__/home.test.tsx | 12 -- .../frontend/src/__tests__/login.test.tsx | 11 -- .../frontend/src/admin/Admin.tsx | 37 ---- .../frontend/src/admin/Todo.tsx | 40 ----- .../frontend/src/admin/Todo/ToDoCreate.tsx | 18 -- .../frontend/src/admin/Todo/ToDoEdit.tsx | 19 -- .../frontend/src/admin/Todo/ToDoList.tsx | 20 --- .../frontend/src/admin/Todo/index.ts | 3 - .../frontend/src/admin/Users/UserCreate.tsx | 21 --- .../frontend/src/admin/Users/UserEdit.tsx | 22 --- .../frontend/src/admin/Users/UserList.tsx | 24 --- .../frontend/src/admin/Users/index.ts | 3 - .../frontend/src/admin/authProvider.ts | 55 ------ .../frontend/src/admin/index.ts | 1 - .../frontend/src/config/index.tsx | 3 - fastapi-react-project/frontend/src/decs.d.ts | 1 - fastapi-react-project/frontend/src/index.css | 13 -- fastapi-react-project/frontend/src/index.tsx | 12 -- fastapi-react-project/frontend/src/logo.svg | 7 - .../frontend/src/react-app-env.d.ts | 1 - .../frontend/src/utils/api.ts | 13 -- .../frontend/src/utils/auth.ts | 118 ------------ .../frontend/src/utils/index.ts | 2 - .../frontend/src/views/Home.tsx | 69 ------- .../frontend/src/views/Login.tsx | 151 ---------------- .../frontend/src/views/PrivateRoute.tsx | 25 --- .../frontend/src/views/Protected.tsx | 5 - .../frontend/src/views/SignUp.tsx | 138 -------------- .../frontend/src/views/index.ts | 5 - fastapi-react-project/frontend/tsconfig.json | 19 -- fastapi-react-project/nginx/nginx.conf | 22 --- fastapi-react-project/scripts/build.sh | 16 -- fastapi-react-project/scripts/test.sh | 7 - fastapi-react-project/scripts/test_backend.sh | 6 - 113 files changed, 2956 deletions(-) delete mode 100644 fastapi-react-project/.prettierignore delete mode 100644 fastapi-react-project/README.md delete mode 100644 fastapi-react-project/backend/Dockerfile delete mode 100644 fastapi-react-project/backend/alembic.ini delete mode 100644 fastapi-react-project/backend/app/__init__.py delete mode 100644 fastapi-react-project/backend/app/__pycache__/__init__.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/__pycache__/main.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/__pycache__/tasks.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/alembic.ini delete mode 100644 fastapi-react-project/backend/app/alembic/README delete mode 100644 fastapi-react-project/backend/app/alembic/__init__.py delete mode 100644 fastapi-react-project/backend/app/alembic/__pycache__/env.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/alembic/env.py delete mode 100644 fastapi-react-project/backend/app/alembic/script.py.mako delete mode 100644 fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py delete mode 100644 fastapi-react-project/backend/app/alembic/versions/__pycache__/91979b40eb38_create_users_table.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/alembic/versions/__pycache__/a377025b5f0b_added_todo_table.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py delete mode 100644 fastapi-react-project/backend/app/api/__init__.py delete mode 100644 fastapi-react-project/backend/app/api/__pycache__/__init__.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/api/api_v1/__init__.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/__pycache__/__init__.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__init__.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/__init__.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/auth.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/todos.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/users.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/auth.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/tests/__init__.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/todos.py delete mode 100644 fastapi-react-project/backend/app/api/api_v1/routers/users.py delete mode 100644 fastapi-react-project/backend/app/api/dependencies/__init__.py delete mode 100644 fastapi-react-project/backend/app/core/__init__.py delete mode 100644 fastapi-react-project/backend/app/core/__pycache__/__init__.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/core/__pycache__/auth.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/core/__pycache__/celery_app.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/core/__pycache__/config.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/core/__pycache__/security.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/core/auth.py delete mode 100644 fastapi-react-project/backend/app/core/celery_app.py delete mode 100644 fastapi-react-project/backend/app/core/config.py delete mode 100644 fastapi-react-project/backend/app/core/security.py delete mode 100644 fastapi-react-project/backend/app/db/__init__.py delete mode 100644 fastapi-react-project/backend/app/db/__pycache__/__init__.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/db/__pycache__/crud.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/db/__pycache__/models.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/db/__pycache__/schemas.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/db/__pycache__/session.cpython-38.pyc delete mode 100644 fastapi-react-project/backend/app/db/crud.py delete mode 100644 fastapi-react-project/backend/app/db/models.py delete mode 100644 fastapi-react-project/backend/app/db/schemas.py delete mode 100644 fastapi-react-project/backend/app/db/session.py delete mode 100644 fastapi-react-project/backend/app/initial_data.py delete mode 100644 fastapi-react-project/backend/app/main.py delete mode 100644 fastapi-react-project/backend/app/tasks.py delete mode 100644 fastapi-react-project/backend/app/tests/__init__.py delete mode 100644 fastapi-react-project/backend/app/tests/test_main.py delete mode 100644 fastapi-react-project/backend/app/tests/test_tasks.py delete mode 100644 fastapi-react-project/backend/conftest.py delete mode 100644 fastapi-react-project/backend/pyproject.toml delete mode 100644 fastapi-react-project/backend/requirements.txt delete mode 100644 fastapi-react-project/docker-compose.yml delete mode 100644 fastapi-react-project/frontend/.dockerignore delete mode 100644 fastapi-react-project/frontend/.eslintrc.js delete mode 100644 fastapi-react-project/frontend/.prettierrc.js delete mode 100644 fastapi-react-project/frontend/Dockerfile delete mode 100644 fastapi-react-project/frontend/README.md delete mode 100644 fastapi-react-project/frontend/package.json delete mode 100644 fastapi-react-project/frontend/public/favicon.ico delete mode 100644 fastapi-react-project/frontend/public/index.html delete mode 100644 fastapi-react-project/frontend/public/logo192.png delete mode 100644 fastapi-react-project/frontend/public/logo512.png delete mode 100644 fastapi-react-project/frontend/public/manifest.json delete mode 100644 fastapi-react-project/frontend/public/robots.txt delete mode 100644 fastapi-react-project/frontend/run.sh delete mode 100644 fastapi-react-project/frontend/src/App.tsx delete mode 100644 fastapi-react-project/frontend/src/Routes.tsx delete mode 100644 fastapi-react-project/frontend/src/__tests__/home.test.tsx delete mode 100644 fastapi-react-project/frontend/src/__tests__/login.test.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Admin.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Todo.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Todo/index.ts delete mode 100644 fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Users/UserList.tsx delete mode 100644 fastapi-react-project/frontend/src/admin/Users/index.ts delete mode 100644 fastapi-react-project/frontend/src/admin/authProvider.ts delete mode 100644 fastapi-react-project/frontend/src/admin/index.ts delete mode 100644 fastapi-react-project/frontend/src/config/index.tsx delete mode 100644 fastapi-react-project/frontend/src/decs.d.ts delete mode 100644 fastapi-react-project/frontend/src/index.css delete mode 100644 fastapi-react-project/frontend/src/index.tsx delete mode 100644 fastapi-react-project/frontend/src/logo.svg delete mode 100644 fastapi-react-project/frontend/src/react-app-env.d.ts delete mode 100644 fastapi-react-project/frontend/src/utils/api.ts delete mode 100644 fastapi-react-project/frontend/src/utils/auth.ts delete mode 100644 fastapi-react-project/frontend/src/utils/index.ts delete mode 100644 fastapi-react-project/frontend/src/views/Home.tsx delete mode 100644 fastapi-react-project/frontend/src/views/Login.tsx delete mode 100644 fastapi-react-project/frontend/src/views/PrivateRoute.tsx delete mode 100644 fastapi-react-project/frontend/src/views/Protected.tsx delete mode 100644 fastapi-react-project/frontend/src/views/SignUp.tsx delete mode 100644 fastapi-react-project/frontend/src/views/index.ts delete mode 100644 fastapi-react-project/frontend/tsconfig.json delete mode 100644 fastapi-react-project/nginx/nginx.conf delete mode 100644 fastapi-react-project/scripts/build.sh delete mode 100644 fastapi-react-project/scripts/test.sh delete mode 100644 fastapi-react-project/scripts/test_backend.sh diff --git a/fastapi-react-project/.prettierignore b/fastapi-react-project/.prettierignore deleted file mode 100644 index 412c2574..00000000 --- a/fastapi-react-project/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -docker-compose.yml \ No newline at end of file diff --git a/fastapi-react-project/README.md b/fastapi-react-project/README.md deleted file mode 100644 index bbd43623..00000000 --- a/fastapi-react-project/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# y - -## Features - -- **FastAPI** with Python 3.8 -- **React 16** with Typescript, Redux, and react-router -- Postgres -- SqlAlchemy with Alembic for migrations -- Pytest for backend tests -- Jest for frontend tests -- Perttier/Eslint (with Airbnb style guide) -- Docker compose for easier development -- Nginx as a reverse proxy to allow backend and frontend on the same port - -## Development - -The only dependencies for this project should be docker and docker-compose. - -### Quick Start - -Starting the project with hot-reloading enabled -(the first time it will take a while): - -```bash -docker-compose up -d -``` - -To run the alembic migrations (for the users table): - -```bash -docker-compose run --rm backend alembic upgrade head -``` - -And navigate to http://localhost:8000 - -_Note: If you see an Nginx error at first with a `502: Bad Gateway` page, you may have to wait for webpack to build the development server (the nginx container builds much more quickly)._ - -Auto-generated docs will be at -http://localhost:8000/api/docs - -### Rebuilding containers: - -``` -docker-compose build -``` - -### Restarting containers: - -``` -docker-compose restart -``` - -### Bringing containers down: - -``` -docker-compose down -``` - -### Frontend Development - -Alternatively to running inside docker, it can sometimes be easier -to use npm directly for quicker reloading. To run using npm: - -``` -cd frontend -npm install -npm start -``` - -This should redirect you to http://localhost:3000 - -### Frontend Tests - -``` -cd frontend -npm install -npm test -``` - -## Migrations - -Migrations are run using alembic. To run all migrations: - -``` -docker-compose run --rm backend alembic upgrade head -``` - -To create a new migration: - -``` -alembic revision -m "create users table" -``` - -And fill in `upgrade` and `downgrade` methods. For more information see -[Alembic's official documentation](https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script). - -## Testing - -There is a helper script for both frontend and backend tests: - -``` -./scripts/test.sh -``` - -### Backend Tests - -``` -docker-compose run backend pytest -``` - -any arguments to pytest can also be passed after this command - -### Frontend Tests - -``` -docker-compose run frontend test -``` - -This is the same as running npm test from within the frontend directory - -## Logging - -``` -docker-compose logs -``` - -Or for a specific service: - -``` -docker-compose logs -f name_of_service # frontend|backend|db -``` - -## Project Layout - -``` -backend -└── app - ├── alembic - │ └── versions # where migrations are located - ├── api - │ └── api_v1 - │ └── endpoints - ├── core # config - ├── db # db models - ├── tests # pytest - └── main.py # entrypoint to backend - -frontend -└── public -└── src - ├── components - │ └── Home.tsx - ├── config - │ └── index.tsx # constants - ├── __tests__ - │ └── test_home.tsx - ├── index.tsx # entrypoint - └── App.tsx # handles routing -``` diff --git a/fastapi-react-project/backend/Dockerfile b/fastapi-react-project/backend/Dockerfile deleted file mode 100644 index 10aef33a..00000000 --- a/fastapi-react-project/backend/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ - -FROM python:3.8 - -RUN mkdir /app -WORKDIR /app - -RUN apt update && \ - apt install -y postgresql-client - -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . \ No newline at end of file diff --git a/fastapi-react-project/backend/alembic.ini b/fastapi-react-project/backend/alembic.ini deleted file mode 100644 index 53e6f13f..00000000 --- a/fastapi-react-project/backend/alembic.ini +++ /dev/null @@ -1,82 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = app/alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# timezone to use when rendering the date -# within the migration file as well as the filename. -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -timezone = America/Los_Angeles - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; this defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat alembic/versions - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S \ No newline at end of file diff --git a/fastapi-react-project/backend/app/__init__.py b/fastapi-react-project/backend/app/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index f6a8e8f8af2397d5bc740f4edbbc34f5ccf156b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BmKe3f*p)HAl%-d%69$!@;WHf=eu95(cdP@#gN7D1@mA|zid$MdrBc5E** zQ+f^J2XJVP`~&X%i@9>*#<@a*H?vE!0(GnzzwddU^KGZoa&Ud}=a1}_?>N63WN|eR z`4qPt631Z*WaUc3`HrVnd z0grWmCTxYRzV^;ZyovNJ*1IIE&(>Z$NZGcOb);;tO-tE<{&*Jx)?-^E0u)xx$%wG+ z3yj8cns~_%3VNBPJo zJ_9N9s{E?TQjYk>2vm~QLO`jKG*kHrB*r$fS{ZNkB4&3oik@l5_(3? zum+E@V;(uLsG*JN8~4&7&Kbo_k%NIJny8_DsZs@+vP11>ypk|*#fo7xF+!e{aH@1D zKu)Ss!Y_{4K;GLf`aZF{Nb_l!EoN0QW$S_P@m*kB}%<((u7K8 zCnxuYvtg4%8-&28&(S;Y5HTSL^ANt%hz&^uMkF*w@J0}(Y4nmo+2L^BjI$>1{3|oG zrp*hidsnpUL7kP!MUIG!O!VaGH(x&d?0ItV;OmFFb=}yukF|@_Z(D0tpQXIac+Ft` zkeG~@X#q*1T~U_{)wadAB@aI?Dpqs2kB_}0@8i+MDG6zW=fdYw;^CyzzZ-r?xEDbWEE%ZAC3pAZ2$lO diff --git a/fastapi-react-project/backend/app/__pycache__/tasks.cpython-38.pyc b/fastapi-react-project/backend/app/__pycache__/tasks.cpython-38.pyc deleted file mode 100644 index 19250f6cc8f3a13e1e1014b3e67a4000360c9c7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 360 zcmYjMJ5Iwu5ZzgC9AhN~Z~?cLkhuUtNc3rdCXHozrXg!=?QR4?R6xrK08w+LZ7C2} zpkgKgg^}KTR`1Q5y_imO#nFG>;~Uox8UBai;e_E2IH5wYs)IRzBDmD3F?j+?C_(e0 zafIokd4RdW@tG>KFIK1$V&0M^x2l_AIOlPvX&1h>O`uXobKN_PsiUxTS1pL*H>B5$ zQ-+rt5$@EpdeyvI&u;eu`A~$$FN%%W7w>H5LN`q}SgPek z=w57;b{XecIg0x%soLIAwLRPkCmNB�R$~Z+$l1SUA~H*W=RCv3$VG{z;O<5AAVK AcK`qY diff --git a/fastapi-react-project/backend/app/alembic.ini b/fastapi-react-project/backend/app/alembic.ini deleted file mode 100644 index bfcc3c7f..00000000 --- a/fastapi-react-project/backend/app/alembic.ini +++ /dev/null @@ -1,85 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# timezone to use when rendering the date -# within the migration file as well as the filename. -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; this defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat alembic/versions - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/fastapi-react-project/backend/app/alembic/README b/fastapi-react-project/backend/app/alembic/README deleted file mode 100644 index 98e4f9c4..00000000 --- a/fastapi-react-project/backend/app/alembic/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/fastapi-react-project/backend/app/alembic/__init__.py b/fastapi-react-project/backend/app/alembic/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/alembic/__pycache__/env.cpython-38.pyc b/fastapi-react-project/backend/app/alembic/__pycache__/env.cpython-38.pyc deleted file mode 100644 index 5cdf51f87f52af8aca4aa77e2aa523303eced031..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1801 zcmZvc&5s*36u|AtWHOl~yW1_TKtjmkQX*9;7bFfV#O}6-N@!KKtw6bqoUxNQcszsc z*)Q#0x=388_!qKA{-wEcK>P~`1kavizl2BQZ@>5a-p}vtV2~gv#UJn3!!AOj!8pLK-;%979EZLi%61-&Bj0aBzccRmek9|(JMLn{!$mSq zc)*iI?-IkfyXcPxti$4;(0GG&Spw}Q>#;txTRdDoxI+3J-@HT^sfcg6mf_cEIQSDR zLI*=!CsQH$exasf2BS_^D9cYQbho*h3B}W?F7gznn+;)E6cSd$J!*L7gxoGI80^Du zoL?+jVur7ry5L~kf4=)>cW?Lgi}dZ`H^ZO~3R6cjZn--84QcRRQ~k9~%kmkOJfDc{ z87z*<)4Buu(n`xuoM`7%gmn+I{7MR1O6i(wY9mcT5056-rD z8D61ZH+-*s1#bBceSyA3hv<96+F)@+_d(!crARJjnp#mPLxdtvis@8>Tb_`-V0@Gi zC~xM%kj6um=7!igcSGk#axCneEGlCOC2(=SpDIRF0W*rc@IHPDM~Z4TznLK*k!Be$ zEuBd2PtCdJgc@?pr5q8mcS_6;qAZ1)d4-K6l4K(q?+c&o=RS~70o#thII?VS#MgZU1)P|DWjIw11T)mRHhT5n5p|r0125* zi_)5dVdR6ZTM*m2d+l$%>BbKx9B56grpnOFg{$7sl}c}-H^h1acHW?-9i;W+ySvg= zd_RRXvketWA{^qtf6?DjC&Bs=%w}$BU7STu*gpJb4wXaSqD63p0l?NJI)_)cgKP+G zVB3p`wcfWb@Ht*~0K(5N0vj*7=YdTCPRI&2UiMaS#>@U)IPYV6_VfQSs_rBDQbCM> zDU)$UwJ3n1h6fjHhYYZ=rtz9lYD|#{zvhTg5DD;WgY+%Nn>e3sm>(sT*_`L6BM9zU z@75UJdsJ#Z6(@DqC6G*lY?w@L#QHEE3`*{IR4T{f#bt9;j>o!mH1 zUte!LfV?5tsut(9wB9#7r#s^j&0YOd`vrDxU~Qf~^ghCFq`Zr#-1 l*}sZ-2bL^;6IM^+m-+T diff --git a/fastapi-react-project/backend/app/alembic/env.py b/fastapi-react-project/backend/app/alembic/env.py deleted file mode 100644 index 5cbeca06..00000000 --- a/fastapi-react-project/backend/app/alembic/env.py +++ /dev/null @@ -1,81 +0,0 @@ -import os -from logging.config import fileConfig - -from alembic import context -from sqlalchemy import engine_from_config -from sqlalchemy import pool - -from app.db.models import Base - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = Base.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def get_url(): - return os.getenv("DATABASE_URL") - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - Calls to context.execute() here emit the given string to the - script output. - """ - # url = config.get_main_option("sqlalchemy.url") - url = get_url() - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online(): - """Run migrations in 'online' mode. - In this scenario we need to create an Engine - and associate a connection with the context. - """ - configuration = config.get_section(config.config_ini_section) - configuration["sqlalchemy.url"] = get_url() - connectable = engine_from_config( - configuration, - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/fastapi-react-project/backend/app/alembic/script.py.mako b/fastapi-react-project/backend/app/alembic/script.py.mako deleted file mode 100644 index 2c015630..00000000 --- a/fastapi-react-project/backend/app/alembic/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py b/fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py deleted file mode 100644 index af661b44..00000000 --- a/fastapi-react-project/backend/app/alembic/versions/91979b40eb38_create_users_table.py +++ /dev/null @@ -1,34 +0,0 @@ -"""create users table - -Revision ID: 91979b40eb38 -Revises: -Create Date: 2020-03-23 14:53:53.101322 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = "91979b40eb38" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - op.create_table( - "user", - sa.Column("id", sa.Integer, primary_key=True), - sa.Column("email", sa.String(50), nullable=False), - sa.Column("first_name", sa.String(100)), - sa.Column("last_name", sa.String(100)), - sa.Column("address", sa.String(100)), - sa.Column("hashed_password", sa.String(100), nullable=False), - sa.Column("is_active", sa.Boolean, nullable=False), - sa.Column("is_superuser", sa.Boolean, nullable=False), - ) - - -def downgrade(): - op.drop_table("user") diff --git a/fastapi-react-project/backend/app/alembic/versions/__pycache__/91979b40eb38_create_users_table.cpython-38.pyc b/fastapi-react-project/backend/app/alembic/versions/__pycache__/91979b40eb38_create_users_table.cpython-38.pyc deleted file mode 100644 index d3c20d2cd8a7371e91a58f0d6f1cec4bf719aa2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1001 zcmZuv&2Q5%6pxd%`B=LRfdFv|#~!xzV+dMJ2+;{?mmL^fa~vE(BCcFH zuSfnRUpb)t1xTEDPCBgu?7SEM{OsTR^4o4Vv~UzZKLd9x>xTiG!$$A|MIE66E4F}u zEl6g^HZf~QII#m(r*UVKEisnOv4dcWqQ0T3tSbVfvfn#kqjtdo_p4LmR%KsdIxAw5 zF?&}I1od9HA}c(NcsexVMe$?G3zhi&p`b*20qQo@&<=OWP^D!L+tY znPyBb=?A{lEuOJdTn{n)S7`I|g%GBsI((N(RneSf{2I`z1HslWl5D@aNWVBT9jNG-#t&7H=sLB^Jv{RZ7ZhR(?Tq=Tze;Z#m_Oy_E{y< z{9Jo4i$ZXgf4Ag)yjrW@M+dAd8xcI4rOCksz5;B#I@s(JUAw8@NaJk3TxxGoo=XP& zF7hcuY7v*%WS87SF|+>vk`K}HPpci=tXsW}i>B2pvM#ZGDvh~kH(7=ti?VTh8+)^Q zfX8$I#bsVc@))5qm9dC?gu1t-JxZZSD8(u_DXuxyN5Mog=h;%;!IRpT>o>2vhK}CC ynPhn~r`Sg>ln#NHJO@RKyiTn4^WU)iqpX01;Li#2t6fyC*CD>^VwG?@{`MaxlK^i3 diff --git a/fastapi-react-project/backend/app/alembic/versions/__pycache__/a377025b5f0b_added_todo_table.cpython-38.pyc b/fastapi-react-project/backend/app/alembic/versions/__pycache__/a377025b5f0b_added_todo_table.cpython-38.pyc deleted file mode 100644 index b4eb7b3325075e870c5c11b430a05d02a70da56a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1596 zcmZux&u`l{6c%M!l4UtbQx6?-nb&Q=JRpf1*L7nQ!%}y}iVo?JW;^&I$bPKDKtCLk z&XS!6>=f*{9qnWPC0=*xKd=Ek411*P#6c%PAI0xIeSGHo=pSpfz{2ade}0EQJj?n^ zPgXA*C*R;Bo*;lVuz-M_kklU7M8B7k(!c>{P=@lrh0?&o#|Ia@U#&p}e5l|UKhecaWqU?Nf zI0#quRk*4<(s`>_h?71(;%@|L%?J=_pO(Ou_RN`EPy*+wWSBBCO zZkP%(-VHPJ!Jd=3M{upv3a%GGFvm5)9^)26^~y1kDuhtOFGKU?+6ue=0b8hSph^gL zP{q4eDI%LDa!Y?!!`!=b-B=47RzFl_iMj?iV@RyBITgk;$`l9v<8 zUEFvTVuG0B%%s;)OTR=kjaj0~#Sko62Fi;8I2EF~#O*G|v+nXUf;+I%X+V^&dD(V}ALPUiR}$NFK9HUTd-p=!GJ0R2U}` ziyAG2pT2ta{kI2)xH&wZEZDae{|xIf@V>VjPbS5a&~(T~yXTbaLm+ln9U9?ppRIW;*0?Hady$+|oo~k#s^c5K*2PN0@c~w_N{8n!_}qUlU#J Q27=>-#CIHG?vj7+KOJhI;{X5v diff --git a/fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py b/fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py deleted file mode 100644 index e0c14b27..00000000 --- a/fastapi-react-project/backend/app/alembic/versions/a377025b5f0b_added_todo_table.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Added Todo table - -Revision ID: a377025b5f0b -Revises: 91979b40eb38 -Create Date: 2023-06-18 23:36:55.497350-07:00 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'a377025b5f0b' -down_revision = '91979b40eb38' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('todos', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(), nullable=False), - sa.Column('description', sa.String(), nullable=True), - sa.Column('is_done', sa.Boolean(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_todos_id'), 'todos', ['id'], unique=False) - op.alter_column('user', 'is_active', - existing_type=sa.BOOLEAN(), - nullable=True) - op.alter_column('user', 'is_superuser', - existing_type=sa.BOOLEAN(), - nullable=True) - op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) - op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False) - op.drop_column('user', 'address') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('address', sa.VARCHAR(length=100), autoincrement=False, nullable=True)) - op.drop_index(op.f('ix_user_id'), table_name='user') - op.drop_index(op.f('ix_user_email'), table_name='user') - op.alter_column('user', 'is_superuser', - existing_type=sa.BOOLEAN(), - nullable=False) - op.alter_column('user', 'is_active', - existing_type=sa.BOOLEAN(), - nullable=False) - op.drop_index(op.f('ix_todos_id'), table_name='todos') - op.drop_table('todos') - # ### end Alembic commands ### diff --git a/fastapi-react-project/backend/app/api/__init__.py b/fastapi-react-project/backend/app/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/api/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/api/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index b4680ad5db2ee26d41dae1d06cb29c0b30521abf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BkKe3;4vlb}; diff --git a/fastapi-react-project/backend/app/api/api_v1/__init__.py b/fastapi-react-project/backend/app/api/api_v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/api/api_v1/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index de65e9cd5f147c03eba78331845b95ae89f9a79d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B=Ke3&ryk0@&Ee@O9{FKt1R6CHy&p^xo0HHt_p8x;= diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__init__.py b/fastapi-react-project/backend/app/api/api_v1/routers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 41549911aba0067875df8f6af928950488dd0b63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B&ryk0@&Ee@O9{FKt1R6CFXpMjVG0FbL2>Hq)$ diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/auth.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/auth.cpython-38.pyc deleted file mode 100644 index 232bfef2f3888f84945b53d34839b4fe9fee61a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1620 zcmd5+OK%)S5T5RN?CkFBdX3{if(Xky%flv!3lc&x&PK5UYs+5CXfC7K>9#XwUUc^m z+ge{@uKou(z|j}}1pk0zUpXNDLL@L%?X_$q4jgHzzpkz+RabpgHQSv|%Ys$@{*3+F zu&m$ka(%gQxer_Z48SZ#GAkz`At=*~=5}c3PUz%r=;mJN8NHn~azFHq>}1Wn6}F7* zX6-x(10#D`C+~*cd@h_L7Pn6<#|nGlJfHvZCD!V{C4)?v^!}`vYRO21MUlU#ZoTPc~vHpO%7c^6zRc)x%Qc14lPkHr6)F zT5+Kp4|v53CUxi0c)T$@NO+~vve2GXv8p9_YN|BnjAtr_i?_#B#3ms5k|(uD)!~5Z z`54~fg-VlH@u-&Iw|;8m@~*O#v+pv(D6><-oKyQ5QJ!k-`^-Hh$M%%&H^qJC9Xo}?8pjTN zX@Raib%0SiwOR9&9=lVQwOIQ(1;&EW(Vev-DTUyP>SOFhob!Gu`qhlP7rmXGosW>t z%M`WuC65IcXSQx%@DwE_Uq-L}%h&}H zDv(*dhy{*5{rFzAJzCiwKU&{h3x^K`On@btXz=RF>gsT7D;lpq9*&~nH_#))=w6?W;=y5jlRFPEKHNiW$dq|>QK8`xtA8`TfPloIX&VKYPR>M=Ye$Y*&gwz1G*NW ze|fI>5bTZ`%h-=~_5WzNn=46D)`jZF8Suys`}`o4N{SAID!K@B2nz^{2uP%O8R0g< zYY2A$pz>{$-#~a1;i}&AvwD9Pt*(zAezmqbzN)oA_QX2~?;^a1a2EkD8m|%4mnqrAfz=N3* z;iCf|l`ygVqHjBw*ClKdGYW5T2P`?XGquMN$<1#Ev7Xxa=Voxj~hSf=Va~iUP_cmp!18HDgDv-IbVG zK`i8xxWWry9)Rb_Tg;UcJOYIsx_c$X4!$s|nd$BMdiLvYdREWt^^igP_Uae;m&e#2 z_;9^}k9`E)yw4evO!AavJmHy0MCK$;<|Zyjzep>I2eXs4s`=GwI9VXGb|ISny-6 zTv@%~GL+Fzl?6{W30qU`w@^ZD$@+zm4S9!f+km?dd;S;Kluf|3WSek1fZLU~?x$Mv z?u8>e@*W`{0CMkkB)xerd1)c5U_JuRl8+PYb23f2h8jDpOehshX%< z8XFucGa2VbeHnA>ov5NOkK&50qE(FL7!WJdMnkOzg(AO=q)OH5p{nGl#K(>epF!Lc zg4zJ>PhipdXni#;W?ql>eHIJb(94(ivoUOEcWyrS)yrWzm8zeQa|QH*)$&kaYRl%p zjQtgK(}kg6bH3o`Lh^#o#e)CJeiL(cDx^4Ib5zEeZH8TFpQ@reNYgIyA~+Swee1BCbD@=OCA_SEsjh4l(al zbHP~a(0~WfL4XqC+?hL&HJ|Oh$vQuyth-VbgHdW|A?7-Ou-MhxFzNd^bYQS9zK)VB zhfQ?Wh{H1e9qIdSv;#b9; zoi{rZzqgr1#H_5<#}HtsTB`cNwO~q)xDjhu!G<>WSI|uj2H@t*v52k<_4zBHwf^zI6i~|=NoqVljY8=P4Av{oCLo06g>69X@5`foql_7ibB$$fqos4G zr;@G`)tSb_)JRUFYRNiu_(Ej^qfvid#{rAEtT)pyFOucjI`Cbx?iX+fbYrYD$_pwz z>rTet#N)QkWLl7-)+=+PgsvrzS5{L&2M6#Gnx^VQ_|swL7=|q#h#KGZJ0cV=7d+tN s()GP7!Nu!F4L$I?bOY~_tgA-UaMyMS>kNLcA>toe2W%r+d%YF%UsQse=l}o! diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/users.cpython-38.pyc b/fastapi-react-project/backend/app/api/api_v1/routers/__pycache__/users.cpython-38.pyc deleted file mode 100644 index 0660a6cf48b779bdc93bea6adcbf713ff87d5299..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2176 zcmb7E&2HO95auo^k@}-$`A2rFI7;Fa(7|?lED#iJlcFe4G=*DV_9D>QjV*(u)FtV} zs(h((ZC{}71N1q1>>KR0hoZ00)=g)Ik}L;-i!Q<8?98{rZ)a!ruvRNuFb2PWCx0zj z)^9jj97ULX2ygNOvn**z7FrP#EaHMkwy+~dI8i|q82Wfv6fS)2(2GiRYtgz`kD8*%EX5|4V~Gv1sWwmV zNLRV1TWV#~lAbJmZ!24sCJbbA!k%;G$nqtV6|A&HdH!;wWH!bev+>tg0H8$=ti)G z&}`kDch$>Ym`T-*2eE?Pc{6v<#vDzD17Do~0&mg=ky>LYgbQ1;l#T5P`_cM|kF8TK z`D1HrLyGNDv)qR9sY=^H7`BNISw_ygyYKIhYEK4n3cK1p2;yVav9$+bjUB4kxF;&W zYJhDI)rOm&t73}gxv?q^GHf&b5){a`qpzJ2)iyJzq1YdY2>PsHBv+>~ieH|!^A z16C#2pjOS}t4!YoZB)vk`;qEF$Ib`p_|n!f4`U0q$42Y(?G0YUv^_EqqYl%XfFwN^ zEtnb?8FVA{2qp;!VHNZ?$WeXz>u(9rmltquuAbH7)Y!wH&qe{1`GNQ_fdk=iB>ch| zJH&5$&M%11iCGL?t^}V>y6^UOLNARcy1A$yc60%J612QlL;gd4|PB!G4eIZH|bS~Ufz8l12WHW zSwF-G_QE!aASQ4&P)Cw<#8ps$pgm?|KEX<6qdb*|LkWG|RxhD|`tk7%mbmCfDsQJ+ zBC2>DJcETf(VE@?{eL7bXZ4gWn76a4AggKa+Mxee?xvR&AahDpjg{JY$GFdfB!#}x zShO1JPGi0_CQf4?nPPfAgrpdj8XuZ*Paf?hDoOf-Si9gCm8r3(GfACJ)kmkRk*3BB ziuuysiHd?m*U*F2t(Q>LL6{mw>J7Bo3$pY?FQWtd*-&{V-MsG&)5eA?(m0>PaZ!U) zs~Ni=r&O-S84kdM)nV*mmQwmnG0y>Qe4)Vbn$gt5-eb5%vrv7&z?7VUXfcmhS%Oxvs{+P?=QXYUi!iXlug&en GYxKX~&h_yC diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/auth.py b/fastapi-react-project/backend/app/api/api_v1/routers/auth.py deleted file mode 100644 index 05247f58..00000000 --- a/fastapi-react-project/backend/app/api/api_v1/routers/auth.py +++ /dev/null @@ -1,63 +0,0 @@ -from fastapi.security import OAuth2PasswordRequestForm -from fastapi import APIRouter, Depends, HTTPException, status -from datetime import timedelta - -from app.db.session import get_db -from app.core import security -from app.core.auth import authenticate_user, sign_up_new_user - -auth_router = r = APIRouter() - - -@r.post("/token") -async def login( - db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends() -): - user = authenticate_user(db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - - access_token_expires = timedelta( - minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES - ) - if user.is_superuser: - permissions = "admin" - else: - permissions = "user" - access_token = security.create_access_token( - data={"sub": user.email, "permissions": permissions}, - expires_delta=access_token_expires, - ) - - return {"access_token": access_token, "token_type": "bearer"} - - -@r.post("/signup") -async def signup( - db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends() -): - user = sign_up_new_user(db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Account already exists", - headers={"WWW-Authenticate": "Bearer"}, - ) - - access_token_expires = timedelta( - minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES - ) - if user.is_superuser: - permissions = "admin" - else: - permissions = "user" - access_token = security.create_access_token( - data={"sub": user.email, "permissions": permissions}, - expires_delta=access_token_expires, - ) - - return {"access_token": access_token, "token_type": "bearer"} diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/tests/__init__.py b/fastapi-react-project/backend/app/api/api_v1/routers/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py b/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py deleted file mode 100644 index 3e73790a..00000000 --- a/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_auth.py +++ /dev/null @@ -1,66 +0,0 @@ -from app.core import security - -# Monkey patch function we can use to shave a second off our tests by skipping the password hashing check -def verify_password_mock(first: str, second: str): - return True - - -def test_login(client, test_user, monkeypatch): - # Patch the test to skip password hashing check for speed - monkeypatch.setattr(security, "verify_password", verify_password_mock) - - response = client.post( - "/api/token", - data={"username": test_user.email, "password": "nottheactualpass"}, - ) - assert response.status_code == 200 - - -def test_signup(client, monkeypatch): - def get_password_hash_mock(first: str, second: str): - return True - - monkeypatch.setattr(security, "get_password_hash", get_password_hash_mock) - - response = client.post( - "/api/signup", - data={"username": "some@email.com", "password": "randompassword"}, - ) - assert response.status_code == 200 - - -def test_resignup(client, test_user, monkeypatch): - # Patch the test to skip password hashing check for speed - monkeypatch.setattr(security, "verify_password", verify_password_mock) - - response = client.post( - "/api/signup", - data={ - "username": test_user.email, - "password": "password_hashing_is_skipped_via_monkey_patch", - }, - ) - assert response.status_code == 409 - - -def test_wrong_password( - client, test_db, test_user, test_password, monkeypatch -): - def verify_password_failed_mock(first: str, second: str): - return False - - monkeypatch.setattr( - security, "verify_password", verify_password_failed_mock - ) - - response = client.post( - "/api/token", data={"username": test_user.email, "password": "wrong"} - ) - assert response.status_code == 401 - - -def test_wrong_login(client, test_db, test_user, test_password): - response = client.post( - "/api/token", data={"username": "fakeuser", "password": test_password} - ) - assert response.status_code == 401 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py b/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py deleted file mode 100644 index 5f285b03..00000000 --- a/fastapi-react-project/backend/app/api/api_v1/routers/tests/test_users.py +++ /dev/null @@ -1,110 +0,0 @@ -from app.db import models - - -def test_get_users(client, test_superuser, superuser_token_headers): - response = client.get("/api/v1/users", headers=superuser_token_headers) - assert response.status_code == 200 - assert response.json() == [ - { - "id": test_superuser.id, - "email": test_superuser.email, - "is_active": test_superuser.is_active, - "is_superuser": test_superuser.is_superuser, - } - ] - - -def test_delete_user(client, test_superuser, test_db, superuser_token_headers): - response = client.delete( - f"/api/v1/users/{test_superuser.id}", headers=superuser_token_headers - ) - assert response.status_code == 200 - assert test_db.query(models.User).all() == [] - - -def test_delete_user_not_found(client, superuser_token_headers): - response = client.delete( - "/api/v1/users/4321", headers=superuser_token_headers - ) - assert response.status_code == 404 - - -def test_edit_user(client, test_superuser, superuser_token_headers): - new_user = { - "email": "newemail@email.com", - "is_active": False, - "is_superuser": True, - "first_name": "Joe", - "last_name": "Smith", - "password": "new_password", - } - - response = client.put( - f"/api/v1/users/{test_superuser.id}", - json=new_user, - headers=superuser_token_headers, - ) - assert response.status_code == 200 - new_user["id"] = test_superuser.id - new_user.pop("password") - assert response.json() == new_user - - -def test_edit_user_not_found(client, test_db, superuser_token_headers): - new_user = { - "email": "newemail@email.com", - "is_active": False, - "is_superuser": False, - "password": "new_password", - } - response = client.put( - "/api/v1/users/1234", json=new_user, headers=superuser_token_headers - ) - assert response.status_code == 404 - - -def test_get_user( - client, - test_user, - superuser_token_headers, -): - response = client.get( - f"/api/v1/users/{test_user.id}", headers=superuser_token_headers - ) - assert response.status_code == 200 - assert response.json() == { - "id": test_user.id, - "email": test_user.email, - "is_active": bool(test_user.is_active), - "is_superuser": test_user.is_superuser, - } - - -def test_user_not_found(client, superuser_token_headers): - response = client.get("/api/v1/users/123", headers=superuser_token_headers) - assert response.status_code == 404 - - -def test_authenticated_user_me(client, user_token_headers): - response = client.get("/api/v1/users/me", headers=user_token_headers) - assert response.status_code == 200 - - -def test_unauthenticated_routes(client): - response = client.get("/api/v1/users/me") - assert response.status_code == 401 - response = client.get("/api/v1/users") - assert response.status_code == 401 - response = client.get("/api/v1/users/123") - assert response.status_code == 401 - response = client.put("/api/v1/users/123") - assert response.status_code == 401 - response = client.delete("/api/v1/users/123") - assert response.status_code == 401 - - -def test_unauthorized_routes(client, user_token_headers): - response = client.get("/api/v1/users", headers=user_token_headers) - assert response.status_code == 403 - response = client.get("/api/v1/users/123", headers=user_token_headers) - assert response.status_code == 403 diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/todos.py b/fastapi-react-project/backend/app/api/api_v1/routers/todos.py deleted file mode 100644 index b3790dbb..00000000 --- a/fastapi-react-project/backend/app/api/api_v1/routers/todos.py +++ /dev/null @@ -1,85 +0,0 @@ -from fastapi import APIRouter, Request, Depends, Response -import typing as t - -from app.db.session import get_db -from app.db.crud import ( - get_todos, - get_todo, - create_todo, - delete_todo, - edit_todo, -) -from app.db.schemas import TodoCreate, TodoEdit, TodoOut - -todos_router = r = APIRouter() - -@r.get( - "/todos", - response_model=t.List[TodoOut], - response_model_exclude_none=True, -) -async def todos_list( - response: Response, - db=Depends(get_db), -): - """ - Get all todos - """ - todos = get_todos(db) - response.headers["Content-Range"] = f"0-9/{len(todos)}" - return todos - - -@r.get( - "/todos/{todo_id}", - response_model=TodoOut, - response_model_exclude_none=True, -) -async def todo_details( - request: Request, - todo_id: int, - db=Depends(get_db), -): - """ - Get any todo details - """ - todo = get_todo(db, todo_id) - return todo - -@r.post("/todos", response_model=TodoOut, response_model_exclude_none=True) -async def todo_create( - request: Request, - todo: TodoCreate, - db=Depends(get_db), -): - """ - Create a new todo - """ - return create_todo(db, todo) - -@r.put( - "/todos/{todo_id}", response_model=TodoOut, response_model_exclude_none=True -) -async def todo_id_edit( - request: Request, - todo_id: int, - todo: TodoEdit, - db=Depends(get_db), -): - """ - Update existing todo - """ - return edit_todo(db, todo_id, todo) - -@r.delete( - "/todos/{todo_id}", response_model=TodoOut, response_model_exclude_none=True -) -async def todo_delete( - request: Request, - todo_id: int, - db=Depends(get_db), -): - """ - Delete existing todo - """ - return delete_todo(db, todo_id) \ No newline at end of file diff --git a/fastapi-react-project/backend/app/api/api_v1/routers/users.py b/fastapi-react-project/backend/app/api/api_v1/routers/users.py deleted file mode 100644 index 06167e27..00000000 --- a/fastapi-react-project/backend/app/api/api_v1/routers/users.py +++ /dev/null @@ -1,107 +0,0 @@ -from fastapi import APIRouter, Request, Depends, Response, encoders -import typing as t - -from app.db.session import get_db -from app.db.crud import ( - get_users, - get_user, - create_user, - delete_user, - edit_user, -) -from app.db.schemas import UserCreate, UserEdit, User, UserOut -from app.core.auth import get_current_active_user, get_current_active_superuser - -users_router = r = APIRouter() - - -@r.get( - "/users", - response_model=t.List[User], - response_model_exclude_none=True, -) -async def users_list( - response: Response, - db=Depends(get_db), - current_user=Depends(get_current_active_superuser), -): - """ - Get all users - """ - users = get_users(db) - # This is necessary for react-admin to work - response.headers["Content-Range"] = f"0-9/{len(users)}" - return users - - -@r.get("/users/me", response_model=User, response_model_exclude_none=True) -async def user_me(current_user=Depends(get_current_active_user)): - """ - Get own user - """ - return current_user - - -@r.get( - "/users/{user_id}", - response_model=User, - response_model_exclude_none=True, -) -async def user_details( - request: Request, - user_id: int, - db=Depends(get_db), - current_user=Depends(get_current_active_superuser), -): - """ - Get any user details - """ - user = get_user(db, user_id) - return user - # return encoders.jsonable_encoder( - # user, skip_defaults=True, exclude_none=True, - # ) - - -@r.post("/users", response_model=User, response_model_exclude_none=True) -async def user_create( - request: Request, - user: UserCreate, - db=Depends(get_db), - current_user=Depends(get_current_active_superuser), -): - """ - Create a new user - """ - return create_user(db, user) - - -@r.put( - "/users/{user_id}", response_model=User, response_model_exclude_none=True -) -async def user_edit( - request: Request, - user_id: int, - user: UserEdit, - db=Depends(get_db), - current_user=Depends(get_current_active_superuser), -): - """ - Update existing user - """ - return edit_user(db, user_id, user) - - -@r.delete( - "/users/{user_id}", response_model=User, response_model_exclude_none=True -) -async def user_delete( - request: Request, - user_id: int, - db=Depends(get_db), - current_user=Depends(get_current_active_superuser), -): - """ - Delete existing user - """ - return delete_user(db, user_id) diff --git a/fastapi-react-project/backend/app/api/dependencies/__init__.py b/fastapi-react-project/backend/app/api/dependencies/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/core/__init__.py b/fastapi-react-project/backend/app/core/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/core/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/core/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 9629c87dc1ebc473666984a8d0d7f6315dc45d04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B!Ke3s6a_yU~g%Aw*bAZU1RoP?z+7rdJ1H*cQbyx;q`Yt3ejz>~jt#9su2 z{Dzz9$A!%ou+*xUA--8kGZwPU37yOhU5YX%C0Qk`Sl&&&%n$u62!pH|R;{d( z*0Oq7&l+LF?!B~`wZax9f{E&IW}m{lHEi$G2TaUz{|V&*uRgAXb0D7sDHZC;L-;9`&n;#whs% z5pyAJwBvL$hX@TaWgIoxF!iyJSz<%1AjTCNS9uD8(``uyUJ^^yEMWSgPe1xNT3fxg z)?4}Z?yYe7hH-}58n40YWE~Z2_m-FLF889_%lA$7+Sgx#VsGUe<3R4Dj|$Zu=H*7L zV_8A-RV4F3x`APQLpFd77pCw=;-qT)alDh}F}M2G$*^YA96^v`<6?0rZH`)PtnTJgFHsg*4k-c3Nj>WPSdj54zP9zYnHj`tAoz&@w9mr@Wu?B4 z+yx}BByASU^0qs$^%1ZEnEJVH_s&4x}v3kCy@ za_7JWq%gj0+1>1ci`aM4diM@;7?NX{*cWBT*47|XOLow9E3ZQp1|zX;y?+&7TP2&L zXflqD6|RC+bq)wQf(O|HfJou@r~A^a$}4c%Ju9(kjQ=2pi8f9o&hdZdksTeO2OYi$ zBLl13x+*awre2B{`*Om|lpz)qpcrRCIjB4=Fuq}$Ip*M#$o}C77>Zij?lvyMw(Vjo z%jUrPHU-kAvtzA)4*y(}R9wZzR~XWO&eH~)r)}DB0}>npzQe{j58m!!qiWyOec7nB GtA7J@mhr0q diff --git a/fastapi-react-project/backend/app/core/__pycache__/celery_app.cpython-38.pyc b/fastapi-react-project/backend/app/core/__pycache__/celery_app.cpython-38.pyc deleted file mode 100644 index a27f7d4435296bfe392bd6c74d6da25b3c220722..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 279 zcmWIL<>g`kg8b)ADYJm|V-N=!umCv@KwPW=BvKes7;_kM8KW2(8B!Tjm{OQiSbCYF zm{M3%*fJSYnX;JUSyI@888kUw0#!3;GTvfyPR&Uzs*GYQ&o9bOEvgbJN=?Zuw$j&! z&}PQwmih*oj8SY!Mfo6k?!|#BwDz4nb%skz~($vyaKTXyswq&qbMNB}W zZ*f65@j&%aEXnzKX;IuDZSh6g`kg8b)ADQ!UdF^GcFa|Sdvc3eWVeqRGO-n2;Ni4|JElN#HF3~M0%FjwoE{WoCaSU;EatwBj4-N9E zV%G<%&@VI8WWB|dUwn%vAjsd#)j1^I&(YWQ7F&91Noro%E%D$$A4eZ&4_DvFcqHAP zQCyAzp7CLZ@xdWMD;bK|fYySEU*bSh3&1!zzbI8dIX^EgGhMHs@)n0pZhlH>PO2Rv PP;D_t0|yfe6BiQzu5m!Z diff --git a/fastapi-react-project/backend/app/core/__pycache__/security.cpython-38.pyc b/fastapi-react-project/backend/app/core/__pycache__/security.cpython-38.pyc deleted file mode 100644 index d8e38a06352280da7856ed6acdf96df4b862322d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1237 zcmZ8g&2HQ_5GE-~`{VudI*HL1Xwf1UU2MQWdn$@Li$!9jSqEMlXn7H2k?|^(RszMX zvxR&Kay5_#@WFe`BbC=43co^6o#C#HpcFWq8O@L%XFi^FyKMra_~*U+%Om7(G(I03 zjGtjvA3;bWX+{LDETcISjH2#kUd{z~I?w#v7k(ayfD*-|FN13;8j8!t1(i+Nx@Mv& z+oC16L|b;mmg=aE?A|chll>1|bkSBlIk@rUwj6?|FR9$Qp=zK4`3T}3%O@WKu`TJ8 zgu8cuicCUk9~~bq%#B4nabB{P+)%C@B{wMt+sDLU1nR643uqLmT; zi|N7lKiqu{u<*2UgU1H&bD`%6MZlYZI@FwuU{=@$LvCqJR&cXVbOPyH<)w_0HFnEq zaWxC6#=uWR_hI?>3jkianG{;>1BQi8&2q0?+U*NvqWj|`%*Bj~fgn_GgA6^}E3-JA zKZMyn#wq#W&|46fxE#!bfjV-+VeWvk#$3}Cn-YzVkm)WgHuyv7^vx2?A((mx1wvSh z0fzL~ArHr4%ss-#phGjd>Rp*?RYk5-P!kRfjP(=vo4|AfG5MS6a81Cd$t_utD^^qe zrJ=WMMQXZBuDtnhZ5a=E#jc&NX182=wU^QrrNsDegPNI!X-a9U9Z#)S8SPkT z`FT-f8ru!MO%vmQeReU&8Aq4qk9i>%nfeJIPK6X`(hhtB)@D7%;d0y?@FD*X!~`?2 diff --git a/fastapi-react-project/backend/app/core/auth.py b/fastapi-react-project/backend/app/core/auth.py deleted file mode 100644 index 0b404b2f..00000000 --- a/fastapi-react-project/backend/app/core/auth.py +++ /dev/null @@ -1,75 +0,0 @@ -import jwt -from fastapi import Depends, HTTPException, status -from jwt import PyJWTError - -from app.db import models, schemas, session -from app.db.crud import get_user_by_email, create_user -from app.core import security - - -async def get_current_user( - db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme) -): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode( - token, security.SECRET_KEY, algorithms=[security.ALGORITHM] - ) - email: str = payload.get("sub") - if email is None: - raise credentials_exception - permissions: str = payload.get("permissions") - token_data = schemas.TokenData(email=email, permissions=permissions) - except PyJWTError: - raise credentials_exception - user = get_user_by_email(db, token_data.email) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: models.User = Depends(get_current_user), -): - if not current_user.is_active: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -async def get_current_active_superuser( - current_user: models.User = Depends(get_current_user), -) -> models.User: - if not current_user.is_superuser: - raise HTTPException( - status_code=403, detail="The user doesn't have enough privileges" - ) - return current_user - - -def authenticate_user(db, email: str, password: str): - user = get_user_by_email(db, email) - if not user: - return False - if not security.verify_password(password, user.hashed_password): - return False - return user - - -def sign_up_new_user(db, email: str, password: str): - user = get_user_by_email(db, email) - if user: - return False # User already exists - new_user = create_user( - db, - schemas.UserCreate( - email=email, - password=password, - is_active=True, - is_superuser=False, - ), - ) - return new_user diff --git a/fastapi-react-project/backend/app/core/celery_app.py b/fastapi-react-project/backend/app/core/celery_app.py deleted file mode 100644 index 8355ef0d..00000000 --- a/fastapi-react-project/backend/app/core/celery_app.py +++ /dev/null @@ -1,5 +0,0 @@ -from celery import Celery - -celery_app = Celery("worker", broker="redis://redis:6379/0") - -celery_app.conf.task_routes = {"app.tasks.*": "main-queue"} diff --git a/fastapi-react-project/backend/app/core/config.py b/fastapi-react-project/backend/app/core/config.py deleted file mode 100644 index 3a6f2852..00000000 --- a/fastapi-react-project/backend/app/core/config.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - -PROJECT_NAME = "fastapi-react-project" - -SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL") - -API_V1_STR = "/api/v1" diff --git a/fastapi-react-project/backend/app/core/security.py b/fastapi-react-project/backend/app/core/security.py deleted file mode 100644 index eb0cffce..00000000 --- a/fastapi-react-project/backend/app/core/security.py +++ /dev/null @@ -1,31 +0,0 @@ -import jwt -from fastapi.security import OAuth2PasswordBearer -from passlib.context import CryptContext -from datetime import datetime, timedelta - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token") - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - -SECRET_KEY = "super_secret" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -def get_password_hash(password: str) -> str: - return pwd_context.hash(password) - - -def verify_password(plain_password: str, hashed_password: str) -> bool: - return pwd_context.verify(plain_password, hashed_password) - - -def create_access_token(*, data: dict, expires_delta: timedelta = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt diff --git a/fastapi-react-project/backend/app/db/__init__.py b/fastapi-react-project/backend/app/db/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/db/__pycache__/__init__.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 7cf10b685303a0b422034cd89bbfcd9ce588e43f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114 zcmWIL<>g`kg8b)ADIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2B;Ke3|i|ee^Xyr>{Bj7hDyR@AXKsB~wWbM5=z%-S2hxTfgqrv9YXy-|u_B^7CVc@dphK z{#*=hp(U4@VQ_=9*l4gaYnWw|(b$ZwhF!LG--?~G%Wdw&Uc)c@jZ8VC=iNBl$dz-< z2<y;mmpdFF*(9>aJ%%+x1%_LaqRJde2ozerN}yY)%%re+O3{>tVPyzs&)PlK1K z&+tj?F~z4Le`K)d3_pVLQN21-KL)wu{3uo)StL0{~?gT%H@d**j;B zpTaqF;AQHk_&nrK^99Jy57wXIXE8plS7+)AkUPWA;hgjQ0_M+hYsDyDeoa0snriHR zsr19$XVtJRqgGS7i40^XDKh1)gh_&_<=5~lNxF>|59366N%e8q2uNn~Q79|zAW42{ z30`>|B#$9y@-^jkl2BA4UNz~M-p|6TdNTeVwcP){@M^LsYoAlo`#4!x^rmqC;wr+gwN>z0D zVtYgRWI9RFqQnO=*gUgYo{3rXi|KC4fv2KaxgwMu(HwFI9XfEwJO=;djum5;I@vO-FJLor9`(86s$RdvRPb1PL5cUAe7VEeWMq09RQ-9QOlo z8WVap)n417O~~|Jo{+Jr>XtZ26vuDbuxf7$f7dJBI$|dNWi)CgP#8I)N(9pb@G}93)bAbx2euN-9BB zMo&YPM>pxTLqQQx6I3Z7A5=~;FGxw{_e<$OgfD<0E)vmEy+pmsBy0toE4SKeAVSI$ zVNHZ6zchS;93hG%=J7S1rlZcxRYe%cQ1fW#goweiNa6jzx##*>=3`b|0e8r?6#qf` zQZ>W1aPF3=D|b#Zaxhh|u3aO|2<=2vH@;NO*0RWBV z%#Y@#v*}8|YjvHjyJa!s$%B7QIxqE@xC(VjyG)IR&#G~UBaTffCbCz@9Vs>JOYsp{ z%H~m3>SWWR%86vyAV>#XN-4zqv|fAmL-dN?J|w0-TBJ>Koi?Y`0ST0~9&RY-X%GWu zM*@)vd6Z%#IXq1;ixO;{?PQBIxqz=<;fOr{Xf@-$l;v5@e&T$zdi&W z7G&)JU6oA&DZEaDr{Q(znz(^kWs{zxX6u`Q7~eu<_-43`nRf}KUz2KQh%68}OXM7p z(MS+-l1j-kxP9cLyzV3CH|QTK??Li^3a&Kj4ESiGY1{-#S}1j68%f?nNwI9lmxcAXaSD+xIOe^db^+p2K$1c+TXYdbW z*LRrhV;AM1Aa5MLp@*=m29f`YUBD;c^8tKv58%^PgxecjiheKLy=#&i2)DB=1|BGh zYou)Ut$v`C5}y(IoXAL|d%&}AAk~uig2yA*E7a}l`ujbIGm<^a&pGXQDN zXbXCF4fW3{uZCMZXh-{jQ{%~#IEd-hyRq034MAz5T)EMXnvWEds`nxzpO+``R$Q#M zM7WrQ)sBedMtZkfQB!J2Ru&$dWq%b9f!hHHJ-w9uAWCqH)8xaB)NdM%3L)W(XJlGMQ=M&$4gw?o^?Wr4e2ZZ}Kw*ddv*-^ZWe20c1eR Ap8x;= diff --git a/fastapi-react-project/backend/app/db/__pycache__/models.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/models.cpython-38.pyc deleted file mode 100644 index 643a22ee910ef3ebd8eef94f3505c9dbde7bf86e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1120 zcmah}&2AGh5Z?dYY_drJm7iPTf^wlRfKXLX5kl&LrWapUZai(b*t=VMmy&b2miNe8 z@EktEsdrACm~l#52_$U!%lCQg@p#6YgQCbiY~Ox<72iY8`$6Jvi%@LiFakk%!k3=% znXdvCsE~y!Vv&kjtP+;^WDjJjGL|6^MI;9*XSth?<&X{EdLj|&7f+;+UxaLgIuirb z1E&iS?|ZZS3QzK8iA{H!Mgp&G^16||s%`qN?%)`-P4+uo)yFn_!#kK)3T(dHXsC|s z_pqG#cBDb_u4-y?Qngn;x$|wb!wu{$$obutpxDM?4iUg(zVKKed=^3ku?Vn-mmy0~ zrA|d6zKmE#`od>}>)+hjBavJNY>0e>Yg6QfT|QHaakPhp0!KuL3)4b)~po zmLFkhimR=!OI9PwUud$Wnn%ChDP%F^a# zsT$Es;-j)W>$$wP6lK|whqcsLYfn>gZBPjv6Ny3bO4|IW(x!6`?LhKBbaKMY35c@g z#+)}==z#VNDpT@BSA7CILTdUJG+k`v=}ojd__eXc+tv+nxP{??G%wnv9^!%&hnXT| zeiV%Tsh?pVVRzQQibZj5)0Phi|0xy~Dp(f^i$R=DcLsNU=DXjpmJL zY}_H5xi0XSU40)>KS0=z*SJnuig^-E z4Vqq_%>+K-j-GJRcZ<3?XZb<{&WVzDw>ez1r{w(^0!3s-263&Lo$M z6HrTc;+^m)GIy?~#M|-h$`_4RI-|+dfH4#;{W0k01XL;K{a*pu?B)8&y`m4z(5_7v JPZmyxzX7eJ3labT diff --git a/fastapi-react-project/backend/app/db/__pycache__/schemas.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/schemas.cpython-38.pyc deleted file mode 100644 index b2227b781c20cb3df23a36dba7902c488e62ce52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2998 zcmbVOO>-MX5Z%@8YF8`SmL12j9U#R=0fH)r1I2-IxeOtJ3(5icvX`xmW)e0?S~0VN zGS_hCfA|kk)Lc1n=fsKkMxUjXD|V!sQNNzA?l(Q%y4c!^9en=$_YZOAJI>z(s~_l` zb2R-II^hU+;0)cwbt%^dwZ!|@5uWgWcZ4t9S6<=+2ciyKmp*U+9Eu1yvbYZ15HWCU zaR}TLE#Q{L5%8AS2Hv*&HGp?S8@O$847@A$fcGqJ0^bq)!27a^^IE`n#R2ev#aqDl z#3AsZ#oNF~;u!eY;vG@@!Rg%p2i`dy*EGJ)wEVsha?tHlwfgY@&e8M}bka#&;Uu*S zM|#p1?kVU<0tu&u2iZ?TD+iG4No41QR&H21qKtzZCc4m4@l+GYVU`birjhG3>zDa2 z@`-6d)|0VR6U^$G_<62$nU1odG>t(v6FaeqQmaf;6Q}905R-v~+)C4*C)t40fjnI* zvy-OTXjGJ0nHM9SrYe9l#(!26167BpLUs|lD@W~6X7}I0!)!dZO+0(3`#-`|eKNjO z_*LN_N+xS*kCLelVbQg*LA)xfaghN{VKVZ~HxQ@4w5 z3r*M1MXtq_^A;EO0Vd7QTV5C;g7*qnCw$lun3#4vQZg&0iN=}MzZObVc3yZw zW9B;xH}PyVdB5#0cR6`fjGpH&dfh93%`p=dYRJoyn_M*Y>B9XLFYiI_cIs>UDAK9O zhT?=xo2fe(AZMEI8TVAPnu=cw#!fJ8?$-rgeS#o=iSRyhWGHH<4!E1WdT6__AqS7T&;(U@$ymmnvfy850*3_WT?5G zhIcT!44HXWw7&=#;o*7tCg>GE$x+hE`hBT&S`wMq{-ovQSav(X`W-H#ds8Byg&(e1 z=_+cjw~}+0R!#$31`KK8CCJ{8eaK8ofoWK4zsbt1tN3t?KjHl~k&lPD*36)taNS-` zTnV%H2{&2(E(ShDn}u1+Rn%N>`R5p2T0ZA@T6JEA(!)P0rPSJ5*es*ET`vJ^@?*&2E!#d=j6bX_ge0g*{hM zSMhF<5;l~Cm{w0yaaZkO^~R$<#=sY7^MEJ5)iIWwY1X>-kPbHea^(=>${`+<=bS`O zt2ec=+=TQIWlz0@HI*F~>|9f3wr8tvm>0gHajSVjy$#JqRzJbO8K*OvZM~qTV&I#& z?9=Es^S8;RH+1$pT7NTV`3fI%`odXiI_)GHUy5v0=6%JDOdT02AHA5q0=jPhcGya4 zON333HvibCY$IMV9u?z7kwL|sH!*Ia)9K%~GhVvp8*90d*N)nLJG9@R9klB}!2bhL CV&^>o diff --git a/fastapi-react-project/backend/app/db/__pycache__/session.cpython-38.pyc b/fastapi-react-project/backend/app/db/__pycache__/session.cpython-38.pyc deleted file mode 100644 index aa66b734900da853f414fd7876231f2a62709121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmYjOy>1jS5FUH~?lyZ-L;(^N?KYQ+4k0ci2&5wt9O1{!%4<891$%widx)UWaJ0Mw zl)MX1uq_4R6=-0*0dZr^=$lWY`DVr^lSvL}_QwbLjR1TP$+jh!+(GvYg9J%57{Uk% zm}+Vw77aMku}N4m;8>?7V;KQORQ7VTA#AJ?iBFAX`YB?$iZ;M@WF|)+ffaJRfHMDy z{h=iNL^#DeRjGE_v7&_R%dw7>$z*8Xb3~s^&d?sHIKgVrxMsi%z zzF1sJ&#mCPPlUF^L{!#a!G&A?y1LI-tHEWx|98;o>by^vs;gwZ7xaQKkd#o8Q&%89 zE+;JauQk`=Oqp{>kz?2Y6I1oJn{HjgMWJAKtH3(rVkG+!%1?!L%7xEk*A|cFv-#co zM-N|AhqEWMgW2M!I(htnrI#mmS*VH+@Up`s!xCFzXXts$Sh?2fcDS0qf}tSzXhA87 Kf5*`-P2)dv5sdW! diff --git a/fastapi-react-project/backend/app/db/crud.py b/fastapi-react-project/backend/app/db/crud.py deleted file mode 100644 index ae9aa129..00000000 --- a/fastapi-react-project/backend/app/db/crud.py +++ /dev/null @@ -1,119 +0,0 @@ -from fastapi import HTTPException, status -from sqlalchemy.orm import Session -import typing as t - -from . import models, schemas -from app.core.security import get_password_hash - - -def get_user(db: Session, user_id: int): - user = db.query(models.User).filter(models.User.id == user_id).first() - if not user: - raise HTTPException(status_code=404, detail="User not found") - return user - - -def get_user_by_email(db: Session, email: str) -> schemas.UserBase: - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users( - db: Session, skip: int = 0, limit: int = 100 -) -> t.List[schemas.UserOut]: - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - hashed_password = get_password_hash(user.password) - db_user = models.User( - first_name=user.first_name, - last_name=user.last_name, - email=user.email, - is_active=user.is_active, - is_superuser=user.is_superuser, - hashed_password=hashed_password, - ) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def delete_user(db: Session, user_id: int): - user = get_user(db, user_id) - if not user: - raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found") - db.delete(user) - db.commit() - return user - - -def edit_user( - db: Session, user_id: int, user: schemas.UserEdit -) -> schemas.User: - db_user = get_user(db, user_id) - if not db_user: - raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found") - update_data = user.dict(exclude_unset=True) - - if "password" in update_data: - update_data["hashed_password"] = get_password_hash(user.password) - del update_data["password"] - - for key, value in update_data.items(): - setattr(db_user, key, value) - - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - -def get_todo(db: Session, todo_id: int): - todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first() - if not todo: - raise HTTPException(status_code=404, detail="Todo not found") - return todo - - -def get_todos( - db: Session, skip: int = 0, limit: int = 100 -) -> t.List[schemas.TodoOut]: - return db.query(models.Todo).offset(skip).limit(limit).all() - - -def create_todo(db: Session, todo: schemas.TodoCreate): - db_todo = models.Todo( - title=todo.title, - description=todo.description, - is_done=todo.is_done - ) - db.add(db_todo) - db.commit() - db.refresh(db_todo) - return db_todo - - -def delete_todo(db: Session, todo_id: int): - todo = get_todo(db, todo_id) - if not todo: - raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Todo not found") - db.delete(todo) - db.commit() - return todo - - -def edit_todo( - db: Session, todo_id: int, todo: schemas.TodoEdit -) -> schemas.Todo: - db_todo = get_todo(db, todo_id) - if not db_todo: - raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Todo not found") - update_data = todo.dict(exclude_unset=True) - - for key, value in update_data.items(): - setattr(db_todo, key, value) - - db.add(db_todo) - db.commit() - db.refresh(db_todo) - return \ No newline at end of file diff --git a/fastapi-react-project/backend/app/db/models.py b/fastapi-react-project/backend/app/db/models.py deleted file mode 100644 index c98c3480..00000000 --- a/fastapi-react-project/backend/app/db/models.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlalchemy import Boolean, Column, Integer, String, DateTime, ForeignKey -from sqlalchemy.orm import relationship -from .session import Base -import datetime - - -class User(Base): - __tablename__ = "user" - - id = Column(Integer, primary_key=True, index=True) - email = Column(String, unique=True, index=True, nullable=False) - first_name = Column(String) - last_name = Column(String) - hashed_password = Column(String, nullable=False) - is_active = Column(Boolean, default=True) - is_superuser = Column(Boolean, default=False) - -class Todo(Base): - __tablename__ = "todos" - - id = Column(Integer, primary_key=True, index=True) - title = Column(String, nullable=False) - description = Column(String, nullable=True) - is_done = Column(Boolean, default=True) - created_at = Column(DateTime, default=datetime.datetime.utcnow) - # user_id = Column(Integer, ForeignKey("user.id")) - - # user = relationship("User", back_populates="todos") \ No newline at end of file diff --git a/fastapi-react-project/backend/app/db/schemas.py b/fastapi-react-project/backend/app/db/schemas.py deleted file mode 100644 index 9da769e0..00000000 --- a/fastapi-react-project/backend/app/db/schemas.py +++ /dev/null @@ -1,73 +0,0 @@ -from pydantic import BaseModel -import typing as t - - -class UserBase(BaseModel): - email: str - is_active: bool = True - is_superuser: bool = False - first_name: str = None - last_name: str = None - - -class UserOut(UserBase): - pass - - -class UserCreate(UserBase): - password: str - - class Config: - orm_mode = True - - -class UserEdit(UserBase): - password: t.Optional[str] = None - - class Config: - orm_mode = True - - -class User(UserBase): - id: int - - class Config: - orm_mode = True - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - email: str = None - permissions: str = "user" - - -class TodoBase(BaseModel): - title: str - description: t.Optional[str] = None - is_done: bool = True - - -class TodoCreate(TodoBase): - pass - - -class TodoEdit(TodoBase): - pass - - -class TodoOut(TodoBase): - id: int - - class Config: - orm_mode = True - - -class Todo(TodoBase): - id: int - - class Config: - orm_mode = True \ No newline at end of file diff --git a/fastapi-react-project/backend/app/db/session.py b/fastapi-react-project/backend/app/db/session.py deleted file mode 100644 index d7e2f6c5..00000000 --- a/fastapi-react-project/backend/app/db/session.py +++ /dev/null @@ -1,21 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -from app.core import config - -engine = create_engine( - config.SQLALCHEMY_DATABASE_URI, -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() diff --git a/fastapi-react-project/backend/app/initial_data.py b/fastapi-react-project/backend/app/initial_data.py deleted file mode 100644 index dc781698..00000000 --- a/fastapi-react-project/backend/app/initial_data.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 - -from app.db.session import get_db -from app.db.crud import create_user -from app.db.schemas import UserCreate -from app.db.session import SessionLocal - - -def init() -> None: - db = SessionLocal() - - create_user( - db, - UserCreate( - email="admin@fastapi-react-project.com", - password="password", - is_active=True, - is_superuser=True, - ), - ) - - -if __name__ == "__main__": - print("Creating superuser admin@fastapi-react-project.com") - init() - print("Superuser created") diff --git a/fastapi-react-project/backend/app/main.py b/fastapi-react-project/backend/app/main.py deleted file mode 100644 index b9a512c3..00000000 --- a/fastapi-react-project/backend/app/main.py +++ /dev/null @@ -1,51 +0,0 @@ -from fastapi import FastAPI, Depends -from starlette.requests import Request -import uvicorn - -from app.api.api_v1.routers.users import users_router -from app.api.api_v1.routers.auth import auth_router -from app.api.api_v1.routers.todos import todos_router -from app.core import config -from app.db.session import SessionLocal -from app.core.auth import get_current_active_user -from app.core.celery_app import celery_app -from app import tasks - - -app = FastAPI( - title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api" -) - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - request.state.db = SessionLocal() - response = await call_next(request) - request.state.db.close() - return response - - -@app.get("/api/v1") -async def root(): - return {"message": "Hello World"} - - -@app.get("/api/v1/task") -async def example_task(): - celery_app.send_task("app.tasks.example_task", args=["Hello World"]) - - return {"message": "success"} - - -# Routers -app.include_router( - users_router, - prefix="/api/v1", - tags=["users"], - dependencies=[Depends(get_current_active_user)], -) -app.include_router(auth_router, prefix="/api", tags=["auth"]) -app.include_router(todos_router, prefix="/api/v1", tags=["todos"]) - -if __name__ == "__main__": - uvicorn.run("main:app", host="0.0.0.0", reload=True, port=8888) diff --git a/fastapi-react-project/backend/app/tasks.py b/fastapi-react-project/backend/app/tasks.py deleted file mode 100644 index c17d5063..00000000 --- a/fastapi-react-project/backend/app/tasks.py +++ /dev/null @@ -1,6 +0,0 @@ -from app.core.celery_app import celery_app - - -@celery_app.task(acks_late=True) -def example_task(word: str) -> str: - return f"test task returns {word}" diff --git a/fastapi-react-project/backend/app/tests/__init__.py b/fastapi-react-project/backend/app/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi-react-project/backend/app/tests/test_main.py b/fastapi-react-project/backend/app/tests/test_main.py deleted file mode 100644 index 024cf9e5..00000000 --- a/fastapi-react-project/backend/app/tests/test_main.py +++ /dev/null @@ -1,4 +0,0 @@ -def test_read_main(client): - response = client.get("/api/v1") - assert response.status_code == 200 - assert response.json() == {"message": "Hello World"} diff --git a/fastapi-react-project/backend/app/tests/test_tasks.py b/fastapi-react-project/backend/app/tests/test_tasks.py deleted file mode 100644 index 7f49f1ca..00000000 --- a/fastapi-react-project/backend/app/tests/test_tasks.py +++ /dev/null @@ -1,6 +0,0 @@ -from app import tasks - - -def test_example_task(): - task_output = tasks.example_task("Hello World") - assert task_output == "test task returns Hello World" diff --git a/fastapi-react-project/backend/conftest.py b/fastapi-react-project/backend/conftest.py deleted file mode 100644 index ecd831dc..00000000 --- a/fastapi-react-project/backend/conftest.py +++ /dev/null @@ -1,169 +0,0 @@ -import pytest -from sqlalchemy import create_engine, event -from sqlalchemy.orm import sessionmaker -from sqlalchemy_utils import database_exists, create_database, drop_database -from fastapi.testclient import TestClient -import typing as t - -from app.core import config, security -from app.db.session import Base, get_db -from app.db import models -from app.main import app - - -def get_test_db_url() -> str: - return f"{config.SQLALCHEMY_DATABASE_URI}_test" - - -@pytest.fixture -def test_db(): - """ - Modify the db session to automatically roll back after each test. - This is to avoid tests affecting the database state of other tests. - """ - # Connect to the test database - engine = create_engine( - get_test_db_url(), - ) - - connection = engine.connect() - trans = connection.begin() - - # Run a parent transaction that can roll back all changes - test_session_maker = sessionmaker( - autocommit=False, autoflush=False, bind=engine - ) - test_session = test_session_maker() - test_session.begin_nested() - - @event.listens_for(test_session, "after_transaction_end") - def restart_savepoint(s, transaction): - if transaction.nested and not transaction._parent.nested: - s.expire_all() - s.begin_nested() - - yield test_session - - # Roll back the parent transaction after the test is complete - test_session.close() - trans.rollback() - connection.close() - - -@pytest.fixture(scope="session", autouse=True) -def create_test_db(): - """ - Create a test database and use it for the whole test session. - """ - - test_db_url = get_test_db_url() - - # Create the test database - assert not database_exists( - test_db_url - ), "Test database already exists. Aborting tests." - create_database(test_db_url) - test_engine = create_engine(test_db_url) - Base.metadata.create_all(test_engine) - - # Run the tests - yield - - # Drop the test database - drop_database(test_db_url) - - -@pytest.fixture -def client(test_db): - """ - Get a TestClient instance that reads/write to the test database. - """ - - def get_test_db(): - yield test_db - - app.dependency_overrides[get_db] = get_test_db - - yield TestClient(app) - - -@pytest.fixture -def test_password() -> str: - return "securepassword" - - -def get_password_hash() -> str: - """ - Password hashing can be expensive so a mock will be much faster - """ - return "supersecrethash" - - -@pytest.fixture -def test_user(test_db) -> models.User: - """ - Make a test user in the database - """ - - user = models.User( - email="fake@email.com", - hashed_password=get_password_hash(), - is_active=True, - ) - test_db.add(user) - test_db.commit() - return user - - -@pytest.fixture -def test_superuser(test_db) -> models.User: - """ - Superuser for testing - """ - - user = models.User( - email="fakeadmin@email.com", - hashed_password=get_password_hash(), - is_superuser=True, - ) - test_db.add(user) - test_db.commit() - return user - - -def verify_password_mock(first: str, second: str) -> bool: - return True - - -@pytest.fixture -def user_token_headers( - client: TestClient, test_user, test_password, monkeypatch -) -> t.Dict[str, str]: - monkeypatch.setattr(security, "verify_password", verify_password_mock) - - login_data = { - "username": test_user.email, - "password": test_password, - } - r = client.post("/api/token", data=login_data) - tokens = r.json() - a_token = tokens["access_token"] - headers = {"Authorization": f"Bearer {a_token}"} - return headers - - -@pytest.fixture -def superuser_token_headers( - client: TestClient, test_superuser, test_password, monkeypatch -) -> t.Dict[str, str]: - monkeypatch.setattr(security, "verify_password", verify_password_mock) - - login_data = { - "username": test_superuser.email, - "password": test_password, - } - r = client.post("/api/token", data=login_data) - tokens = r.json() - a_token = tokens["access_token"] - headers = {"Authorization": f"Bearer {a_token}"} - return headers diff --git a/fastapi-react-project/backend/pyproject.toml b/fastapi-react-project/backend/pyproject.toml deleted file mode 100644 index 627a23c9..00000000 --- a/fastapi-react-project/backend/pyproject.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tool.black] -line-length = 80 \ No newline at end of file diff --git a/fastapi-react-project/backend/requirements.txt b/fastapi-react-project/backend/requirements.txt deleted file mode 100644 index 7f8389d3..00000000 --- a/fastapi-react-project/backend/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -alembic==1.4.3 -Authlib==0.14.3 -fastapi==0.65.2 -celery==5.0.0 -redis==3.5.3 -httpx==0.15.5 -ipython==7.31.1 -itsdangerous==1.1.0 -Jinja2==2.11.3 -psycopg2==2.8.6 -pytest==6.1.0 -requests==2.24.0 -SQLAlchemy==1.3.19 -uvicorn==0.12.1 -passlib==1.7.2 -bcrypt==3.2.0 -sqlalchemy-utils==0.36.8 -python-multipart==0.0.5 -pyjwt==1.7.1 \ No newline at end of file diff --git a/fastapi-react-project/docker-compose.yml b/fastapi-react-project/docker-compose.yml deleted file mode 100644 index b1a41e33..00000000 --- a/fastapi-react-project/docker-compose.yml +++ /dev/null @@ -1,71 +0,0 @@ -version: '3.7' -services: - nginx: - image: nginx:1.17 - volumes: - - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf - ports: - - 8000:80 - depends_on: - - backend - - frontend - - redis: - image: redis - ports: - - 6379:6379 - - postgres: - image: postgres:12 - restart: always - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - ports: - - '5432:5432' - volumes: - - db-data:/var/lib/postgresql/data:cached - - worker: - build: - context: backend - dockerfile: Dockerfile - command: celery --app app.tasks worker --loglevel=DEBUG -Q main-queue -c 1 - - flower: - image: mher/flower - command: celery flower --broker=redis://redis:6379/0 --port=5555 - ports: - - 5555:5555 - depends_on: - - "redis" - - backend: - build: - context: backend - dockerfile: Dockerfile - command: python app/main.py - tty: true - volumes: - - ./backend:/app/:cached - - ./.docker/.ipython:/root/.ipython:cached - environment: - PYTHONPATH: . - DATABASE_URL: 'postgresql://postgres:password@postgres:5432/postgres' - depends_on: - - "postgres" - - frontend: - build: - context: frontend - dockerfile: Dockerfile - stdin_open: true - volumes: - - './frontend:/app:cached' - - './frontend/node_modules:/app/node_modules:cached' - environment: - - NODE_ENV=development - - -volumes: - db-data: diff --git a/fastapi-react-project/frontend/.dockerignore b/fastapi-react-project/frontend/.dockerignore deleted file mode 100644 index 25c8fdba..00000000 --- a/fastapi-react-project/frontend/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -package-lock.json \ No newline at end of file diff --git a/fastapi-react-project/frontend/.eslintrc.js b/fastapi-react-project/frontend/.eslintrc.js deleted file mode 100644 index 428e4222..00000000 --- a/fastapi-react-project/frontend/.eslintrc.js +++ /dev/null @@ -1,51 +0,0 @@ -let rules = { - 'max-len': ['error', 80, 2, { ignoreUrls: true }], - 'no-console': [0], - 'no-restricted-syntax': 'off', - 'no-continue': 'off', - 'no-underscore-dangle': 'off', - 'import/extensions': 'off', - 'import/no-unresolved': 'off', - 'operator-linebreak': 'off', - 'implicit-arrow-linebreak': 'off', - 'react/destructuring-assignment': 'off', - 'jsx-a11y/click-events-have-key-events': 'off', - 'jsx-a11y/no-static-element-interactions': 'off', - 'react/jsx-one-expression-per-line': 'off', - 'react/jsx-filename-extension': [2, { extensions: ['.ts', '.tsx'] }], - 'lines-between-class-members': [ - 'error', - 'always', - { exceptAfterSingleLine: true }, - ], -}; - -module.exports = { - extends: ['airbnb', 'plugin:prettier/recommended', 'prettier/react'], - parser: 'babel-eslint', - rules, - env: { - browser: true, - commonjs: true, - node: true, - jest: true, - es6: true, - }, - plugins: ['react', 'react-hooks', 'jsx-a11y'], - settings: { - ecmascript: 6, - jsx: true, - 'import/resolver': { - node: { - paths: ['src'], - }, - }, - 'import/parsers': { - '@typescript-eslint/parser': ['.ts', '.tsx'], - }, - react: { - pragma: 'React', - version: '16.8', - }, - }, -}; diff --git a/fastapi-react-project/frontend/.prettierrc.js b/fastapi-react-project/frontend/.prettierrc.js deleted file mode 100644 index 158883bf..00000000 --- a/fastapi-react-project/frontend/.prettierrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - printWidth: 80, - singleQuote: true, - trailingComma: 'es5', -}; diff --git a/fastapi-react-project/frontend/Dockerfile b/fastapi-react-project/frontend/Dockerfile deleted file mode 100644 index d398ff4f..00000000 --- a/fastapi-react-project/frontend/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:12 - -ADD package.json /package.json - -ENV NODE_PATH=/node_modules -ENV PATH=$PATH:/node_modules/.bin -RUN npm install - -WORKDIR /app -ADD . /app - -EXPOSE 8000 -EXPOSE 35729 - -ENTRYPOINT ["/bin/bash", "/app/run.sh"] -CMD ["start"] diff --git a/fastapi-react-project/frontend/README.md b/fastapi-react-project/frontend/README.md deleted file mode 100644 index 54ef0943..00000000 --- a/fastapi-react-project/frontend/README.md +++ /dev/null @@ -1,68 +0,0 @@ -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting - -### Analyzing the Bundle Size - -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size - -### Making a Progressive Web App - -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app - -### Advanced Configuration - -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration - -### Deployment - -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment - -### `npm run build` fails to minify - -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/fastapi-react-project/frontend/package.json b/fastapi-react-project/frontend/package.json deleted file mode 100644 index 14dd1f81..00000000 --- a/fastapi-react-project/frontend/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "fastapi-react", - "version": "0.1.0", - "private": true, - "dependencies": { - "ra-data-json-server": "^3.5.2", - "ra-data-simple-rest": "^3.3.2", - "react": "^16.13.1", - "react-admin": "^3.5.2", - "react-dom": "^16.13.1", - "react-router-dom": "^5.1.2", - "react-scripts": "3.4.3", - "react-truncate": "^2.4.0", - "standard": "^14.3.3", - "jwt-decode": "^3.0.0", - "@material-ui/lab": "^4.0.0-alpha.54" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "CI=true react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "airbnb" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "typescript": "^4.0.2", - "@testing-library/jest-dom": "^5.11.1", - "@testing-library/react": "^11.0.4", - "@typescript-eslint/eslint-plugin": "^2.24.0", - "@typescript-eslint/parser": "^2.24.0", - "@testing-library/user-event": "^12.0.11", - "@types/jest": "^26.0.3", - "@types/node": "^14.0.1", - "@types/react": "^16.9.19", - "@types/react-dom": "^16.9.5", - "@types/react-router-dom": "^5.1.3", - "@types/jwt-decode": "^2.2.1", - "eslint-config-airbnb": "^18.1.0", - "eslint-config-react-app": "^5.2.1", - "eslint-plugin-flowtype": "^4.6.0", - "eslint-plugin-import": "^2.20.1", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.19.0", - "eslint-plugin-react-hooks": "^2.5.1", - "prettier": "^2.0.5", - "react-test-renderer": "^16.13.1" - } -} diff --git a/fastapi-react-project/frontend/public/favicon.ico b/fastapi-react-project/frontend/public/favicon.ico deleted file mode 100644 index bcd5dfd67cd0361b78123e95c2dd96031f27f743..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3150 zcmaKtc{Ei0AIGn;MZ^<@lHD*OV;K7~W1q3jSjJcqNywTkMOhP*k~Oj?GO|6{m(*C2 zC7JA+hN%%Bp7T4;J@?%2_x=5zbI<2~->=X60stMr0B~{wzpi9D0MG|# zyuANt7z6;uz%?PEfAnimLl^)6h5ARwGXemG2>?hqQv-I^Gpyh$JH}Ag92}3{$a#z& zd`il2Sb#$U&e&4#^4R|GTgk!Qs+x*PCL{2+`uB5mqtnqLaaw`*H2oqJ?XF(zUACc2 zSibBrdQzcidqv*TK}rpEv1ie&;Famq2IK5%4c}1Jt2b1x_{y1C!?EU)@`_F)yN*NK z)(u03@%g%uDawwXGAMm%EnP9FgoucUedioDwL~{6RVO@A-Q$+pwVRR%WYR>{K3E&Q zzqzT!EEZ$_NHGYM6&PK#CGUV$pTWsiI5#~m>htoJ!vbc0=gm3H8sz8KzIiVN5xdCT z%;}`UH2Pc8))1VS-unh?v4*H*NIy5On{MRKw7BTmOO9oE2UApwkCl9Z?^dod9M^#w z51tEZhf+#dpTo#GDDy#kuzoIjMjZ?%v*h$ z*vwUMOjGc?R0(FjLWkMD)kca4z6~H45FIzQ!Zzu&-yWyMdCBsDr2`l}Q{8fH$H@O< z$&snNzbqLk?(GIe?!PVh?F~2qk4z^rMcp$P^hw^rUPjyCyoNTRw%;hNOwrCoN?G0E z!wT^=4Loa9@O{t;Wk(Nj=?ms1Z?UN_;21m%sUm?uib=pg&x|u)8pP#l--$;B9l47n zUUnMV0sXLe*@Gvy>XWjRoqc2tOzgYn%?g@Lb8C&WsxV1Kjssh^ZBs*Ysr+E6%tsC_ zCo-)hkYY=Bn?wMB4sqm?WS>{kh<6*DO)vXnQpQ9`-_qF6!#b;3Nf@;#B>e2j$yokl6F|9p1<($2 z=WSr%)Z?^|r6njhgbuMrIN>8JE05u0x5t@_dEfbGn9r0hK4c2vp>(*$GXsjeLL_uz zWpyfUgdv!~-2N;llVzik#s2*XB*%7u8(^sJv&T3pzaR&<9({17Zs~UY>#ugZZkHBs zD+>0_an$?}utGp$dcXtyFHnTQZJ}SF=oZ}X07dz~K>^o(vjTzw8ZQc!Fw1W=&Z?9% zv63|~l}70sJbY?H8ON8j)w5=6OpXuaZ}YT03`2%u8{;B0Vafo_iY7&BiQTbRkdJBYL}?%ATfmc zLG$uXt$@3j#OIjALdT&Ut$=9F8cgV{w_f5eS)PjoVi z&oemp-SKJ~UuGuCP1|iY?J^S&P z)-IG?O-*=z6kfZrX5H*G=aQ{ZaqnOqP@&+_;nq@mA>EcjgxrYX8EK|Iq4&E&rxR?R z8N$QOdRwY zr{P`O)=87>YLHtFfGXW z6P)ucrhj~It_9w<^v5>T6N1U}+BkS))=WX*2JY=}^b2czGhH<`?`(}}qMcpPx_%>M zM|fs(+I1m&_h(zqp-HgP>re$2O^o$q)xu#fl0ivOJE({duU)a*OD(eYgSi^cdTn}pqcPM(;S)2%1By^Wh%-CaC%>d9hi`7J zaxL7@;nhA>PE%s99&;z{8>VFgf{u!(-B-x7Of6ueme+ScryL`h(^qKE)DtieWY>-7 zgB)VJESQS4*1LU(2&@pgLvSt{(((C?K_V(rQk``i&5}ZPG;G^FiPlZ$7|-vEmMWlU z5lQ%iK2nu=h2wd_7>gK@vX=*AG+u~rQP$NwPC`ZA?4nh{3tui1x@bT6-;Rk3yDQ>d z?3qRD#+PeV7#FAa>s`Xwxsx_oRFcN$StW2=CW`=qObsT?SD^#^jM1Yk}PSPxJ zG@-_mnNU_)vM|iLRSI>UMp|hatyS}17R{10IuL0TLlupt>9dRs_SPQbv7BLYyC#qv16E-y@XZ= z-!p7I%#r-BVi$nQq3&ssRc_IC%R6$tA&^s_l46880~Wst3@>(|EO<}T4~ci~#!=e; zD)B>o%1+$ksURD1p7I-<3ehlFyVkqrySf&gg>Bp0Z9?JaG|gyTZ{Cb8SdvAWVmFX7v2ohs!OCc!Udk zUITUpmZ33rKLI#(&lDj}cKA#dpL4Fil=$5pu_wi1XJR!llw` zSItPBDEdMHk2>c7#%lBxZHHvtVUOZ$}v?=?AT~9!Jcqa@IJGuMg(s^7r>pcTrd)pS`{5Cu8WPey` z9)!!OUUY@L%9Q+bZa*S5`3f_|lFCPN6kdp_M2>{le8;cn^XUsPa+TUk47qd6)IBR% zk*&Ip?!Ge_gmmdj)BX}P_5o@VI2*wbZ^>UhFju}0gQZh!pP%4XT9{@w;G#b3XK8sN zF(7i$Jv(IM$8Akys9dhP^^~H2(7BfJp}yDW1#@!CL-!mGcSCnJ599WK9MV@yo_u$v MDeX2GIKR{Qf5okjU;qFB diff --git a/fastapi-react-project/frontend/public/index.html b/fastapi-react-project/frontend/public/index.html deleted file mode 100644 index aa069f27..00000000 --- a/fastapi-react-project/frontend/public/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - React App - - - -
- - - diff --git a/fastapi-react-project/frontend/public/logo192.png b/fastapi-react-project/frontend/public/logo192.png deleted file mode 100644 index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN diff --git a/fastapi-react-project/frontend/public/manifest.json b/fastapi-react-project/frontend/public/manifest.json deleted file mode 100644 index 080d6c77..00000000 --- a/fastapi-react-project/frontend/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/fastapi-react-project/frontend/public/robots.txt b/fastapi-react-project/frontend/public/robots.txt deleted file mode 100644 index e9e57dc4..00000000 --- a/fastapi-react-project/frontend/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/fastapi-react-project/frontend/run.sh b/fastapi-react-project/frontend/run.sh deleted file mode 100644 index 5d2b8435..00000000 --- a/fastapi-react-project/frontend/run.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -case $1 in - start) - # The '| cat' is to trick Node that this is an non-TTY terminal - # then react-scripts won't clear the console. - npm start | cat - ;; - build) - npm build - ;; - test) - npm test $@ - ;; - *) - npm "$@" - ;; -esac \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/App.tsx b/fastapi-react-project/frontend/src/App.tsx deleted file mode 100644 index f41354b7..00000000 --- a/fastapi-react-project/frontend/src/App.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React, { FC } from 'react'; -import { Routes } from './Routes'; - -const App: FC = () => ; - -export default App; diff --git a/fastapi-react-project/frontend/src/Routes.tsx b/fastapi-react-project/frontend/src/Routes.tsx deleted file mode 100644 index 065ce319..00000000 --- a/fastapi-react-project/frontend/src/Routes.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { FC } from 'react'; -import { Switch, Route } from 'react-router-dom'; -import { useHistory } from 'react-router'; -import { makeStyles } from '@material-ui/core/styles'; - -import { Home, Login, SignUp, Protected, PrivateRoute } from './views'; -import { Admin } from './admin'; -import { Todo } from './admin/Todo'; - -import { logout } from './utils/auth'; - -const useStyles = makeStyles((theme) => ({ - app: { - textAlign: 'center', - }, - header: { - backgroundColor: '#282c34', - minHeight: '100vh', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - fontSize: 'calc(10px + 2vmin)', - color: 'white', - }, -})); - -export const Routes: FC = () => { - const classes = useStyles(); - const history = useHistory(); - - return ( - - - - - - - - -
-
- - - { - logout(); - history.push('/'); - return null; - }} - /> - - -
-
-
- ); -}; diff --git a/fastapi-react-project/frontend/src/__tests__/home.test.tsx b/fastapi-react-project/frontend/src/__tests__/home.test.tsx deleted file mode 100644 index ef03bdef..00000000 --- a/fastapi-react-project/frontend/src/__tests__/home.test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import { Home } from '../views/Home'; - -it('Home renders correctly', () => { - const home = render(); - expect(home.getByText('Admin Dashboard')).toBeInTheDocument(); - expect(home.getByText('Protected Route')).toBeInTheDocument(); - expect(home.getByText('Login')).toBeInTheDocument(); - expect(home.getByText('Sign Up')).toBeInTheDocument(); -}); diff --git a/fastapi-react-project/frontend/src/__tests__/login.test.tsx b/fastapi-react-project/frontend/src/__tests__/login.test.tsx deleted file mode 100644 index 1dae198d..00000000 --- a/fastapi-react-project/frontend/src/__tests__/login.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import { Login } from '../views'; - -it('Login renders correctly', () => { - const login = render(); - expect(login.getByText('Email')).toBeInTheDocument(); - expect(login.getByText('Password')).toBeInTheDocument(); - expect(login.getByText('Login')).toBeInTheDocument(); -}); diff --git a/fastapi-react-project/frontend/src/admin/Admin.tsx b/fastapi-react-project/frontend/src/admin/Admin.tsx deleted file mode 100644 index daa12188..00000000 --- a/fastapi-react-project/frontend/src/admin/Admin.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { FC } from 'react'; -import { fetchUtils, Admin as ReactAdmin, Resource } from 'react-admin'; -import simpleRestProvider from 'ra-data-simple-rest'; -import authProvider from './authProvider'; - -import { UserList, UserEdit, UserCreate } from './Users'; - -const httpClient = (url: any, options: any) => { - if (!options) { - options = {}; - } - if (!options.headers) { - options.headers = new Headers({ Accept: 'application/json' }); - } - const token = localStorage.getItem('token'); - options.headers.set('Authorization', `Bearer ${token}`); - return fetchUtils.fetchJson(url, options); -}; - -const dataProvider = simpleRestProvider('api/v1', httpClient); - -export const Admin: FC = () => { - return ( - - {(permissions: 'admin' | 'user') => [ - permissions === 'admin' ? ( - - ) : null, - ]} - - ); -}; diff --git a/fastapi-react-project/frontend/src/admin/Todo.tsx b/fastapi-react-project/frontend/src/admin/Todo.tsx deleted file mode 100644 index 36b1a858..00000000 --- a/fastapi-react-project/frontend/src/admin/Todo.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { FC } from 'react'; -import { fetchUtils, Admin as ReactAdmin, Resource } from 'react-admin'; -import simpleRestProvider from 'ra-data-simple-rest'; -import authProvider from './authProvider'; - -import ToDoList from './Todo/ToDoList' -import ToDoCreate from './Todo/ToDoCreate' -import ToDoEdit from './Todo/ToDoEdit' - - -const httpClient = (url: any, options: any) => { - if (!options) { - options = {}; - } - if (!options.headers) { - options.headers = new Headers({ Accept: 'application/json' }); - } - const token = localStorage.getItem('token'); - options.headers.set('Authorization', `Bearer ${token}`); - return fetchUtils.fetchJson(url, options); -}; - -const dataProvider = simpleRestProvider('api/v1', httpClient); - -export const Todo: FC = () => { - return ( - - {(permissions: 'admin' | 'user') => [ - permissions === 'admin' ? ( - - ) : null, - ]} - - ); -}; diff --git a/fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx b/fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx deleted file mode 100644 index 737ea5a6..00000000 --- a/fastapi-react-project/frontend/src/admin/Todo/ToDoCreate.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { FC } from 'react'; -import { - Create, - SimpleForm, - TextInput, - BooleanInput -} from 'react-admin'; - -const ToDoCreate: FC = (props) => ( - - - - - - - -); -export default ToDoCreate \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx b/fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx deleted file mode 100644 index 62527429..00000000 --- a/fastapi-react-project/frontend/src/admin/Todo/ToDoEdit.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { FC } from 'react'; -import { - Edit, - SimpleForm, - TextInput, - BooleanInput -} from 'react-admin'; - -const ToDoEdit: FC = (props) => ( - - - - - - - - -); -export default ToDoEdit \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx b/fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx deleted file mode 100644 index 31e8aecc..00000000 --- a/fastapi-react-project/frontend/src/admin/Todo/ToDoList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { FC } from 'react'; -import { - List, - Datagrid, - TextField, - EditButton, -} from 'react-admin'; - -const ToDoList: FC = (props) => ( - - - - - - - - - -); -export default ToDoList \ No newline at end of file diff --git a/fastapi-react-project/frontend/src/admin/Todo/index.ts b/fastapi-react-project/frontend/src/admin/Todo/index.ts deleted file mode 100644 index 9c45cae5..00000000 --- a/fastapi-react-project/frontend/src/admin/Todo/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './ToDoEdit'; -export * from './ToDoList'; -export * from './ToDoCreate'; diff --git a/fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx b/fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx deleted file mode 100644 index 466a5182..00000000 --- a/fastapi-react-project/frontend/src/admin/Users/UserCreate.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; -import { - Create, - SimpleForm, - TextInput, - PasswordInput, - BooleanInput, -} from 'react-admin'; - -export const UserCreate: FC = (props) => ( - - - - - - - - - - -); diff --git a/fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx b/fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx deleted file mode 100644 index 7925d192..00000000 --- a/fastapi-react-project/frontend/src/admin/Users/UserEdit.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { FC } from 'react'; -import { - Edit, - SimpleForm, - TextInput, - PasswordInput, - BooleanInput, -} from 'react-admin'; - -export const UserEdit: FC = (props) => ( - - - - - - - - - - - -); diff --git a/fastapi-react-project/frontend/src/admin/Users/UserList.tsx b/fastapi-react-project/frontend/src/admin/Users/UserList.tsx deleted file mode 100644 index dce27f7e..00000000 --- a/fastapi-react-project/frontend/src/admin/Users/UserList.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// in src/users.js -import React, { FC } from 'react'; -import { - List, - Datagrid, - TextField, - BooleanField, - EmailField, - EditButton, -} from 'react-admin'; - -export const UserList: FC = (props) => ( - - - - - - - - - - - -); diff --git a/fastapi-react-project/frontend/src/admin/Users/index.ts b/fastapi-react-project/frontend/src/admin/Users/index.ts deleted file mode 100644 index 999f7e00..00000000 --- a/fastapi-react-project/frontend/src/admin/Users/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './UserEdit'; -export * from './UserList'; -export * from './UserCreate'; diff --git a/fastapi-react-project/frontend/src/admin/authProvider.ts b/fastapi-react-project/frontend/src/admin/authProvider.ts deleted file mode 100644 index 1e0fe3ae..00000000 --- a/fastapi-react-project/frontend/src/admin/authProvider.ts +++ /dev/null @@ -1,55 +0,0 @@ -import decodeJwt from 'jwt-decode'; - -type loginFormType = { - username: string; - password: string; -}; - -const authProvider = { - login: ({ username, password }: loginFormType) => { - let formData = new FormData(); - formData.append('username', username); - formData.append('password', password); - const request = new Request('/api/token', { - method: 'POST', - body: formData, - }); - return fetch(request) - .then((response) => { - if (response.status < 200 || response.status >= 300) { - throw new Error(response.statusText); - } - return response.json(); - }) - .then(({ access_token }) => { - const decodedToken: any = decodeJwt(access_token); - if (decodedToken.permissions !== 'admin') { - throw new Error('Forbidden'); - } - localStorage.setItem('token', access_token); - localStorage.setItem('permissions', decodedToken.permissions); - }); - }, - logout: () => { - localStorage.removeItem('token'); - localStorage.removeItem('permissions'); - return Promise.resolve(); - }, - checkError: (error: { status: number }) => { - const status = error.status; - if (status === 401 || status === 403) { - localStorage.removeItem('token'); - return Promise.reject(); - } - return Promise.resolve(); - }, - checkAuth: () => - localStorage.getItem('token') ? Promise.resolve() : Promise.reject(), - getPermissions: () => { - const role = localStorage.getItem('permissions'); - return role ? Promise.resolve(role) : Promise.reject(); - // localStorage.getItem('token') ? Promise.resolve() : Promise.reject(), - }, -}; - -export default authProvider; diff --git a/fastapi-react-project/frontend/src/admin/index.ts b/fastapi-react-project/frontend/src/admin/index.ts deleted file mode 100644 index c956a8fd..00000000 --- a/fastapi-react-project/frontend/src/admin/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Admin'; diff --git a/fastapi-react-project/frontend/src/config/index.tsx b/fastapi-react-project/frontend/src/config/index.tsx deleted file mode 100644 index 31dc509f..00000000 --- a/fastapi-react-project/frontend/src/config/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const BASE_URL: string = 'http://localhost:8000'; -export const BACKEND_URL: string = - 'http://localhost:8000/api/v1'; diff --git a/fastapi-react-project/frontend/src/decs.d.ts b/fastapi-react-project/frontend/src/decs.d.ts deleted file mode 100644 index 5557bb84..00000000 --- a/fastapi-react-project/frontend/src/decs.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'react-admin'; diff --git a/fastapi-react-project/frontend/src/index.css b/fastapi-react-project/frontend/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/fastapi-react-project/frontend/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/fastapi-react-project/frontend/src/index.tsx b/fastapi-react-project/frontend/src/index.tsx deleted file mode 100644 index 9a6816b7..00000000 --- a/fastapi-react-project/frontend/src/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { BrowserRouter as Router } from 'react-router-dom'; -import './index.css'; -import App from './App'; - -ReactDOM.render( - - - , - document.getElementById('root') -); diff --git a/fastapi-react-project/frontend/src/logo.svg b/fastapi-react-project/frontend/src/logo.svg deleted file mode 100644 index 6b60c104..00000000 --- a/fastapi-react-project/frontend/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/fastapi-react-project/frontend/src/react-app-env.d.ts b/fastapi-react-project/frontend/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5f..00000000 --- a/fastapi-react-project/frontend/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/fastapi-react-project/frontend/src/utils/api.ts b/fastapi-react-project/frontend/src/utils/api.ts deleted file mode 100644 index 6b7e2f0a..00000000 --- a/fastapi-react-project/frontend/src/utils/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BACKEND_URL } from '../config'; - -export const getMessage = async () => { - const response = await fetch(BACKEND_URL); - - const data = await response.json(); - - if (data.message) { - return data.message; - } - - return Promise.reject('Failed to get message from backend'); -}; diff --git a/fastapi-react-project/frontend/src/utils/auth.ts b/fastapi-react-project/frontend/src/utils/auth.ts deleted file mode 100644 index bbcf39eb..00000000 --- a/fastapi-react-project/frontend/src/utils/auth.ts +++ /dev/null @@ -1,118 +0,0 @@ -import decodeJwt from 'jwt-decode'; - -export const isAuthenticated = () => { - const permissions = localStorage.getItem('permissions'); - if (!permissions) { - return false; - } - return permissions === 'user' || permissions === 'admin' ? true : false; -}; - -/** - * Login to backend and store JSON web token on success - * - * @param email - * @param password - * @returns JSON data containing access token on success - * @throws Error on http errors or failed attempts - */ -export const login = async (email: string, password: string) => { - // Assert email or password is not empty - if (!(email.length > 0) || !(password.length > 0)) { - throw new Error('Email or password was not provided'); - } - const formData = new FormData(); - // OAuth2 expects form data, not JSON data - formData.append('username', email); - formData.append('password', password); - - const request = new Request('/api/token', { - method: 'POST', - body: formData, - }); - - const response = await fetch(request); - - if (response.status === 500) { - throw new Error('Internal server error'); - } - - const data = await response.json(); - - if (response.status > 400 && response.status < 500) { - if (data.detail) { - throw data.detail; - } - throw data; - } - - if ('access_token' in data) { - const decodedToken: any = decodeJwt(data['access_token']); - localStorage.setItem('token', data['access_token']); - localStorage.setItem('permissions', decodedToken.permissions); - } - - return data; -}; - -/** - * Sign up via backend and store JSON web token on success - * - * @param email - * @param password - * @returns JSON data containing access token on success - * @throws Error on http errors or failed attempts - */ -export const signUp = async ( - email: string, - password: string, - passwordConfirmation: string -) => { - // Assert email or password or password confirmation is not empty - if (!(email.length > 0)) { - throw new Error('Email was not provided'); - } - if (!(password.length > 0)) { - throw new Error('Password was not provided'); - } - if (!(passwordConfirmation.length > 0)) { - throw new Error('Password confirmation was not provided'); - } - - const formData = new FormData(); - // OAuth2 expects form data, not JSON data - formData.append('username', email); - formData.append('password', password); - - const request = new Request('/api/signup', { - method: 'POST', - body: formData, - }); - - const response = await fetch(request); - - if (response.status === 500) { - throw new Error('Internal server error'); - } - - const data = await response.json(); - if (response.status > 400 && response.status < 500) { - if (data.detail) { - throw data.detail; - } - throw data; - } - - if ('access_token' in data) { - const decodedToken: any = decodeJwt(data['access_token']); - localStorage.setItem('token', data['access_token']); - localStorage.setItem('permissions', decodedToken.permissions); - } - - return data; -}; - -export const logout = () => { - localStorage.removeItem('token'); - localStorage.removeItem('permissions'); -}; diff --git a/fastapi-react-project/frontend/src/utils/index.ts b/fastapi-react-project/frontend/src/utils/index.ts deleted file mode 100644 index abb0c9d6..00000000 --- a/fastapi-react-project/frontend/src/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './auth'; -export * from './api'; diff --git a/fastapi-react-project/frontend/src/views/Home.tsx b/fastapi-react-project/frontend/src/views/Home.tsx deleted file mode 100644 index 50ba5f97..00000000 --- a/fastapi-react-project/frontend/src/views/Home.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { FC, useState } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; - -import { getMessage } from '../utils/api'; -import { isAuthenticated } from '../utils/auth'; - -const useStyles = makeStyles((theme) => ({ - link: { - color: '#61dafb', - }, -})); - -export const Home: FC = () => { - const [message, setMessage] = useState(''); - const [error, setError] = useState(''); - const classes = useStyles(); - - const queryBackend = async () => { - try { - const message = await getMessage(); - setMessage(message); - } catch (err) { - setError(String(err)); - } - }; - - return ( - <> - {!message && !error && ( - queryBackend()}> - Click to make request to backend - - )} - {message && ( -

- {message} -

- )} - {error && ( -

- Error: {error} -

- )} - - Admin Dashboard - - - Todo Dashboard - - - Protected Route - - {isAuthenticated() ? ( - - Logout - - ) : ( - <> - - Login - - - Sign Up - - - )} - - ); -}; diff --git a/fastapi-react-project/frontend/src/views/Login.tsx b/fastapi-react-project/frontend/src/views/Login.tsx deleted file mode 100644 index 26298c8a..00000000 --- a/fastapi-react-project/frontend/src/views/Login.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { FC, useState } from 'react'; -import { - Paper, - Grid, - TextField, - Button, - FormControlLabel, - Checkbox, -} from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { Face, Fingerprint } from '@material-ui/icons'; -import { Alert } from '@material-ui/lab'; -import { Redirect } from 'react-router-dom'; -import { useHistory } from 'react-router'; - -import { login, isAuthenticated } from '../utils/auth'; - -const useStyles = makeStyles((theme) => ({ - margin: { - margin: theme.spacing(2), - }, - padding: { - padding: theme.spacing(1), - }, - button: { - textTransform: 'none', - }, - marginTop: { - marginTop: 10, - }, -})); - -export const Login: FC = () => { - const classes = useStyles(); - const history = useHistory(); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - - const handleSubmit = async (_: React.MouseEvent) => { - setError(''); - try { - const data = await login(email, password); - - if (data) { - history.push('/'); - } - } catch (err) { - if (err instanceof Error) { - // handle errors thrown from frontend - setError(err.message); - } else { - // handle errors thrown from backend - setError(String(err)); - } - } - }; - - return isAuthenticated() ? ( - - ) : ( - -
- - - - - - ) => - setEmail(e.currentTarget.value) - } - fullWidth - autoFocus - required - /> - - - - - - - - ) => - setPassword(e.currentTarget.value) - } - fullWidth - required - /> - - -
- - {error && ( - - {error} - - )} - - - - } - label="Remember me" - /> - - - - - - - {' '} - {' '} -   - - -
-
- ); -}; diff --git a/fastapi-react-project/frontend/src/views/PrivateRoute.tsx b/fastapi-react-project/frontend/src/views/PrivateRoute.tsx deleted file mode 100644 index 285fc5fd..00000000 --- a/fastapi-react-project/frontend/src/views/PrivateRoute.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { FC } from 'react'; -import { Route, Redirect } from 'react-router-dom'; - -import { isAuthenticated } from '../utils/auth'; - -type PrivateRouteType = { - component: React.ComponentType; - path?: string | string[]; -}; - -export const PrivateRoute: FC = ({ - component, - ...rest -}: any) => ( - - isAuthenticated() === true ? ( - React.createElement(component, props) - ) : ( - - ) - } - /> -); diff --git a/fastapi-react-project/frontend/src/views/Protected.tsx b/fastapi-react-project/frontend/src/views/Protected.tsx deleted file mode 100644 index 078414ad..00000000 --- a/fastapi-react-project/frontend/src/views/Protected.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React, { FC } from 'react'; - -export const Protected: FC = () => { - return

This component is protected

; -}; diff --git a/fastapi-react-project/frontend/src/views/SignUp.tsx b/fastapi-react-project/frontend/src/views/SignUp.tsx deleted file mode 100644 index 0ede11ee..00000000 --- a/fastapi-react-project/frontend/src/views/SignUp.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React, { FC, useState } from 'react'; -import { Paper, Grid, TextField, Button } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { Face, Fingerprint } from '@material-ui/icons'; -import { Alert } from '@material-ui/lab'; -import { Redirect } from 'react-router-dom'; -import { useHistory } from 'react-router'; - -import { signUp, isAuthenticated } from '../utils/auth'; - -const useStyles = makeStyles((theme) => ({ - margin: { - margin: theme.spacing(2), - }, - padding: { - padding: theme.spacing(1), - }, - button: { - textTransform: 'none', - }, - marginTop: { - marginTop: 10, - }, -})); - -export const SignUp: FC = () => { - const classes = useStyles(); - const history = useHistory(); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [passwordConfirmation, setPasswordConfirmation] = useState(''); - const [error, setError] = useState(''); - - const handleSubmit = async (_: React.MouseEvent) => { - // Password confirmation validation - if (password !== passwordConfirmation) setError('Passwords do not match'); - else { - setError(''); - try { - const data = await signUp(email, password, passwordConfirmation); - - if (data) { - history.push('/'); - } - } catch (err) { - if (err instanceof Error) { - // handle errors thrown from frontend - setError(err.message); - } else { - // handle errors thrown from backend - setError(String(err)); - } - } - } - }; - - return isAuthenticated() ? ( - - ) : ( - -
- - - - - - ) => - setEmail(e.currentTarget.value) - } - fullWidth - autoFocus - required - /> - - - - - - - - ) => - setPassword(e.currentTarget.value) - } - fullWidth - required - /> - - - - - - - - ) => - setPasswordConfirmation(e.currentTarget.value) - } - fullWidth - required - /> - - -
- - {error && ( - - {error} - - )} - - - - -
-
- ); -}; diff --git a/fastapi-react-project/frontend/src/views/index.ts b/fastapi-react-project/frontend/src/views/index.ts deleted file mode 100644 index 797586c5..00000000 --- a/fastapi-react-project/frontend/src/views/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Home'; -export * from './Login'; -export * from './SignUp'; -export * from './Protected'; -export * from './PrivateRoute'; diff --git a/fastapi-react-project/frontend/tsconfig.json b/fastapi-react-project/frontend/tsconfig.json deleted file mode 100644 index 4a41017b..00000000 --- a/fastapi-react-project/frontend/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react" - }, - "include": ["src", "decs.d.ts"] -} diff --git a/fastapi-react-project/nginx/nginx.conf b/fastapi-react-project/nginx/nginx.conf deleted file mode 100644 index 10a3d32d..00000000 --- a/fastapi-react-project/nginx/nginx.conf +++ /dev/null @@ -1,22 +0,0 @@ -server { - listen 80; - server_name fastapi-react-project; - - location / { - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Server $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_pass http://frontend:3000; - - proxy_redirect off; - - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - location /api { - proxy_pass http://backend:8888/api; - } -} diff --git a/fastapi-react-project/scripts/build.sh b/fastapi-react-project/scripts/build.sh deleted file mode 100644 index 77a36455..00000000 --- a/fastapi-react-project/scripts/build.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# Exit in case of error -set -e - -# Build and run containers -docker-compose up -d - -# Hack to wait for postgres container to be up before running alembic migrations -sleep 5; - -# Run migrations -docker-compose run --rm backend alembic upgrade head - -# Create initial data -docker-compose run --rm backend python3 app/initial_data.py \ No newline at end of file diff --git a/fastapi-react-project/scripts/test.sh b/fastapi-react-project/scripts/test.sh deleted file mode 100644 index 9f1d0d33..00000000 --- a/fastapi-react-project/scripts/test.sh +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env bash - -# Exit in case of error -set -e - -docker-compose run backend pytest -docker-compose run frontend test \ No newline at end of file diff --git a/fastapi-react-project/scripts/test_backend.sh b/fastapi-react-project/scripts/test_backend.sh deleted file mode 100644 index b5fb2c2e..00000000 --- a/fastapi-react-project/scripts/test_backend.sh +++ /dev/null @@ -1,6 +0,0 @@ -#! /usr/bin/env bash - -# Exit in case of error -set -e - -docker-compose run backend pytest $@ \ No newline at end of file