Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2d3957b
initial documentation commit
rbuels Feb 22, 2025
286c434
refactor models.py into a multi-file package
rbuels Feb 22, 2025
f1582c1
add first draft of models for attachments and patients
rbuels Feb 22, 2025
6c0bdb7
feat: Add patients routes to backend API
rbuels Feb 22, 2025
e8010e2
feat: Add CRUD routes for patients with authorization checks
rbuels Feb 22, 2025
9ec3e0b
refactor: Update patients route to use Patient model instead of Item
rbuels Feb 22, 2025
9ead27f
test: Add comprehensive test suite for patient CRUD routes
rbuels Feb 22, 2025
b3d1ccb
refactor: Convert test_patients.py from async to sync tests
rbuels Feb 22, 2025
67dd823
add .aider* files to gitignore
rbuels Feb 22, 2025
deacf0d
register patients route
rbuels Feb 22, 2025
2234f3b
some improvements to patient and attachment models
rbuels Feb 22, 2025
b3e3b3b
feat: Add CRUD routes and model for Attachments
rbuels Feb 22, 2025
0de656b
test: Add comprehensive tests for attachment CRUD routes
rbuels Feb 22, 2025
1472bc0
get the (pretty good!) generated attachment tests passing
rbuels Feb 22, 2025
2d24b5e
fix patient and attachment test data sticking around after tests run
rbuels Feb 24, 2025
50dae3e
feat: Add GIN index on patient medical_history column for improved te…
rbuels Feb 24, 2025
b4f3a56
test: Add test for patient medical history text search
rbuels Feb 24, 2025
f83332c
add history_text query param to GET .../patients, get its test passing
rbuels Feb 24, 2025
697315b
test: Add tests for name_exact, name_text, and attachment MIME type f…
rbuels Feb 24, 2025
fa1bb5c
add more filters to .../patients route and get tests passing
rbuels Feb 24, 2025
6e64968
feat: Add storage_path field to Attachment model for S3 file paths
rbuels Feb 24, 2025
2b39d31
add dep on boto3 for AWS interactions
rbuels Feb 24, 2025
8aa9d8d
fix multiple alembic heads
rbuels Feb 24, 2025
0b3213d
add config and env variables for AWS credentials
rbuels Feb 24, 2025
63220cc
first draft of attachment presigned url generation, delete routes for…
rbuels Feb 24, 2025
31db2e0
fix alembic not detecting the medical history fulltext index
rbuels Feb 24, 2025
e74e709
URL presigning starting to work
rbuels Feb 24, 2025
40cc190
test: Add test for read_attachment_content route with presigned URL r…
rbuels Feb 24, 2025
a9ed2a9
get ai-written tests passing
rbuels Feb 24, 2025
7e8b778
remove extraneous things from the root README
rbuels Feb 24, 2025
343f985
updated README
rbuels Feb 24, 2025
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
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ SENTRY_DSN=
# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
DOCKER_IMAGE_FRONTEND=frontend

AWS_ACCESS_KEY_ID=fake-access-key-changethis
AWS_SECRET_ACCESS_KEY=fake-secret-key-changethis
AWS_REGION=us-east-1
AWS_S3_ATTACHMENTS_BUCKET=patient-attachments
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules/
/playwright-report/
/blob-report/
/playwright/.cache/
.aider*
268 changes: 62 additions & 206 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,239 +1,95 @@
# Full Stack FastAPI Template
# MCG Take-Home Assessment - Patient Care Management System

<a href="https://github.com/fastapi/full-stack-fastapi-template/actions?query=workflow%3ATest" target="_blank"><img src="https://github.com/fastapi/full-stack-fastapi-template/workflows/Test/badge.svg" alt="Test"></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>
## Design

## Technology Stack and Features
See the system design in [Excalidraw here](https://excalidraw.com/#json=4cLkV3RAlGQ95xFiVW1fY,QTeiqBvSWKEk77UItun2aA)

- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) for the Python backend API.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) for the Python SQL database interactions (ORM).
- 🔍 [Pydantic](https://docs.pydantic.dev), used by FastAPI, for the data validation and settings management.
- 💾 [PostgreSQL](https://www.postgresql.org) as the SQL database.
- 🚀 [React](https://react.dev) for the frontend.
- 💃 Using TypeScript, hooks, Vite, and other parts of a modern frontend stack.
- 🎨 [Chakra UI](https://chakra-ui.com) for the frontend components.
- 🤖 An automatically generated frontend client.
- 🧪 [Playwright](https://playwright.dev) for End-to-End testing.
- 🦇 Dark mode support.
- 🐋 [Docker Compose](https://www.docker.com) for development and production.
- 🔒 Secure password hashing by default.
- 🔑 JWT (JSON Web Token) authentication.
- 📫 Email based password recovery.
- ✅ Tests with [Pytest](https://pytest.org).
- 📞 [Traefik](https://traefik.io) as a reverse proxy / load balancer.
- 🚢 Deployment instructions using Docker Compose, including how to set up a frontend Traefik proxy to handle automatic HTTPS certificates.
- 🏭 CI (continuous integration) and CD (continuous deployment) based on GitHub Actions.
## Implementation

### Dashboard Login
This is based on the [FastAPI full stack template](https://fastapi.tiangolo.com/project-generation/), started as a fork of that project.

[![API docs](img/login.png)](https://github.com/fastapi/full-stack-fastapi-template)
It includes an API server based on Python and FastAPI, with an SQLModel ORM,
Pydantic type checking, connects to a PostgreSQL database with
migrations managed by Alembic, and creates AWS presigned URLs for attachment
uploads and downloads using `boto3`.

### Dashboard - Admin
- Added SQLModel classes and associated Alembic migrations for Patient and Attachment tables
- Added configuration variables for setting AWS credentials for use by `boto3`
- Implemented `/patients/...` and `/attachments/...` API routes as specified in the "REST API Sketch" section of the [Design Document](https://excalidraw.com/#json=4cLkV3RAlGQ95xFiVW1fY,QTeiqBvSWKEk77UItun2aA)
- Implemented automated tests for all API routes based on `pytest` and the FastAPI TestClient.
- All patient and attachment APIs require a valid JWT authentication token present in the request headers.

[![API docs](img/dashboard.png)](https://github.com/fastapi/full-stack-fastapi-template)
AI coding assistants used in development: [GitHub Copilot](https://github.com/features/copilot) and [Aider](https://aider.chat) connected to Claude 3.5-sonnet. Aider proved quite adept at writing tests.

### Dashboard - Create User
### Design Notes

[![API docs](img/dashboard-create.png)](https://github.com/fastapi/full-stack-fastapi-template)
Publicly visible patient and attachment IDs are non-sequential UUIDs, which is a security best practice.

### Dashboard - Items
File uploads and downloads go directly to and from AWS S3 via presigned URLs, bypassing the application server, which greatly reduces load on the application server and improves upload and download performance.

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

### Dashboard - User Settings
#### Authentication and User Management

[![API docs](img/dashboard-user-settings.png)](https://github.com/fastapi/full-stack-fastapi-template)
Fully functional user management and authentication APIs are
included in the base project, including password reset emails.
See the template documentation for details.

### Dashboard - Dark Mode
#### POST `/api/v1/patients`

[![API docs](img/dashboard-dark.png)](https://github.com/fastapi/full-stack-fastapi-template)
Creates a new patient record.

### Interactive API Documentation
headers: user's JWT token
response: JSON representation of new patient record

[![API docs](img/docs.png)](https://github.com/fastapi/full-stack-fastapi-template)
#### GET `/api/v1/patients/<patient_uuid>`

## How To Use It
Gets the details of a single patient.

You can **just fork or clone** this repository and use it as is.
request headers: user's JWT token
response: JSON representation of patient

✨ It just works. ✨
#### GET `/api/v1/patients?<query>`

### How to Use a Private Repository
Retrieves patient records.

If you want to have a private repository, GitHub won't allow you to simply fork it as it doesn't allow changing the visibility of forks.
request headers: user's JWT token
query parameters (optional):
- name_text
- name_exact
- history_text
- has_attachment_mime_type
- skip, limit (for paging)
response:
JSON array of matching patient records,
filtered according to query params.

But you can do the following:
#### POST `/api/v1/attachments`

- Create a new GitHub repo, for example `my-full-stack`.
- Clone this repository manually, set the name with the name of the project you want to use, for example `my-full-stack`:
Creates a new attachment.

```bash
git clone [email protected]:fastapi/full-stack-fastapi-template.git my-full-stack
```
request headers:
user's JWT token
- Content-Type: <mime_type>
body:
- file_name
response:
- id
- upload_url: presigned upload URL

- Enter into the new directory:
Note: after creating the attachment, the client should upload the file
itself to the provided presigned AWS S3 URL.

```bash
cd my-full-stack
```
#### GET `/api/v1/attachments/<attachment_uuid>/content`

- Set the new origin to your new repository, copy it from the GitHub interface, for example:
Retrieves attachment content

```bash
git remote set-url origin [email protected]:octocat/my-full-stack.git
```
request headers: user's JWT token
response:
302 redirect to presigned blob storage URL

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

```bash
git remote add upstream [email protected]:fastapi/full-stack-fastapi-template.git
```

- Push the code to your new repository:

```bash
git push -u origin master
```

### Update From the Original Template

After cloning the repository, and after doing changes, you might want to get the latest changes from this original template.

- Make sure you added the original repository as a remote, you can check it with:

```bash
git remote -v

origin [email protected]:octocat/my-full-stack.git (fetch)
origin [email protected]:octocat/my-full-stack.git (push)
upstream [email protected]:fastapi/full-stack-fastapi-template.git (fetch)
upstream [email protected]:fastapi/full-stack-fastapi-template.git (push)
```

- Pull the latest changes without merging:

```bash
git pull --no-commit upstream master
```

This will download the latest changes from this template without committing them, that way you can check everything is right before committing.

- If there are conflicts, solve them in your editor.

- Once you are done, commit the changes:

```bash
git merge --continue
```

### Configure

You can then update configs in the `.env` files to customize your configurations.

Before deploying it, make sure you change at least the values for:

- `SECRET_KEY`
- `FIRST_SUPERUSER_PASSWORD`
- `POSTGRES_PASSWORD`

You can (and should) pass these as environment variables from secrets.

Read the [deployment.md](./deployment.md) docs for more details.

### Generate Secret Keys

Some environment variables in the `.env` file have a default value of `changethis`.

You have to change them with a secret key, to generate secret keys you can run the following command:

```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```

Copy the content and use that as password / secret key. And run that again to generate another secure key.

## How To Use It - Alternative With Copier

This repository also supports generating a new project using [Copier](https://copier.readthedocs.io).

It will copy all the files, ask you configuration questions, and update the `.env` files with your answers.

### Install Copier

You can install Copier with:

```bash
pip install copier
```

Or better, if you have [`pipx`](https://pipx.pypa.io/), you can run it with:

```bash
pipx install copier
```

**Note**: If you have `pipx`, installing copier is optional, you could run it directly.

### Generate a Project With Copier

Decide a name for your new project's directory, you will use it below. For example, `my-awesome-project`.

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
```

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
```

**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.

### Input Variables

Copier will ask you for some data, you might want to have at hand before generating the project.

But don't worry, you can just update any of that in the `.env` files afterwards.

The input variables, with their default values (some auto generated) are:

- `project_name`: (default: `"FastAPI Project"`) The name of the project, shown to API users (in .env).
- `stack_name`: (default: `"fastapi-project"`) The name of the stack used for Docker Compose labels and project name (no spaces, no periods) (in .env).
- `secret_key`: (default: `"changethis"`) The secret key for the project, used for security, stored in .env, you can generate one with the method above.
- `first_superuser`: (default: `"[email protected]"`) The email of the first superuser (in .env).
- `first_superuser_password`: (default: `"changethis"`) The password of the first superuser (in .env).
- `smtp_host`: (default: "") The SMTP server host to send emails, you can set it later in .env.
- `smtp_user`: (default: "") The SMTP server user to send emails, you can set it later in .env.
- `smtp_password`: (default: "") The SMTP server password to send emails, you can set it later in .env.
- `emails_from_email`: (default: `"[email protected]"`) The email account to send emails from, you can set it later in .env.
- `postgres_password`: (default: `"changethis"`) The password for the PostgreSQL database, stored in .env, you can generate one with the method above.
- `sentry_dsn`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env.

## Backend Development

Backend docs: [backend/README.md](./backend/README.md).

## Frontend Development

Frontend docs: [frontend/README.md](./frontend/README.md).

## Deployment

Deployment docs: [deployment.md](./deployment.md).

## Development

General development docs: [development.md](./development.md).

This includes using Docker Compose, custom local domains, `.env` configurations, etc.

## Release Notes

Check the file [release-notes.md](./release-notes.md).
NOTE: AWS presigned URL generation supports setting the Content-Disposition header (and thus the downloaded file name) that will be provided to the client, which is what is implemented here.

## License

The Full Stack FastAPI Template is licensed under the terms of the MIT license.
This is a fork of the [FastAPI full stack template](https://fastapi.tiangolo.com/project-generation/) and is licensed under the terms of the MIT license.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Add Patient and Attachment models

Revision ID: 5c21bc8e2271
Revises: 1a31ce608336
Create Date: 2025-02-21 20:56:29.918096

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = '5c21bc8e2271'
down_revision = '1a31ce608336'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('patient',
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('dob', sa.DateTime(), nullable=True),
sa.Column('contact_info', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('medical_history', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('id', sa.Uuid(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('attachment',
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('dob', sa.DateTime(), nullable=True),
sa.Column('contact_info', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('medical_history', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('patient_id', sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(['patient_id'], ['patient.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('attachment')
op.drop_table('patient')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""add fulltext index for patient.medical_history

Revision ID: 60300545fecf
Revises: 9e47d43a4b8a
Create Date: 2025-02-23 19:56:05.494933

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = '60300545fecf'
down_revision = '9e47d43a4b8a'
branch_labels = None
depends_on = None


def upgrade() -> None:
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
op.create_index(
'ix_patient_medical_history_gin',
'patient',
['medical_history'],
unique=False,
postgresql_using='gin',
postgresql_ops={'medical_history': 'gin_trgm_ops'}
)

def downgrade() -> None:
op.drop_index('ix_patient_medical_history_gin', table_name='patient')

Loading
Loading