From a84a585e28d788034f6b5927d86a4670a662e49d Mon Sep 17 00:00:00 2001 From: Andreas Vester Date: Thu, 29 Jan 2026 20:54:41 +0100 Subject: [PATCH 1/2] fix: Database migrations for fresh deployments Create initial migration that properly creates all tables instead of attempting to alter non-existent ones. Closes #20 --- alembic/versions/000_initial_schema.py | 101 ++++++++++++++++++ .../f07b511c58dc_added_date_columns.py | 47 -------- 2 files changed, 101 insertions(+), 47 deletions(-) create mode 100644 alembic/versions/000_initial_schema.py delete mode 100644 alembic/versions/f07b511c58dc_added_date_columns.py diff --git a/alembic/versions/000_initial_schema.py b/alembic/versions/000_initial_schema.py new file mode 100644 index 0000000..6b2f68c --- /dev/null +++ b/alembic/versions/000_initial_schema.py @@ -0,0 +1,101 @@ +"""Initial schema creation. + +Revision ID: 000_initial +Revises: +Create Date: 2026-01-29 00:00:00.000000 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "000_initial" +down_revision: str | Sequence[str] | None = None +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema.""" + # Create applications table + op.create_table( + "applications", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("first_name", sa.String(), nullable=False), + sa.Column("last_name", sa.String(), nullable=False), + sa.Column("applicant_email", sa.String(), nullable=False), + sa.Column("department", sa.String(), nullable=False), + sa.Column("project_title", sa.String(), nullable=False), + sa.Column("project_description", sa.String(), nullable=False), + sa.Column("costs", sa.Float(), nullable=False), + sa.Column( + "status", + sa.Enum("pending", "approved", "rejected", name="applicationstatus"), + nullable=False, + server_default="pending", + ), + sa.Column( + "created_at", + sa.DateTime(), + server_default=sa.text("datetime('now', 'localtime')"), + nullable=False, + ), + sa.Column("concluded_at", sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index("ix_applications_id", "applications", ["id"], unique=False) + + # Create votes table + op.create_table( + "votes", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("application_id", sa.Integer(), nullable=False), + sa.Column("voter_email", sa.String(), nullable=False), + sa.Column("token", sa.String(), nullable=False), + sa.Column( + "vote", + sa.Enum("approve", "reject", "abstain", name="voteoption"), + nullable=True, + ), + sa.Column( + "vote_status", + sa.Enum("pending", "cast", name="votestatus"), + nullable=False, + server_default="pending", + ), + sa.Column("voted_at", sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(["application_id"], ["applications.id"]), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("token"), + ) + op.create_index("ix_votes_id", "votes", ["id"], unique=False) + op.create_index("ix_votes_token", "votes", ["token"], unique=False) + + # Create attachments table + op.create_table( + "attachments", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("application_id", sa.Integer(), nullable=False), + sa.Column("filename", sa.String(), nullable=False), + sa.Column("filepath", sa.String(), nullable=False), + sa.Column("mime_type", sa.String(), nullable=False), + sa.ForeignKeyConstraint(["application_id"], ["applications.id"]), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("filepath"), + ) + op.create_index("ix_attachments_id", "attachments", ["id"], unique=False) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_index("ix_attachments_id", table_name="attachments") + op.drop_table("attachments") + op.drop_index("ix_votes_token", table_name="votes") + op.drop_index("ix_votes_id", table_name="votes") + op.drop_table("votes") + op.drop_index("ix_applications_id", table_name="applications") + op.drop_table("applications") diff --git a/alembic/versions/f07b511c58dc_added_date_columns.py b/alembic/versions/f07b511c58dc_added_date_columns.py deleted file mode 100644 index fd7f197..0000000 --- a/alembic/versions/f07b511c58dc_added_date_columns.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Added date columns. - -Revision ID: f07b511c58dc -Revises: -Create Date: 2026-01-20 22:07:59.314701 - -""" - -from collections.abc import Sequence - -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "f07b511c58dc" -down_revision: str | Sequence[str] | None = None -branch_labels: str | Sequence[str] | None = None -depends_on: str | Sequence[str] | None = None - - -def upgrade() -> None: - """Upgrade schema.""" - # Use batch_alter_table to work around SQLite's ALTER TABLE limitations - with op.batch_alter_table("applications", schema=None) as batch_op: - batch_op.add_column( - sa.Column( - "created_at", - sa.DateTime(), - server_default=sa.text("datetime('now', 'localtime')"), - nullable=False, - ) - ) - batch_op.add_column(sa.Column("concluded_at", sa.DateTime(), nullable=True)) - - with op.batch_alter_table("votes", schema=None) as batch_op: - batch_op.add_column(sa.Column("voted_at", sa.DateTime(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - """Downgrade schema.""" - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column("votes", "voted_at") - op.drop_column("applications", "concluded_at") - op.drop_column("applications", "created_at") - # ### end Alembic commands ### From 69a117cfd1c010d682796376e23f36fc230c17a4 Mon Sep 17 00:00:00 2001 From: Andreas Vester Date: Thu, 29 Jan 2026 21:03:11 +0100 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b1bd1..1e32efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +### Fix + +- Database migration for fresh deployments ([GH#20](https://github.com/andreas-vester/ProjectVote/issues/20)). + ## [0.5.0] - 2026-01-23 ### Added