Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@
<a href="https://github.com/fastapi/full-stack-fastapi-template/actions?query=workflow%3A%22Test+Backend%22" target="_blank"><img src="https://github.com/fastapi/full-stack-fastapi-template/workflows/Test%20Backend/badge.svg" alt="Test Backend"></a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/full-stack-fastapi-template" target="_blank"><img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/full-stack-fastapi-template.svg" alt="Coverage"></a>

## Goals of This Fork

- 📁 Have the file structure more closely follow
[zhanymkanov/fastapi-best-practices](https://github.com/zhanymkanov/fastapi-best-practices)
and [Netflix/dispatch](https://github.com/Netflix/dispatch).
- 📧 Support case insensitive email addresses.
- The original template intentionally did not support case insensitive email
addresses because according to [RFC
5321](https://www.rfc-editor.org/rfc/rfc5321#section-2.3.11) email addresses can
be case sensitive. In practice, most websites ignore the standard to simplify the
experience for the end user.
- ⚡ Automatically load models and routers based on the project folder structure.
- 🧪 Run backend tests on a seperate test database.
- 🧹 Stricter linter configurations.
- 🏠 Set the default URL to be the root domain instead of the dashboard subdomain.
- 📙 Changed Alembic to use timestamp based file names.

## Technology Stack and Features

- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) for the Python backend API.
Expand All @@ -28,23 +45,23 @@

### Dashboard Login

[![API docs](img/login.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![API docs](img/login.png)](https://github.com/ryn-cx/full-stack-fastapi-template)

### Dashboard - Admin

[![API docs](img/dashboard.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![API docs](img/dashboard.png)](https://github.com/ryn-cx/full-stack-fastapi-template)

### Dashboard - Items

[![API docs](img/dashboard-items.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![API docs](img/dashboard-items.png)](https://github.com/ryn-cx/full-stack-fastapi-template)

### Dashboard - Dark Mode

[![API docs](img/dashboard-dark.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![API docs](img/dashboard-dark.png)](https://github.com/ryn-cx/full-stack-fastapi-template)

### Interactive API Documentation

[![API docs](img/docs.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![API docs](img/docs.png)](https://github.com/ryn-cx/full-stack-fastapi-template)

## How To Use It

Expand All @@ -62,7 +79,7 @@ But you can do the following:
- Clone this repository manually, set the name with the name of the project you want to use, for example `my-full-stack`:

```bash
git clone git@github.com:fastapi/full-stack-fastapi-template.git my-full-stack
git clone https://github.com/ryn-cx/full-stack-fastapi-template.git my-full-stack
```

- Enter into the new directory:
Expand All @@ -74,13 +91,13 @@ cd my-full-stack
- Set the new origin to your new repository, copy it from the GitHub interface, for example:

```bash
git remote set-url origin git@github.com:octocat/my-full-stack.git
git remote set-url origin https://github.com/octocat/my-full-stack.git
```

- Add this repo as another "remote" to allow you to get updates later:

```bash
git remote add upstream git@github.com:fastapi/full-stack-fastapi-template.git
git remote add upstream https://github.com/ryn-cx/full-stack-fastapi-template.git
```

- Push the code to your new repository:
Expand All @@ -98,10 +115,10 @@ After cloning the repository, and after doing changes, you might want to get the
```bash
git remote -v

origin git@github.com:octocat/my-full-stack.git (fetch)
origin git@github.com:octocat/my-full-stack.git (push)
upstream git@github.com:fastapi/full-stack-fastapi-template.git (fetch)
upstream git@github.com:fastapi/full-stack-fastapi-template.git (push)
origin https://github.com/octocat/my-full-stack.git (fetch)
origin https://github.com/octocat/my-full-stack.git (push)
upstream https://github.com/ryn-cx/full-stack-fastapi-template.git (fetch)
upstream https://github.com/ryn-cx/full-stack-fastapi-template.git (push)
```

- Pull the latest changes without merging:
Expand Down Expand Up @@ -175,16 +192,16 @@ Decide a name for your new project's directory, you will use it below. For examp
Go to the directory that will be the parent of your project, and run the command with your project's name:

```bash
copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust
copier copy https://github.com/ryn-cx/full-stack-fastapi-template my-awesome-project --trust
```

If you have `pipx` and you didn't install `copier`, you can run it directly:

```bash
pipx run copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust
pipx run copier copy https://github.com/ryn-cx/full-stack-fastapi-template my-awesome-project --trust
```

**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/fastapi/full-stack-fastapi-template/blob/master/.copier/update_dotenv.py) that updates your `.env` files.
**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/ryn-cx/full-stack-fastapi-template/blob/master/.copier/update_dotenv.py) that updates your `.env` files.

### Input Variables

Expand Down
2 changes: 1 addition & 1 deletion backend/alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
script_location = app/alembic

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s

# timezone to use when rendering the date
# within the migration file as well as the filename.
Expand Down
9 changes: 7 additions & 2 deletions backend/app/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@
# target_metadata = mymodel.Base.metadata
# target_metadata = None

from app.models import SQLModel # noqa
from app.core.config import settings # noqa
from app.constants import APP_PATH
from importlib import import_module
from sqlmodel import SQLModel
for model_file in APP_PATH.glob("*/models.py"):
import_module(f"app.{model_file.parent.name}.models")

from app.config import settings # noqa

target_metadata = SQLModel.metadata

Expand Down
File renamed without changes.
File renamed without changes.
14 changes: 0 additions & 14 deletions backend/app/api/main.py

This file was deleted.

123 changes: 0 additions & 123 deletions backend/app/api/routes/login.py

This file was deleted.

File renamed without changes.
5 changes: 5 additions & 0 deletions backend/app/auth/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALGORITHM = "HS256"

# Dummy hash to use for timing attack prevention when user is not found
# This is an Argon2 hash of a random password, used to ensure constant-time comparison
DUMMY_HASH = "$argon2id$v=19$m=65536,t=3,p=4$MjQyZWE1MzBjYjJlZTI0Yw$YTU4NGM5ZTZmYjE2NzZlZjY0ZWY3ZGRkY2U2OWFjNjk"
28 changes: 17 additions & 11 deletions backend/app/api/deps.py → backend/app/auth/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from pydantic import ValidationError
from sqlmodel import Session

from app.core import security
from app.core.config import settings
from app.core.db import engine
from app.models import TokenPayload, User
from app.auth.constants import ALGORITHM
from app.auth.schemas import TokenPayload
from app.config import settings
from app.database import engine
from app.users.models import User

reusable_oauth2 = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
tokenUrl=f"{settings.API_V1_STR}/login/access-token",
)


Expand All @@ -29,9 +30,7 @@ def get_db() -> Generator[Session, None, None]:

def get_current_user(session: SessionDep, token: TokenDep) -> User:
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
)
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
token_data = TokenPayload(**payload)
except (InvalidTokenError, ValidationError):
raise HTTPException(
Expand All @@ -40,9 +39,15 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
)
user = session.get(User, token_data.sub)
if not user:
raise HTTPException(status_code=404, detail="User not found")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
)
if not user.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Inactive user",
)
return user


Expand All @@ -52,6 +57,7 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
def get_current_active_superuser(current_user: CurrentUser) -> User:
if not current_user.is_superuser:
raise HTTPException(
status_code=403, detail="The user doesn't have enough privileges"
status_code=status.HTTP_403_FORBIDDEN,
detail="The user doesn't have enough privileges",
)
return current_user
Loading
Loading