From 8b3d4cc12bbe3727ee88d244143563f7449243e5 Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:35:48 +0530 Subject: [PATCH 1/8] database migration for user_id to int --- ...b01f457_change_user_id_from_uuid_to_int.py | 107 ++++++++++++++++++ ...d1_add_fk_constraint_to_user_id_columns.py | 74 ++++++++++++ backend/app/models/api_key.py | 2 +- backend/app/models/collection.py | 2 +- backend/app/models/document.py | 2 +- backend/app/models/project_user.py | 2 +- backend/app/models/user.py | 6 +- 7 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py create mode 100644 backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py diff --git a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py new file mode 100644 index 000000000..6895fd4ad --- /dev/null +++ b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py @@ -0,0 +1,107 @@ +"""Change user_id from uuid to int + +Revision ID: 4da48b01f457 +Revises: 904ed70e7dab +Create Date: 2025-06-05 13:59:46.816459 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = '4da48b01f457' +down_revision = '904ed70e7dab' +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + + # Drop foreign key constraints + conn.execute(sa.text('ALTER TABLE document DROP CONSTRAINT document_owner_id_fkey;')) + conn.execute(sa.text('ALTER TABLE collection DROP CONSTRAINT collection_owner_id_fkey;')) + conn.execute(sa.text('ALTER TABLE projectuser DROP CONSTRAINT projectuser_user_id_fkey;')) + conn.execute(sa.text('ALTER TABLE apikey DROP CONSTRAINT apikey_user_id_fkey;')) + + # Drop primary key constraint on "user" table + conn.execute(sa.text('ALTER TABLE "user" DROP CONSTRAINT user_pkey;')) + + # Create mapping table from UUID to INT + conn.execute(sa.text(''' + CREATE TABLE uuid_to_int_map ( + user_id_uuid UUID PRIMARY KEY, + user_id_int INT GENERATED ALWAYS AS IDENTITY + ); + ''')) + + # Populate mapping table + conn.execute(sa.text('INSERT INTO uuid_to_int_map (user_id_uuid) SELECT id FROM "user";')) + + # Add new_id to user table and populate it + conn.execute(sa.text('ALTER TABLE "user" ADD COLUMN new_id INT;')) + conn.execute(sa.text(''' + UPDATE "user" SET new_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE "user".id = uuid_map.user_id_uuid; + ''')) + + # document + conn.execute(sa.text('ALTER TABLE document ADD COLUMN new_owner_id INT;')) + conn.execute(sa.text(''' + UPDATE document SET new_owner_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE document.owner_id = uuid_map.user_id_uuid; + ''')) + + # collection + conn.execute(sa.text('ALTER TABLE collection ADD COLUMN new_owner_id INT;')) + conn.execute(sa.text(''' + UPDATE collection SET new_owner_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE collection.owner_id = uuid_map.user_id_uuid; + ''')) + + # projectuser + conn.execute(sa.text('ALTER TABLE projectuser ADD COLUMN new_user_id INT;')) + conn.execute(sa.text(''' + UPDATE projectuser SET new_user_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE projectuser.user_id = uuid_map.user_id_uuid; + ''')) + + # apikey + conn.execute(sa.text('ALTER TABLE apikey ADD COLUMN new_user_id INT;')) + conn.execute(sa.text(''' + UPDATE apikey SET new_user_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE apikey.user_id = uuid_map.user_id_uuid; + ''')) + + # Drop old columns and rename new ones + conn.execute(sa.text('ALTER TABLE document DROP COLUMN owner_id;')) + conn.execute(sa.text('ALTER TABLE document RENAME COLUMN new_owner_id TO owner_id;')) + + conn.execute(sa.text('ALTER TABLE collection DROP COLUMN owner_id;')) + conn.execute(sa.text('ALTER TABLE collection RENAME COLUMN new_owner_id TO owner_id;')) + + conn.execute(sa.text('ALTER TABLE projectuser DROP COLUMN user_id;')) + conn.execute(sa.text('ALTER TABLE projectuser RENAME COLUMN new_user_id TO user_id;')) + + conn.execute(sa.text('ALTER TABLE apikey DROP COLUMN user_id;')) + conn.execute(sa.text('ALTER TABLE apikey RENAME COLUMN new_user_id TO user_id;')) + + conn.execute(sa.text('ALTER TABLE "user" DROP COLUMN id;')) + conn.execute(sa.text('ALTER TABLE "user" RENAME COLUMN new_id TO id;')) + + # Re-add primary key + conn.execute(sa.text('ALTER TABLE "user" ADD PRIMARY KEY (id);')) + + # Drop mapping table + conn.execute(sa.text('DROP TABLE uuid_to_int_map;')) + + +def downgrade(): + pass diff --git a/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py new file mode 100644 index 000000000..c6c4ab523 --- /dev/null +++ b/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py @@ -0,0 +1,74 @@ +"""Add FK constraint to user_id columns + +Revision ID: b18795be76d1 +Revises: 4da48b01f457 +Create Date: 2025-06-05 15:29:59.330755 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = 'b18795be76d1' +down_revision = '4da48b01f457' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('apikey', 'user_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'apikey', 'user', ['user_id'], ['id'], ondelete='CASCADE') + op.alter_column('collection', 'owner_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'collection', 'user', ['owner_id'], ['id'], ondelete='CASCADE') + op.alter_column('document', 'owner_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'document', 'user', ['owner_id'], ['id'], ondelete='CASCADE') + op.alter_column('projectuser', 'user_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'projectuser', 'user', ['user_id'], ['id'], ondelete='CASCADE') + + op.alter_column('credential', 'credential', + existing_type=sa.VARCHAR(), + nullable=True) + op.drop_constraint('credential_organization_id_fkey', 'credential', type_='foreignkey') + op.drop_constraint('credential_project_id_fkey', 'credential', type_='foreignkey') + op.create_foreign_key(None, 'credential', 'project', ['project_id'], ['id']) + op.create_foreign_key(None, 'credential', 'organization', ['organization_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'projectuser', type_='foreignkey') + op.alter_column('projectuser', 'user_id', + existing_type=sa.INTEGER(), + nullable=True) + op.drop_constraint(None, 'document', type_='foreignkey') + op.alter_column('document', 'owner_id', + existing_type=sa.INTEGER(), + nullable=True) + op.drop_constraint(None, 'credential', type_='foreignkey') + op.drop_constraint(None, 'credential', type_='foreignkey') + op.create_foreign_key('credential_project_id_fkey', 'credential', 'project', ['project_id'], ['id'], ondelete='SET NULL') + op.create_foreign_key('credential_organization_id_fkey', 'credential', 'organization', ['organization_id'], ['id'], ondelete='CASCADE') + op.alter_column('credential', 'credential', + existing_type=sa.VARCHAR(), + nullable=False) + op.drop_constraint(None, 'collection', type_='foreignkey') + op.alter_column('collection', 'owner_id', + existing_type=sa.INTEGER(), + nullable=True) + op.drop_constraint(None, 'apikey', type_='foreignkey') + op.alter_column('apikey', 'user_id', + existing_type=sa.INTEGER(), + nullable=True) + # ### end Alembic commands ### diff --git a/backend/app/models/api_key.py b/backend/app/models/api_key.py index 188f529b3..ccbeee303 100644 --- a/backend/app/models/api_key.py +++ b/backend/app/models/api_key.py @@ -14,7 +14,7 @@ class APIKeyBase(SQLModel): project_id: int = Field( foreign_key="project.id", nullable=False, ondelete="CASCADE" ) - user_id: uuid.UUID = Field( + user_id: int = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE" ) key: str = Field( diff --git a/backend/app/models/collection.py b/backend/app/models/collection.py index 1e74d29c7..2d7ea2a5e 100644 --- a/backend/app/models/collection.py +++ b/backend/app/models/collection.py @@ -12,7 +12,7 @@ class Collection(SQLModel, table=True): default_factory=uuid4, primary_key=True, ) - owner_id: UUID = Field( + owner_id: int = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE", diff --git a/backend/app/models/document.py b/backend/app/models/document.py index c0c02ee75..71da74c1b 100644 --- a/backend/app/models/document.py +++ b/backend/app/models/document.py @@ -12,7 +12,7 @@ class Document(SQLModel, table=True): default_factory=uuid4, primary_key=True, ) - owner_id: UUID = Field( + owner_id: int = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE", diff --git a/backend/app/models/project_user.py b/backend/app/models/project_user.py index cbc18efd5..3467d5514 100644 --- a/backend/app/models/project_user.py +++ b/backend/app/models/project_user.py @@ -11,7 +11,7 @@ class ProjectUserBase(SQLModel): project_id: int = Field( foreign_key="project.id", nullable=False, ondelete="CASCADE" ) - user_id: uuid.UUID = Field( + user_id: int = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE" ) is_admin: bool = Field( diff --git a/backend/app/models/user.py b/backend/app/models/user.py index f39c6161c..66a7e932c 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -46,7 +46,7 @@ class UpdatePassword(SQLModel): # Database model, database table inferred from class name class User(UserBase, table=True): - id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + id: int = Field(default=None, primary_key=True) hashed_password: str documents: list["Document"] = Relationship( back_populates="owner", cascade_delete=True @@ -61,7 +61,7 @@ class User(UserBase, table=True): class UserOrganization(UserBase): - id: uuid.UUID + id: int organization_id: int | None @@ -71,7 +71,7 @@ class UserProjectOrg(UserOrganization): # Properties to return via API, id is always required class UserPublic(UserBase): - id: uuid.UUID + id: int class UsersPublic(SQLModel): From 7f93f62f8580294430f9729f713942c4a9e09dda Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:12:39 +0530 Subject: [PATCH 2/8] changes in api and crud --- .../4da48b01f457_change_user_id_from_uuid_to_int.py | 6 ++++++ backend/app/api/routes/api_keys.py | 2 +- backend/app/api/routes/onboarding.py | 2 +- backend/app/api/routes/project_user.py | 4 ++-- backend/app/api/routes/users.py | 6 +++--- backend/app/crud/api_key.py | 2 +- backend/app/crud/collection.py | 2 +- backend/app/crud/document.py | 2 +- backend/app/crud/project_user.py | 10 +++++----- 9 files changed, 21 insertions(+), 15 deletions(-) diff --git a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py index 6895fd4ad..a4cf50156 100644 --- a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py +++ b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py @@ -99,6 +99,12 @@ def upgrade(): # Re-add primary key conn.execute(sa.text('ALTER TABLE "user" ADD PRIMARY KEY (id);')) + # Create sequence for new integer IDs + conn.execute(sa.text('DROP SEQUENCE user_id_seq;')) + conn.execute(sa.text('CREATE SEQUENCE user_id_seq START 1;')) + conn.execute(sa.text("ALTER TABLE \"user\" ALTER COLUMN id SET DEFAULT nextval('user_id_seq');")) + conn.execute(sa.text("SELECT setval('user_id_seq', (SELECT MAX(id) FROM \"user\"));")) + # Drop mapping table conn.execute(sa.text('DROP TABLE uuid_to_int_map;')) diff --git a/backend/app/api/routes/api_keys.py b/backend/app/api/routes/api_keys.py index 8d6905908..977dd02a5 100644 --- a/backend/app/api/routes/api_keys.py +++ b/backend/app/api/routes/api_keys.py @@ -20,7 +20,7 @@ @router.post("/", response_model=APIResponse[APIKeyPublic]) def create_key( project_id: int, - user_id: uuid.UUID, + user_id: int, session: Session = Depends(get_db), current_user: User = Depends(get_current_active_superuser), ): diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index fa8559d55..f267e37f6 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -40,7 +40,7 @@ class OnboardingRequest(BaseModel): class OnboardingResponse(BaseModel): organization_id: int project_id: int - user_id: uuid.UUID + user_id: int api_key: str diff --git a/backend/app/api/routes/project_user.py b/backend/app/api/routes/project_user.py index e5723891a..17bd5b8aa 100644 --- a/backend/app/api/routes/project_user.py +++ b/backend/app/api/routes/project_user.py @@ -22,7 +22,7 @@ ) def add_user( request: Request, - user_id: uuid.UUID, + user_id: int, is_admin: bool = False, session: Session = Depends(get_db), current_user: UserProjectOrg = Depends(verify_user_project_organization), @@ -81,7 +81,7 @@ def list_project_users( ) def remove_user( request: Request, - user_id: uuid.UUID, + user_id: int, session: Session = Depends(get_db), current_user: UserProjectOrg = Depends(verify_user_project_organization), ): diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 60a043bc6..956461010 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -160,7 +160,7 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any: @router.get("/{user_id}", response_model=UserPublic, include_in_schema=False) def read_user_by_id( - user_id: uuid.UUID, session: SessionDep, current_user: CurrentUser + user_id: int, session: SessionDep, current_user: CurrentUser ) -> Any: """ Get a specific user by id. @@ -185,7 +185,7 @@ def read_user_by_id( def update_user_endpoint( *, session: SessionDep, - user_id: uuid.UUID, + user_id: int, user_in: UserUpdate, ) -> Any: """ @@ -215,7 +215,7 @@ def update_user_endpoint( include_in_schema=False, ) def delete_user( - session: SessionDep, current_user: CurrentUser, user_id: uuid.UUID + session: SessionDep, current_user: CurrentUser, user_id: int ) -> Message: """ Delete a user. diff --git a/backend/app/crud/api_key.py b/backend/app/crud/api_key.py index 5a4f3a047..905d0a1b1 100644 --- a/backend/app/crud/api_key.py +++ b/backend/app/crud/api_key.py @@ -19,7 +19,7 @@ def generate_api_key() -> tuple[str, str]: def create_api_key( - session: Session, organization_id: int, user_id: uuid.UUID, project_id: int + session: Session, organization_id: int, user_id: int, project_id: int ) -> APIKeyPublic: """ Generates a new API key for an organization and associates it with a user. diff --git a/backend/app/crud/collection.py b/backend/app/crud/collection.py index 830466833..7b91f4fbd 100644 --- a/backend/app/crud/collection.py +++ b/backend/app/crud/collection.py @@ -11,7 +11,7 @@ class CollectionCrud: - def __init__(self, session: Session, owner_id: UUID): + def __init__(self, session: Session, owner_id: int): self.session = session self.owner_id = owner_id diff --git a/backend/app/crud/document.py b/backend/app/crud/document.py index db1dce4d0..32b59f484 100644 --- a/backend/app/crud/document.py +++ b/backend/app/crud/document.py @@ -8,7 +8,7 @@ class DocumentCrud: - def __init__(self, session: Session, owner_id: UUID): + def __init__(self, session: Session, owner_id: int): self.session = session self.owner_id = owner_id diff --git a/backend/app/crud/project_user.py b/backend/app/crud/project_user.py index 8a3d88350..e598d53af 100644 --- a/backend/app/crud/project_user.py +++ b/backend/app/crud/project_user.py @@ -6,7 +6,7 @@ from app.core.util import now -def is_project_admin(session: Session, user_id: str, project_id: int) -> bool: +def is_project_admin(session: Session, user_id: int, project_id: int) -> bool: """ Checks if a user is an admin of the given project. """ @@ -23,7 +23,7 @@ def is_project_admin(session: Session, user_id: str, project_id: int) -> bool: # Add a user to a project def add_user_to_project( - session: Session, project_id: uuid.UUID, user_id: uuid.UUID, is_admin: bool = False + session: Session, project_id: int, user_id: int, is_admin: bool = False ) -> ProjectUserPublic: """ Adds a user to a project. @@ -48,7 +48,7 @@ def add_user_to_project( def remove_user_from_project( - session: Session, project_id: uuid.UUID, user_id: uuid.UUID + session: Session, project_id: int, user_id: int ) -> None: """ Removes a user from a project. @@ -70,7 +70,7 @@ def remove_user_from_project( def get_users_by_project( - session: Session, project_id: uuid.UUID, skip: int = 0, limit: int = 100 + session: Session, project_id: int, skip: int = 0, limit: int = 100 ) -> tuple[list[ProjectUserPublic], int]: """ Returns paginated users in a given project along with the total count. @@ -95,7 +95,7 @@ def get_users_by_project( # Check if a user belongs to an at least one project in organization def is_user_part_of_organization( - session: Session, user_id: uuid.UUID, org_id: int + session: Session, user_id: int, org_id: int ) -> bool: """ Checks if a user is part of at least one project within the organization. From cf30151a12e8ad0bdbce0cc088a702464bf0b97d Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:08:09 +0530 Subject: [PATCH 3/8] fix test cases --- ...b01f457_change_user_id_from_uuid_to_int.py | 116 ++++++++++++------ ...d1_add_fk_constraint_to_user_id_columns.py | 110 +++++++++-------- backend/app/crud/project_user.py | 8 +- backend/app/models/project_user.py | 4 +- backend/app/tests/api/routes/test_api_key.py | 6 +- .../app/tests/api/routes/test_project_user.py | 7 +- backend/app/tests/api/routes/test_users.py | 20 +-- backend/app/tests/conftest.py | 2 +- .../test_crud_collection_read_one.py | 2 +- .../documents/test_crud_document_delete.py | 2 +- backend/app/tests/crud/test_org.py | 5 +- backend/app/tests/crud/test_project_user.py | 4 +- backend/app/tests/utils/utils.py | 15 ++- 13 files changed, 187 insertions(+), 114 deletions(-) diff --git a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py index a4cf50156..a60aafefd 100644 --- a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py +++ b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py @@ -11,8 +11,8 @@ # revision identifiers, used by Alembic. -revision = '4da48b01f457' -down_revision = '904ed70e7dab' +revision = "4da48b01f457" +down_revision = "904ed70e7dab" branch_labels = None depends_on = None @@ -21,77 +21,115 @@ def upgrade(): conn = op.get_bind() # Drop foreign key constraints - conn.execute(sa.text('ALTER TABLE document DROP CONSTRAINT document_owner_id_fkey;')) - conn.execute(sa.text('ALTER TABLE collection DROP CONSTRAINT collection_owner_id_fkey;')) - conn.execute(sa.text('ALTER TABLE projectuser DROP CONSTRAINT projectuser_user_id_fkey;')) - conn.execute(sa.text('ALTER TABLE apikey DROP CONSTRAINT apikey_user_id_fkey;')) + conn.execute( + sa.text("ALTER TABLE document DROP CONSTRAINT document_owner_id_fkey;") + ) + conn.execute( + sa.text("ALTER TABLE collection DROP CONSTRAINT collection_owner_id_fkey;") + ) + conn.execute( + sa.text("ALTER TABLE projectuser DROP CONSTRAINT projectuser_user_id_fkey;") + ) + conn.execute(sa.text("ALTER TABLE apikey DROP CONSTRAINT apikey_user_id_fkey;")) # Drop primary key constraint on "user" table conn.execute(sa.text('ALTER TABLE "user" DROP CONSTRAINT user_pkey;')) # Create mapping table from UUID to INT - conn.execute(sa.text(''' + conn.execute( + sa.text( + """ CREATE TABLE uuid_to_int_map ( user_id_uuid UUID PRIMARY KEY, user_id_int INT GENERATED ALWAYS AS IDENTITY ); - ''')) + """ + ) + ) # Populate mapping table - conn.execute(sa.text('INSERT INTO uuid_to_int_map (user_id_uuid) SELECT id FROM "user";')) + conn.execute( + sa.text('INSERT INTO uuid_to_int_map (user_id_uuid) SELECT id FROM "user";') + ) # Add new_id to user table and populate it conn.execute(sa.text('ALTER TABLE "user" ADD COLUMN new_id INT;')) - conn.execute(sa.text(''' + conn.execute( + sa.text( + """ UPDATE "user" SET new_id = uuid_map.user_id_int FROM uuid_to_int_map uuid_map WHERE "user".id = uuid_map.user_id_uuid; - ''')) + """ + ) + ) # document - conn.execute(sa.text('ALTER TABLE document ADD COLUMN new_owner_id INT;')) - conn.execute(sa.text(''' + conn.execute(sa.text("ALTER TABLE document ADD COLUMN new_owner_id INT;")) + conn.execute( + sa.text( + """ UPDATE document SET new_owner_id = uuid_map.user_id_int FROM uuid_to_int_map uuid_map WHERE document.owner_id = uuid_map.user_id_uuid; - ''')) + """ + ) + ) # collection - conn.execute(sa.text('ALTER TABLE collection ADD COLUMN new_owner_id INT;')) - conn.execute(sa.text(''' + conn.execute(sa.text("ALTER TABLE collection ADD COLUMN new_owner_id INT;")) + conn.execute( + sa.text( + """ UPDATE collection SET new_owner_id = uuid_map.user_id_int FROM uuid_to_int_map uuid_map WHERE collection.owner_id = uuid_map.user_id_uuid; - ''')) + """ + ) + ) # projectuser - conn.execute(sa.text('ALTER TABLE projectuser ADD COLUMN new_user_id INT;')) - conn.execute(sa.text(''' + conn.execute(sa.text("ALTER TABLE projectuser ADD COLUMN new_user_id INT;")) + conn.execute( + sa.text( + """ UPDATE projectuser SET new_user_id = uuid_map.user_id_int FROM uuid_to_int_map uuid_map WHERE projectuser.user_id = uuid_map.user_id_uuid; - ''')) + """ + ) + ) # apikey - conn.execute(sa.text('ALTER TABLE apikey ADD COLUMN new_user_id INT;')) - conn.execute(sa.text(''' + conn.execute(sa.text("ALTER TABLE apikey ADD COLUMN new_user_id INT;")) + conn.execute( + sa.text( + """ UPDATE apikey SET new_user_id = uuid_map.user_id_int FROM uuid_to_int_map uuid_map WHERE apikey.user_id = uuid_map.user_id_uuid; - ''')) + """ + ) + ) # Drop old columns and rename new ones - conn.execute(sa.text('ALTER TABLE document DROP COLUMN owner_id;')) - conn.execute(sa.text('ALTER TABLE document RENAME COLUMN new_owner_id TO owner_id;')) + conn.execute(sa.text("ALTER TABLE document DROP COLUMN owner_id;")) + conn.execute( + sa.text("ALTER TABLE document RENAME COLUMN new_owner_id TO owner_id;") + ) - conn.execute(sa.text('ALTER TABLE collection DROP COLUMN owner_id;')) - conn.execute(sa.text('ALTER TABLE collection RENAME COLUMN new_owner_id TO owner_id;')) + conn.execute(sa.text("ALTER TABLE collection DROP COLUMN owner_id;")) + conn.execute( + sa.text("ALTER TABLE collection RENAME COLUMN new_owner_id TO owner_id;") + ) - conn.execute(sa.text('ALTER TABLE projectuser DROP COLUMN user_id;')) - conn.execute(sa.text('ALTER TABLE projectuser RENAME COLUMN new_user_id TO user_id;')) + conn.execute(sa.text("ALTER TABLE projectuser DROP COLUMN user_id;")) + conn.execute( + sa.text("ALTER TABLE projectuser RENAME COLUMN new_user_id TO user_id;") + ) - conn.execute(sa.text('ALTER TABLE apikey DROP COLUMN user_id;')) - conn.execute(sa.text('ALTER TABLE apikey RENAME COLUMN new_user_id TO user_id;')) + conn.execute(sa.text("ALTER TABLE apikey DROP COLUMN user_id;")) + conn.execute(sa.text("ALTER TABLE apikey RENAME COLUMN new_user_id TO user_id;")) conn.execute(sa.text('ALTER TABLE "user" DROP COLUMN id;')) conn.execute(sa.text('ALTER TABLE "user" RENAME COLUMN new_id TO id;')) @@ -100,13 +138,19 @@ def upgrade(): conn.execute(sa.text('ALTER TABLE "user" ADD PRIMARY KEY (id);')) # Create sequence for new integer IDs - conn.execute(sa.text('DROP SEQUENCE user_id_seq;')) - conn.execute(sa.text('CREATE SEQUENCE user_id_seq START 1;')) - conn.execute(sa.text("ALTER TABLE \"user\" ALTER COLUMN id SET DEFAULT nextval('user_id_seq');")) - conn.execute(sa.text("SELECT setval('user_id_seq', (SELECT MAX(id) FROM \"user\"));")) + conn.execute(sa.text("DROP SEQUENCE user_id_seq;")) + conn.execute(sa.text("CREATE SEQUENCE user_id_seq START 1;")) + conn.execute( + sa.text( + "ALTER TABLE \"user\" ALTER COLUMN id SET DEFAULT nextval('user_id_seq');" + ) + ) + conn.execute( + sa.text("SELECT setval('user_id_seq', (SELECT MAX(id) FROM \"user\"));") + ) # Drop mapping table - conn.execute(sa.text('DROP TABLE uuid_to_int_map;')) + conn.execute(sa.text("DROP TABLE uuid_to_int_map;")) def downgrade(): diff --git a/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py index c6c4ab523..da4aacce7 100644 --- a/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py +++ b/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py @@ -11,64 +11,78 @@ # revision identifiers, used by Alembic. -revision = 'b18795be76d1' -down_revision = '4da48b01f457' +revision = "b18795be76d1" +down_revision = "4da48b01f457" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('apikey', 'user_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'apikey', 'user', ['user_id'], ['id'], ondelete='CASCADE') - op.alter_column('collection', 'owner_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'collection', 'user', ['owner_id'], ['id'], ondelete='CASCADE') - op.alter_column('document', 'owner_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'document', 'user', ['owner_id'], ['id'], ondelete='CASCADE') - op.alter_column('projectuser', 'user_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'projectuser', 'user', ['user_id'], ['id'], ondelete='CASCADE') + op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=False) + op.create_foreign_key( + None, "apikey", "user", ["user_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "collection", "owner_id", existing_type=sa.INTEGER(), nullable=False + ) + op.create_foreign_key( + None, "collection", "user", ["owner_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=False) + op.create_foreign_key( + None, "document", "user", ["owner_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "projectuser", "user_id", existing_type=sa.INTEGER(), nullable=False + ) + op.create_foreign_key( + None, "projectuser", "user", ["user_id"], ["id"], ondelete="CASCADE" + ) - op.alter_column('credential', 'credential', - existing_type=sa.VARCHAR(), - nullable=True) - op.drop_constraint('credential_organization_id_fkey', 'credential', type_='foreignkey') - op.drop_constraint('credential_project_id_fkey', 'credential', type_='foreignkey') - op.create_foreign_key(None, 'credential', 'project', ['project_id'], ['id']) - op.create_foreign_key(None, 'credential', 'organization', ['organization_id'], ['id']) + op.alter_column( + "credential", "credential", existing_type=sa.VARCHAR(), nullable=True + ) + op.drop_constraint( + "credential_organization_id_fkey", "credential", type_="foreignkey" + ) + op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") + op.create_foreign_key(None, "credential", "project", ["project_id"], ["id"]) + op.create_foreign_key( + None, "credential", "organization", ["organization_id"], ["id"] + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'projectuser', type_='foreignkey') - op.alter_column('projectuser', 'user_id', - existing_type=sa.INTEGER(), - nullable=True) - op.drop_constraint(None, 'document', type_='foreignkey') - op.alter_column('document', 'owner_id', - existing_type=sa.INTEGER(), - nullable=True) - op.drop_constraint(None, 'credential', type_='foreignkey') - op.drop_constraint(None, 'credential', type_='foreignkey') - op.create_foreign_key('credential_project_id_fkey', 'credential', 'project', ['project_id'], ['id'], ondelete='SET NULL') - op.create_foreign_key('credential_organization_id_fkey', 'credential', 'organization', ['organization_id'], ['id'], ondelete='CASCADE') - op.alter_column('credential', 'credential', - existing_type=sa.VARCHAR(), - nullable=False) - op.drop_constraint(None, 'collection', type_='foreignkey') - op.alter_column('collection', 'owner_id', - existing_type=sa.INTEGER(), - nullable=True) - op.drop_constraint(None, 'apikey', type_='foreignkey') - op.alter_column('apikey', 'user_id', - existing_type=sa.INTEGER(), - nullable=True) + op.drop_constraint(None, "projectuser", type_="foreignkey") + op.alter_column("projectuser", "user_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "document", type_="foreignkey") + op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "credential", type_="foreignkey") + op.drop_constraint(None, "credential", type_="foreignkey") + op.create_foreign_key( + "credential_project_id_fkey", + "credential", + "project", + ["project_id"], + ["id"], + ondelete="SET NULL", + ) + op.create_foreign_key( + "credential_organization_id_fkey", + "credential", + "organization", + ["organization_id"], + ["id"], + ondelete="CASCADE", + ) + op.alter_column( + "credential", "credential", existing_type=sa.VARCHAR(), nullable=False + ) + op.drop_constraint(None, "collection", type_="foreignkey") + op.alter_column("collection", "owner_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "apikey", type_="foreignkey") + op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=True) # ### end Alembic commands ### diff --git a/backend/app/crud/project_user.py b/backend/app/crud/project_user.py index e598d53af..3b5dedd1d 100644 --- a/backend/app/crud/project_user.py +++ b/backend/app/crud/project_user.py @@ -47,9 +47,7 @@ def add_user_to_project( return ProjectUserPublic.model_validate(project_user) -def remove_user_from_project( - session: Session, project_id: int, user_id: int -) -> None: +def remove_user_from_project(session: Session, project_id: int, user_id: int) -> None: """ Removes a user from a project. """ @@ -94,9 +92,7 @@ def get_users_by_project( # Check if a user belongs to an at least one project in organization -def is_user_part_of_organization( - session: Session, user_id: int, org_id: int -) -> bool: +def is_user_part_of_organization(session: Session, user_id: int, org_id: int) -> bool: """ Checks if a user is part of at least one project within the organization. """ diff --git a/backend/app/models/project_user.py b/backend/app/models/project_user.py index 3467d5514..57028436f 100644 --- a/backend/app/models/project_user.py +++ b/backend/app/models/project_user.py @@ -11,9 +11,7 @@ class ProjectUserBase(SQLModel): project_id: int = Field( foreign_key="project.id", nullable=False, ondelete="CASCADE" ) - user_id: int = Field( - foreign_key="user.id", nullable=False, ondelete="CASCADE" - ) + user_id: int = Field(foreign_key="user.id", nullable=False, ondelete="CASCADE") is_admin: bool = Field( default=False, nullable=False ) # Determines if user is an admin of the project diff --git a/backend/app/tests/api/routes/test_api_key.py b/backend/app/tests/api/routes/test_api_key.py index 3c1f49c25..0191a594b 100644 --- a/backend/app/tests/api/routes/test_api_key.py +++ b/backend/app/tests/api/routes/test_api_key.py @@ -58,7 +58,7 @@ def test_create_api_key(db: Session, superuser_token_headers: dict[str, str]): assert "id" in data["data"] assert "key" in data["data"] assert data["data"]["organization_id"] == org.id - assert data["data"]["user_id"] == str(user.id) + assert data["data"]["user_id"] == user.id def test_create_duplicate_api_key(db: Session, superuser_token_headers: dict[str, str]): @@ -101,7 +101,7 @@ def test_list_api_keys(db: Session, superuser_token_headers: dict[str, str]): first_key = data["data"][0] assert first_key["organization_id"] == org.id - assert first_key["user_id"] == str(user.id) + assert first_key["user_id"] == user.id def test_get_api_key(db: Session, superuser_token_headers: dict[str, str]): @@ -121,7 +121,7 @@ def test_get_api_key(db: Session, superuser_token_headers: dict[str, str]): assert data["success"] is True assert data["data"]["id"] == api_key.id assert data["data"]["organization_id"] == api_key.organization_id - assert data["data"]["user_id"] == str(user.id) + assert data["data"]["user_id"] == user.id def test_get_nonexistent_api_key(db: Session, superuser_token_headers: dict[str, str]): diff --git a/backend/app/tests/api/routes/test_project_user.py b/backend/app/tests/api/routes/test_project_user.py index fab5f9937..85b5241ab 100644 --- a/backend/app/tests/api/routes/test_project_user.py +++ b/backend/app/tests/api/routes/test_project_user.py @@ -5,7 +5,7 @@ from app.core.config import settings from app.models import User, Project, ProjectUser, Organization from app.crud.project_user import add_user_to_project -from app.tests.utils.utils import random_email +from app.tests.utils.utils import random_email, get_non_existent_id from app.tests.utils.user import authentication_token_from_email from app.core.security import get_password_hash from app.main import app @@ -63,7 +63,7 @@ def test_add_user_to_project( assert response.status_code == 200, response.text added_user = response.json()["data"] - assert added_user["user_id"] == str(user.id) + assert added_user["user_id"] == user.id assert added_user["project_id"] == project.id assert added_user["is_admin"] is True @@ -75,9 +75,10 @@ def test_add_user_not_found( Test adding a non-existing user to a project (should return 404). """ organization, project = create_organization_and_project(db) + user_id = get_non_existent_id(db, User) response = client.post( - f"{settings.API_V1_STR}/project/users/{uuid.uuid4()}?is_admin=false&project_id={project.id}&organization_id={organization.id}", + f"{settings.API_V1_STR}/project/users/{user_id}?is_admin=false&project_id={project.id}&organization_id={organization.id}", headers=superuser_token_headers, ) diff --git a/backend/app/tests/api/routes/test_users.py b/backend/app/tests/api/routes/test_users.py index 68cb3adab..ac518075e 100644 --- a/backend/app/tests/api/routes/test_users.py +++ b/backend/app/tests/api/routes/test_users.py @@ -8,7 +8,7 @@ from app.core.config import settings from app.core.security import verify_password from app.models import User, UserCreate -from app.tests.utils.utils import random_email, random_lower_string +from app.tests.utils.utils import random_email, random_lower_string, get_non_existent_id def test_get_users_superuser_me( @@ -103,10 +103,12 @@ def test_get_existing_user_current_user(client: TestClient, db: Session) -> None def test_get_existing_user_permissions_error( - client: TestClient, normal_user_token_headers: dict[str, str] + db: Session, client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: + non_existent_user_id = get_non_existent_id(db, User) + r = client.get( - f"{settings.API_V1_STR}/users/{uuid.uuid4()}", + f"{settings.API_V1_STR}/users/{non_existent_user_id}", headers=normal_user_token_headers, ) assert r.status_code == 403 @@ -347,11 +349,13 @@ def test_update_user( def test_update_user_not_exists( - client: TestClient, superuser_token_headers: dict[str, str] + db: Session, client: TestClient, superuser_token_headers: dict[str, str] ) -> None: + non_existent_user_id = get_non_existent_id(db, User) + data = {"full_name": "Updated_full_name"} r = client.patch( - f"{settings.API_V1_STR}/users/{uuid.uuid4()}", + f"{settings.API_V1_STR}/users/{non_existent_user_id}", headers=superuser_token_headers, json=data, ) @@ -445,10 +449,12 @@ def test_delete_user_super_user( def test_delete_user_not_found( - client: TestClient, superuser_token_headers: dict[str, str] + db: Session, client: TestClient, superuser_token_headers: dict[str, str] ) -> None: + non_existent_user_id = get_non_existent_id(db, User) + r = client.delete( - f"{settings.API_V1_STR}/users/{uuid.uuid4()}", + f"{settings.API_V1_STR}/users/{non_existent_user_id}", headers=superuser_token_headers, ) assert r.status_code == 404 diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index fa36ddf0d..8ea01daca 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -27,8 +27,8 @@ def db() -> Generator[Session, None, None]: yield session # Delete data in reverse dependency order session.execute(delete(ProjectUser)) # Many-to-many relationship - session.execute(delete(Project)) session.execute(delete(Credential)) + session.execute(delete(Project)) session.execute(delete(Organization)) session.execute(delete(APIKey)) session.execute(delete(User)) diff --git a/backend/app/tests/crud/collections/test_crud_collection_read_one.py b/backend/app/tests/crud/collections/test_crud_collection_read_one.py index 6c28f31b4..22edba532 100644 --- a/backend/app/tests/crud/collections/test_crud_collection_read_one.py +++ b/backend/app/tests/crud/collections/test_crud_collection_read_one.py @@ -36,7 +36,7 @@ def test_can_select_valid_id(self, db: Session): def test_cannot_select_others_collections(self, db: Session): collection = mk_collection(db) - other = uuid_increment(collection.owner_id) + other = collection.owner_id + 1 crud = CollectionCrud(db, other) with pytest.raises(NoResultFound): crud.read_one(collection.id) diff --git a/backend/app/tests/crud/documents/test_crud_document_delete.py b/backend/app/tests/crud/documents/test_crud_document_delete.py index 304dd5647..8b42451a5 100644 --- a/backend/app/tests/crud/documents/test_crud_document_delete.py +++ b/backend/app/tests/crud/documents/test_crud_document_delete.py @@ -33,7 +33,7 @@ def test_delete_follows_insert(self, document: Document): def test_cannot_delete_others_documents(self, db: Session): store = DocumentStore(db) document = store.put() - other_owner_id = store.documents.index.peek() + other_owner_id = store.documents.owner_id + 1 crud = DocumentCrud(db, other_owner_id) with pytest.raises(NoResultFound): diff --git a/backend/app/tests/crud/test_org.py b/backend/app/tests/crud/test_org.py index 77ef5b46c..7efba0ec3 100644 --- a/backend/app/tests/crud/test_org.py +++ b/backend/app/tests/crud/test_org.py @@ -2,7 +2,7 @@ from app.crud.organization import create_organization, get_organization_by_id from app.models import Organization, OrganizationCreate -from app.tests.utils.utils import random_lower_string +from app.tests.utils.utils import random_lower_string, get_non_existent_id def test_create_organization(db: Session) -> None: @@ -30,7 +30,8 @@ def test_get_organization_by_id(db: Session) -> None: def test_get_non_existent_organization(db: Session) -> None: """Test retrieving a non-existent organization should return None.""" + org_id = get_non_existent_id(db, Organization) fetched_org = get_organization_by_id( - session=db, org_id=999 + session=db, org_id=org_id ) # Assuming ID 999 does not exist assert fetched_org is None diff --git a/backend/app/tests/crud/test_project_user.py b/backend/app/tests/crud/test_project_user.py index e5eea790c..72025ae66 100644 --- a/backend/app/tests/crud/test_project_user.py +++ b/backend/app/tests/crud/test_project_user.py @@ -5,7 +5,7 @@ from app.crud import project_user as project_user_crud from app.models import ProjectUser, ProjectUserPublic, User, Project, Organization -from app.tests.utils.utils import random_email +from app.tests.utils.utils import random_email, get_non_existent_id from app.core.security import get_password_hash @@ -111,7 +111,7 @@ def test_remove_user_from_project_not_member(db: Session) -> None: organization, project = create_organization_and_project(db) project_id = project.id - user_id = uuid.uuid4() + user_id = get_non_existent_id(db, User) with pytest.raises( ValueError, match="User is not a member of this project or already removed" diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 271b338ef..2945e8b35 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -4,12 +4,16 @@ import pytest from fastapi.testclient import TestClient -from sqlmodel import Session +from sqlmodel import Session, select +from typing import Type, TypeVar from app.core.config import settings from app.crud.user import get_user_by_email +T = TypeVar("T") + + @pytest.fixture(scope="class") def openai_credentials(): settings.OPENAI_API_KEY = "sk-fake123" @@ -40,6 +44,15 @@ def get_user_id_by_email(db: Session): return user.id +def get_non_existent_id(session: Session, model: Type[T]) -> int: + """ + Returns an ID that does not exist for the given model. + It fetches the current max ID and adds 1. + """ + result = session.exec(select(model.id).order_by(model.id.desc())).first() + return (result or 0) + 1 + + class SequentialUuidGenerator: def __init__(self, start=0): self.start = start From 5c6d144d76774b3463cef307d63c620ba7dc78bd Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:47:44 +0530 Subject: [PATCH 4/8] fix migration --- .../4da48b01f457_change_user_id_from_uuid_to_int.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py index a60aafefd..fbc6393cc 100644 --- a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py +++ b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py @@ -138,8 +138,11 @@ def upgrade(): conn.execute(sa.text('ALTER TABLE "user" ADD PRIMARY KEY (id);')) # Create sequence for new integer IDs - conn.execute(sa.text("DROP SEQUENCE user_id_seq;")) - conn.execute(sa.text("CREATE SEQUENCE user_id_seq START 1;")) + conn.execute( + sa.text( + "CREATE SEQUENCE user_id_seq START 1 OWNED BY \"user\".id;" + ) + ) conn.execute( sa.text( "ALTER TABLE \"user\" ALTER COLUMN id SET DEFAULT nextval('user_id_seq');" From 494533ff1b42ac90a43f14e59d37ff19e1932a8d Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:48:29 +0530 Subject: [PATCH 5/8] run precommit --- .../4da48b01f457_change_user_id_from_uuid_to_int.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py index fbc6393cc..86822e646 100644 --- a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py +++ b/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py @@ -138,11 +138,7 @@ def upgrade(): conn.execute(sa.text('ALTER TABLE "user" ADD PRIMARY KEY (id);')) # Create sequence for new integer IDs - conn.execute( - sa.text( - "CREATE SEQUENCE user_id_seq START 1 OWNED BY \"user\".id;" - ) - ) + conn.execute(sa.text('CREATE SEQUENCE user_id_seq START 1 OWNED BY "user".id;')) conn.execute( sa.text( "ALTER TABLE \"user\" ALTER COLUMN id SET DEFAULT nextval('user_id_seq');" From 0827b8a209433f24fcd08b96d6a554b8ae043b81 Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:16:53 +0530 Subject: [PATCH 6/8] fix migration head --- ... 66abc97f3782_user_id_from_uuid_to_int.py} | 12 +-- ...b0_add_fk_constraint_to_user_id_columns.py | 73 +++++++++++++++ ...d1_add_fk_constraint_to_user_id_columns.py | 88 ------------------- backend/app/models/api_key.py | 4 +- 4 files changed, 80 insertions(+), 97 deletions(-) rename backend/app/alembic/versions/{4da48b01f457_change_user_id_from_uuid_to_int.py => 66abc97f3782_user_id_from_uuid_to_int.py} (95%) create mode 100644 backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py delete mode 100644 backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py diff --git a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py similarity index 95% rename from backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py rename to backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py index 86822e646..c92da6a61 100644 --- a/backend/app/alembic/versions/4da48b01f457_change_user_id_from_uuid_to_int.py +++ b/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py @@ -1,8 +1,8 @@ -"""Change user_id from uuid to int +"""user_id from uuid to int -Revision ID: 4da48b01f457 -Revises: 904ed70e7dab -Create Date: 2025-06-05 13:59:46.816459 +Revision ID: 66abc97f3782 +Revises: 60b6c511a485 +Create Date: 2025-06-16 11:05:36.196795 """ from alembic import op @@ -11,8 +11,8 @@ # revision identifiers, used by Alembic. -revision = "4da48b01f457" -down_revision = "904ed70e7dab" +revision = '66abc97f3782' +down_revision = '60b6c511a485' branch_labels = None depends_on = None diff --git a/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py new file mode 100644 index 000000000..dfbc61dc8 --- /dev/null +++ b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py @@ -0,0 +1,73 @@ +"""add fk constraint to user id columns + +Revision ID: 8e7dc5eab0b0 +Revises: 66abc97f3782 +Create Date: 2025-06-16 11:08:46.893642 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = '8e7dc5eab0b0' +down_revision = '66abc97f3782' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('apikey', 'user_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'apikey', 'user', ['user_id'], ['id'], ondelete='CASCADE') + op.alter_column('collection', 'owner_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'collection', 'user', ['owner_id'], ['id'], ondelete='CASCADE') + op.alter_column('credential', 'credential', + existing_type=sa.VARCHAR(), + nullable=True) + op.drop_constraint('credential_project_id_fkey', 'credential', type_='foreignkey') + op.drop_constraint('credential_organization_id_fkey', 'credential', type_='foreignkey') + op.create_foreign_key(None, 'credential', 'project', ['project_id'], ['id']) + op.create_foreign_key(None, 'credential', 'organization', ['organization_id'], ['id']) + op.alter_column('document', 'owner_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'document', 'user', ['owner_id'], ['id'], ondelete='CASCADE') + op.alter_column('projectuser', 'user_id', + existing_type=sa.INTEGER(), + nullable=False) + op.create_foreign_key(None, 'projectuser', 'user', ['user_id'], ['id'], ondelete='CASCADE') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'projectuser', type_='foreignkey') + op.alter_column('projectuser', 'user_id', + existing_type=sa.INTEGER(), + nullable=True) + op.drop_constraint(None, 'document', type_='foreignkey') + op.alter_column('document', 'owner_id', + existing_type=sa.INTEGER(), + nullable=True) + op.drop_constraint(None, 'credential', type_='foreignkey') + op.drop_constraint(None, 'credential', type_='foreignkey') + op.create_foreign_key('credential_organization_id_fkey', 'credential', 'organization', ['organization_id'], ['id'], ondelete='CASCADE') + op.create_foreign_key('credential_project_id_fkey', 'credential', 'project', ['project_id'], ['id'], ondelete='SET NULL') + op.alter_column('credential', 'credential', + existing_type=sa.VARCHAR(), + nullable=False) + op.drop_constraint(None, 'collection', type_='foreignkey') + op.alter_column('collection', 'owner_id', + existing_type=sa.INTEGER(), + nullable=True) + op.drop_constraint(None, 'apikey', type_='foreignkey') + op.alter_column('apikey', 'user_id', + existing_type=sa.INTEGER(), + nullable=True) + # ### end Alembic commands ### diff --git a/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py deleted file mode 100644 index da4aacce7..000000000 --- a/backend/app/alembic/versions/b18795be76d1_add_fk_constraint_to_user_id_columns.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Add FK constraint to user_id columns - -Revision ID: b18795be76d1 -Revises: 4da48b01f457 -Create Date: 2025-06-05 15:29:59.330755 - -""" -from alembic import op -import sqlalchemy as sa -import sqlmodel.sql.sqltypes - - -# revision identifiers, used by Alembic. -revision = "b18795be76d1" -down_revision = "4da48b01f457" -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=False) - op.create_foreign_key( - None, "apikey", "user", ["user_id"], ["id"], ondelete="CASCADE" - ) - op.alter_column( - "collection", "owner_id", existing_type=sa.INTEGER(), nullable=False - ) - op.create_foreign_key( - None, "collection", "user", ["owner_id"], ["id"], ondelete="CASCADE" - ) - op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=False) - op.create_foreign_key( - None, "document", "user", ["owner_id"], ["id"], ondelete="CASCADE" - ) - op.alter_column( - "projectuser", "user_id", existing_type=sa.INTEGER(), nullable=False - ) - op.create_foreign_key( - None, "projectuser", "user", ["user_id"], ["id"], ondelete="CASCADE" - ) - - op.alter_column( - "credential", "credential", existing_type=sa.VARCHAR(), nullable=True - ) - op.drop_constraint( - "credential_organization_id_fkey", "credential", type_="foreignkey" - ) - op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") - op.create_foreign_key(None, "credential", "project", ["project_id"], ["id"]) - op.create_foreign_key( - None, "credential", "organization", ["organization_id"], ["id"] - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, "projectuser", type_="foreignkey") - op.alter_column("projectuser", "user_id", existing_type=sa.INTEGER(), nullable=True) - op.drop_constraint(None, "document", type_="foreignkey") - op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=True) - op.drop_constraint(None, "credential", type_="foreignkey") - op.drop_constraint(None, "credential", type_="foreignkey") - op.create_foreign_key( - "credential_project_id_fkey", - "credential", - "project", - ["project_id"], - ["id"], - ondelete="SET NULL", - ) - op.create_foreign_key( - "credential_organization_id_fkey", - "credential", - "organization", - ["organization_id"], - ["id"], - ondelete="CASCADE", - ) - op.alter_column( - "credential", "credential", existing_type=sa.VARCHAR(), nullable=False - ) - op.drop_constraint(None, "collection", type_="foreignkey") - op.alter_column("collection", "owner_id", existing_type=sa.INTEGER(), nullable=True) - op.drop_constraint(None, "apikey", type_="foreignkey") - op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=True) - # ### end Alembic commands ### diff --git a/backend/app/models/api_key.py b/backend/app/models/api_key.py index ccbeee303..22387e1bf 100644 --- a/backend/app/models/api_key.py +++ b/backend/app/models/api_key.py @@ -14,9 +14,7 @@ class APIKeyBase(SQLModel): project_id: int = Field( foreign_key="project.id", nullable=False, ondelete="CASCADE" ) - user_id: int = Field( - foreign_key="user.id", nullable=False, ondelete="CASCADE" - ) + user_id: int = Field(foreign_key="user.id", nullable=False, ondelete="CASCADE") key: str = Field( default_factory=lambda: secrets.token_urlsafe(32), unique=True, index=True ) From 73b14534e9b7bbab3bfb9fb9db61bd9c16291b5e Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:21:52 +0530 Subject: [PATCH 7/8] pre commit run --- .../66abc97f3782_user_id_from_uuid_to_int.py | 4 +- ...b0_add_fk_constraint_to_user_id_columns.py | 110 ++++++++++-------- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py index c92da6a61..0035b705b 100644 --- a/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py +++ b/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py @@ -11,8 +11,8 @@ # revision identifiers, used by Alembic. -revision = '66abc97f3782' -down_revision = '60b6c511a485' +revision = "66abc97f3782" +down_revision = "60b6c511a485" branch_labels = None depends_on = None diff --git a/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py index dfbc61dc8..d842dc8d7 100644 --- a/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py +++ b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py @@ -11,63 +11,77 @@ # revision identifiers, used by Alembic. -revision = '8e7dc5eab0b0' -down_revision = '66abc97f3782' +revision = "8e7dc5eab0b0" +down_revision = "66abc97f3782" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('apikey', 'user_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'apikey', 'user', ['user_id'], ['id'], ondelete='CASCADE') - op.alter_column('collection', 'owner_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'collection', 'user', ['owner_id'], ['id'], ondelete='CASCADE') - op.alter_column('credential', 'credential', - existing_type=sa.VARCHAR(), - nullable=True) - op.drop_constraint('credential_project_id_fkey', 'credential', type_='foreignkey') - op.drop_constraint('credential_organization_id_fkey', 'credential', type_='foreignkey') - op.create_foreign_key(None, 'credential', 'project', ['project_id'], ['id']) - op.create_foreign_key(None, 'credential', 'organization', ['organization_id'], ['id']) - op.alter_column('document', 'owner_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'document', 'user', ['owner_id'], ['id'], ondelete='CASCADE') - op.alter_column('projectuser', 'user_id', - existing_type=sa.INTEGER(), - nullable=False) - op.create_foreign_key(None, 'projectuser', 'user', ['user_id'], ['id'], ondelete='CASCADE') + op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=False) + op.create_foreign_key( + None, "apikey", "user", ["user_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "collection", "owner_id", existing_type=sa.INTEGER(), nullable=False + ) + op.create_foreign_key( + None, "collection", "user", ["owner_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "credential", "credential", existing_type=sa.VARCHAR(), nullable=True + ) + op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") + op.drop_constraint( + "credential_organization_id_fkey", "credential", type_="foreignkey" + ) + op.create_foreign_key(None, "credential", "project", ["project_id"], ["id"]) + op.create_foreign_key( + None, "credential", "organization", ["organization_id"], ["id"] + ) + op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=False) + op.create_foreign_key( + None, "document", "user", ["owner_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "projectuser", "user_id", existing_type=sa.INTEGER(), nullable=False + ) + op.create_foreign_key( + None, "projectuser", "user", ["user_id"], ["id"], ondelete="CASCADE" + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'projectuser', type_='foreignkey') - op.alter_column('projectuser', 'user_id', - existing_type=sa.INTEGER(), - nullable=True) - op.drop_constraint(None, 'document', type_='foreignkey') - op.alter_column('document', 'owner_id', - existing_type=sa.INTEGER(), - nullable=True) - op.drop_constraint(None, 'credential', type_='foreignkey') - op.drop_constraint(None, 'credential', type_='foreignkey') - op.create_foreign_key('credential_organization_id_fkey', 'credential', 'organization', ['organization_id'], ['id'], ondelete='CASCADE') - op.create_foreign_key('credential_project_id_fkey', 'credential', 'project', ['project_id'], ['id'], ondelete='SET NULL') - op.alter_column('credential', 'credential', - existing_type=sa.VARCHAR(), - nullable=False) - op.drop_constraint(None, 'collection', type_='foreignkey') - op.alter_column('collection', 'owner_id', - existing_type=sa.INTEGER(), - nullable=True) - op.drop_constraint(None, 'apikey', type_='foreignkey') - op.alter_column('apikey', 'user_id', - existing_type=sa.INTEGER(), - nullable=True) + op.drop_constraint(None, "projectuser", type_="foreignkey") + op.alter_column("projectuser", "user_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "document", type_="foreignkey") + op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "credential", type_="foreignkey") + op.drop_constraint(None, "credential", type_="foreignkey") + op.create_foreign_key( + "credential_organization_id_fkey", + "credential", + "organization", + ["organization_id"], + ["id"], + ondelete="CASCADE", + ) + op.create_foreign_key( + "credential_project_id_fkey", + "credential", + "project", + ["project_id"], + ["id"], + ondelete="SET NULL", + ) + op.alter_column( + "credential", "credential", existing_type=sa.VARCHAR(), nullable=False + ) + op.drop_constraint(None, "collection", type_="foreignkey") + op.alter_column("collection", "owner_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "apikey", type_="foreignkey") + op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=True) # ### end Alembic commands ### From 39e88d5ee6306c3822545d6486851f0b8657c538 Mon Sep 17 00:00:00 2001 From: Aviraj <100823015+avirajsingh7@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:56:21 +0530 Subject: [PATCH 8/8] drop unnecessary migration --- ...b0_add_fk_constraint_to_user_id_columns.py | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py index d842dc8d7..09fe3ef99 100644 --- a/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py +++ b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py @@ -29,17 +29,6 @@ def upgrade(): op.create_foreign_key( None, "collection", "user", ["owner_id"], ["id"], ondelete="CASCADE" ) - op.alter_column( - "credential", "credential", existing_type=sa.VARCHAR(), nullable=True - ) - op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") - op.drop_constraint( - "credential_organization_id_fkey", "credential", type_="foreignkey" - ) - op.create_foreign_key(None, "credential", "project", ["project_id"], ["id"]) - op.create_foreign_key( - None, "credential", "organization", ["organization_id"], ["id"] - ) op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=False) op.create_foreign_key( None, "document", "user", ["owner_id"], ["id"], ondelete="CASCADE" @@ -59,27 +48,6 @@ def downgrade(): op.alter_column("projectuser", "user_id", existing_type=sa.INTEGER(), nullable=True) op.drop_constraint(None, "document", type_="foreignkey") op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=True) - op.drop_constraint(None, "credential", type_="foreignkey") - op.drop_constraint(None, "credential", type_="foreignkey") - op.create_foreign_key( - "credential_organization_id_fkey", - "credential", - "organization", - ["organization_id"], - ["id"], - ondelete="CASCADE", - ) - op.create_foreign_key( - "credential_project_id_fkey", - "credential", - "project", - ["project_id"], - ["id"], - ondelete="SET NULL", - ) - op.alter_column( - "credential", "credential", existing_type=sa.VARCHAR(), nullable=False - ) op.drop_constraint(None, "collection", type_="foreignkey") op.alter_column("collection", "owner_id", existing_type=sa.INTEGER(), nullable=True) op.drop_constraint(None, "apikey", type_="foreignkey")