diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index b183bb37..55dfbd29 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -75,6 +75,12 @@ jobs: export POSTGRES_DATABASE_URL="$POSTGRES_TEST_DATABASE_URL" pdm run alembic upgrade heads + - name: Seed database with reference data + working-directory: ./backend + run: | + export POSTGRES_DATABASE_URL="$POSTGRES_TEST_DATABASE_URL" + pdm run seed + - name: Run linting working-directory: ./backend run: | @@ -169,6 +175,12 @@ jobs: export POSTGRES_DATABASE_URL="$POSTGRES_TEST_DATABASE_URL" pdm run alembic upgrade heads + - name: Seed database with reference data + working-directory: ./backend + run: | + export POSTGRES_DATABASE_URL="$POSTGRES_TEST_DATABASE_URL" + pdm run seed + - name: Start backend server working-directory: ./backend run: | diff --git a/README.md b/README.md index f2f02323..18448931 100644 --- a/README.md +++ b/README.md @@ -331,3 +331,15 @@ To apply the migration, run the following command: ```bash pdm run alembic upgrade head ``` + +## Database Seeding + +```bash +# Seed the database with reference data +cd backend && pdm run seed + +### Adding New Seed Data + +1. Create or modify seed files in `backend/app/seeds/` +2. Update the runner in `backend/app/seeds/runner.py` +3. Run `pdm run seed` to apply changes diff --git a/backend/app/seeds/__init__.py b/backend/app/seeds/__init__.py new file mode 100644 index 00000000..3b8b110d --- /dev/null +++ b/backend/app/seeds/__init__.py @@ -0,0 +1,10 @@ +""" +Database seeding system for LLSC backend. + +This module provides a clean separation between schema migrations and reference data seeding. +All reference data (roles, treatments, experiences, etc.) is managed here instead of in migrations. +""" + +from .runner import seed_database + +__all__ = ["seed_database"] diff --git a/backend/app/seeds/experiences.py b/backend/app/seeds/experiences.py new file mode 100644 index 00000000..fff1e318 --- /dev/null +++ b/backend/app/seeds/experiences.py @@ -0,0 +1,36 @@ +"""Seed experiences data.""" + +from sqlalchemy.orm import Session + +from app.models.Experience import Experience + + +def seed_experiences(session: Session) -> None: + """Seed the experiences table with cancer-related experiences.""" + + experiences_data = [ + {"id": 1, "name": "Brain Fog"}, + {"id": 2, "name": "Communication Challenges"}, + {"id": 3, "name": "Compassion Fatigue"}, + {"id": 4, "name": "Feeling Overwhelmed"}, + {"id": 5, "name": "Fatigue"}, + {"id": 6, "name": "Fertility Issues"}, + {"id": 7, "name": "Graft vs Host"}, + {"id": 8, "name": "Returning to work or school after/during treatment"}, + {"id": 9, "name": "Speaking to your family or friends about the diagnosis"}, + {"id": 10, "name": "Relapse"}, + {"id": 11, "name": "Anxiety / Depression"}, + {"id": 12, "name": "PTSD"}, + ] + + for experience_data in experiences_data: + # Check if experience already exists + existing_experience = session.query(Experience).filter_by(id=experience_data["id"]).first() + if not existing_experience: + experience = Experience(**experience_data) + session.add(experience) + print(f"Added experience: {experience_data['name']}") + else: + print(f"Experience already exists: {experience_data['name']}") + + session.commit() diff --git a/backend/app/seeds/forms.py b/backend/app/seeds/forms.py new file mode 100644 index 00000000..78010a24 --- /dev/null +++ b/backend/app/seeds/forms.py @@ -0,0 +1,42 @@ +"""Seed forms data.""" + +import uuid + +from sqlalchemy.orm import Session + +from app.models.Form import Form + + +def seed_forms(session: Session) -> None: + """Seed the forms table with default form configurations.""" + + forms_data = [ + { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Participant Intake Form", + "version": 1, + "type": "intake", + }, + { + "id": "12345678-1234-1234-1234-123456789013", + "name": "Volunteer Intake Form", + "version": 1, + "type": "intake", + }, + ] + + for form_data in forms_data: + # Check if form already exists + form_id = uuid.UUID(form_data["id"]) + existing_form = session.query(Form).filter_by(id=form_id).first() + if not existing_form: + # Convert string UUID to UUID object + form_data_copy = form_data.copy() + form_data_copy["id"] = form_id + form = Form(**form_data_copy) + session.add(form) + print(f"Added form: {form_data['name']}") + else: + print(f"Form already exists: {form_data['name']}") + + session.commit() diff --git a/backend/app/seeds/qualities.py b/backend/app/seeds/qualities.py new file mode 100644 index 00000000..291cda49 --- /dev/null +++ b/backend/app/seeds/qualities.py @@ -0,0 +1,32 @@ +"""Seed qualities data.""" + +from sqlalchemy.orm import Session + +from app.models.Quality import Quality + + +def seed_qualities(session: Session) -> None: + """Seed the qualities table with matching qualities.""" + + qualities_data = [ + {"slug": "same_age", "label": "the same age as"}, + {"slug": "same_gender_identity", "label": "the same gender identity as"}, + {"slug": "same_ethnic_or_cultural_group", "label": "the same ethnic or cultural group as"}, + {"slug": "same_marital_status", "label": "the same marital status as"}, + {"slug": "same_parental_status", "label": "the same parental status as"}, + {"slug": "same_diagnosis", "label": "the same diagnosis as"}, + ] + + for quality_data in qualities_data: + # Check if quality already exists + existing_quality = session.query(Quality).filter_by(slug=quality_data["slug"]).first() + if not existing_quality: + quality = Quality(**quality_data) + session.add(quality) + print(f"Added quality: {quality_data['slug']}") + else: + # Update label in case it changed + existing_quality.label = quality_data["label"] + print(f"Quality already exists (updated label): {quality_data['slug']}") + + session.commit() diff --git a/backend/app/seeds/roles.py b/backend/app/seeds/roles.py new file mode 100644 index 00000000..da4c6531 --- /dev/null +++ b/backend/app/seeds/roles.py @@ -0,0 +1,27 @@ +"""Seed roles data.""" + +from sqlalchemy.orm import Session + +from app.models.Role import Role + + +def seed_roles(session: Session) -> None: + """Seed the roles table with default roles.""" + + roles_data = [ + {"id": 1, "name": "participant"}, + {"id": 2, "name": "volunteer"}, + {"id": 3, "name": "admin"}, + ] + + for role_data in roles_data: + # Check if role already exists + existing_role = session.query(Role).filter_by(id=role_data["id"]).first() + if not existing_role: + role = Role(**role_data) + session.add(role) + print(f"Added role: {role_data['name']}") + else: + print(f"Role already exists: {role_data['name']}") + + session.commit() diff --git a/backend/app/seeds/runner.py b/backend/app/seeds/runner.py new file mode 100644 index 00000000..11de1d22 --- /dev/null +++ b/backend/app/seeds/runner.py @@ -0,0 +1,102 @@ +"""Database seeding runner.""" + +import argparse +import logging +import os +import sys + +from dotenv import load_dotenv +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from app.utilities.constants import LOGGER_NAME + +# Import all seed functions +from .experiences import seed_experiences +from .forms import seed_forms +from .qualities import seed_qualities +from .roles import seed_roles +from .treatments import seed_treatments + +# Load environment variables +load_dotenv() + +log = logging.getLogger(LOGGER_NAME("seeds")) + + +def get_database_session(): + """Create a database session for seeding.""" + database_url = os.getenv("POSTGRES_DATABASE_URL") + if not database_url: + raise ValueError("POSTGRES_DATABASE_URL environment variable is required") + + engine = create_engine(database_url) + SessionLocal = sessionmaker(bind=engine) + return SessionLocal() + + +def seed_database(verbose: bool = True) -> None: + """ + Run all database seeding functions. + + Args: + verbose: Whether to print detailed output + """ + if verbose: + print("🌱 Starting database seeding...") + + session = get_database_session() + + try: + # Run all seed functions in dependency order + seed_functions = [ + ("Roles", seed_roles), + ("Treatments", seed_treatments), + ("Experiences", seed_experiences), + ("Qualities", seed_qualities), + ("Forms", seed_forms), + ] + + for name, seed_func in seed_functions: + if verbose: + print(f"\nšŸ“¦ Seeding {name}...") + try: + seed_func(session) + if verbose: + print(f"āœ… {name} seeded successfully") + except Exception as e: + print(f"āŒ Error seeding {name}: {str(e)}") + log.error(f"Error seeding {name}: {str(e)}") + raise + + if verbose: + print("\nšŸŽ‰ Database seeding completed successfully!") + + except Exception as e: + session.rollback() + if verbose: + print(f"\nāŒ Database seeding failed: {str(e)}") + raise + finally: + session.close() + + +def main(): + """CLI entry point for database seeding.""" + + parser = argparse.ArgumentParser(description="Seed the LLSC database with reference data") + parser.add_argument("--quiet", "-q", action="store_true", help="Suppress output") + parser.add_argument("--env", help="Environment (currently unused but for future extension)") + + args = parser.parse_args() + + try: + seed_database(verbose=not args.quiet) + sys.exit(0) + except Exception as e: + print(f"Seeding failed: {str(e)}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/backend/app/seeds/treatments.py b/backend/app/seeds/treatments.py new file mode 100644 index 00000000..b52de32e --- /dev/null +++ b/backend/app/seeds/treatments.py @@ -0,0 +1,38 @@ +"""Seed treatments data.""" + +from sqlalchemy.orm import Session + +from app.models.Treatment import Treatment + + +def seed_treatments(session: Session) -> None: + """Seed the treatments table with cancer treatments.""" + + treatments_data = [ + {"id": 1, "name": "Unknown"}, + {"id": 2, "name": "Watch and Wait / Active Surveillance"}, + {"id": 3, "name": "Chemotherapy"}, + {"id": 4, "name": "Immunotherapy"}, + {"id": 5, "name": "Oral Chemotherapy"}, + {"id": 6, "name": "Radiation"}, + {"id": 7, "name": "Maintenance Chemotherapy"}, + {"id": 8, "name": "Palliative Care"}, + {"id": 9, "name": "Transfusions"}, + {"id": 10, "name": "Autologous Stem Cell Transplant"}, + {"id": 11, "name": "Allogeneic Stem Cell Transplant"}, + {"id": 12, "name": "Haplo Stem Cell Transplant"}, + {"id": 13, "name": "CAR-T"}, + {"id": 14, "name": "BTK Inhibitors"}, + ] + + for treatment_data in treatments_data: + # Check if treatment already exists + existing_treatment = session.query(Treatment).filter_by(id=treatment_data["id"]).first() + if not existing_treatment: + treatment = Treatment(**treatment_data) + session.add(treatment) + print(f"Added treatment: {treatment_data['name']}") + else: + print(f"Treatment already exists: {treatment_data['name']}") + + session.commit() diff --git a/backend/migrations/versions/062c84c8ff35_change_to_timezone_aware_timeblocks.py b/backend/migrations/versions/062c84c8ff35_change_to_timezone_aware_timeblocks.py deleted file mode 100644 index 46b4353f..00000000 --- a/backend/migrations/versions/062c84c8ff35_change_to_timezone_aware_timeblocks.py +++ /dev/null @@ -1,43 +0,0 @@ -"""change to timezone aware TimeBlocks - -Revision ID: 062c84c8ff35 -Revises: d6ee93e07a70 -Create Date: 2025-06-01 13:19:01.160699 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "062c84c8ff35" -down_revision: Union[str, None] = "d6ee93e07a70" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column( - "time_blocks", - "start_time", - existing_type=postgresql.TIMESTAMP(), - type_=sa.DateTime(timezone=True), - existing_nullable=True, - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column( - "time_blocks", - "start_time", - existing_type=sa.DateTime(timezone=True), - type_=postgresql.TIMESTAMP(), - existing_nullable=True, - ) - # ### end Alembic commands ### diff --git a/backend/migrations/versions/1e275ca8f74d_seed_treatments_table.py b/backend/migrations/versions/1e275ca8f74d_seed_treatments_table.py deleted file mode 100644 index 0ef12ad4..00000000 --- a/backend/migrations/versions/1e275ca8f74d_seed_treatments_table.py +++ /dev/null @@ -1,44 +0,0 @@ -"""seed treatments table - -Revision ID: 1e275ca8f74d -Revises: f4225af5f02c -Create Date: 2025-06-29 15:29:42.176058 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "1e275ca8f74d" -down_revision: Union[str, None] = "f4225af5f02c" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.bulk_insert( - sa.table( - "treatments", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - ), - [ - {"id": 1, "name": "Chemotherapy"}, - {"id": 2, "name": "Immunotherapy"}, - {"id": 3, "name": "Radiation Therapy"}, - {"id": 4, "name": "Surgery"}, - {"id": 5, "name": "Targeted Therapy"}, - {"id": 6, "name": "Hormone Therapy"}, - {"id": 7, "name": "Stem Cell Transplant"}, - {"id": 8, "name": "CAR-T Cell Therapy"}, - {"id": 9, "name": "Clinical Trial"}, - {"id": 10, "name": "Palliative Care"}, - ], - ) - - -def downgrade() -> None: - op.execute("DELETE FROM treatments WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)") diff --git a/backend/migrations/versions/2a086aa5a4ad_seed_forms_table.py b/backend/migrations/versions/2a086aa5a4ad_seed_forms_table.py deleted file mode 100644 index 2952dfd4..00000000 --- a/backend/migrations/versions/2a086aa5a4ad_seed_forms_table.py +++ /dev/null @@ -1,52 +0,0 @@ -"""seed_forms_table - -Revision ID: 2a086aa5a4ad -Revises: b11e40c23435 -Create Date: 2025-07-20 14:48:35.540230 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "2a086aa5a4ad" -down_revision: Union[str, None] = "b11e40c23435" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Insert initial form records for participant and volunteer intake - op.bulk_insert( - sa.table( - "forms", - sa.Column("id", sa.UUID(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("version", sa.Integer(), nullable=False), - sa.Column("type", sa.String(), nullable=False), - ), - [ - { - "id": "12345678-1234-1234-1234-123456789012", - "name": "Participant Intake Form", - "version": 1, - "type": "intake", - }, - { - "id": "12345678-1234-1234-1234-123456789013", - "name": "Volunteer Intake Form", - "version": 1, - "type": "intake", - }, - ], - ) - - -def downgrade() -> None: - # Remove the seeded forms - op.execute( - "DELETE FROM forms WHERE id IN ('12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013')" - ) diff --git a/backend/migrations/versions/40bc7d1cefc4_add_schedule_schedule_state_and_time_.py b/backend/migrations/versions/40bc7d1cefc4_add_schedule_schedule_state_and_time_.py deleted file mode 100644 index 13f103ab..00000000 --- a/backend/migrations/versions/40bc7d1cefc4_add_schedule_schedule_state_and_time_.py +++ /dev/null @@ -1,61 +0,0 @@ -"""add schedule, schedule_state, and time_block models - -Revision ID: 40bc7d1cefc4 -Revises: c9bc2b4d1036 -Create Date: 2024-11-22 18:08:50.027085 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "40bc7d1cefc4" -down_revision: Union[str, None] = "c9bc2b4d1036" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "schedule_states", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "schedules", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("scheduled_time", sa.DateTime(), nullable=True), - sa.Column("duration", sa.Interval(), nullable=True), - sa.Column("state_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["state_id"], - ["schedule_states.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "time_blocks", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("schedule_id", sa.Integer(), nullable=False), - sa.Column("start_time", sa.DateTime(), nullable=True), - sa.Column("end_time", sa.DateTime(), nullable=True), - sa.ForeignKeyConstraint( - ["schedule_id"], - ["schedules.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("time_blocks") - op.drop_table("schedules") - op.drop_table("schedule_states") - # ### end Alembic commands ### diff --git a/backend/migrations/versions/4ba3479cb8df_create_user_table_and_roles.py b/backend/migrations/versions/4ba3479cb8df_create_user_table_and_roles.py deleted file mode 100644 index 45262f1d..00000000 --- a/backend/migrations/versions/4ba3479cb8df_create_user_table_and_roles.py +++ /dev/null @@ -1,50 +0,0 @@ -"""create user table and roles - -Revision ID: 4ba3479cb8df -Revises: -Create Date: 2024-10-03 00:41:13.800838 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "4ba3479cb8df" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "roles", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "users", - sa.Column("id", sa.String(), nullable=False), - sa.Column("first_name", sa.String(length=80), nullable=False), - sa.Column("last_name", sa.String(length=80), nullable=False), - sa.Column("email", sa.String(length=120), nullable=False), - sa.Column("role_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["role_id"], - ["roles.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("email"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("users") - op.drop_table("roles") - # ### end Alembic commands ### diff --git a/backend/migrations/versions/55934750df90_add_loved_one_fields_to_userdata.py b/backend/migrations/versions/55934750df90_add_loved_one_fields_to_userdata.py deleted file mode 100644 index 9f25f8a9..00000000 --- a/backend/migrations/versions/55934750df90_add_loved_one_fields_to_userdata.py +++ /dev/null @@ -1,77 +0,0 @@ -"""add_loved_one_fields_to_userdata - -Revision ID: 55934750df90 -Revises: 62d18260aaed -Create Date: 2025-07-13 16:22:01.195384 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "55934750df90" -down_revision: Union[str, None] = "62d18260aaed" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Add loved one demographics fields to user_data - op.add_column("user_data", sa.Column("loved_one_gender_identity", sa.String(50), nullable=True)) - op.add_column("user_data", sa.Column("loved_one_age", sa.String(10), nullable=True)) - - # Add loved one cancer experience fields to user_data - op.add_column("user_data", sa.Column("loved_one_diagnosis", sa.String(100), nullable=True)) - op.add_column("user_data", sa.Column("loved_one_date_of_diagnosis", sa.Date(), nullable=True)) - op.add_column("user_data", sa.Column("loved_one_other_treatment", sa.Text(), nullable=True)) - op.add_column("user_data", sa.Column("loved_one_other_experience", sa.Text(), nullable=True)) - - # Create bridge table for user loved one treatments - op.create_table( - "user_loved_one_treatments", - sa.Column("user_data_id", postgresql.UUID(as_uuid=True), nullable=False), - sa.Column("treatment_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["treatment_id"], - ["treatments.id"], - ), - sa.ForeignKeyConstraint( - ["user_data_id"], - ["user_data.id"], - ), - sa.PrimaryKeyConstraint("user_data_id", "treatment_id"), - ) - - # Create bridge table for user loved one experiences - op.create_table( - "user_loved_one_experiences", - sa.Column("user_data_id", postgresql.UUID(as_uuid=True), nullable=False), - sa.Column("experience_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["experience_id"], - ["experiences.id"], - ), - sa.ForeignKeyConstraint( - ["user_data_id"], - ["user_data.id"], - ), - sa.PrimaryKeyConstraint("user_data_id", "experience_id"), - ) - - -def downgrade() -> None: - # Drop bridge tables - op.drop_table("user_loved_one_experiences") - op.drop_table("user_loved_one_treatments") - - # Drop loved one fields from user_data - op.drop_column("user_data", "loved_one_other_experience") - op.drop_column("user_data", "loved_one_other_treatment") - op.drop_column("user_data", "loved_one_date_of_diagnosis") - op.drop_column("user_data", "loved_one_diagnosis") - op.drop_column("user_data", "loved_one_age") - op.drop_column("user_data", "loved_one_gender_identity") diff --git a/backend/migrations/versions/59bb2488a76b_insert_roles.py b/backend/migrations/versions/59bb2488a76b_insert_roles.py deleted file mode 100644 index b7404496..00000000 --- a/backend/migrations/versions/59bb2488a76b_insert_roles.py +++ /dev/null @@ -1,37 +0,0 @@ -"""insert roles - -Revision ID: 59bb2488a76b -Revises: 4ba3479cb8df -Create Date: 2024-10-16 16:55:42.324525 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "59bb2488a76b" -down_revision: Union[str, None] = "4ba3479cb8df" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.bulk_insert( - sa.table( - "roles", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - ), - [ - {"id": 1, "name": "participant"}, - {"id": 2, "name": "volunteer"}, - {"id": 3, "name": "admin"}, - ], - ) - - -def downgrade() -> None: - op.execute("DELETE FROM roles WHERE id IN (1, 2, 3)") diff --git a/backend/migrations/versions/62d18260aaed_merge_restored_migrations.py b/backend/migrations/versions/62d18260aaed_merge_restored_migrations.py deleted file mode 100644 index a387b675..00000000 --- a/backend/migrations/versions/62d18260aaed_merge_restored_migrations.py +++ /dev/null @@ -1,23 +0,0 @@ -"""merge_restored_migrations - -Revision ID: 62d18260aaed -Revises: abcd1234active, f11846c50673, fef3717e0fc2 -Create Date: 2025-07-13 16:22:15.595766 - -""" - -from typing import Sequence, Union - -# revision identifiers, used by Alembic. -revision: str = "62d18260aaed" -down_revision: Union[str, None] = ("abcd1234active", "f11846c50673", "fef3717e0fc2") -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - pass - - -def downgrade() -> None: - pass diff --git a/backend/migrations/versions/747735a17ed8_add_missing_fields_to_user_data.py b/backend/migrations/versions/747735a17ed8_add_missing_fields_to_user_data.py deleted file mode 100644 index 6c03b263..00000000 --- a/backend/migrations/versions/747735a17ed8_add_missing_fields_to_user_data.py +++ /dev/null @@ -1,70 +0,0 @@ -"""add missing fields to user_data - -Revision ID: 747735a17ed8 -Revises: 78073cc5fe98 -Create Date: 2025-07-13 16:24:01.195384 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "747735a17ed8" -down_revision: Union[str, None] = "78073cc5fe98" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Add missing fields to user_data table - op.add_column("user_data", sa.Column("first_name", sa.String(80), nullable=True)) - op.add_column("user_data", sa.Column("last_name", sa.String(80), nullable=True)) - op.add_column("user_data", sa.Column("city", sa.String(100), nullable=True)) - op.add_column("user_data", sa.Column("province", sa.String(50), nullable=True)) - op.add_column("user_data", sa.Column("postal_code", sa.String(10), nullable=True)) - - # Demographics fields - op.add_column("user_data", sa.Column("gender_identity", sa.String(50), nullable=True)) - op.add_column("user_data", sa.Column("pronouns", sa.JSON, nullable=True)) # Array of strings - op.add_column("user_data", sa.Column("ethnic_group", sa.JSON, nullable=True)) # Array of strings - op.add_column("user_data", sa.Column("marital_status", sa.String(50), nullable=True)) - op.add_column("user_data", sa.Column("has_kids", sa.String(10), nullable=True)) - - # Cancer experience fields - op.add_column("user_data", sa.Column("diagnosis", sa.String(100), nullable=True)) - op.add_column("user_data", sa.Column("date_of_diagnosis", sa.Date, nullable=True)) - - # "Other" text fields for custom entries - op.add_column("user_data", sa.Column("other_treatment", sa.Text, nullable=True)) - op.add_column("user_data", sa.Column("other_experience", sa.Text, nullable=True)) - op.add_column("user_data", sa.Column("other_ethnic_group", sa.Text, nullable=True)) - op.add_column("user_data", sa.Column("gender_identity_custom", sa.Text, nullable=True)) - - # Flow control fields - op.add_column("user_data", sa.Column("has_blood_cancer", sa.String(10), nullable=True)) - op.add_column("user_data", sa.Column("caring_for_someone", sa.String(10), nullable=True)) - - -def downgrade() -> None: - # Remove added columns - op.drop_column("user_data", "caring_for_someone") - op.drop_column("user_data", "has_blood_cancer") - op.drop_column("user_data", "gender_identity_custom") - op.drop_column("user_data", "other_ethnic_group") - op.drop_column("user_data", "other_experience") - op.drop_column("user_data", "other_treatment") - op.drop_column("user_data", "date_of_diagnosis") - op.drop_column("user_data", "diagnosis") - op.drop_column("user_data", "has_kids") - op.drop_column("user_data", "marital_status") - op.drop_column("user_data", "ethnic_group") - op.drop_column("user_data", "pronouns") - op.drop_column("user_data", "gender_identity") - op.drop_column("user_data", "postal_code") - op.drop_column("user_data", "province") - op.drop_column("user_data", "city") - op.drop_column("user_data", "last_name") - op.drop_column("user_data", "first_name") diff --git a/backend/migrations/versions/78073cc5fe98_update_treatments_to_match_frontend.py b/backend/migrations/versions/78073cc5fe98_update_treatments_to_match_frontend.py deleted file mode 100644 index 8b53a3ea..00000000 --- a/backend/migrations/versions/78073cc5fe98_update_treatments_to_match_frontend.py +++ /dev/null @@ -1,83 +0,0 @@ -"""update_treatments_to_match_frontend - -Revision ID: 78073cc5fe98 -Revises: 55934750df90 -Create Date: 2025-07-13 16:23:01.195384 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "78073cc5fe98" -down_revision: Union[str, None] = "55934750df90" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Update treatments to match frontend expectations - connection = op.get_bind() - - # Clear existing treatments and insert new ones - connection.execute(sa.text("DELETE FROM user_treatments")) - connection.execute(sa.text("DELETE FROM user_loved_one_treatments")) - connection.execute(sa.text("DELETE FROM treatments")) - - # Insert updated treatments that match frontend - treatments = [ - (1, "Chemotherapy"), - (2, "Immunotherapy"), - (3, "Radiation Therapy"), - (4, "Surgery"), - (5, "Targeted Therapy"), - (6, "Hormone Therapy"), - (7, "Stem Cell Transplant"), - (8, "CAR-T Cell Therapy"), - (9, "Clinical Trial"), - (10, "Palliative Care"), - (11, "Supportive Care"), - (12, "Watchful Waiting"), - (13, "Maintenance Therapy"), - (14, "Combination Therapy"), - (15, "Experimental Treatment"), - ] - - for treatment_id, treatment_name in treatments: - connection.execute( - sa.text("INSERT INTO treatments (id, name) VALUES (:id, :name)"), - {"id": treatment_id, "name": treatment_name}, - ) - - -def downgrade() -> None: - # Restore original treatments - connection = op.get_bind() - - # Clear current treatments - connection.execute(sa.text("DELETE FROM user_treatments")) - connection.execute(sa.text("DELETE FROM user_loved_one_treatments")) - connection.execute(sa.text("DELETE FROM treatments")) - - # Insert original treatments - original_treatments = [ - (1, "Chemotherapy"), - (2, "Immunotherapy"), - (3, "Radiation Therapy"), - (4, "Surgery"), - (5, "Targeted Therapy"), - (6, "Hormone Therapy"), - (7, "Stem Cell Transplant"), - (8, "CAR-T Cell Therapy"), - (9, "Clinical Trial"), - (10, "Palliative Care"), - ] - - for treatment_id, treatment_name in original_treatments: - connection.execute( - sa.text("INSERT INTO treatments (id, name) VALUES (:id, :name)"), - {"id": treatment_id, "name": treatment_name}, - ) diff --git a/backend/migrations/versions/79de0b981dd8_add_auth_id_to_user_model.py b/backend/migrations/versions/79de0b981dd8_add_auth_id_to_user_model.py deleted file mode 100644 index 6e3f7871..00000000 --- a/backend/migrations/versions/79de0b981dd8_add_auth_id_to_user_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Add auth_id to User model - -Revision ID: 79de0b981dd8 -Revises: 59bb2488a76b -Create Date: 2024-10-16 17:06:45.820859 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "79de0b981dd8" -down_revision: Union[str, None] = "59bb2488a76b" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column("users", sa.Column("auth_id", sa.String(), nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column("users", "auth_id") - # ### end Alembic commands ### diff --git a/backend/migrations/versions/7b797eccb3aa_add_user_id_foreign_key_to_user_data.py b/backend/migrations/versions/7b797eccb3aa_add_user_id_foreign_key_to_user_data.py deleted file mode 100644 index 9e67b7c7..00000000 --- a/backend/migrations/versions/7b797eccb3aa_add_user_id_foreign_key_to_user_data.py +++ /dev/null @@ -1,43 +0,0 @@ -"""add_user_id_foreign_key_to_user_data - -Revision ID: 7b797eccb3aa -Revises: 2a086aa5a4ad -Create Date: 2025-07-20 15:20:07.646983 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "7b797eccb3aa" -down_revision: Union[str, None] = "2a086aa5a4ad" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Step 1: Add user_id column as nullable initially - op.add_column("user_data", sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=True)) - - # Step 2: Delete existing relationships and user_data records (assuming test data) - op.execute("DELETE FROM user_experiences") - op.execute("DELETE FROM user_treatments") - op.execute("DELETE FROM user_loved_one_experiences") - op.execute("DELETE FROM user_loved_one_treatments") - op.execute("DELETE FROM user_data") - - # Step 3: Make the column NOT NULL now that table is empty - op.alter_column("user_data", "user_id", nullable=False) - - # Step 4: Add foreign key constraint - op.create_foreign_key("fk_user_data_user_id", "user_data", "users", ["user_id"], ["id"]) - - -def downgrade() -> None: - # Remove foreign key constraint and user_id column - op.drop_constraint("fk_user_data_user_id", "user_data", type_="foreignkey") - op.drop_column("user_data", "user_id") diff --git a/backend/migrations/versions/88c4cf2a6bd2_merge_heads.py b/backend/migrations/versions/88c4cf2a6bd2_merge_heads.py deleted file mode 100644 index f8f1989e..00000000 --- a/backend/migrations/versions/88c4cf2a6bd2_merge_heads.py +++ /dev/null @@ -1,23 +0,0 @@ -"""merge heads - -Revision ID: 88c4cf2a6bd2 -Revises: abcd1234active, d6d4e2e5af85, fef3717e0fc2 -Create Date: 2025-07-20 16:06:01.056373 - -""" - -from typing import Sequence, Union - -# revision identifiers, used by Alembic. -revision: str = "88c4cf2a6bd2" -down_revision: Union[str, None] = ("abcd1234active", "d6d4e2e5af85", "fef3717e0fc2") -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - pass - - -def downgrade() -> None: - pass diff --git a/backend/migrations/versions/8bfb115acac1_add_approved_to_users.py b/backend/migrations/versions/8bfb115acac1_add_approved_to_users.py deleted file mode 100644 index a6fb10a0..00000000 --- a/backend/migrations/versions/8bfb115acac1_add_approved_to_users.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add approved to users - -Revision ID: 8bfb115acac1 -Revises: c9bc2b4d1036 -Create Date: 2025-06-04 16:50:38.609239 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "8bfb115acac1" -down_revision: Union[str, None] = "c9bc2b4d1036" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column("users", sa.Column("approved", sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column("users", "approved") - # ### end Alembic commands ### diff --git a/backend/migrations/versions/905b6788b114_initial_schema.py b/backend/migrations/versions/905b6788b114_initial_schema.py new file mode 100644 index 00000000..d15f9f7c --- /dev/null +++ b/backend/migrations/versions/905b6788b114_initial_schema.py @@ -0,0 +1,305 @@ +"""initial_schema + +Revision ID: 905b6788b114 +Revises: +Create Date: 2025-09-12 21:07:17.497258 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "905b6788b114" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "experiences", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "forms", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("version", sa.Integer(), nullable=False), + sa.Column( + "type", + sa.Enum("intake", "ranking", "secondary", "become_volunteer", "become_participant", name="form_type"), + nullable=False, + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "match_status", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=80), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "qualities", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("slug", sa.String(), nullable=False), + sa.Column("label", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("slug"), + ) + op.create_table( + "roles", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=80), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "time_blocks", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("start_time", sa.DateTime(timezone=True), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "treatments", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "users", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("first_name", sa.String(length=80), nullable=True), + sa.Column("last_name", sa.String(length=80), nullable=True), + sa.Column("email", sa.String(length=120), nullable=False), + sa.Column("role_id", sa.Integer(), nullable=False), + sa.Column("auth_id", sa.String(), nullable=False), + sa.Column("approved", sa.Boolean(), nullable=True), + sa.Column("active", sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint( + ["role_id"], + ["roles.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), + ) + op.create_table( + "available_times", + sa.Column("time_block_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["time_block_id"], + ["time_blocks.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("time_block_id", "user_id"), + ) + op.create_table( + "form_submissions", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("form_id", sa.UUID(), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("submitted_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("answers", postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.ForeignKeyConstraint( + ["form_id"], + ["forms.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "matches", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("participant_id", sa.UUID(), nullable=False), + sa.Column("volunteer_id", sa.UUID(), nullable=False), + sa.Column("chosen_time_block_id", sa.Integer(), nullable=True), + sa.Column("match_status_id", sa.Integer(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=True), + sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=True), + sa.ForeignKeyConstraint( + ["chosen_time_block_id"], + ["time_blocks.id"], + ), + sa.ForeignKeyConstraint( + ["match_status_id"], + ["match_status.id"], + ), + sa.ForeignKeyConstraint( + ["participant_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["volunteer_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "ranking_preferences", + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("target_role", sa.Enum("patient", "caregiver", name="target_role"), nullable=False), + sa.Column("kind", sa.Enum("quality", "treatment", "experience", name="ranking_kind"), nullable=True), + sa.Column("quality_id", sa.Integer(), nullable=True), + sa.Column("treatment_id", sa.Integer(), nullable=True), + sa.Column("experience_id", sa.Integer(), nullable=True), + sa.Column("scope", sa.Enum("self", "loved_one", name="ranking_scope"), nullable=False), + sa.Column("rank", sa.Integer(), nullable=False), + sa.CheckConstraint( + "(kind <> 'experience') OR (experience_id IS NOT NULL AND quality_id IS NULL AND treatment_id IS NULL)", + name="ck_ranking_pref_experience_fields", + ), + sa.CheckConstraint( + "(kind <> 'quality') OR (quality_id IS NOT NULL AND treatment_id IS NULL AND experience_id IS NULL)", + name="ck_ranking_pref_quality_fields", + ), + sa.CheckConstraint( + "(kind <> 'treatment') OR (treatment_id IS NOT NULL AND quality_id IS NULL AND experience_id IS NULL)", + name="ck_ranking_pref_treatment_fields", + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("user_id", "target_role", "rank"), + ) + op.create_table( + "user_data", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("first_name", sa.String(length=80), nullable=True), + sa.Column("last_name", sa.String(length=80), nullable=True), + sa.Column("date_of_birth", sa.Date(), nullable=True), + sa.Column("email", sa.String(length=120), nullable=True), + sa.Column("phone", sa.String(length=20), nullable=True), + sa.Column("city", sa.String(length=100), nullable=True), + sa.Column("province", sa.String(length=50), nullable=True), + sa.Column("postal_code", sa.String(length=10), nullable=True), + sa.Column("gender_identity", sa.String(length=50), nullable=True), + sa.Column("pronouns", sa.JSON(), nullable=True), + sa.Column("ethnic_group", sa.JSON(), nullable=True), + sa.Column("marital_status", sa.String(length=50), nullable=True), + sa.Column("has_kids", sa.String(length=10), nullable=True), + sa.Column("diagnosis", sa.String(length=100), nullable=True), + sa.Column("date_of_diagnosis", sa.Date(), nullable=True), + sa.Column("other_treatment", sa.Text(), nullable=True), + sa.Column("other_experience", sa.Text(), nullable=True), + sa.Column("other_ethnic_group", sa.Text(), nullable=True), + sa.Column("gender_identity_custom", sa.Text(), nullable=True), + sa.Column("has_blood_cancer", sa.String(length=10), nullable=True), + sa.Column("caring_for_someone", sa.String(length=10), nullable=True), + sa.Column("loved_one_gender_identity", sa.String(length=50), nullable=True), + sa.Column("loved_one_age", sa.String(length=10), nullable=True), + sa.Column("loved_one_diagnosis", sa.String(length=100), nullable=True), + sa.Column("loved_one_date_of_diagnosis", sa.Date(), nullable=True), + sa.Column("loved_one_other_treatment", sa.Text(), nullable=True), + sa.Column("loved_one_other_experience", sa.Text(), nullable=True), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "suggested_times", + sa.Column("match_id", sa.Integer(), nullable=False), + sa.Column("time_block_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["match_id"], + ["matches.id"], + ), + sa.ForeignKeyConstraint( + ["time_block_id"], + ["time_blocks.id"], + ), + sa.PrimaryKeyConstraint("match_id", "time_block_id"), + ) + op.create_table( + "user_experiences", + sa.Column("user_data_id", sa.UUID(), nullable=True), + sa.Column("experience_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["experience_id"], + ["experiences.id"], + ), + sa.ForeignKeyConstraint( + ["user_data_id"], + ["user_data.id"], + ), + ) + op.create_table( + "user_loved_one_experiences", + sa.Column("user_data_id", sa.UUID(), nullable=True), + sa.Column("experience_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["experience_id"], + ["experiences.id"], + ), + sa.ForeignKeyConstraint( + ["user_data_id"], + ["user_data.id"], + ), + ) + op.create_table( + "user_loved_one_treatments", + sa.Column("user_data_id", sa.UUID(), nullable=True), + sa.Column("treatment_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["treatment_id"], + ["treatments.id"], + ), + sa.ForeignKeyConstraint( + ["user_data_id"], + ["user_data.id"], + ), + ) + op.create_table( + "user_treatments", + sa.Column("user_data_id", sa.UUID(), nullable=True), + sa.Column("treatment_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["treatment_id"], + ["treatments.id"], + ), + sa.ForeignKeyConstraint( + ["user_data_id"], + ["user_data.id"], + ), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("user_treatments") + op.drop_table("user_loved_one_treatments") + op.drop_table("user_loved_one_experiences") + op.drop_table("user_experiences") + op.drop_table("suggested_times") + op.drop_table("user_data") + op.drop_table("ranking_preferences") + op.drop_table("matches") + op.drop_table("form_submissions") + op.drop_table("available_times") + op.drop_table("users") + op.drop_table("treatments") + op.drop_table("time_blocks") + op.drop_table("roles") + op.drop_table("qualities") + op.drop_table("match_status") + op.drop_table("forms") + op.drop_table("experiences") + # ### end Alembic commands ### diff --git a/backend/migrations/versions/991416bdc3f6_insert_schedule_states.py b/backend/migrations/versions/991416bdc3f6_insert_schedule_states.py deleted file mode 100644 index fb906946..00000000 --- a/backend/migrations/versions/991416bdc3f6_insert_schedule_states.py +++ /dev/null @@ -1,38 +0,0 @@ -"""insert schedule states - -Revision ID: 991416bdc3f6 -Revises: 40bc7d1cefc4 -Create Date: 2024-11-22 18:21:24.174362 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "991416bdc3f6" -down_revision: Union[str, None] = "40bc7d1cefc4" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.bulk_insert( - sa.table( - "schedule_states", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - ), - [ - {"id": 1, "name": "PENDING_VOLUNTEER_RESPONSE"}, - {"id": 2, "name": "PENDING_PARTICIPANT_RESPONSE"}, - {"id": 3, "name": "SCHEDULED"}, - {"id": 4, "name": "COMPLETED"}, - ], - ) - - -def downgrade() -> None: - op.execute("DELETE FROM schedule_states WHERE id IN (1, 2, 3, 4)") diff --git a/backend/migrations/versions/9d7570569af9_ranking_unified_preferences_table_seed_.py b/backend/migrations/versions/9d7570569af9_ranking_unified_preferences_table_seed_.py deleted file mode 100644 index e28b27d5..00000000 --- a/backend/migrations/versions/9d7570569af9_ranking_unified_preferences_table_seed_.py +++ /dev/null @@ -1,135 +0,0 @@ -"""ranking: unified preferences table + seed qualities - -Revision ID: 9d7570569af9 -Revises: fb0638c24174 -Create Date: 2025-08-31 20:49:12.042730 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "9d7570569af9" -down_revision: Union[str, None] = "fb0638c24174" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Drop legacy ranking_preferences table if it exists - bind = op.get_bind() - inspector = sa.inspect(bind) - if "ranking_preferences" in inspector.get_table_names(): - op.drop_table("ranking_preferences") - - # Create ENUM types - target_role_enum = postgresql.ENUM("patient", "caregiver", name="target_role", create_type=False) - kind_enum = postgresql.ENUM("quality", "treatment", "experience", name="ranking_kind", create_type=False) - scope_enum = postgresql.ENUM("self", "loved_one", name="ranking_scope", create_type=False) - target_role_enum.create(bind, checkfirst=True) - kind_enum.create(bind, checkfirst=True) - scope_enum.create(bind, checkfirst=True) - - # Create unified ranking_preferences table - op.create_table( - "ranking_preferences", - sa.Column("user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id"), nullable=False), - sa.Column("target_role", target_role_enum, nullable=False), - sa.Column("kind", kind_enum, nullable=False), - sa.Column("quality_id", sa.Integer(), nullable=True), - sa.Column("treatment_id", sa.Integer(), nullable=True), - sa.Column("experience_id", sa.Integer(), nullable=True), - sa.Column("scope", scope_enum, nullable=False), - sa.Column("rank", sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint("user_id", "target_role", "rank"), - ) - - # Add check constraints to enforce exclusivity by kind - op.create_check_constraint( - "ck_ranking_pref_quality_fields", - "ranking_preferences", - "(kind <> 'quality') OR (quality_id IS NOT NULL AND treatment_id IS NULL AND experience_id IS NULL AND scope IS NOT NULL)", - ) - op.create_check_constraint( - "ck_ranking_pref_treatment_fields", - "ranking_preferences", - "(kind <> 'treatment') OR (treatment_id IS NOT NULL AND quality_id IS NULL AND experience_id IS NULL AND scope IS NOT NULL)", - ) - op.create_check_constraint( - "ck_ranking_pref_experience_fields", - "ranking_preferences", - "(kind <> 'experience') OR (experience_id IS NOT NULL AND quality_id IS NULL AND treatment_id IS NULL AND scope IS NOT NULL)", - ) - - # Helpful indexes - op.create_index("ix_ranking_pref_user_target", "ranking_preferences", ["user_id", "target_role"]) - op.create_index("ix_ranking_pref_user_kind", "ranking_preferences", ["user_id", "kind"]) - - # Remove any legacy/static qualities not in the approved set (idempotent) - op.execute( - """ - DELETE FROM qualities - WHERE slug NOT IN ( - 'same_age', - 'same_gender_identity', - 'same_ethnic_or_cultural_group', - 'same_marital_status', - 'same_parental_status', - 'same_diagnosis' - ); - """ - ) - - # Seed qualities (idempotent) using slug/label pairs - # Using plain SQL for ON CONFLICT DO NOTHING - qualities = [ - ("same_age", "the same age as"), - ("same_gender_identity", "the same gender identity as"), - ("same_ethnic_or_cultural_group", "the same ethnic or cultural group as"), - ("same_marital_status", "the same marital status as"), - ("same_parental_status", "the same parental status as"), - ("same_diagnosis", "the same diagnosis as"), - ] - conn = op.get_bind() - # Ensure the sequence is aligned to current MAX(id) to avoid PK conflicts - conn.execute( - sa.text("SELECT setval(pg_get_serial_sequence('qualities','id'), COALESCE((SELECT MAX(id) FROM qualities), 0))") - ) - # First update labels for any existing slugs - for slug, label in qualities: - conn.execute( - sa.text("UPDATE qualities SET label = :label WHERE slug = :slug"), - {"slug": slug, "label": label}, - ) - # Then insert any missing slugs - for slug, label in qualities: - conn.execute( - sa.text( - "INSERT INTO qualities (slug, label) " - "SELECT :slug, :label WHERE NOT EXISTS (SELECT 1 FROM qualities WHERE slug = :slug)" - ), - {"slug": slug, "label": label}, - ) - - -def downgrade() -> None: - # Drop unified table - op.drop_index("ix_ranking_pref_user_kind", table_name="ranking_preferences") - op.drop_index("ix_ranking_pref_user_target", table_name="ranking_preferences") - op.drop_constraint("ck_ranking_pref_experience_fields", "ranking_preferences", type_="check") - op.drop_constraint("ck_ranking_pref_treatment_fields", "ranking_preferences", type_="check") - op.drop_constraint("ck_ranking_pref_quality_fields", "ranking_preferences", type_="check") - op.drop_table("ranking_preferences") - - # Drop ENUMs if present - bind = op.get_bind() - target_role_enum = postgresql.ENUM("patient", "caregiver", name="target_role") - kind_enum = postgresql.ENUM("quality", "treatment", "experience", name="ranking_kind") - scope_enum = postgresql.ENUM("self", "loved_one", name="ranking_scope") - scope_enum.drop(bind, checkfirst=True) - kind_enum.drop(bind, checkfirst=True) - target_role_enum.drop(bind, checkfirst=True) diff --git a/backend/migrations/versions/abcd1234_add_active_column_to_users.py b/backend/migrations/versions/abcd1234_add_active_column_to_users.py deleted file mode 100644 index 01d96ff2..00000000 --- a/backend/migrations/versions/abcd1234_add_active_column_to_users.py +++ /dev/null @@ -1,22 +0,0 @@ -"""add active column to users - -Revision ID: abcd1234active -Revises: df571b763807 -Create Date: 2025-07-08 -""" - -import sqlalchemy as sa -from alembic import op - -revision = "abcd1234active" -down_revision = "df571b763807" -branch_labels = None -depends_on = None - - -def upgrade(): - op.add_column("users", sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true())) - - -def downgrade(): - op.drop_column("users", "active") diff --git a/backend/migrations/versions/b11e40c23435_increase_varchar_field_lengths.py b/backend/migrations/versions/b11e40c23435_increase_varchar_field_lengths.py deleted file mode 100644 index 7da47d23..00000000 --- a/backend/migrations/versions/b11e40c23435_increase_varchar_field_lengths.py +++ /dev/null @@ -1,44 +0,0 @@ -"""increase_varchar_field_lengths - -Revision ID: b11e40c23435 -Revises: 747735a17ed8 -Create Date: 2025-07-13 16:42:47.456742 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "b11e40c23435" -down_revision: Union[str, None] = "747735a17ed8" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Increase VARCHAR(10) fields to VARCHAR(256) to accommodate longer values - op.alter_column("user_data", "has_kids", existing_type=sa.VARCHAR(10), type_=sa.VARCHAR(256), nullable=True) - - op.alter_column("user_data", "has_blood_cancer", existing_type=sa.VARCHAR(10), type_=sa.VARCHAR(256), nullable=True) - - op.alter_column( - "user_data", "caring_for_someone", existing_type=sa.VARCHAR(10), type_=sa.VARCHAR(256), nullable=True - ) - - op.alter_column("user_data", "loved_one_age", existing_type=sa.VARCHAR(10), type_=sa.VARCHAR(256), nullable=True) - - -def downgrade() -> None: - # Revert back to VARCHAR(10) - op.alter_column("user_data", "loved_one_age", existing_type=sa.VARCHAR(256), type_=sa.VARCHAR(10), nullable=True) - - op.alter_column( - "user_data", "caring_for_someone", existing_type=sa.VARCHAR(256), type_=sa.VARCHAR(10), nullable=True - ) - - op.alter_column("user_data", "has_blood_cancer", existing_type=sa.VARCHAR(256), type_=sa.VARCHAR(10), nullable=True) - - op.alter_column("user_data", "has_kids", existing_type=sa.VARCHAR(256), type_=sa.VARCHAR(10), nullable=True) diff --git a/backend/migrations/versions/c9bc2b4d1036_update_users_id_to_uuid_with_default.py b/backend/migrations/versions/c9bc2b4d1036_update_users_id_to_uuid_with_default.py deleted file mode 100644 index 2c8cc4b4..00000000 --- a/backend/migrations/versions/c9bc2b4d1036_update_users_id_to_uuid_with_default.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Update users id to UUID with default - -Revision ID: c9bc2b4d1036 -Revises: 79de0b981dd8 -Create Date: 2024-10-16 17:13:53.820521 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "c9bc2b4d1036" -down_revision: Union[str, None] = "79de0b981dd8" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column( - "users", - "id", - existing_type=sa.VARCHAR(), - type_=sa.UUID(), - postgresql_using="id::uuid", - server_default=sa.text("gen_random_uuid()"), - existing_nullable=False, - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column( - "users", - "id", - existing_type=sa.UUID(), - type_=sa.VARCHAR(), - postgresql_using="id::text", - server_default=None, - existing_nullable=False, - ) - # ### end Alembic commands ### diff --git a/backend/migrations/versions/d6d4e2e5af85_set_first_last_name_to_nullable.py b/backend/migrations/versions/d6d4e2e5af85_set_first_last_name_to_nullable.py deleted file mode 100644 index 8b411ff5..00000000 --- a/backend/migrations/versions/d6d4e2e5af85_set_first_last_name_to_nullable.py +++ /dev/null @@ -1,32 +0,0 @@ -"""set-first-last-name-to-nullable - -Revision ID: d6d4e2e5af85 -Revises: c9bc2b4d1036 -Create Date: 2025-06-11 22:22:13.206761 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "d6d4e2e5af85" -down_revision: Union[str, None] = "c9bc2b4d1036" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column("users", "first_name", existing_type=sa.VARCHAR(length=80), nullable=True) - op.alter_column("users", "last_name", existing_type=sa.VARCHAR(length=80), nullable=True) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column("users", "last_name", existing_type=sa.VARCHAR(length=80), nullable=False) - op.alter_column("users", "first_name", existing_type=sa.VARCHAR(length=80), nullable=False) - # ### end Alembic commands ### diff --git a/backend/migrations/versions/d6ee93e07a70_add_match_matchstatus_suggestedtime_.py b/backend/migrations/versions/d6ee93e07a70_add_match_matchstatus_suggestedtime_.py deleted file mode 100644 index 3fcfb939..00000000 --- a/backend/migrations/versions/d6ee93e07a70_add_match_matchstatus_suggestedtime_.py +++ /dev/null @@ -1,142 +0,0 @@ -"""add Match, MatchStatus, SuggestedTime, AvailableTime. delete Schedule, ScheduleStatus - -Revision ID: d6ee93e07a70 -Revises: df571b763807 -Create Date: 2025-03-11 21:11:38.464490 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "d6ee93e07a70" -down_revision: Union[str, None] = "df571b763807" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "match_status", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.bulk_insert( - sa.table( - "match_status", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - ), - [ - {"id": 1, "name": "PENDING_ADMIN_APPROVAL"}, - {"id": 2, "name": "APPROVED"}, - {"id": 3, "name": "REJECTED"}, - {"id": 4, "name": "PENDING_PARTICIPANT_RESPONSE"}, - {"id": 5, "name": "PENDING_VOLUNTEER_RESPONSE"}, - {"id": 6, "name": "SCHEDULED"}, - {"id": 7, "name": "DECLINED"}, - {"id": 8, "name": "CANCELLED"}, - {"id": 9, "name": "COMPLETED"}, - ], - ) - op.create_table( - "available_times", - sa.Column("time_block_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.UUID(), nullable=False), - sa.ForeignKeyConstraint( - ["time_block_id"], - ["time_blocks.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("time_block_id", "user_id"), - ) - op.create_table( - "matches", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("participant_id", sa.UUID(), nullable=False), - sa.Column("volunteer_id", sa.UUID(), nullable=False), - sa.Column("chosen_time_block_id", sa.Integer(), nullable=True), - sa.Column("match_status_id", sa.Integer(), nullable=False), - sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.ForeignKeyConstraint( - ["chosen_time_block_id"], - ["time_blocks.id"], - ), - sa.ForeignKeyConstraint( - ["match_status_id"], - ["match_status.id"], - ), - sa.ForeignKeyConstraint( - ["participant_id"], - ["users.id"], - ), - sa.ForeignKeyConstraint( - ["volunteer_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "suggested_times", - sa.Column("match_id", sa.Integer(), nullable=False), - sa.Column("time_block_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["match_id"], - ["matches.id"], - ), - sa.ForeignKeyConstraint( - ["time_block_id"], - ["time_blocks.id"], - ), - sa.PrimaryKeyConstraint("match_id", "time_block_id"), - ) - op.drop_constraint("time_blocks_schedule_id_fkey", "time_blocks", type_="foreignkey") - op.drop_column("time_blocks", "schedule_id") - op.drop_column("time_blocks", "end_time") - op.drop_table("schedules") - op.drop_table("schedule_status") - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column("time_blocks", sa.Column("end_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - op.add_column("time_blocks", sa.Column("schedule_id", sa.INTEGER(), autoincrement=False, nullable=False)) - op.create_foreign_key("time_blocks_schedule_id_fkey", "time_blocks", "schedules", ["schedule_id"], ["id"]) - op.create_table( - "schedule_status", - sa.Column( - "id", - sa.INTEGER(), - server_default=sa.text("nextval('schedule_status_id_seq'::regclass)"), - autoincrement=True, - nullable=False, - ), - sa.Column("name", sa.VARCHAR(length=80), autoincrement=False, nullable=False), - sa.PrimaryKeyConstraint("id", name="schedule_status_pkey"), - postgresql_ignore_search_path=False, - ) - op.create_table( - "schedules", - sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), - sa.Column("scheduled_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), - sa.Column("duration", postgresql.INTERVAL(), autoincrement=False, nullable=True), - sa.Column("status_id", sa.INTEGER(), autoincrement=False, nullable=False), - sa.ForeignKeyConstraint(["status_id"], ["schedule_status.id"], name="schedules_status_id_fkey"), - sa.PrimaryKeyConstraint("id", name="schedules_pkey"), - ) - op.drop_table("suggested_times") - op.drop_table("matches") - op.drop_table("available_times") - op.drop_table("match_status") - # ### end Alembic commands ### diff --git a/backend/migrations/versions/df571b763807_change_schedulestate_to_schedulestatus.py b/backend/migrations/versions/df571b763807_change_schedulestate_to_schedulestatus.py deleted file mode 100644 index 29568345..00000000 --- a/backend/migrations/versions/df571b763807_change_schedulestate_to_schedulestatus.py +++ /dev/null @@ -1,69 +0,0 @@ -"""change ScheduleState to ScheduleStatus - -Revision ID: df571b763807 -Revises: 991416bdc3f6 -Create Date: 2025-02-03 21:09:12.497053 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "df571b763807" -down_revision: Union[str, None] = "991416bdc3f6" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "schedule_status", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.drop_constraint("schedules_state_id_fkey", "schedules", type_="foreignkey") - op.drop_table("schedule_states") - op.add_column("schedules", sa.Column("status_id", sa.Integer(), nullable=False)) - op.create_foreign_key(None, "schedules", "schedule_status", ["status_id"], ["id"]) - op.drop_column("schedules", "state_id") - - op.bulk_insert( - sa.table( - "schedule_status", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=80), nullable=False), - ), - [ - {"id": 1, "name": "PENDING_VOLUNTEER_RESPONSE"}, - {"id": 2, "name": "PENDING_PARTICIPANT_RESPONSE"}, - {"id": 3, "name": "SCHEDULED"}, - {"id": 4, "name": "COMPLETED"}, - ], - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column( - "schedules", - sa.Column("state_id", sa.INTEGER(), autoincrement=False, nullable=False), - ) - op.drop_constraint(None, "schedules", type_="foreignkey") - op.create_foreign_key("schedules_state_id_fkey", "schedules", "schedule_states", ["state_id"], ["id"]) - op.drop_column("schedules", "status_id") - op.create_table( - "schedule_states", - sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), - sa.Column("name", sa.VARCHAR(length=80), autoincrement=False, nullable=False), - sa.PrimaryKeyConstraint("id", name="schedule_states_pkey"), - ) - op.execute("DELETE FROM schedule_status WHERE id IN (1, 2, 3, 4)") - op.drop_table("schedule_status") - - # ### end Alembic commands ### diff --git a/backend/migrations/versions/e6cf430117b4_seed_experiences_table.py b/backend/migrations/versions/e6cf430117b4_seed_experiences_table.py deleted file mode 100644 index 2cb5818e..00000000 --- a/backend/migrations/versions/e6cf430117b4_seed_experiences_table.py +++ /dev/null @@ -1,49 +0,0 @@ -"""seed experiences table - -Revision ID: e6cf430117b4 -Revises: 1e275ca8f74d -Create Date: 2025-06-29 15:29:47.004086 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "e6cf430117b4" -down_revision: Union[str, None] = "1e275ca8f74d" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.bulk_insert( - sa.table( - "experiences", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - ), - [ - {"id": 1, "name": "PTSD"}, - {"id": 2, "name": "Relapse"}, - {"id": 3, "name": "Anxiety"}, - {"id": 4, "name": "Depression"}, - {"id": 5, "name": "Fatigue"}, - {"id": 6, "name": "Neuropathy"}, - {"id": 7, "name": "Hair Loss"}, - {"id": 8, "name": "Nausea"}, - {"id": 9, "name": "Loss of Appetite"}, - {"id": 10, "name": "Sleep Problems"}, - {"id": 11, "name": "Cognitive Changes"}, - {"id": 12, "name": "Financial Stress"}, - {"id": 13, "name": "Relationship Changes"}, - {"id": 14, "name": "Body Image Issues"}, - {"id": 15, "name": "Survivorship Concerns"}, - ], - ) - - -def downgrade() -> None: - op.execute("DELETE FROM experiences WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)") diff --git a/backend/migrations/versions/f11846c50673_seed_qualities_table.py b/backend/migrations/versions/f11846c50673_seed_qualities_table.py deleted file mode 100644 index 7e3268c5..00000000 --- a/backend/migrations/versions/f11846c50673_seed_qualities_table.py +++ /dev/null @@ -1,47 +0,0 @@ -"""seed qualities table - -Revision ID: f11846c50673 -Revises: e6cf430117b4 -Create Date: 2025-06-29 15:29:51.672149 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "f11846c50673" -down_revision: Union[str, None] = "e6cf430117b4" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.bulk_insert( - sa.table( - "qualities", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("slug", sa.String(), nullable=False), - sa.Column("label", sa.String(), nullable=False), - ), - [ - {"id": 1, "slug": "same_age", "label": "Similar Age"}, - {"id": 2, "slug": "same_diagnosis", "label": "Same Diagnosis"}, - {"id": 3, "slug": "same_stage", "label": "Same Cancer Stage"}, - {"id": 4, "slug": "same_treatment", "label": "Similar Treatment"}, - {"id": 5, "slug": "same_location", "label": "Geographic Proximity"}, - {"id": 6, "slug": "same_gender", "label": "Same Gender"}, - {"id": 7, "slug": "family_status", "label": "Similar Family Status"}, - {"id": 8, "slug": "career_stage", "label": "Similar Career Stage"}, - {"id": 9, "slug": "shared_interests", "label": "Shared Interests/Hobbies"}, - {"id": 10, "slug": "communication_style", "label": "Communication Style"}, - {"id": 11, "slug": "emotional_support", "label": "Emotional Support Preference"}, - {"id": 12, "slug": "practical_support", "label": "Practical Support Preference"}, - ], - ) - - -def downgrade() -> None: - op.execute("DELETE FROM qualities WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)") diff --git a/backend/migrations/versions/f4225af5f02c_create_forms_schema_tables.py b/backend/migrations/versions/f4225af5f02c_create_forms_schema_tables.py deleted file mode 100644 index 63ace26b..00000000 --- a/backend/migrations/versions/f4225af5f02c_create_forms_schema_tables.py +++ /dev/null @@ -1,138 +0,0 @@ -"""create forms schema tables - -Revision ID: f4225af5f02c -Revises: c9bc2b4d1036 -Create Date: 2025-06-29 15:28:37.734725 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "f4225af5f02c" -down_revision: Union[str, None] = "c9bc2b4d1036" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "experiences", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("name"), - ) - op.create_table( - "forms", - sa.Column("id", sa.UUID(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("version", sa.Integer(), nullable=False), - sa.Column( - "type", - sa.Enum("intake", "ranking", "secondary", "become_volunteer", "become_participant", name="form_type"), - nullable=False, - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "qualities", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("slug", sa.String(), nullable=False), - sa.Column("label", sa.String(), nullable=False), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("slug"), - ) - op.create_table( - "treatments", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("name"), - ) - op.create_table( - "user_data", - sa.Column("id", sa.UUID(), nullable=False), - sa.Column("date_of_birth", sa.Date(), nullable=True), - sa.Column("email", sa.String(length=120), nullable=True), - sa.Column("phone", sa.String(length=20), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "user_experiences", - sa.Column("user_data_id", sa.UUID(), nullable=True), - sa.Column("experience_id", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["experience_id"], - ["experiences.id"], - ), - sa.ForeignKeyConstraint( - ["user_data_id"], - ["user_data.id"], - ), - ) - op.create_table( - "user_treatments", - sa.Column("user_data_id", sa.UUID(), nullable=True), - sa.Column("treatment_id", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["treatment_id"], - ["treatments.id"], - ), - sa.ForeignKeyConstraint( - ["user_data_id"], - ["user_data.id"], - ), - ) - op.create_table( - "form_submissions", - sa.Column("id", sa.UUID(), nullable=False), - sa.Column("form_id", sa.UUID(), nullable=False), - sa.Column("user_id", sa.UUID(), nullable=False), - sa.Column("submitted_at", sa.DateTime(timezone=True), nullable=True), - sa.Column("answers", postgresql.JSONB(astext_type=sa.Text()), nullable=False), - sa.ForeignKeyConstraint( - ["form_id"], - ["forms.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "ranking_preferences", - sa.Column("user_id", sa.UUID(), nullable=False), - sa.Column("quality_id", sa.Integer(), nullable=False), - sa.Column("rank", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["quality_id"], - ["qualities.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("user_id", "quality_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("ranking_preferences") - op.drop_table("form_submissions") - op.drop_table("user_treatments") - op.drop_table("user_experiences") - op.drop_table("user_data") - op.drop_table("treatments") - op.drop_table("qualities") - op.drop_table("forms") - op.drop_table("experiences") - # ### end Alembic commands ### diff --git a/backend/migrations/versions/fb0638c24174_merge_heads_before_ranking_work.py b/backend/migrations/versions/fb0638c24174_merge_heads_before_ranking_work.py deleted file mode 100644 index 88083102..00000000 --- a/backend/migrations/versions/fb0638c24174_merge_heads_before_ranking_work.py +++ /dev/null @@ -1,23 +0,0 @@ -"""merge heads before ranking work - -Revision ID: fb0638c24174 -Revises: 7b797eccb3aa, 88c4cf2a6bd2 -Create Date: 2025-08-31 20:48:25.460360 - -""" - -from typing import Sequence, Union - -# revision identifiers, used by Alembic. -revision: str = "fb0638c24174" -down_revision: Union[str, None] = ("7b797eccb3aa", "88c4cf2a6bd2") -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - pass - - -def downgrade() -> None: - pass diff --git a/backend/migrations/versions/fef3717e0fc2_merge_timeblocks_and_auth_migration_.py b/backend/migrations/versions/fef3717e0fc2_merge_timeblocks_and_auth_migration_.py deleted file mode 100644 index e3b88488..00000000 --- a/backend/migrations/versions/fef3717e0fc2_merge_timeblocks_and_auth_migration_.py +++ /dev/null @@ -1,23 +0,0 @@ -"""merge timeblocks and auth migration heads - -Revision ID: fef3717e0fc2 -Revises: 062c84c8ff35, 8bfb115acac1 -Create Date: 2025-06-15 12:24:49.480099 - -""" - -from typing import Sequence, Union - -# revision identifiers, used by Alembic. -revision: str = "fef3717e0fc2" -down_revision: Union[str, None] = ("062c84c8ff35", "8bfb115acac1") -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - pass - - -def downgrade() -> None: - pass diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 5fa5a945..8b198a8f 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -38,6 +38,8 @@ docker-db = {composite = ["dc-down", "dc-up"]} db-dev = {composite = ["docker-db", "dev"]} revision = "alembic revision --autogenerate" upgrade = "alembic upgrade head" +seed = "python -m app.seeds.runner" +db-reset = {composite = ["docker-db", "upgrade", "seed"]} tests = "pytest -v" [tool.pytest.ini_options] diff --git a/backend/tests/unit/test_ranking_service.py b/backend/tests/unit/test_ranking_service.py index a26ac809..7f4983ef 100644 --- a/backend/tests/unit/test_ranking_service.py +++ b/backend/tests/unit/test_ranking_service.py @@ -46,7 +46,7 @@ def db_session() -> Session: session.add(r) session.commit() - # Qualities should have been seeded by migrations; assert presence + # Qualities should be seeded by CI before tests run assert session.query(Quality).count() >= 6 # Ensure sequences are aligned after seeding (avoid PK collisions when inserting)