Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""user_id from uuid to int

Revision ID: 66abc97f3782
Revises: 60b6c511a485
Create Date: 2025-06-16 11:05:36.196795

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = "66abc97f3782"
down_revision = "60b6c511a485"
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);'))

# 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(
"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;"))


def downgrade():
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""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("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, "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 ###
2 changes: 1 addition & 1 deletion backend/app/api/routes/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
):
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/routes/onboarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/routes/project_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
):
Expand Down
6 changes: 3 additions & 3 deletions backend/app/api/routes/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
"""
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion backend/app/crud/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion backend/app/crud/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion backend/app/crud/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 5 additions & 9 deletions backend/app/crud/project_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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.
Expand All @@ -47,9 +47,7 @@ def add_user_to_project(
return ProjectUserPublic.model_validate(project_user)


def remove_user_from_project(
session: Session, project_id: uuid.UUID, user_id: uuid.UUID
) -> None:
def remove_user_from_project(session: Session, project_id: int, user_id: int) -> None:
"""
Removes a user from a project.
"""
Expand All @@ -70,7 +68,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.
Expand All @@ -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: uuid.UUID, 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.
"""
Expand Down
4 changes: 1 addition & 3 deletions backend/app/models/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ class APIKeyBase(SQLModel):
project_id: int = Field(
foreign_key="project.id", nullable=False, ondelete="CASCADE"
)
user_id: uuid.UUID = 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
)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/models/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion backend/app/models/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 1 addition & 3 deletions backend/app/models/project_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ class ProjectUserBase(SQLModel):
project_id: int = Field(
foreign_key="project.id", nullable=False, ondelete="CASCADE"
)
user_id: uuid.UUID = 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
Expand Down
Loading