diff --git a/backend/app/alembic/versions/8d7a05fd0ad4_item_table_drop.py b/backend/app/alembic/versions/8d7a05fd0ad4_item_table_drop.py new file mode 100644 index 00000000..db9686a5 --- /dev/null +++ b/backend/app/alembic/versions/8d7a05fd0ad4_item_table_drop.py @@ -0,0 +1,41 @@ +"""item table drop + +Revision ID: 8d7a05fd0ad4 +Revises: c43313eca57d +Create Date: 2025-04-08 15:26:46.613516 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = "8d7a05fd0ad4" +down_revision = "c43313eca57d" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("item") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "item", + sa.Column( + "description", sa.VARCHAR(length=255), autoincrement=False, nullable=True + ), + sa.Column("title", sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column("id", sa.UUID(), autoincrement=False, nullable=False), + sa.Column("owner_id", sa.UUID(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint( + ["owner_id"], ["user.id"], name="item_owner_id_fkey", ondelete="CASCADE" + ), + sa.PrimaryKeyConstraint("id", name="item_pkey"), + ) + # ### end Alembic commands ### diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 6cd2c443..d6d21fdd 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -3,7 +3,6 @@ from app.api.routes import ( api_keys, documents, - items, login, organization, project, @@ -19,7 +18,6 @@ api_router.include_router(login.router) api_router.include_router(users.router) api_router.include_router(utils.router) -api_router.include_router(items.router) api_router.include_router(documents.router) api_router.include_router(threads.router) api_router.include_router(organization.router) diff --git a/backend/app/api/routes/items.py b/backend/app/api/routes/items.py deleted file mode 100644 index 177dc1e4..00000000 --- a/backend/app/api/routes/items.py +++ /dev/null @@ -1,109 +0,0 @@ -import uuid -from typing import Any - -from fastapi import APIRouter, HTTPException -from sqlmodel import func, select - -from app.api.deps import CurrentUser, SessionDep -from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message - -router = APIRouter(prefix="/items", tags=["items"]) - - -@router.get("/", response_model=ItemsPublic) -def read_items( - session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100 -) -> Any: - """ - Retrieve items. - """ - - if current_user.is_superuser: - count_statement = select(func.count()).select_from(Item) - count = session.exec(count_statement).one() - statement = select(Item).offset(skip).limit(limit) - items = session.exec(statement).all() - else: - count_statement = ( - select(func.count()) - .select_from(Item) - .where(Item.owner_id == current_user.id) - ) - count = session.exec(count_statement).one() - statement = ( - select(Item) - .where(Item.owner_id == current_user.id) - .offset(skip) - .limit(limit) - ) - items = session.exec(statement).all() - - return ItemsPublic(data=items, count=count) - - -@router.get("/{id}", response_model=ItemPublic) -def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any: - """ - Get item by ID. - """ - item = session.get(Item, id) - if not item: - raise HTTPException(status_code=404, detail="Item not found") - if not current_user.is_superuser and (item.owner_id != current_user.id): - raise HTTPException(status_code=400, detail="Not enough permissions") - return item - - -@router.post("/", response_model=ItemPublic) -def create_item( - *, session: SessionDep, current_user: CurrentUser, item_in: ItemCreate -) -> Any: - """ - Create new item. - """ - item = Item.model_validate(item_in, update={"owner_id": current_user.id}) - session.add(item) - session.commit() - session.refresh(item) - return item - - -@router.put("/{id}", response_model=ItemPublic) -def update_item( - *, - session: SessionDep, - current_user: CurrentUser, - id: uuid.UUID, - item_in: ItemUpdate, -) -> Any: - """ - Update an item. - """ - item = session.get(Item, id) - if not item: - raise HTTPException(status_code=404, detail="Item not found") - if not current_user.is_superuser and (item.owner_id != current_user.id): - raise HTTPException(status_code=400, detail="Not enough permissions") - update_dict = item_in.model_dump(exclude_unset=True) - item.sqlmodel_update(update_dict) - session.add(item) - session.commit() - session.refresh(item) - return item - - -@router.delete("/{id}") -def delete_item( - session: SessionDep, current_user: CurrentUser, id: uuid.UUID -) -> Message: - """ - Delete an item. - """ - item = session.get(Item, id) - if not item: - raise HTTPException(status_code=404, detail="Item not found") - if not current_user.is_superuser and (item.owner_id != current_user.id): - raise HTTPException(status_code=400, detail="Not enough permissions") - session.delete(item) - session.commit() - return Message(message="Item deleted successfully") diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 1d6a4f70..c4b19d20 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -13,7 +13,6 @@ from app.core.security import get_password_hash, verify_password from app.crud import create_user, get_user_by_email, update_user from app.models import ( - Item, Message, UpdatePassword, User, @@ -219,8 +218,6 @@ def delete_user( raise HTTPException( status_code=403, detail="Super users are not allowed to delete themselves" ) - statement = delete(Item).where(col(Item.owner_id) == user_id) - session.exec(statement) # type: ignore session.delete(user) session.commit() return Message(message="User deleted successfully") diff --git a/backend/app/crud/__init__.py b/backend/app/crud/__init__.py index 85edaa12..1edef543 100644 --- a/backend/app/crud/__init__.py +++ b/backend/app/crud/__init__.py @@ -1,6 +1,5 @@ from .user import ( authenticate, - create_item, create_user, get_user_by_email, update_user, diff --git a/backend/app/crud/user.py b/backend/app/crud/user.py index 905bf487..a30cc7e6 100644 --- a/backend/app/crud/user.py +++ b/backend/app/crud/user.py @@ -4,7 +4,7 @@ from sqlmodel import Session, select from app.core.security import get_password_hash, verify_password -from app.models import Item, ItemCreate, User, UserCreate, UserUpdate +from app.models import User, UserCreate, UserUpdate def create_user(*, session: Session, user_create: UserCreate) -> User: @@ -44,11 +44,3 @@ def authenticate(*, session: Session, email: str, password: str) -> User | None: if not verify_password(password, db_user.hashed_password): return None return db_user - - -def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item: - db_item = Item.model_validate(item_in, update={"owner_id": owner_id}) - session.add(db_item) - session.commit() - session.refresh(db_item) - return db_item diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 95525118..9b8d7a5f 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -2,7 +2,7 @@ from .auth import Token, TokenPayload from .document import Document -from .item import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate + from .message import Message from .project_user import ( diff --git a/backend/app/models/item.py b/backend/app/models/item.py deleted file mode 100644 index 096d0c3e..00000000 --- a/backend/app/models/item.py +++ /dev/null @@ -1,42 +0,0 @@ -import uuid - -from sqlmodel import Field, Relationship, SQLModel - -from .user import User - - -# Shared properties -class ItemBase(SQLModel): - title: str = Field(min_length=1, max_length=255) - description: str | None = Field(default=None, max_length=255) - - -# Properties to receive on item creation -class ItemCreate(ItemBase): - pass - - -# Properties to receive on item update -class ItemUpdate(ItemBase): - title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore - - -# Database model, database table inferred from class name -class Item(ItemBase, table=True): - id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) - title: str = Field(max_length=255) - owner_id: uuid.UUID = Field( - foreign_key="user.id", nullable=False, ondelete="CASCADE" - ) - owner: User | None = Relationship(back_populates="items") - - -# Properties to return via API, id is always required -class ItemPublic(ItemBase): - id: uuid.UUID - owner_id: uuid.UUID - - -class ItemsPublic(SQLModel): - data: list[ItemPublic] - count: int diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 16c5fd39..d56e21f9 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -48,7 +48,6 @@ class UpdatePassword(SQLModel): class User(UserBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) hashed_password: str - items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True) documents: list["Document"] = Relationship( back_populates="owner", cascade_delete=True ) diff --git a/backend/app/tests/api/routes/test_items.py b/backend/app/tests/api/routes/test_items.py deleted file mode 100644 index c215238a..00000000 --- a/backend/app/tests/api/routes/test_items.py +++ /dev/null @@ -1,164 +0,0 @@ -import uuid - -from fastapi.testclient import TestClient -from sqlmodel import Session - -from app.core.config import settings -from app.tests.utils.item import create_random_item - - -def test_create_item( - client: TestClient, superuser_token_headers: dict[str, str] -) -> None: - data = {"title": "Foo", "description": "Fighters"} - response = client.post( - f"{settings.API_V1_STR}/items/", - headers=superuser_token_headers, - json=data, - ) - assert response.status_code == 200 - content = response.json() - assert content["title"] == data["title"] - assert content["description"] == data["description"] - assert "id" in content - assert "owner_id" in content - - -def test_read_item( - client: TestClient, superuser_token_headers: dict[str, str], db: Session -) -> None: - item = create_random_item(db) - response = client.get( - f"{settings.API_V1_STR}/items/{item.id}", - headers=superuser_token_headers, - ) - assert response.status_code == 200 - content = response.json() - assert content["title"] == item.title - assert content["description"] == item.description - assert content["id"] == str(item.id) - assert content["owner_id"] == str(item.owner_id) - - -def test_read_item_not_found( - client: TestClient, superuser_token_headers: dict[str, str] -) -> None: - response = client.get( - f"{settings.API_V1_STR}/items/{uuid.uuid4()}", - headers=superuser_token_headers, - ) - assert response.status_code == 404 - content = response.json() - assert content["detail"] == "Item not found" - - -def test_read_item_not_enough_permissions( - client: TestClient, normal_user_token_headers: dict[str, str], db: Session -) -> None: - item = create_random_item(db) - response = client.get( - f"{settings.API_V1_STR}/items/{item.id}", - headers=normal_user_token_headers, - ) - assert response.status_code == 400 - content = response.json() - assert content["detail"] == "Not enough permissions" - - -def test_read_items( - client: TestClient, superuser_token_headers: dict[str, str], db: Session -) -> None: - create_random_item(db) - create_random_item(db) - response = client.get( - f"{settings.API_V1_STR}/items/", - headers=superuser_token_headers, - ) - assert response.status_code == 200 - content = response.json() - assert len(content["data"]) >= 2 - - -def test_update_item( - client: TestClient, superuser_token_headers: dict[str, str], db: Session -) -> None: - item = create_random_item(db) - data = {"title": "Updated title", "description": "Updated description"} - response = client.put( - f"{settings.API_V1_STR}/items/{item.id}", - headers=superuser_token_headers, - json=data, - ) - assert response.status_code == 200 - content = response.json() - assert content["title"] == data["title"] - assert content["description"] == data["description"] - assert content["id"] == str(item.id) - assert content["owner_id"] == str(item.owner_id) - - -def test_update_item_not_found( - client: TestClient, superuser_token_headers: dict[str, str] -) -> None: - data = {"title": "Updated title", "description": "Updated description"} - response = client.put( - f"{settings.API_V1_STR}/items/{uuid.uuid4()}", - headers=superuser_token_headers, - json=data, - ) - assert response.status_code == 404 - content = response.json() - assert content["detail"] == "Item not found" - - -def test_update_item_not_enough_permissions( - client: TestClient, normal_user_token_headers: dict[str, str], db: Session -) -> None: - item = create_random_item(db) - data = {"title": "Updated title", "description": "Updated description"} - response = client.put( - f"{settings.API_V1_STR}/items/{item.id}", - headers=normal_user_token_headers, - json=data, - ) - assert response.status_code == 400 - content = response.json() - assert content["detail"] == "Not enough permissions" - - -def test_delete_item( - client: TestClient, superuser_token_headers: dict[str, str], db: Session -) -> None: - item = create_random_item(db) - response = client.delete( - f"{settings.API_V1_STR}/items/{item.id}", - headers=superuser_token_headers, - ) - assert response.status_code == 200 - content = response.json() - assert content["message"] == "Item deleted successfully" - - -def test_delete_item_not_found( - client: TestClient, superuser_token_headers: dict[str, str] -) -> None: - response = client.delete( - f"{settings.API_V1_STR}/items/{uuid.uuid4()}", - headers=superuser_token_headers, - ) - assert response.status_code == 404 - content = response.json() - assert content["detail"] == "Item not found" - - -def test_delete_item_not_enough_permissions( - client: TestClient, normal_user_token_headers: dict[str, str], db: Session -) -> None: - item = create_random_item(db) - response = client.delete( - f"{settings.API_V1_STR}/items/{item.id}", - headers=normal_user_token_headers, - ) - assert response.status_code == 400 - content = response.json() - assert content["detail"] == "Not enough permissions" diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 2fa16040..9cd6c497 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -9,7 +9,6 @@ from app.main import app from app.models import ( APIKey, - Item, Organization, Project, ProjectUser, @@ -28,7 +27,6 @@ def db() -> Generator[Session, None, None]: session.execute(delete(ProjectUser)) # Many-to-many relationship session.execute(delete(Project)) session.execute(delete(Organization)) - session.execute(delete(Item)) session.execute(delete(APIKey)) session.execute(delete(User)) session.commit() diff --git a/backend/app/tests/utils/item.py b/backend/app/tests/utils/item.py deleted file mode 100644 index 6e32b3a8..00000000 --- a/backend/app/tests/utils/item.py +++ /dev/null @@ -1,16 +0,0 @@ -from sqlmodel import Session - -from app import crud -from app.models import Item, ItemCreate -from app.tests.utils.user import create_random_user -from app.tests.utils.utils import random_lower_string - - -def create_random_item(db: Session) -> Item: - user = create_random_user(db) - owner_id = user.id - assert owner_id is not None - title = random_lower_string() - description = random_lower_string() - item_in = ItemCreate(title=title, description=description) - return crud.create_item(session=db, item_in=item_in, owner_id=owner_id)