diff --git a/.github/workflows/check-migrations.yml b/.github/workflows/check-migrations.yml new file mode 100644 index 00000000..b2b6728f --- /dev/null +++ b/.github/workflows/check-migrations.yml @@ -0,0 +1,37 @@ +name: Check DB migrations +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.13 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Create virtual environment and install dependencies + run: | + uv venv + uv sync --extra dev + + - name: Run migration checks + run: | + source .venv/bin/activate + python generate_migrations.py --check diff --git a/generate_migrations.py b/generate_migrations.py index 7bd1c8d0..9b58f0c7 100644 --- a/generate_migrations.py +++ b/generate_migrations.py @@ -16,11 +16,29 @@ def run(cmd, env=None): subprocess.run(cmd, shell=True, check=True, env=env or os.environ) +def print_db_schema(): + print("๐Ÿ“ฆ Printing database schema...") + query = """ + SELECT table_name, column_name, data_type + FROM information_schema.columns + WHERE table_schema = 'public' + ORDER BY table_name, ordinal_position; + """ + psql_cmd = ( + f'psql -h localhost -p {DEFAULT_PORT} -U {POSTGRES_USER} ' + f'-c "{query.strip()}"' + ) + env = os.environ.copy() + env["PGPASSWORD"] = POSTGRES_PASSWORD + run(psql_cmd, env=env) + + @click.command() @click.option('--revision-message', '-m', required=False, help="Message for Alembic revision.") @click.option('--check', is_flag=True, help="Only run 'alembic check' after DB container is up.") -def generate_migrations(revision_message, check): - """Spin up a temp Postgres DB, apply migrations or run alembic check.""" +@click.option('--print-schema', is_flag=True, help="Print the schema of the database after upgrade/check.") +def generate_migrations(revision_message, check, print_schema): + """Spin up a temp Postgres DB, apply migrations or run alembic check, optionally print schema.""" if not check and not revision_message: raise click.UsageError("Missing option '-m' / '--revision-message'. Required unless using --check.") @@ -39,19 +57,21 @@ def generate_migrations(revision_message, check): ) print("โณ Waiting for database to be ready...") - time.sleep(5) # Could be enhanced with pg_isready + time.sleep(5) + + print("๐Ÿงฑ Applying existing Alembic migrations...") + run("alembic upgrade head") if check: - print("๐Ÿงฑ Applying existing Alembic migrations...") - run("alembic upgrade head") print("๐Ÿ” Running 'alembic check'...") run("alembic check") - else: - print("๐Ÿงฑ Applying existing Alembic migrations...") - run("alembic upgrade head") + elif revision_message: print("๐Ÿ“ Generating new Alembic revision...") - run(f"alembic revision --autogenerate -m \"{revision_message}\"") + run(f'alembic revision --autogenerate -m "{revision_message}"') + + if print_schema: + print_db_schema() finally: print("๐Ÿงน Cleaning up: stopping container...") diff --git a/migrations/versions/101f45395233_add_group_membership.py b/migrations/versions/07161e26f537_group_membership.py similarity index 83% rename from migrations/versions/101f45395233_add_group_membership.py rename to migrations/versions/07161e26f537_group_membership.py index 6d302439..a0a50b6a 100644 --- a/migrations/versions/101f45395233_add_group_membership.py +++ b/migrations/versions/07161e26f537_group_membership.py @@ -1,8 +1,8 @@ -"""add_group_membership +"""group_membership -Revision ID: 101f45395233 +Revision ID: 07161e26f537 Revises: -Create Date: 2025-07-01 16:29:48.072722 +Create Date: 2025-07-03 11:48:44.293471 """ from typing import Sequence, Union @@ -13,7 +13,7 @@ # revision identifiers, used by Alembic. -revision: str = '101f45395233' +revision: str = '07161e26f537' down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -26,7 +26,7 @@ def upgrade() -> None: sa.Column('group', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('user_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('user_email', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('approval_status', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('approval_status', sa.Enum('APPROVED', 'PENDING', 'REVOKED', name='ApprovalStatusEnum'), nullable=False), sa.Column('updated_at', sa.DateTime(), nullable=False), sa.Column('updated_by_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('updated_by_email', sqlmodel.sql.sqltypes.AutoString(), nullable=False), diff --git a/migrations/versions/e55434038b96_status_enum.py b/migrations/versions/e55434038b96_status_enum.py deleted file mode 100644 index b81eb7f5..00000000 --- a/migrations/versions/e55434038b96_status_enum.py +++ /dev/null @@ -1,37 +0,0 @@ -"""status_enum - -Revision ID: e55434038b96 -Revises: 101f45395233 -Create Date: 2025-07-03 09:41:04.657535 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -import sqlmodel - - -# revision identifiers, used by Alembic. -revision: str = 'e55434038b96' -down_revision: Union[str, None] = '101f45395233' -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('groupmembership', 'approval_status', - existing_type=sa.VARCHAR(), - type_=sa.Enum('APPROVED', 'PENDING', 'REVOKED', name='ApprovalStatusEnum'), - existing_nullable=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('groupmembership', 'approval_status', - existing_type=sa.Enum('APPROVED', 'PENDING', 'REVOKED', name='ApprovalStatusEnum'), - type_=sa.VARCHAR(), - existing_nullable=False) - # ### end Alembic commands ###