Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
bd48ba8
feat: allowing override mb in field component
lucyqqi May 25, 2025
63a1e25
add style to input group component
lucyqqi May 25, 2025
528d2d5
added open sans serif font
lucyqqi May 25, 2025
060a705
login page onboarding done
lucyqqi May 25, 2025
3f6d836
Create APIClients folder and authAPIClient file
UmairHundekar Jun 1, 2025
8ab42b5
made participant form
lucyqqi Jun 1, 2025
09b3adc
added authTypes file and fixed folder location
UmairHundekar Jun 5, 2025
165e005
fixed email light grey text
lucyqqi Jun 5, 2025
cfc9eef
Merge remote-tracking branch 'origin/main' into lucyqqi/LLSC-67-login…
lucyqqi Jun 5, 2025
7d630e0
Updated FE APIClient Calls to be more like Starter Code
UmairHundekar Jun 12, 2025
ae05b56
feat: added user sign up form
lucyqqi Jun 12, 2025
7dd8f79
install axios and fix auth register function naming issues
lucyqqi Jun 12, 2025
3e870ae
feat: add per support welcome & confirmation page
lucyqqi Jun 15, 2025
7a14a22
feat: password reset page
lucyqqi Jun 15, 2025
810b288
finished login logic with confirmation
lucyqqi Jun 15, 2025
504f80f
admin login page flow
lucyqqi Jun 19, 2025
b95a4d8
changed verify page to new layout
lucyqqi Jun 19, 2025
28f4038
register admin authapi
lucyqqi Jun 19, 2025
8ff1d51
register works for admin
lucyqqi Jun 19, 2025
a2ea568
add register function from apiauth to participant login
lucyqqi Jun 19, 2025
7312ca1
Reset Password Email Sent
UmairHundekar Jun 22, 2025
363ff4b
Hopefully reset working
UmairHundekar Jun 26, 2025
598677b
Additional Changes
UmairHundekar Jun 26, 2025
903943b
Send emails for verfication and resetpassword work
UmairHundekar Jul 3, 2025
d265571
Revert "Send emails for verfication and resetpassword work"
UmairHundekar Jul 3, 2025
92b67e9
email verification send works
lucyqqi Jul 3, 2025
18493b1
login registration, email verification email, & reset password
lucyqqi Jul 13, 2025
dd3694f
Fixed Password Mismatch When Reset
UmairHundekar Jul 20, 2025
b3fa419
Auto unselect text box when clicking submit button for password reset
UmairHundekar Jul 20, 2025
d381752
fix: email resend verification right redirect page
lucyqqi Jul 20, 2025
4921f29
added open sans serif font
lucyqqi May 25, 2025
29c7955
login page onboarding done
lucyqqi May 25, 2025
12cf00c
Create APIClients folder and authAPIClient file
UmairHundekar Jun 1, 2025
6a65cfc
made participant form
lucyqqi Jun 1, 2025
309e33c
added authTypes file and fixed folder location
UmairHundekar Jun 5, 2025
24f3f2f
fixed email light grey text
lucyqqi Jun 5, 2025
56cba08
Updated FE APIClient Calls to be more like Starter Code
UmairHundekar Jun 12, 2025
4352dfb
feat: added user sign up form
lucyqqi Jun 12, 2025
5724710
install axios and fix auth register function naming issues
lucyqqi Jun 12, 2025
ae81a54
feat: add per support welcome & confirmation page
lucyqqi Jun 15, 2025
853f56f
feat: password reset page
lucyqqi Jun 15, 2025
41108ce
finished login logic with confirmation
lucyqqi Jun 15, 2025
34b4762
admin login page flow
lucyqqi Jun 19, 2025
fd43b42
changed verify page to new layout
lucyqqi Jun 19, 2025
7464a0f
register admin authapi
lucyqqi Jun 19, 2025
2cb67d9
register works for admin
lucyqqi Jun 19, 2025
027e16f
add register function from apiauth to participant login
lucyqqi Jun 19, 2025
d9c5e15
Reset Password Email Sent
UmairHundekar Jun 22, 2025
5dfa3b1
Hopefully reset working
UmairHundekar Jun 26, 2025
f8a0998
Additional Changes
UmairHundekar Jun 26, 2025
efd72e3
Send emails for verfication and resetpassword work
UmairHundekar Jul 3, 2025
c19ab56
Revert "Send emails for verfication and resetpassword work"
UmairHundekar Jul 3, 2025
1a85389
email verification send works
lucyqqi Jul 3, 2025
6d82867
login registration, email verification email, & reset password
lucyqqi Jul 13, 2025
24fe2f0
Fixed Password Mismatch When Reset
UmairHundekar Jul 20, 2025
051d80c
Auto unselect text box when clicking submit button for password reset
UmairHundekar Jul 20, 2025
9b3a290
fix: email resend verification right redirect page
lucyqqi Jul 20, 2025
a2b4959
Fixed merge
UmairHundekar Jul 20, 2025
da2d566
radiobutton styling
lucyqqi Jul 20, 2025
afc554e
linked sign in page with intake forms
UmairHundekar Jul 27, 2025
4a44fa2
Linter fixes
UmairHundekar Jul 27, 2025
7f88c6d
Changed name of baseAPIClient.ts
UmairHundekar Jul 27, 2025
5bb3ff6
Rename BaseAPIClient.ts to baseAPIClient.ts
UmairHundekar Jul 27, 2025
da14206
[LLSC-82] Intake Form DB Migrations + CRUD operations (#45)
richieb21 Jul 21, 2025
86bbad8
fix: formatting issues
lucyqqi Aug 4, 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
214 changes: 214 additions & 0 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
name: Backend CI

on:
push:
branches: [ main, develop ]
paths:
- 'backend/**'
- '.github/workflows/backend-ci.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'backend/**'
- '.github/workflows/backend-ci.yml'

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
python-version: [3.12]

services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpassword
POSTGRES_USER: testuser
POSTGRES_DB: llsc_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install PDM
run: |
pip install pdm

- name: Cache PDM dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pdm
key: ${{ runner.os }}-pdm-${{ hashFiles('backend/pdm.lock') }}
restore-keys: |
${{ runner.os }}-pdm-

- name: Install dependencies
working-directory: ./backend
run: |
pdm sync --group test --group lint --group dev

- name: Set up environment variables
working-directory: ./backend
run: |
echo "POSTGRES_DATABASE_URL=postgresql://testuser:testpassword@localhost:5432/llsc_test" >> .env
echo "SECRET_KEY=test-secret-key-for-ci" >> .env
echo "ENVIRONMENT=test" >> .env

- name: Run database migrations
working-directory: ./backend
run: |
pdm run alembic upgrade head

- name: Run linting
working-directory: ./backend
run: |
pdm run ruff check .
pdm run ruff format --check .

# TODO: Re-enable mypy when type annotations are improved
# - name: Run type checking
# working-directory: ./backend
# run: |
# pdm run mypy app/ --ignore-missing-imports

- name: Run unit tests
working-directory: ./backend
run: |
pdm run python -m pytest tests/unit/ -v --cov=app --cov-report=xml --cov-report=term-missing

- name: Run integration tests
working-directory: ./backend
run: |
pdm run python -m pytest tests/functional/ -v

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./backend/coverage.xml
directory: ./backend
flags: backend
name: backend-coverage

- name: Run security scan
working-directory: ./backend
run: |
pdm run bandit -r app/ -f json -o security-report.json || true

- name: Upload security report
uses: actions/upload-artifact@v4
if: always()
with:
name: security-report
path: backend/security-report.json

e2e-tests:
runs-on: ubuntu-latest
needs: test

services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpassword
POSTGRES_USER: testuser
POSTGRES_DB: llsc_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.12

- name: Install PDM
run: |
pip install pdm

- name: Install dependencies
working-directory: ./backend
run: |
pdm sync --group test --group lint --group dev

- name: Set up environment variables
working-directory: ./backend
run: |
echo "POSTGRES_DATABASE_URL=postgresql://testuser:testpassword@localhost:5432/llsc_test" >> .env
echo "SECRET_KEY=test-secret-key-for-ci" >> .env
echo "ENVIRONMENT=test" >> .env
echo "TEST_SCRIPT_BACKEND_URL=http://localhost:8000" >> .env
echo "[email protected]" >> .env
echo "TEST_SCRIPT_PASSWORD=testpassword" >> .env

- name: Run database migrations
working-directory: ./backend
run: |
pdm run alembic upgrade head

- name: Start backend server
working-directory: ./backend
run: |
pdm run uvicorn app.server:app --host 0.0.0.0 --port 8000 &
sleep 10 # Wait for server to start

- name: Run E2E tests
working-directory: ./
run: |
pdm run python -m pytest e2e-tests/ -v --tb=short

docker-build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image
working-directory: ./backend
run: |
docker build -t llsc-backend:latest .

- name: Test Docker image
run: |
docker run --rm llsc-backend:latest python --version

notify:
runs-on: ubuntu-latest
needs: [test, e2e-tests]
if: always()

steps:
- name: Notify on success
if: needs.test.result == 'success' && needs.e2e-tests.result == 'success'
run: |
echo "✅ All tests passed! Backend is ready for deployment."

- name: Notify on failure
if: needs.test.result == 'failure' || needs.e2e-tests.result == 'failure'
run: |
echo "❌ Tests failed! Please check the logs."
exit 1
138 changes: 138 additions & 0 deletions backend/FORMS_SCHEMA_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Forms Schema Documentation

This document describes the database schema for the forms system that was implemented.

## Tables Created

### 1. Core User Data

#### `user_data`
Stores single-valued user fields:
- `id` - UUID primary key
- `date_of_birth` - Date field
- `email` - String field
- `phone` - String field

### 2. Multi-valued Reference Tables

#### `treatments`
Stores available treatment options:
- `id` - Integer primary key
- `name` - Unique treatment name

Pre-populated with:
- Chemotherapy, Immunotherapy, Radiation Therapy, Surgery, Targeted Therapy, Hormone Therapy, Stem Cell Transplant, CAR-T Cell Therapy, Clinical Trial, Palliative Care

#### `experiences`
Stores cancer-related experiences:
- `id` - Integer primary key
- `name` - Unique experience name

Pre-populated with:
- PTSD, Relapse, Anxiety, Depression, Fatigue, Neuropathy, Hair Loss, Nausea, Loss of Appetite, Sleep Problems, Cognitive Changes, Financial Stress, Relationship Changes, Body Image Issues, Survivorship Concerns

#### `qualities`
Stores ranking/matching qualities:
- `id` - Integer primary key
- `slug` - Unique identifier (e.g., 'same_age')
- `label` - Human-readable description

Pre-populated with matching criteria like same age, diagnosis, treatment, location, etc.

### 3. Bridge Tables (Many-to-Many)

#### `user_treatments`
Links users to their treatments

#### `user_experiences`
Links users to their experiences

### 4. Ranking System

#### `ranking_preferences`
Stores user ranking preferences:
- `user_id` - Reference to users table
- `quality_id` - Reference to qualities table
- `rank` - Integer ranking (1 = most important)

### 5. Form System

#### `forms`
Form definitions and versioning:
- `id` - UUID primary key
- `name` - Form name
- `version` - Version number
- `type` - Enum (intake, ranking, secondary, become_volunteer, become_participant)

#### `form_submissions`
Raw form submission data:
- `id` - UUID primary key
- `form_id` - Reference to forms table
- `user_id` - Reference to users table
- `submitted_at` - Timestamp
- `answers` - JSONB field with raw form data

## Usage Examples

### Accessing Multi-valued Fields
Thanks to SQLAlchemy relationships, you can access multi-valued fields as lists:

```python
from app.models import UserData

# Get a user
user = session.query(UserData).first()

# Access treatments as a list
user_treatments = user.treatments # Returns list of Treatment objects
treatment_names = [t.name for t in user.treatments]

# Access experiences as a list
user_experiences = user.experiences # Returns list of Experience objects
```

### Creating Form Submissions
```python
from app.models import FormSubmission
import json

submission = FormSubmission(
form_id=form_uuid,
user_id=user_uuid,
answers={
"date_of_birth": "1990-01-01",
"treatments": ["Chemotherapy", "Radiation Therapy"],
"experiences": ["Anxiety", "Fatigue"],
# ... other form fields
}
)
session.add(submission)
session.commit()
```

### Setting Ranking Preferences
```python
from app.models import RankingPreference

# User ranks "same_diagnosis" as most important (rank 1)
pref = RankingPreference(
user_id=user_uuid,
quality_id=2, # same_diagnosis quality
rank=1
)
session.add(pref)
session.commit()
```

## Form Processing Workflow

1. **Form Submission**: Raw data stored in `form_submissions.answers` as JSON
2. **Data Parsing**: Custom parser extracts structured data from JSON
3. **Database Population**: Parsed data populates `user_data` and relationship tables
4. **Versioning**: Multiple submissions create new form versions while preserving history

This design allows for:
- Flexible form structures without schema changes
- Historical tracking of all submissions
- Structured querying of user data for matching algorithms
- Easy addition of new treatment/experience/quality options
7 changes: 7 additions & 0 deletions backend/app/interfaces/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,10 @@ def is_authorized_by_email(self, access_token, requested_email):
:rtype: bool
"""
pass

@abstractmethod
def verify_email(self, email):
"""
Verify the email address of the user with the given email
"""
pass
9 changes: 8 additions & 1 deletion backend/app/middleware/auth_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ def __init__(self, app: ASGIApp, public_paths: List[str] = None):
self.logger = logging.getLogger(LOGGER_NAME("auth_middleware"))

def is_public_path(self, path: str) -> bool:
return path in self.public_paths
for public_path in self.public_paths:
# Handle parameterized routes by checking if path starts with the pattern
if public_path.endswith("{email}") and path.startswith(public_path.replace("{email}", "")):
return True
# Exact match for non-parameterized routes
if path == public_path:
return True
return False

async def dispatch(self, request: Request, call_next):
if self.is_public_path(request.url.path):
Expand Down
13 changes: 13 additions & 0 deletions backend/app/models/Experience.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship

from .Base import Base


class Experience(Base):
__tablename__ = "experiences"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False) # 'PTSD', 'Relapse', etc.

# Back reference for many-to-many relationship
users = relationship("UserData", secondary="user_experiences", back_populates="experiences")
Loading
Loading