From 9f7317fc352e48bd8512f3a988c2115a17eaac06 Mon Sep 17 00:00:00 2001 From: Yasser Tahiri Date: Mon, 17 Apr 2023 01:03:09 +0400 Subject: [PATCH] WIP: Start Migration to New Boilerplate (#143) * WIP: Start Migration to New Version of the Boilerplate * remove unused files * WIP: Produce the New Version of the Boilerplate * update the markdown content --- .dockerignore | 20 -- .github/FUNDING.yml | 2 +- .github/Header.svg | 74 -------- .github/ISSUE_TEMPLATE/bug_report.md | 34 ---- .github/ISSUE_TEMPLATE/feature_request.md | 20 -- .github/Secrets Sync Action.yml | 2 - .github/dependabot.yml | 23 ++- .github/workflows/docker-publish.yml | 63 ------ .github/workflows/lint.yaml | 27 +++ .github/workflows/lint.yml | 27 --- .github/workflows/test.yaml | 36 ++++ .gitignore | 1 + .pre-commit-config.yaml | 70 ++++--- Dockerfile | 14 +- LICENSE | 222 ++-------------------- Makefile | 30 --- README.md | 145 ++++++-------- app/__init__.py | 1 + app/api/__init__.py | 0 {api => app/api}/api.py | 3 +- {api => app/api}/deps.py | 19 +- app/api/endpoints/__init__.py | 0 {api => app/api}/endpoints/login.py | 51 ++--- {api => app/api}/endpoints/users.py | 50 ++--- app/base/__init__.py | 0 {crud => app/base}/base.py | 11 +- crud/crud_user.py => app/base/user.py | 9 +- app/core/__init__.py | 0 {core => app/core}/config.py | 0 db/session.py => app/core/database.py | 15 +- {db => app/core}/tasks.py | 8 +- app/extension/__init__.py | 0 {core => app/extension}/password.py | 3 +- {core => app/extension}/security.py | 3 +- {core => app/extension}/tasks.py | 3 +- main.py => app/main.py | 22 +-- app/models/__init__.py | 0 schemas/user.py => app/models/schema.py | 13 ++ models/user.py => app/models/tables.py | 3 +- db/base_class.py | 13 -- .env.test => docker-compose.env | 0 docker-compose.yaml | 8 +- docs/CONTRIBUTING.md | 35 ---- docs/Code_of_Conduct.md | 132 ------------- docs/Privacy_Policy.md | 74 -------- docs/Terms_&_Conditions.md | 123 ------------ pyproject.toml | 129 +++++++++++++ requirements.txt | 60 ------ schemas/msg.py | 5 - schemas/token.py | 12 -- scripts/docker.sh | 6 + scripts/format.sh | 6 + scripts/test.sh | 9 + static/css/style.css | 202 -------------------- static/js/main.js | 16 -- templates/index.html | 78 -------- tests/__init__.py | 0 tests/test_version.py | 5 + 58 files changed, 461 insertions(+), 1476 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .github/Header.svg delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/Secrets Sync Action.yml delete mode 100644 .github/workflows/docker-publish.yml create mode 100644 .github/workflows/lint.yaml delete mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test.yaml delete mode 100644 Makefile create mode 100644 app/__init__.py create mode 100644 app/api/__init__.py rename {api => app/api}/api.py (99%) rename {api => app/api}/deps.py (87%) create mode 100644 app/api/endpoints/__init__.py rename {api => app/api}/endpoints/login.py (62%) rename {api => app/api}/endpoints/users.py (70%) create mode 100644 app/base/__init__.py rename {crud => app/base}/base.py (86%) rename crud/crud_user.py => app/base/user.py (90%) create mode 100644 app/core/__init__.py rename {core => app/core}/config.py (100%) rename db/session.py => app/core/database.py (51%) rename {db => app/core}/tasks.py (51%) create mode 100644 app/extension/__init__.py rename {core => app/extension}/password.py (99%) rename {core => app/extension}/security.py (99%) rename {core => app/extension}/tasks.py (85%) rename main.py => app/main.py (65%) create mode 100644 app/models/__init__.py rename schemas/user.py => app/models/schema.py (81%) rename models/user.py => app/models/tables.py (92%) delete mode 100644 db/base_class.py rename .env.test => docker-compose.env (100%) delete mode 100644 docs/CONTRIBUTING.md delete mode 100644 docs/Code_of_Conduct.md delete mode 100644 docs/Privacy_Policy.md delete mode 100644 docs/Terms_&_Conditions.md create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 schemas/msg.py delete mode 100644 schemas/token.py create mode 100644 scripts/docker.sh create mode 100644 scripts/format.sh create mode 100644 scripts/test.sh delete mode 100644 static/css/style.css delete mode 100644 static/js/main.js delete mode 100644 templates/index.html create mode 100644 tests/__init__.py create mode 100644 tests/test_version.py diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 41550af..0000000 --- a/.dockerignore +++ /dev/null @@ -1,20 +0,0 @@ -__pycache__ -*.pyc -*.pyo -*.pyd -.Python -.env* -pip-log.txt -pip-delete-this-directory.txt -.tox -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -*.log -tests -scripts -postman -./postgres-data \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7d9489b..cc10e8e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: ["https://paypal.me/yassertahiri"] \ No newline at end of file +github: yezz123 diff --git a/.github/Header.svg b/.github/Header.svg deleted file mode 100644 index 3350c49..0000000 --- a/.github/Header.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - FRDP - - - - - - - - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 080d347..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - -- OS: [e.g. Ubuntu] -- Browser [e.g. chrome, safari] -- Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/Secrets Sync Action.yml b/.github/Secrets Sync Action.yml deleted file mode 100644 index 8f5ece3..0000000 --- a/.github/Secrets Sync Action.yml +++ /dev/null @@ -1,2 +0,0 @@ -- name: Secrets Sync Action - uses: google/secrets-sync-action@v1.4.1 \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cf7a39f..6614569 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,18 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - version: 2 + updates: - - package-ecosystem: "pip" # See documentation for possible values - directory: "/" # Location of package manifests + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + commit-message: + prefix: ⬆ + + # Python + - package-ecosystem: "pip" + directory: "/" schedule: - interval: "daily" + interval: "monthly" + commit-message: + prefix: ⬆ diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index e880e56..0000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Docker - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - schedule: - - cron: '33 23 * * *' - push: - branches: [ main ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] - pull_request: - branches: [ main ] - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..8f8a6a6 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,27 @@ +name: Lint and Format + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + fail-fast: false + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: pip install -e .[lint] + - name: Lint + run: bash scripts/format.sh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 785470c..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: FRDP CI - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - linter: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10"] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: pull the image - run: make pull - - name: build the image - run: make build - - name: Lint - run: make lint diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..9c7a652 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,36 @@ +name: Test Suite + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize] + +jobs: + tests: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + matrix: + python-version: ["3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/cache@v3 + id: cache + with: + path: ${{ env.pythonLocation }} + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v02 + - name: Upgrade pip + run: | + python -m pip install --upgrade pip + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: pip install -e .[test] + - name: Test with pytest + run: bash scripts/test.sh diff --git a/.gitignore b/.gitignore index 5547013..96a4d1f 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ venv.bak/ .mypy_cache/ .vscode/ .idea/ +.ruff_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da64831..82340b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,39 +1,33 @@ repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 - hooks: - - id: check-merge-conflict - - id: check-added-large-files - - id: check-ast - - id: check-symlinks - - id: trailing-whitespace - - id: check-json - - id: debug-statements - - id: pretty-format-json - args: ["--autofix", "--allow-missing-credentials"] - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - args: ["--profile", "black"] - - repo: https://gitlab.com/pycqa/flake8 - rev: 4.0.1 - hooks: - - id: flake8 - additional_dependencies: [flake8-print] - files: '\.py$' - exclude: docs/ - args: - - --select=F403,F406,F821,T003 - - repo: https://github.com/humitos/mirrors-autoflake - rev: v1.3 - hooks: - - id: autoflake - files: '\.py$' - exclude: '^\..*' - args: ["--in-place"] - - repo: https://github.com/psf/black - rev: 21.7b0 - hooks: - - id: black - args: ["--target-version", "py38"] \ No newline at end of file +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-toml + - id: check-yaml + args: + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: + - --py3-plus + - --keep-runtime-typing +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.261 + hooks: + - id: ruff + args: + - --fix +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + name: isort (python) +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black diff --git a/Dockerfile b/Dockerfile index adc9257..c3fcb25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Pull base image -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8 +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 # Set environment varibles ENV PYTHONDONTWRITEBYTECODE 1 @@ -11,10 +11,14 @@ RUN apt install -y -q build-essential python3-pip python3-dev RUN pip3 install -U pip setuptools wheel RUN pip3 install gunicorn uvloop httptools -COPY requirements.txt /app/requirements.txt -RUN pip3 install -r /app/requirements.txt -COPY ./ /app + +# Install dependencies +COPY pyproject.toml ./pyproject.toml +RUN pip install -e .[test,lint] + +# Copy project +COPY . . ENV ACCESS_LOG=${ACCESS_LOG:-/proc/1/fd/1} -ENV ERROR_LOG=${ERROR_LOG:-/proc/1/fd/2} \ No newline at end of file +ENV ERROR_LOG=${ERROR_LOG:-/proc/1/fd/2} diff --git a/LICENSE b/LICENSE index 261eeb9..3e9d4a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT License + +Copyright (c) 2023 Yasser Tahiri + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index af5376a..0000000 --- a/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -help: - @echo "Targets:" - @echo " make test" - @echo " make start" - @echo " make down" - @echo " make pull" - @echo " make build" - @echo " make lint" - @echo " make clean" - -start: - docker-compose up -d - -down: - docker-compose down - -pull: - docker-compose pull - -build: - docker-compose build - -lint: - docker-compose run --rm server pre-commit run --all-files - -clean: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + \ No newline at end of file diff --git a/README.md b/README.md index a185c77..d644054 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,43 @@ -![Header](.github/Header.svg) +# FRDP -# FRDP [![CodeFactor](https://www.codefactor.io/repository/github/bnademoverflow/frdp/badge/main)](https://www.codefactor.io/repository/github/bnademoverflow/frdp/overview/main) +Boilerplate code for quick docker implementation of REST API with JWT Authentication using FastAPI, PostgreSQL and PgAdmin. -Boilerplate code for quick docker implementation of REST API with JWT Authentication using FastAPI, PostgreSQL and PgAdmin ⛏. +## Project setup -## Getting Started - -### Features - -| Auth | Description | -|-------------------|---------------------------------------------------------------------------------| -|Login Access Token | OAuth2 compatible token login, get an access token for future requests | -|Check Session | Test if a user is logged in by checking if a valid access token is in the header| -|Recover Password | Password Recovery | -|Reset Password | Reset your password | - -| User | Description | -|------------------------------------|--------------------------------------------------| -|Create New User | Create a new user | -|Get Current User By Id | Get current user | -|Update Current User | Update own user | -|Update Other User (SuperUser) | Update a user | -|Create User (Without Authentication)| Create new user without the need to be logged in.| - -### Prerequisites - -* [FastAPI](https://fastapi.tiangolo.com) -* [PostgreSQL - postgres12](https://hub.docker.com/_/postgres) -* [PgAdmin](https://hub.docker.com/r/dpage/pgadmin4) -* [jwt](https://jwt.io) - -### Project setup - -```sh +```shell # clone the repo -$ git clone https://github.com/BnademOverflow/FRDP +$ git clone https://github.com/yezz123/FRDP # move to the project folder $ cd FRDP ``` -### Creating virtual environment - -* Create a virtual environment using virtualenv. +## Getting Started -```shell -# creating virtual environment -$ virtualenv venv +### Features -# activate virtual environment -$ source venv/bin/activate +| Auth | Description | +|-------------------|---------------------------------------------------------------------------------| +|`Login Access Token` | OAuth2 compatible token login, get an access token for future requests | +|`Check Session` | Test if a user is logged in by checking if a valid access token is in the header| +|`Recover Password` | Password Recovery | +|`Reset Password` | Reset your password | -# install all dependencies -$ pip install -r requirements.txt -``` +| User | Description | +|------------------------------------|--------------------------------------------------| +|`Create New User` | Create a new user | +|`Get Current User By Id` | Get current user | +|`Update Current User` | Update own user | +|`Update Other User (SuperUser)` | Update a user | +|`Create User (Without Authentication)`| Create new user without the need to be logged in.| -### Environment variables +## Environment variables -* [x] Using PostgreSQL as database server ⛏. -* [x] Using PgAdmin as database client ⛏. -* [x] Dockerized the Boilerplate code ⛏. +- [x] Using PostgreSQL as database server. +- [x] Using PgAdmin as database client. +- [x] Dockerized the Boilerplate code. -* Drop your own Configuration at the `.env.sample` and Don't Forget to change the Name to `.env` (`$ cp .env.sample .env`). +- Drop your own Configuration at the `docker-compose.env` file. ```conf # Backend API Configuration @@ -89,51 +65,54 @@ PGADMIN_DEFAULT_EMAIL= PGADMIN_DEFAULT_PASSWORD= ``` -## Running the Docker Container +## Development 🚧 + +### Features + +- use `hatch` to manage dependencies. +- use `pytest` to run tests. +- GitHub Actions to run tests, lints. +- Use `Dependabot` to keep dependencies up to date. +- use `pre-commit` to manage formatting and linting. + - use `black` to format code. + - use `ruff` to lint code. + - use `isort` to sort imports. + +### Setup environment 📦 + +You should create a virtual environment and activate it: -* We have the Dockerfile created in above section. Now, we will use the Dockerfile to create the image of the FastAPI app and then start the FastAPI app container. -* Using a preconfigured `Makefile` tor run the Docker Compose: +```bash +python -m venv venv/ +``` -```sh -# Pull the latest image -$ make pull +```bash +source venv/bin/activate +``` -# Build the image -$ make build +And then install the development dependencies: -# Run the container -$ make start +```bash +# Install dependencies +pip install -e .[test,lint] ``` -## Preconfigured Packages +### Run tests 🌝 -Includes preconfigured packages to kick start FRDP by just setting appropriate configuration. +You can run all the tests with: -| Package | Usage | -| ------------------------------------------------------------ | ---------------------------------------------------------------- | -| [uvicorn](https://www.uvicorn.org/) | a lightning-fast ASGI server implementation, using uvloop and httptools. | -| [Python-Jose](https://github.com/mpdavis/python-jose) | a JavaScript Object Signing and Encryption implementation in Python. | -| [SQLAlchemy](https://www.sqlalchemy.org/) | is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL. | -| [starlette](https://www.starlette.io/) | a lightweight ASGI framework/toolkit, which is ideal for building high performance asyncio services. | -| [passlib](https://passlib.readthedocs.io/en/stable/) | a password hashing library for Python 2 & 3, which provides cross-platform implementations of over 30 password hashing algorithms | -| [bcrypt](https://github.com/pyca/bcrypt/) | Good password hashing for your software and your servers. | -| [python-jose](https://python-jose.readthedocs.io/en/latest/) | The JavaScript Object Signing and Encryption (JOSE) technologies - JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and JSON Web Algorithms (JWA). | -| [jinja2](https://jinja.palletsprojects.com/en/3.0.x/) | a very fast and easy to use stand-alone template engine for Python. | -| [psycopg2](https://www.psycopg.org/) | a Python PostgreSQL database adapter. | +```bash +bash scripts/test.sh +``` -## [Contributing](docs/CONTRIBUTING.md) +### Format the code 🍂 -* Join the FRDP Creator and Contribute to the Project if you have any enhancement or add-ons to create a good and Secure Project, Help any User to Use it in a good and simple way. -* See the [open issues](https://github.com/BnademOverflow/FRDP/issues) for a list of proposed features (and known issues) or [open pull request](https://github.com/BnademOverflow/FRDP/pulls) before starting work on a new feature. -* Check also [Docs](docs/README.md) for more information about FRDP. -* Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. +Execute the following command to apply `pre-commit` formatting: -1. Fork the Project -2. Create your Feature Branch (`git checkout -b hello/world`) -3. Commit your Changes (`git commit -m 'Add hello world'`) -4. Push to the Branch (`git push origin feature/helloworld`) -5. Open a Pull Request +```bash +bash scripts/format.sh +``` ## License -This project is licensed under the terms of the [Apache-2.0 License](LICENSE). +This project is licensed under the terms of the MIT license. diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..8c0d5d5 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +__version__ = "2.0.0" diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api.py b/app/api/api.py similarity index 99% rename from api/api.py rename to app/api/api.py index 4b14fac..940da10 100644 --- a/api/api.py +++ b/app/api/api.py @@ -1,6 +1,5 @@ -from fastapi import APIRouter - from api.endpoints import login, users +from fastapi import APIRouter api_router = APIRouter() api_router.include_router(login.router, tags=["Auth"]) diff --git a/api/deps.py b/app/api/deps.py similarity index 87% rename from api/deps.py rename to app/api/deps.py index 5248214..343fdba 100644 --- a/api/deps.py +++ b/app/api/deps.py @@ -1,19 +1,18 @@ from typing import Generator +import models +from base.user import CRUDUser +from core.config import settings +from core.database import SessionLocal +from extension import security from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import jwt +from models.schema import TokenPayload +from models.tables import User from pydantic import ValidationError from sqlalchemy.orm import Session -import models -from core import security -from core.config import settings -from crud.crud_user import CRUDUser -from db.session import SessionLocal -from models.user import User -from schemas.token import TokenPayload - reusable_oauth2 = OAuth2PasswordBearer(tokenUrl=f"{settings.API}/login/access-token") @@ -36,11 +35,11 @@ def get_current_user( token, settings.SECRET_KEY, algorithms=[security.ALGORITHM] ) token_data = TokenPayload(**payload) - except (jwt.JWTError, ValidationError): + except (jwt.JWTError, ValidationError) as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials", - ) + ) from e user = user1.get(db, id=token_data.sub) if not user: raise HTTPException(status_code=404, detail="User not found") diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/endpoints/login.py b/app/api/endpoints/login.py similarity index 62% rename from api/endpoints/login.py rename to app/api/endpoints/login.py index 92f35d3..db9b26b 100644 --- a/api/endpoints/login.py +++ b/app/api/endpoints/login.py @@ -1,22 +1,17 @@ from datetime import timedelta from typing import Any +from api.deps import get_current_user, get_db +from base.user import CRUDUser +from core.config import settings +from extension.password import verify_password_reset_token +from extension.security import create_access_token, get_password_hash from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm +from models.schema import Msg, Token, User +from models.tables import User as models_user from sqlalchemy.orm import Session -import crud -import models -from api import deps -from core import security -from core.config import settings -from core.password import generate_password_reset_token, verify_password_reset_token -from core.security import get_password_hash -from crud.crud_user import CRUDUser -from schemas.msg import Msg -from schemas.token import Token -from schemas.user import User - router = APIRouter() user1 = CRUDUser(User) @@ -24,11 +19,8 @@ @router.post("/login/access-token", response_model=Token) def login_access_token( - db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends() + db: Session = Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends() ) -> Any: - """ - OAuth2 compatible token login, get an access token for future requests - """ user = user1.authenticate(db, email=form_data.username, password=form_data.password) if not user: raise HTTPException(status_code=400, detail="Incorrect email or password") @@ -36,7 +28,7 @@ def login_access_token( raise HTTPException(status_code=400, detail="Inactive user") access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) return { - "access_token": security.create_access_token( + "access_token": create_access_token( user.id, expires_delta=access_token_expires ), "token_type": "bearer", @@ -45,11 +37,8 @@ def login_access_token( @router.post("/login/test-token", response_model=User) def Check_Session( - current_user: models.user.User = Depends(deps.get_current_user), + current_user: models_user = Depends(get_current_user), ) -> Any: - """ - Test if a user is logged in by checking if a valid access token is in the header - """ return current_user @@ -59,37 +48,33 @@ def Check_Session( status_code=200, response_description="Success", ) -def recover_password(email: str, db: Session = Depends(deps.get_db)) -> Any: - """ - Password Recovery - """ +def recover_password(email: str, db: Session = Depends(get_db)) -> Any: user = user1.get_by_email(db, email=email) - if not user: raise HTTPException( status_code=422, detail="The user with this username does not exist in the system.", ) - return {"msg": generate_password_reset_token(email=email)} + raise HTTPException( + status_code=200, + detail=f"An email with instructions to reset your password has been sent to {email}", + ) @router.post("/reset-password/", response_model=Msg) def reset_password( token: str = Body(...), new_password: str = Body(...), - db: Session = Depends(deps.get_db), + db: Session = Depends(get_db), ) -> Any: - """ - Reset your password - """ email = verify_password_reset_token(token) if not email: raise HTTPException(status_code=400, detail="Invalid token") - user = crud.user.get_by_email(db, email=email) + user = CRUDUser.get_by_email(db, email=email) if not user: raise HTTPException(status_code=404, detail="User not found") hashed_password = get_password_hash(new_password) user.hashed_password = hashed_password db.add(user) db.commit() - return {"msg": "Password updated successfully!"} + raise HTTPException(status_code=200, detail="Password updated successfully") diff --git a/api/endpoints/users.py b/app/api/endpoints/users.py similarity index 70% rename from api/endpoints/users.py rename to app/api/endpoints/users.py index c68e7ce..1158201 100644 --- a/api/endpoints/users.py +++ b/app/api/endpoints/users.py @@ -1,18 +1,15 @@ from typing import Any +from api.deps import get_current_active_superuser, get_current_active_user, get_db +from base.user import CRUDUser +from core.config import settings from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.encoders import jsonable_encoder +from models.schema import User, UserCreate, UserUpdate +from models.tables import User as models_user from pydantic.networks import EmailStr from sqlalchemy.orm import Session -import crud -import models -import schemas -from api import deps -from core.config import settings -from crud.crud_user import CRUDUser -from schemas.user import User, UserCreate, UserUpdate - router = APIRouter() user1 = CRUDUser(User) @@ -21,13 +18,10 @@ @router.post("/", response_model=User) def create_New_user( *, - db: Session = Depends(deps.get_db), - user_in: schemas.user.UserCreate, - current_user: models.user.User = Depends(deps.get_current_active_superuser), + db: Session = Depends(get_db), + user_in: UserCreate, + current_user: models_user = Depends(get_current_active_superuser), ) -> Any: - """ - Create new user. - """ user = user1.get_by_email(db, email=user_in.email) if user: raise HTTPException( @@ -41,15 +35,12 @@ def create_New_user( @router.put("/me", response_model=User) def update_Current_User( *, - db: Session = Depends(deps.get_db), + db: Session = Depends(get_db), password: str = Body(None), full_name: str = Body(None), email: EmailStr = Body(None), - current_user: models.user.User = Depends(deps.get_current_active_user), + current_user: models_user = Depends(get_current_active_user), ) -> Any: - """ - Update own user. - """ current_user_data = jsonable_encoder(current_user) user_in = UserUpdate(**current_user_data) if password is not None: @@ -63,47 +54,38 @@ def update_Current_User( @router.get("/me", response_model=User) def Get_Current_User_by_ID( - db: Session = Depends(deps.get_db), - current_user: models.user.User = Depends(deps.get_current_active_user), + db: Session = Depends(get_db), + current_user: models_user = Depends(get_current_active_user), ) -> Any: - """ - Get current user. - """ return current_user @router.put("/{user_id}", response_model=User) def update_Other_user( *, - db: Session = Depends(deps.get_db), + db: Session = Depends(get_db), user_id: int, user_in: UserUpdate, - current_user: models.user.User = Depends(deps.get_current_active_superuser), + current_user: models_user = Depends(get_current_active_superuser), ) -> Any: - """ - Update a user. - """ user = user1.get(db, id=user_id) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system", ) - user = crud.user.update(db, db_obj=user, obj_in=user_in) + user = CRUDUser.update(db, db_obj=user, obj_in=user_in) return user @router.post("/Create", response_model=User) def create_user( *, - db: Session = Depends(deps.get_db), + db: Session = Depends(get_db), email: EmailStr = Body(...), full_name: str = Body(None), password: str = Body(...), ) -> Any: - """ - Create new user without the need to be logged in. - """ if not settings.USERS_OPEN_REGISTRATION: raise HTTPException( status_code=403, diff --git a/app/base/__init__.py b/app/base/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crud/base.py b/app/base/base.py similarity index 86% rename from crud/base.py rename to app/base/base.py index 494e5cb..095291a 100644 --- a/crud/base.py +++ b/app/base/base.py @@ -1,11 +1,10 @@ from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union +from core.database import Base from fastapi.encoders import jsonable_encoder from pydantic import BaseModel from sqlalchemy.orm import Session -from db.base_class import Base - ModelType = TypeVar("ModelType", bound=Base) CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) @@ -25,8 +24,8 @@ def get_multi( def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: obj_in_data = jsonable_encoder(obj_in) - db_obj = self.model(**obj_in_data) # type: ignore - return self._extracted_from_update_4(db, db_obj) + db_obj = self.model(**obj_in_data) + return self.commit(db, db_obj) def update( self, @@ -43,9 +42,9 @@ def update( for field in obj_data: if field in update_data: setattr(db_obj, field, update_data[field]) - return self._extracted_from_update_4(db, db_obj) + return self.commit(db, db_obj) - def _extracted_from_update_4(self, db, db_obj): + def commit(self, db, db_obj): db.add(db_obj) db.commit() db.refresh(db_obj) diff --git a/crud/crud_user.py b/app/base/user.py similarity index 90% rename from crud/crud_user.py rename to app/base/user.py index 025b5ec..ce6c860 100644 --- a/crud/crud_user.py +++ b/app/base/user.py @@ -1,12 +1,11 @@ from typing import Any, Dict, Optional, Union +from base.base import CRUDBase +from extension.security import get_password_hash, verify_password +from models.schema import UserCreate, UserUpdate +from models.tables import User from sqlalchemy.orm import Session -from core.security import get_password_hash, verify_password -from crud.base import CRUDBase -from models.user import User -from schemas.user import UserCreate, UserUpdate - class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): def get_by_email(self, db: Session, *, email: str) -> Optional[User]: diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/config.py b/app/core/config.py similarity index 100% rename from core/config.py rename to app/core/config.py diff --git a/db/session.py b/app/core/database.py similarity index 51% rename from db/session.py rename to app/core/database.py index 4e2ceb6..490f675 100644 --- a/db/session.py +++ b/app/core/database.py @@ -1,7 +1,20 @@ +from typing import Any + +from core.config import settings from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import as_declarative, declared_attr from sqlalchemy.orm import sessionmaker -from core.config import settings + +@as_declarative() +class Base: + id: Any + __name__: str + + @declared_attr + def __tablename__(cls) -> str: + return cls.__name__.lower() + engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/db/tasks.py b/app/core/tasks.py similarity index 51% rename from db/tasks.py rename to app/core/tasks.py index 7d7a57a..de083c8 100644 --- a/db/tasks.py +++ b/app/core/tasks.py @@ -13,6 +13,10 @@ async def close_db_connection(app: FastAPI) -> None: try: await app.state._db.disconnect() except Exception as e: - logger.warn("--- DB DISCONNECT ---") + logger.warn( + "Could not disconnect from database. This is probably because the connection was never established." + ) logger.warn(e) - logger.warn("--- DB DISCONNECT ---") + logger.warn( + "If you are running tests, this is probably because you are not using the test database." + ) diff --git a/app/extension/__init__.py b/app/extension/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/password.py b/app/extension/password.py similarity index 99% rename from core/password.py rename to app/extension/password.py index 6baa5b0..3b5ecd4 100644 --- a/core/password.py +++ b/app/extension/password.py @@ -1,9 +1,8 @@ from datetime import datetime, timedelta from typing import Optional -from jose import jwt - from core.config import settings +from jose import jwt def generate_password_reset_token(email: str) -> str: diff --git a/core/security.py b/app/extension/security.py similarity index 99% rename from core/security.py rename to app/extension/security.py index d250fcd..9fbe606 100644 --- a/core/security.py +++ b/app/extension/security.py @@ -1,11 +1,10 @@ from datetime import datetime, timedelta from typing import Any, Union +from core.config import settings from jose import jwt from passlib.context import CryptContext -from core.config import settings - pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") ALGORITHM = "HS256" diff --git a/core/tasks.py b/app/extension/tasks.py similarity index 85% rename from core/tasks.py rename to app/extension/tasks.py index eebac20..0475e61 100644 --- a/core/tasks.py +++ b/app/extension/tasks.py @@ -1,9 +1,8 @@ from typing import Callable +from core.tasks import close_db_connection, connect_to_db from fastapi import FastAPI -from db.tasks import close_db_connection, connect_to_db - def create_start_app_handler(app: FastAPI) -> Callable: async def start_app() -> None: diff --git a/main.py b/app/main.py similarity index 65% rename from main.py rename to app/main.py index 7f9ac9f..ddabcdd 100644 --- a/main.py +++ b/app/main.py @@ -1,17 +1,10 @@ +from api.api import api_router as router +from core import tasks +from core.config import settings +from core.database import Base, engine from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from fastapi.staticfiles import StaticFiles -from fastapi.templating import Jinja2Templates from starlette.requests import Request -from starlette.responses import HTMLResponse - -from api.api import api_router -from core import tasks -from core.config import settings -from db.base_class import Base -from db.session import engine - -templates = Jinja2Templates(directory="templates") def get_application(): @@ -21,7 +14,6 @@ def get_application(): version="1.0.0", openapi_url=f"{settings.API}/openapi.json", ) - app.mount("/static", StaticFiles(directory="static"), name="static") Base.metadata.create_all(bind=engine) app.add_middleware( @@ -39,9 +31,9 @@ def get_application(): app = get_application() -app.include_router(api_router, prefix=settings.API) +app.include_router(router, prefix=settings.API) -@app.get("/", response_class=HTMLResponse) +@app.get("/health", response_model=str) async def home(request: Request): - return templates.TemplateResponse("index.html", {"request": request}) + return {"message": "Hello World"} diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/schemas/user.py b/app/models/schema.py similarity index 81% rename from schemas/user.py rename to app/models/schema.py index 7f5c85a..51796dd 100644 --- a/schemas/user.py +++ b/app/models/schema.py @@ -37,3 +37,16 @@ class User(UserInDBBase): # Additional properties stored in DB class UserInDB(UserInDBBase): hashed_password: str + + +class Token(BaseModel): + access_token: str + token_type: str + + +class TokenPayload(BaseModel): + sub: Optional[int] = None + + +class Msg(BaseModel): + msg: str diff --git a/models/user.py b/app/models/tables.py similarity index 92% rename from models/user.py rename to app/models/tables.py index 3761d6f..401138f 100644 --- a/models/user.py +++ b/app/models/tables.py @@ -1,7 +1,6 @@ +from core.database import Base from sqlalchemy import Boolean, Column, Integer, String -from db.base_class import Base - class User(Base): __tablename__ = "users" diff --git a/db/base_class.py b/db/base_class.py deleted file mode 100644 index ae7879f..0000000 --- a/db/base_class.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Any - -from sqlalchemy.ext.declarative import as_declarative, declared_attr - - -@as_declarative() -class Base: - id: Any - __name__: str - - @declared_attr - def __tablename__(cls) -> str: - return cls.__name__.lower() diff --git a/.env.test b/docker-compose.env similarity index 100% rename from .env.test rename to docker-compose.env diff --git a/docker-compose.yaml b/docker-compose.yaml index ca69a3b..e40c9c1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,9 +7,9 @@ services: container_name: "frdp" volumes: - /var/run/docker.sock:/var/run/docker.sock - command: uvicorn main:app --reload --workers 1 --host 0.0.0.0 --port 8000 + command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000 env_file: - - ./.env.test + - docker-compose.env ports: - 8000:8000 depends_on: @@ -22,7 +22,7 @@ services: volumes: - postgres-data:/var/lib/postgresql/data/ env_file: - - ./.env.test + - docker-compose.env ports: - 5432:5432 @@ -30,7 +30,7 @@ services: container_name: pgadmin image: dpage/pgadmin4 env_file: - - ./.env.test + - docker-compose.env ports: - 5050:80 depends_on: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md deleted file mode 100644 index 49c94f7..0000000 --- a/docs/CONTRIBUTING.md +++ /dev/null @@ -1,35 +0,0 @@ -# Contributing to this repository - -## Getting started - -Before you begin: - -- This Project is provided by FastAPI & Docker. -- Check out the [issues](https://github.com/BnademOverflow/FRDP/issues), [pull requests](https://github.com/BnademOverflow/FRDP/pulls) and [discussions](https://github.com/BnademOverflow/FRDP/discussions). -- Check the [already existing and upcoming features](https://github.com/BnademOverflow/FRDP/#features). - -Before you make your changes, check to see if an [issue exists](https://github.com/BnademOverflow/FRDP/issues) already for the change you want to make. - -### Don't see your issue? Open one - -If you spot something new, [open an issue](https://github.com/BnademOverflow/FRDP/issues/new/choose). We'll use the issue to have a conversation 🗣 about the problem you want to fix. - -### Ready to make a change? Fork the repo - -- Fork using [GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) - -- Fork using the [command line](https://docs.github.com/en/get-started/quickstart/fork-a-repo#cloning-your-forked-repository) - -- Fork with [GitHub Codespaces](https://github.com/features/codespaces): - -### Open a pull request - -When you're done making changes and you'd like to propose them for review, use the [pull request tab](https://github.com/BnademOverflow/FRDP/pulls) to open your PR. - -Once you submit your PR, we will review it with you. The first thing you're going to want to do is a self review 🧾. - -### Your PR is merged - -Congratulations 🎊! - -Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/BnademOverflow/FRDP/graphs/contributors). diff --git a/docs/Code_of_Conduct.md b/docs/Code_of_Conduct.md deleted file mode 100644 index d9cc399..0000000 --- a/docs/Code_of_Conduct.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -**Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -.** - -In the event that your report includes one of the people mentioned above please -feel free to reach out to one of the other people listed instead. - -Followed is an unmodified [Contributor Covenant Code of Conduct 2.0](https://www.contributor-covenant.org/). - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -. Translations are available at -. diff --git a/docs/Privacy_Policy.md b/docs/Privacy_Policy.md deleted file mode 100644 index 44a4a7d..0000000 --- a/docs/Privacy_Policy.md +++ /dev/null @@ -1,74 +0,0 @@ -

Privacy Policy for BnademOverFlow

- -

At BnademOverFlow, accessible from https://bnademoverflow.com/, one of our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information that is collected and recorded by BnademOverFlow and how we use it.

- -

If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us.

- -

This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to the information that they shared and/or collect in BnademOverFlow. This policy is not applicable to any information collected offline or via channels other than this website. Our Privacy Policy was created with the help of the Privacy Policy Generator.

- -

Consent

- -

By using our website, you hereby consent to our Privacy Policy and agree to its terms.

- -

Information we collect

- -

The personal information that you are asked to provide, and the reasons why you are asked to provide it, will be made clear to you at the point we ask you to provide your personal information.

-

If you contact us directly, we may receive additional information about you such as your name, email address, phone number, the contents of the message and/or attachments you may send us, and any other information you may choose to provide.

-

When you register for an Account, we may ask for your contact information, including items such as name, company name, address, email address, and telephone number.

- -

How we use your information

- -

We use the information we collect in various ways, including to:

- -
    -
  • Provide, operate, and maintain our website
  • -
  • Improve, personalize, and expand our website
  • -
  • Understand and analyze how you use our website
  • -
  • Develop new products, services, features, and functionality
  • -
  • Communicate with you, either directly or through one of our partners, including for customer service, to provide you with updates and other information relating to the website, and for marketing and promotional purposes
  • -
  • Send you emails
  • -
  • Find and prevent fraud
  • -
- -

Log Files

- -

BnademOverFlow follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services' analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users' movement on the website, and gathering demographic information.

- -

Advertising Partners Privacy Policies

- -

You may consult this list to find the Privacy Policy for each of the advertising partners of BnademOverFlow.

- -

Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in their respective advertisements and links that appear on BnademOverFlow, which are sent directly to users' browser. They automatically receive your IP address when this occurs. These technologies are used to measure the effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on websites that you visit.

- -

Note that BnademOverFlow has no access to or control over these cookies that are used by third-party advertisers.

- -

Third Party Privacy Policies

- -

BnademOverFlow's Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may include their practices and instructions about how to opt-out of certain options.

- -

You can choose to disable cookies through your individual browser options. To know more detailed information about cookie management with specific web browsers, it can be found at the browsers' respective websites.

- -

CCPA Privacy Rights (Do Not Sell My Personal Information)

- -

Under the CCPA, among other rights, California consumers have the right to:

-

Request that a business that collects a consumer's personal data disclose the categories and specific pieces of personal data that a business has collected about consumers.

-

Request that a business delete any personal data about the consumer that a business has collected.

-

Request that a business that sells a consumer's personal data, not sell the consumer's personal data.

-

If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, please contact us.

- -

GDPR Data Protection Rights

- -

We would like to make sure you are fully aware of all of your data protection rights. Every user is entitled to the following:

-

The right to access – You have the right to request copies of your personal data. We may charge you a small fee for this service.

-

The right to rectification – You have the right to request that we correct any information you believe is inaccurate. You also have the right to request that we complete the information you believe is incomplete.

-

The right to erasure – You have the right to request that we erase your personal data, under certain conditions.

-

The right to restrict processing – You have the right to request that we restrict the processing of your personal data, under certain conditions.

-

The right to object to processing – You have the right to object to our processing of your personal data, under certain conditions.

-

The right to data portability – You have the right to request that we transfer the data that we have collected to another organization, or directly to you, under certain conditions.

-

If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, please contact us.

- -

Children's Information

- -

Another part of our priority is adding protection for children while using the internet. We encourage parents and guardians to observe, participate in, and/or monitor and guide their online activity.

- -

BnademOverFlow does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.

\ No newline at end of file diff --git a/docs/Terms_&_Conditions.md b/docs/Terms_&_Conditions.md deleted file mode 100644 index e4b8411..0000000 --- a/docs/Terms_&_Conditions.md +++ /dev/null @@ -1,123 +0,0 @@ -

Terms and Conditions

- -

Welcome to BnademOverFlow!

- -

These terms and conditions outline the rules and regulations for the use of BnademOverFlow's Website, located at https://bnademoverflow.com/.

- -

By accessing this website we assume you accept these terms and conditions. Do not continue to use BnademOverFlow if you do not agree to take all of the terms and conditions stated on this page.

- -

The following terminology applies to these Terms and Conditions, Privacy Statement and Disclaimer Notice and all Agreements: "Client", "You" and "Your" refers to you, the person log on this website and compliant to the Company’s terms and conditions. "The Company", "Ourselves", "We", "Our" and "Us", refers to our Company. "Party", "Parties", or "Us", refers to both the Client and ourselves. All terms refer to the offer, acceptance and consideration of payment necessary to undertake the process of our assistance to the Client in the most appropriate manner for the express purpose of meeting the Client’s needs in respect of provision of the Company’s stated services, in accordance with and subject to, prevailing law of Netherlands. Any use of the above terminology or other words in the singular, plural, capitalization and/or he/she or they, are taken as interchangeable and therefore as referring to same.

- -

Cookies

- -

We employ the use of cookies. By accessing BnademOverFlow, you agreed to use cookies in agreement with the BnademOverFlow's Privacy Policy.

- -

Most interactive websites use cookies to let us retrieve the user’s details for each visit. Cookies are used by our website to enable the functionality of certain areas to make it easier for people visiting our website. Some of our affiliate/advertising partners may also use cookies.

- -

License

- -

Unless otherwise stated, BnademOverFlow and/or its licensors own the intellectual property rights for all material on BnademOverFlow. All intellectual property rights are reserved. You may access this from BnademOverFlow for your own personal use subjected to restrictions set in these terms and conditions.

- -

You must not:

-
    -
  • Republish material from BnademOverFlow
  • -
  • Sell, rent or sub-license material from BnademOverFlow
  • -
  • Reproduce, duplicate or copy material from BnademOverFlow
  • -
  • Redistribute content from BnademOverFlow
  • -
- -

This Agreement shall begin on the date hereof. Our Terms and Conditions were created with the help of the Terms And Conditions Generator.

- -

Parts of this website offer an opportunity for users to post and exchange opinions and information in certain areas of the website. BnademOverFlow does not filter, edit, publish or review Comments prior to their presence on the website. Comments do not reflect the views and opinions of BnademOverFlow,its agents and/or affiliates. Comments reflect the views and opinions of the person who post their views and opinions. To the extent permitted by applicable laws, BnademOverFlow shall not be liable for the Comments or for any liability, damages or expenses caused and/or suffered as a result of any use of and/or posting of and/or appearance of the Comments on this website.

- -

BnademOverFlow reserves the right to monitor all Comments and to remove any Comments which can be considered inappropriate, offensive or causes breach of these Terms and Conditions.

- -

You warrant and represent that:

- -
    -
  • You are entitled to post the Comments on our website and have all necessary licenses and consents to do so;
  • -
  • The Comments do not invade any intellectual property right, including without limitation copyright, patent or trademark of any third party;
  • -
  • The Comments do not contain any defamatory, libelous, offensive, indecent or otherwise unlawful material which is an invasion of privacy
  • -
  • The Comments will not be used to solicit or promote business or custom or present commercial activities or unlawful activity.
  • -
- -

You hereby grant BnademOverFlow a non-exclusive license to use, reproduce, edit and authorize others to use, reproduce and edit any of your Comments in any and all forms, formats or media.

- -

Hyperlinking to our Content

- -

The following organizations may link to our Website without prior written approval:

- -
    -
  • Government agencies;
  • -
  • Search engines;
  • -
  • News organizations;
  • -
  • Online directory distributors may link to our Website in the same manner as they hyperlink to the Websites of other listed businesses; and
  • -
  • System wide Accredited Businesses except soliciting non-profit organizations, charity shopping malls, and charity fundraising groups which may not hyperlink to our Web site.
  • -
- -

These organizations may link to our home page, to publications or to other Website information so long as the link: (a) is not in any way deceptive; (b) does not falsely imply sponsorship, endorsement or approval of the linking party and its products and/or services; and (c) fits within the context of the linking party’s site.

- -

We may consider and approve other link requests from the following types of organizations:

- -
    -
  • commonly-known consumer and/or business information sources;
  • -
  • dot.com community sites;
  • -
  • associations or other groups representing charities;
  • -
  • online directory distributors;
  • -
  • internet portals;
  • -
  • accounting, law and consulting firms; and
  • -
  • educational institutions and trade associations.
  • -
- -

We will approve link requests from these organizations if we decide that: (a) the link would not make us look unfavorably to ourselves or to our accredited businesses; (b) the organization does not have any negative records with us; (c) the benefit to us from the visibility of the hyperlink compensates the absence of BnademOverFlow; and (d) the link is in the context of general resource information.

- -

These organizations may link to our home page so long as the link: (a) is not in any way deceptive; (b) does not falsely imply sponsorship, endorsement or approval of the linking party and its products or services; and (c) fits within the context of the linking party’s site.

- -

If you are one of the organizations listed in paragraph 2 above and are interested in linking to our website, you must inform us by sending an e-mail to BnademOverFlow. Please include your name, your organization name, contact information as well as the URL of your site, a list of any URLs from which you intend to link to our Website, and a list of the URLs on our site to which you would like to link. Wait 2-3 weeks for a response.

- -

Approved organizations may hyperlink to our Website as follows:

- -
    -
  • By use of our corporate name; or
  • -
  • By use of the uniform resource locator being linked to; or
  • -
  • By use of any other description of our Website being linked to that makes sense within the context and format of content on the linking party’s site.
  • -
- -

No use of BnademOverFlow's logo or other artwork will be allowed for linking absent a trademark license agreement.

- -

iFrames

- -

Without prior approval and written permission, you may not create frames around our Webpages that alter in any way the visual presentation or appearance of our Website.

- -

Content Liability

- -

We shall not be hold responsible for any content that appears on your Website. You agree to protect and defend us against all claims that is rising on your Website. No link(s) should appear on any Website that may be interpreted as libelous, obscene or criminal, or which infringes, otherwise violates, or advocates the infringement or other violation of, any third party rights.

- -

Your Privacy

- -

Please read Privacy Policy

- -

Reservation of Rights

- -

We reserve the right to request that you remove all links or any particular link to our Website. You approve to immediately remove all links to our Website upon request. We also reserve the right to amen these terms and conditions and it’s linking policy at any time. By continuously linking to our Website, you agree to be bound to and follow these linking terms and conditions.

- -

Removal of links from our website

- -

If you find any link on our Website that is offensive for any reason, you are free to contact and inform us any moment. We will consider requests to remove links but we are not obligated to or so or to respond to you directly.

- -

We do not ensure that the information on this website is correct, we do not warrant its completeness or accuracy; nor do we promise to ensure that the website remains available or that the material on the website is kept up to date.

- -

Disclaimer

- -

To the maximum extent permitted by applicable law, we exclude all representations, warranties and conditions relating to our website and the use of this website. Nothing in this disclaimer will:

- -
    -
  • limit or exclude our or your liability for death or personal injury;
  • -
  • limit or exclude our or your liability for fraud or fraudulent misrepresentation;
  • -
  • limit any of our or your liabilities in any way that is not permitted under applicable law; or
  • -
  • exclude any of our or your liabilities that may not be excluded under applicable law.
  • -
- -

The limitations and prohibitions of liability set in this Section and elsewhere in this disclaimer: (a) are subject to the preceding paragraph; and (b) govern all liabilities arising under the disclaimer, including liabilities arising in contract, in tort and for breach of statutory duty.

- -

As long as the website and the information and services on the website are provided free of charge, we will not be liable for any loss or damage of any nature.

\ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..887e120 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,129 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "FRDP" +description = "REST API with JWT Authentication using FastAPI, PostgreSQL and PgAdmin ✨" +readme = "README.md" +requires-python = ">=3.9" +license = "MIT" +authors = [ + { name = "Yasser Tahiri", email = "hello@yezz.me" }, +] + +classifiers = [ + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Framework :: FastAPI", + "Framework :: AsyncIO", + "Framework :: Pydantic", + "Framework :: Pydantic :: 1", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Internet :: WWW/HTTP :: Session", + "Typing :: Typed", +] + +dependencies = [ + "fastapi >=0.65.2,<0.96.0", + "passlib ==1.7.4", + "bcrypt ==4.0.1", + "email-validator >=1.1.0,<1.3.2", + "pyjwt ==2.6.0", + "cryptography==40.0.1", + "httpx==0.24.0", + "python-jose", + "starlette >=0.14.02,<0.26.2", + "SQLAlchemy >=1.4.37,<2.0.10", + "typing-extensions >=3.7.4,<4.5.0", + "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0", + "email-validator >=1.3.0", + "uvicorn==0.21.1", +] + +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/yezz123/frdp" +Funding = 'https://github.com/sponsors/yezz123' + +[project.optional-dependencies] +lint = [ + "pre-commit==3.0.0", + "mypy==0.991", +] +test = [ + "requests==2.28.2", + "pytest==7.2.1", + "pytest-asyncio == 0.20.3", + "pytest-cov==4.0.0", + "pytest-pretty", +] + +[tool.hatch.version] +path = "app/__init__.py" + +[tool.isort] +profile = "black" +known_third_party = ["pydantic", "typing_extensions"] + +[tool.ruff] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # too complex +] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401"] + +[tool.ruff.isort] +known-third-party = ["pydantic", "typing_extensions"] + +[tool.mypy] +plugins = "pydantic.mypy" +follow_imports = "silent" +strict_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +disallow_any_generics = true +check_untyped_defs = true +ignore_missing_imports = true +disallow_untyped_defs = true + +[[tool.mypy.overrides]] +module = "app.tests.*" +ignore_missing_imports = true +check_untyped_defs = true + +[tool.coverage.report] +precision = 2 +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError", + "raise NotImplemented", + "@overload", + "if TYPE_CHECKING:", + "if __name__ == .__main__.:", +] + +[tool.pytest] +testpaths = "tests/" +log_cli = "1" +log_cli_level = "INFO" +log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" +log_cli_date_format= "%Y-%m-%d %H:%M:%S" +asyncio_mode= "auto" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index eba5814..0000000 --- a/requirements.txt +++ /dev/null @@ -1,60 +0,0 @@ -aiofiles==0.8.0 -asgiref==3.4.1 -astroid==2.9.0 -backports.entry-points-selectable==1.1.1 -bcrypt==3.2.0 -cachetools==4.2.4 -certifi==2021.10.8 -cffi==1.15.0 -cfgv==3.3.1 -chardet==4.0.0 -charset-normalizer==2.0.9 -click==8.0.3 -colorama==0.4.4 -cryptography==36.0.1 -cssselect==1.1.0 -cssutils==2.3.0 -databases==0.5.3 -distlib==0.3.4 -dnspython==2.1.0 -ecdsa==0.17.0 -email-validator==1.1.3 -emails==0.6 -fastapi==0.68.1 -filelock==3.4.0 -greenlet==1.1.2 -h11==0.12.0 -identify==2.4.0 -idna==3.3 -isort==5.10.1 -Jinja2==3.0.3 -lazy-object-proxy==1.7.1 -lxml==4.7.1 -MarkupSafe==2.0.1 -mccabe==0.6.1 -nodeenv==1.6.0 -passlib==1.7.4 -platformdirs==2.4.0 -pre-commit==2.16.0 -premailer==3.10.0 -psycopg2==2.9.2 -pyasn1==0.4.8 -pycparser==2.21 -pydantic==1.8.2 -pylint==2.12.2 -python-dateutil==2.8.2 -python-dotenv==0.19.2 -python-jose==3.3.0 -python-multipart==0.0.5 -PyYAML==6.0 -requests==2.26.0 -rsa==4.8 -six==1.16.0 -SQLAlchemy==1.4.28 -starlette==0.14.2 -toml==0.10.2 -typing-extensions==4.0.1 -urllib3==1.26.7 -uvicorn==0.16.0 -virtualenv==20.10.0 -wrapt==1.13.3 diff --git a/schemas/msg.py b/schemas/msg.py deleted file mode 100644 index 945e0c6..0000000 --- a/schemas/msg.py +++ /dev/null @@ -1,5 +0,0 @@ -from pydantic import BaseModel - - -class Msg(BaseModel): - msg: str diff --git a/schemas/token.py b/schemas/token.py deleted file mode 100644 index ea85b46..0000000 --- a/schemas/token.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenPayload(BaseModel): - sub: Optional[int] = None diff --git a/scripts/docker.sh b/scripts/docker.sh new file mode 100644 index 0000000..b207cde --- /dev/null +++ b/scripts/docker.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -x + +docker-compose up -d diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100644 index 0000000..f423597 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -x + +pre-commit run --all-files --verbose --show-diff-on-failure diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100644 index 0000000..925bfd1 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e +set -x + +echo "ENV=${ENV}" + +export PYTHONPATH=. +pytest --cov=app --cov=tests --cov-report=term-missing # --cov-fail-under=80 diff --git a/static/css/style.css b/static/css/style.css deleted file mode 100644 index 56a0aff..0000000 --- a/static/css/style.css +++ /dev/null @@ -1,202 +0,0 @@ -body { - font-family: "Space Grotesk", sans-serif; - margin: 0; - padding: 0; - background: #f5f5f5; -} - -.wrap { - padding: 1.5rem; -} - -nav { - padding: 16px 36px; - background: #fff; - border-radius: 5px; - box-shadow: 0 6px 16px #f0f3f4; -} - -nav container { - display: flex; - justify-content: space-between; - align-items: center; - margin: 0 auto; -} - -nav container ul, -nav container .nav_menu { - display: flex; - justify-content: space-between; - align-items: center; - list-style: none; - gap: 46px; -} - -nav container li a { - padding: 8px; - letter-spacing: 0.2rem; - font-size: 0.9rem; - text-decoration: none; - text-transform: uppercase; - color: #999999; - transition: color 150ms ease-out; -} - -nav container li a:hover { - color: #030303; -} - -.logo-text { - font-size: 1.4rem; - line-height: 0.8; - letter-spacing: 0.6rem; - font-weight: 700; - position: relative; - text-transform: uppercase; -} - -.logo-text:after { - content: ""; - width: 6px; - height: 6px; - position: absolute; - background: black; - top: calc(100% / 2.7); - margin-left: 12px; - border-radius: 2px; -} - -.nav--button { - text-decoration: none; - text-transform: uppercase; - padding: 12px 24px; - color: white; - border: solid black 2px; - background: black; - transition-property: color, background; - transition-duration: 0.15s; - transition-timing-function: ease-out; - border-radius: 3px; - cursor: pointer; -} - -.nav--button:hover { - color: black; - background: transparent; -} - -.hero { - display: flex; - justify-content: center; - align-items: center; - margin-top: 48px; - border-radius: 5px; - min-height: 50vh; - position: relative; - background-image: url("https://raw.githubusercontent.com/BnademOverflow/BnademOverflow-Community/main/assets/background.png"); - background-position: center; - background-repeat: no-repeat; - background-size: cover; -} - -.hero a { - display: flex; - justify-content: center; - align-items: center; - position: absolute; - padding-top: 24px; - color: white; - height: 100%; - width: 5%; -} - -.hero a.left { - left: 0; -} - -.hero a.right { - right: 0; -} - -.hero--button { - text-decoration: none; - text-transform: uppercase; - padding: 12px 24px; - color: black; - border: solid white 2px; - background: white; - transition-property: color, background; - transition-duration: 0.15s; - transition-timing-function: ease-out; - border-radius: 3px; - cursor: pointer; - margin-top: 24px; -} - -.hero--button:hover { - color: #999999; -} - -#nav_big_text { - position: fixed; - bottom: 1.5rem; - right: 1.5rem; - color: #dce2e5; - opacity: 0; - font-size: 18rem; - font-weight: 700; - line-height: 0.8; - text-transform: uppercase; - transform: translateX(10%); - transition-property: transform, opacity; - transition-duration: 0.25s; - transition-timing-function: ease-out; - z-index: -1; -} - -#nav_big_text.big_text_active { - transform: translateX(0%); - opacity: 1; -} - -footer { - display: flex; - justify-content: flex-start; - align-items: center; - position: absolute; - bottom: 24px; -} - -.copyright { - font-size: 0.7rem; - letter-spacing: 0.2rem; - color: #999999; - text-transform: uppercase; -} - -.social_links { - display: flex; - justify-content: flex-start; - align-items: center; - gap: 2px; - margin-right: 24px; -} - -.social_links a { - display: flex; - justify-content: center; - align-items: center; - color: black; - width: 32px; - height: 32px; - border-radius: 5px; - background: transparent; - transition-property: background, opacity; - transition-duration: 0.25s; - transition-timing-function: ease-out; -} - -.social_links a:hover { - background: #fff; - color: #999999; -} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index a08a6eb..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,16 +0,0 @@ -const navLinks = document.querySelectorAll("nav a"); -const navTextCopy = document.getElementById("nav_big_text"); - -for (var i = 0; i < navLinks.length; i++) { - navLinks[i].addEventListener("mouseover", function () { - let navLinkText = this.textContent; - navTextCopy.textContent = navLinkText; - navTextCopy.classList.add("big_text_active"); - }); - - navLinks[i].addEventListener("mouseout", function () { - let navLinkText = this.textContent; - navTextCopy.textContent = navLinkText; - navTextCopy.classList.remove("big_text_active"); - }); -} diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 9c870e1..0000000 --- a/templates/index.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - FRDP - - - - - - - - -
- - - - -
- - - - diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..1788be2 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,5 @@ +from app import __version__ + + +def test_version() -> None: + assert __version__ == "2.0.0"