diff --git a/apps/backend/src/rhesis/backend/app/crud/__init__.py b/apps/backend/src/rhesis/backend/app/crud/__init__.py index 13035a9c40..528228a0f7 100644 --- a/apps/backend/src/rhesis/backend/app/crud/__init__.py +++ b/apps/backend/src/rhesis/backend/app/crud/__init__.py @@ -126,63 +126,6 @@ def get_experiments( ) -# Category CRUD -def get_category( - db: Session, category_id: uuid.UUID, organization_id: str = None, user_id: str = None -) -> Optional[models.Category]: - """Get a single category by ID.""" - return get_item(db, models.Category, category_id, organization_id, user_id) - - -def get_categories( - db: Session, - skip: int = 0, - limit: int = 10, - sort_by: str = "created_at", - sort_order: str = "desc", - filter: str | None = None, - organization_id: str = None, - user_id: str = None, -) -> List[models.Category]: - return get_items( - db, - models.Category, - skip, - limit, - sort_by, - sort_order, - filter, - organization_id=organization_id, - user_id=user_id, - ) - - -def create_category( - db: Session, category: schemas.CategoryCreate, organization_id: str = None, user_id: str = None -) -> models.Category: - """Create category.""" - return create_item(db, models.Category, category, organization_id, user_id) - - -def update_category( - db: Session, - category_id: uuid.UUID, - category: schemas.CategoryUpdate, - organization_id: str = None, - user_id: str = None, -) -> Optional[models.Category]: - """Update category.""" - return update_item(db, models.Category, category_id, category, organization_id, user_id) - - -def delete_category( - db: Session, category_id: uuid.UUID, organization_id: str, user_id: str -) -> Optional[models.Category]: - return delete_item( - db, models.Category, category_id, organization_id=organization_id, user_id=user_id - ) - - # TestSet CRUD def get_test_set( db: Session, test_set_id: uuid.UUID, organization_id: str = None, user_id: str = None @@ -539,118 +482,6 @@ def update_test_configuration( ) -# Status CRUD -def get_status( - db: Session, status_id: uuid.UUID, organization_id: str = None, user_id: str = None -) -> Optional[models.Status]: - """Get a single status by ID.""" - return get_item(db, models.Status, status_id, organization_id, user_id) - - -def get_statuses( - db: Session, - skip: int = 0, - limit: int = 10, - sort_by: str = "created_at", - sort_order: str = "desc", - filter: str | None = None, - organization_id: str = None, - user_id: str = None, -) -> List[models.Status]: - return get_items( - db, - models.Status, - skip, - limit, - sort_by, - sort_order, - filter, - organization_id=organization_id, - user_id=user_id, - ) - - -def create_status( - db: Session, status: schemas.StatusCreate, organization_id: str = None, user_id: str = None -) -> models.Status: - """Create status.""" - return create_item(db, models.Status, status, organization_id, user_id) - - -def update_status( - db: Session, - status_id: uuid.UUID, - status: schemas.StatusUpdate, - organization_id: str = None, - user_id: str = None, -) -> Optional[models.Status]: - """Update status.""" - return update_item(db, models.Status, status_id, status, organization_id, user_id) - - -def delete_status( - db: Session, status_id: uuid.UUID, organization_id: str, user_id: str -) -> Optional[models.Status]: - """Delete status.""" - return delete_item(db, models.Status, status_id, organization_id, user_id) - - -# Topic CRUD -def get_topic( - db: Session, topic_id: uuid.UUID, organization_id: str = None, user_id: str = None -) -> Optional[models.Topic]: - """Get a single topic by ID.""" - return get_item(db, models.Topic, topic_id, organization_id, user_id) - - -def get_topics( - db: Session, - skip: int = 0, - limit: int = 10, - sort_by: str = "created_at", - sort_order: str = "desc", - filter: str | None = None, - organization_id: str = None, - user_id: str = None, -) -> List[models.Topic]: - return get_items( - db, - models.Topic, - skip, - limit, - sort_by, - sort_order, - filter, - organization_id=organization_id, - user_id=user_id, - ) - - -def create_topic( - db: Session, topic: schemas.TopicCreate, organization_id: str = None, user_id: str = None -) -> models.Topic: - """Create topic.""" - return create_item(db, models.Topic, topic, organization_id, user_id) - - -def update_topic( - db: Session, - topic_id: uuid.UUID, - topic: schemas.TopicUpdate, - organization_id: str = None, - user_id: str = None, -) -> Optional[models.Topic]: - """Update topic.""" - return update_item(db, models.Topic, topic_id, topic, organization_id, user_id) - - -def delete_topic( - db: Session, topic_id: uuid.UUID, organization_id: str, user_id: str -) -> Optional[models.Topic]: - """Delete topic.""" - return delete_item(db, models.Topic, topic_id, organization_id, user_id) - - def get_test( db: Session, test_id: uuid.UUID, organization_id: str = None, user_id: str = None ) -> Optional[models.Test]: diff --git a/apps/backend/src/rhesis/backend/app/crud/category.py b/apps/backend/src/rhesis/backend/app/crud/category.py new file mode 100644 index 0000000000..0bea302d87 --- /dev/null +++ b/apps/backend/src/rhesis/backend/app/crud/category.py @@ -0,0 +1,76 @@ +"""CRUD operations for categories. + +Part of the incremental split of the ``crud`` monolith: ``crud/__init__.py`` still holds +the bulk of the functions, and per-entity modules like this one take over as the code +around them is touched -- see ``apps/backend/AGENTS.md``'s crud-layout rule. +""" + +import uuid +from typing import List, Optional + +from sqlalchemy.orm import Session + +from rhesis.backend.app import models, schemas +from rhesis.backend.app.utils.crud_utils import ( + create_item, + delete_item, + get_item, + get_items, + update_item, +) + + +def get_category( + db: Session, category_id: uuid.UUID, organization_id: str = None, user_id: str = None +) -> Optional[models.Category]: + """Get a single category by ID.""" + return get_item(db, models.Category, category_id, organization_id, user_id) + + +def get_categories( + db: Session, + skip: int = 0, + limit: int = 10, + sort_by: str = "created_at", + sort_order: str = "desc", + filter: str | None = None, + organization_id: str = None, + user_id: str = None, +) -> List[models.Category]: + return get_items( + db, + models.Category, + skip, + limit, + sort_by, + sort_order, + filter, + organization_id=organization_id, + user_id=user_id, + ) + + +def create_category( + db: Session, category: schemas.CategoryCreate, organization_id: str = None, user_id: str = None +) -> models.Category: + """Create category.""" + return create_item(db, models.Category, category, organization_id, user_id) + + +def update_category( + db: Session, + category_id: uuid.UUID, + category: schemas.CategoryUpdate, + organization_id: str = None, + user_id: str = None, +) -> Optional[models.Category]: + """Update category.""" + return update_item(db, models.Category, category_id, category, organization_id, user_id) + + +def delete_category( + db: Session, category_id: uuid.UUID, organization_id: str, user_id: str +) -> Optional[models.Category]: + return delete_item( + db, models.Category, category_id, organization_id=organization_id, user_id=user_id + ) diff --git a/apps/backend/src/rhesis/backend/app/crud/status.py b/apps/backend/src/rhesis/backend/app/crud/status.py new file mode 100644 index 0000000000..97f208df5b --- /dev/null +++ b/apps/backend/src/rhesis/backend/app/crud/status.py @@ -0,0 +1,75 @@ +"""CRUD operations for statuses. + +Part of the incremental split of the ``crud`` monolith: ``crud/__init__.py`` still holds +the bulk of the functions, and per-entity modules like this one take over as the code +around them is touched -- see ``apps/backend/AGENTS.md``'s crud-layout rule. +""" + +import uuid +from typing import List, Optional + +from sqlalchemy.orm import Session + +from rhesis.backend.app import models, schemas +from rhesis.backend.app.utils.crud_utils import ( + create_item, + delete_item, + get_item, + get_items, + update_item, +) + + +def get_status( + db: Session, status_id: uuid.UUID, organization_id: str = None, user_id: str = None +) -> Optional[models.Status]: + """Get a single status by ID.""" + return get_item(db, models.Status, status_id, organization_id, user_id) + + +def get_statuses( + db: Session, + skip: int = 0, + limit: int = 10, + sort_by: str = "created_at", + sort_order: str = "desc", + filter: str | None = None, + organization_id: str = None, + user_id: str = None, +) -> List[models.Status]: + return get_items( + db, + models.Status, + skip, + limit, + sort_by, + sort_order, + filter, + organization_id=organization_id, + user_id=user_id, + ) + + +def create_status( + db: Session, status: schemas.StatusCreate, organization_id: str = None, user_id: str = None +) -> models.Status: + """Create status.""" + return create_item(db, models.Status, status, organization_id, user_id) + + +def update_status( + db: Session, + status_id: uuid.UUID, + status: schemas.StatusUpdate, + organization_id: str = None, + user_id: str = None, +) -> Optional[models.Status]: + """Update status.""" + return update_item(db, models.Status, status_id, status, organization_id, user_id) + + +def delete_status( + db: Session, status_id: uuid.UUID, organization_id: str, user_id: str +) -> Optional[models.Status]: + """Delete status.""" + return delete_item(db, models.Status, status_id, organization_id, user_id) diff --git a/apps/backend/src/rhesis/backend/app/crud/topic.py b/apps/backend/src/rhesis/backend/app/crud/topic.py new file mode 100644 index 0000000000..1039291add --- /dev/null +++ b/apps/backend/src/rhesis/backend/app/crud/topic.py @@ -0,0 +1,75 @@ +"""CRUD operations for topics. + +Part of the incremental split of the ``crud`` monolith: ``crud/__init__.py`` still holds +the bulk of the functions, and per-entity modules like this one take over as the code +around them is touched -- see ``apps/backend/AGENTS.md``'s crud-layout rule. +""" + +import uuid +from typing import List, Optional + +from sqlalchemy.orm import Session + +from rhesis.backend.app import models, schemas +from rhesis.backend.app.utils.crud_utils import ( + create_item, + delete_item, + get_item, + get_items, + update_item, +) + + +def get_topic( + db: Session, topic_id: uuid.UUID, organization_id: str = None, user_id: str = None +) -> Optional[models.Topic]: + """Get a single topic by ID.""" + return get_item(db, models.Topic, topic_id, organization_id, user_id) + + +def get_topics( + db: Session, + skip: int = 0, + limit: int = 10, + sort_by: str = "created_at", + sort_order: str = "desc", + filter: str | None = None, + organization_id: str = None, + user_id: str = None, +) -> List[models.Topic]: + return get_items( + db, + models.Topic, + skip, + limit, + sort_by, + sort_order, + filter, + organization_id=organization_id, + user_id=user_id, + ) + + +def create_topic( + db: Session, topic: schemas.TopicCreate, organization_id: str = None, user_id: str = None +) -> models.Topic: + """Create topic.""" + return create_item(db, models.Topic, topic, organization_id, user_id) + + +def update_topic( + db: Session, + topic_id: uuid.UUID, + topic: schemas.TopicUpdate, + organization_id: str = None, + user_id: str = None, +) -> Optional[models.Topic]: + """Update topic.""" + return update_item(db, models.Topic, topic_id, topic, organization_id, user_id) + + +def delete_topic( + db: Session, topic_id: uuid.UUID, organization_id: str, user_id: str +) -> Optional[models.Topic]: + """Delete topic.""" + return delete_item(db, models.Topic, topic_id, organization_id, user_id) diff --git a/apps/backend/src/rhesis/backend/app/routers/category.py b/apps/backend/src/rhesis/backend/app/routers/category.py index edabf552f7..5675c2ce39 100644 --- a/apps/backend/src/rhesis/backend/app/routers/category.py +++ b/apps/backend/src/rhesis/backend/app/routers/category.py @@ -6,8 +6,9 @@ from fastapi.responses import JSONResponse from sqlalchemy.orm import Session -from rhesis.backend.app import crud, models, schemas +from rhesis.backend.app import models, schemas from rhesis.backend.app.auth.user_utils import require_current_user_or_token +from rhesis.backend.app.crud import category as category_crud from rhesis.backend.app.dependencies import ( get_tenant_context, get_tenant_db_session, @@ -42,7 +43,7 @@ def create_category( ): """Create a new category.""" organization_id, user_id = tenant_context - return crud.create_category( + return category_crud.create_category( db=db, category=category, organization_id=organization_id, user_id=user_id ) @@ -70,7 +71,7 @@ def read_categories( organization_id, user_id = tenant_context filter = combine_entity_type_filter(filter, entity_type) - results = crud.get_categories( + results = category_crud.get_categories( db=db, skip=skip, limit=limit, @@ -95,7 +96,7 @@ def read_category( ): """Get a category by ID.""" organization_id, user_id = tenant_context - db_category = crud.get_category( + db_category = category_crud.get_category( db, category_id=category_id, organization_id=organization_id, user_id=user_id ) if db_category is None: @@ -118,7 +119,7 @@ def update_category( ): """Update a category by ID.""" organization_id, user_id = tenant_context - db_category = crud.update_category( + db_category = category_crud.update_category( db, category_id=category_id, category=category, @@ -139,7 +140,7 @@ def delete_category( ): """Delete a category by ID.""" organization_id, user_id = tenant_context - db_category = crud.delete_category( + db_category = category_crud.delete_category( db, category_id=category_id, organization_id=organization_id, user_id=user_id ) if db_category is None: diff --git a/apps/backend/src/rhesis/backend/app/routers/status.py b/apps/backend/src/rhesis/backend/app/routers/status.py index 5a6154427a..8cc5a1af93 100644 --- a/apps/backend/src/rhesis/backend/app/routers/status.py +++ b/apps/backend/src/rhesis/backend/app/routers/status.py @@ -3,8 +3,9 @@ from fastapi import Depends, HTTPException, Query, Response from sqlalchemy.orm import Session -from rhesis.backend.app import crud, models, schemas +from rhesis.backend.app import models, schemas from rhesis.backend.app.auth.user_utils import require_current_user_or_token +from rhesis.backend.app.crud import status as status_crud from rhesis.backend.app.dependencies import ( get_tenant_context, get_tenant_db_session, @@ -36,7 +37,7 @@ def create_status( ): """Create a new status.""" organization_id, user_id = tenant_context - return crud.create_status( + return status_crud.create_status( db=db, status=status, organization_id=organization_id, user_id=user_id ) @@ -59,7 +60,7 @@ def read_statuses( organization_id, user_id = tenant_context filter = combine_entity_type_filter(filter, entity_type) - return crud.get_statuses( + return status_crud.get_statuses( db=db, skip=skip, limit=limit, @@ -80,7 +81,7 @@ def read_status( ): """Get a status by ID.""" organization_id, user_id = tenant_context - db_status = crud.get_status( + db_status = status_crud.get_status( db, status_id=status_id, organization_id=organization_id, user_id=user_id ) if db_status is None: @@ -97,7 +98,7 @@ def delete_status( ): """Delete a status by ID.""" organization_id, user_id = tenant_context - db_status = crud.delete_status( + db_status = status_crud.delete_status( db, status_id=status_id, organization_id=organization_id, user_id=user_id ) if db_status is None: @@ -115,7 +116,7 @@ def update_status( ): """Update a status by ID.""" organization_id, user_id = tenant_context - db_status = crud.update_status( + db_status = status_crud.update_status( db, status_id=status_id, status=status, organization_id=organization_id, user_id=user_id ) if db_status is None: diff --git a/apps/backend/src/rhesis/backend/app/routers/topic.py b/apps/backend/src/rhesis/backend/app/routers/topic.py index bac99f28a9..4d79301f12 100644 --- a/apps/backend/src/rhesis/backend/app/routers/topic.py +++ b/apps/backend/src/rhesis/backend/app/routers/topic.py @@ -6,8 +6,9 @@ from fastapi.responses import JSONResponse from sqlalchemy.orm import Session -from rhesis.backend.app import crud, models, schemas +from rhesis.backend.app import models, schemas from rhesis.backend.app.auth.user_utils import require_current_user_or_token +from rhesis.backend.app.crud import topic as topic_crud from rhesis.backend.app.dependencies import ( get_tenant_context, get_tenant_db_session, @@ -40,7 +41,9 @@ def create_topic( ): """Create a new topic.""" organization_id, user_id = tenant_context - return crud.create_topic(db=db, topic=topic, organization_id=organization_id, user_id=user_id) + return topic_crud.create_topic( + db=db, topic=topic, organization_id=organization_id, user_id=user_id + ) @router.get("/", response_model=list[schemas.TopicDetail]) @@ -66,7 +69,7 @@ def read_topics( organization_id, user_id = tenant_context filter = combine_entity_type_filter(filter, entity_type) - results = crud.get_topics( + results = topic_crud.get_topics( db=db, skip=skip, limit=limit, @@ -91,7 +94,7 @@ def read_topic( ): """Get a topic by ID.""" organization_id, user_id = tenant_context - db_topic = crud.get_topic( + db_topic = topic_crud.get_topic( db, topic_id=topic_id, organization_id=organization_id, user_id=user_id ) if db_topic is None: @@ -108,7 +111,7 @@ def delete_topic( ): """Delete a topic by ID.""" organization_id, user_id = tenant_context - db_topic = crud.delete_topic( + db_topic = topic_crud.delete_topic( db, topic_id=topic_id, organization_id=organization_id, user_id=user_id ) if db_topic is None: @@ -131,7 +134,7 @@ def update_topic( ): """Update a topic by ID.""" organization_id, user_id = tenant_context - db_topic = crud.update_topic( + db_topic = topic_crud.update_topic( db, topic_id=topic_id, topic=topic, organization_id=organization_id, user_id=user_id ) if db_topic is None: diff --git a/apps/backend/src/rhesis/backend/app/services/task_notification.py b/apps/backend/src/rhesis/backend/app/services/task_notification.py index ccea54d2e4..124c33134a 100644 --- a/apps/backend/src/rhesis/backend/app/services/task_notification.py +++ b/apps/backend/src/rhesis/backend/app/services/task_notification.py @@ -8,7 +8,8 @@ from sqlalchemy.orm import Session from rhesis.backend.app import models -from rhesis.backend.app.crud import get_status, get_type_lookup +from rhesis.backend.app.crud import get_type_lookup +from rhesis.backend.app.crud import status as status_crud from rhesis.backend.app.crud import user as user_crud from rhesis.backend.app.models.enums import NotificationEventType from rhesis.backend.app.services.notification import RenderedNotification, notify @@ -45,7 +46,7 @@ def send_task_assignment_notification( creator = user_crud.get_user(db, task.user_id) if task.user_id else None # Get status details - status = get_status(db, task.status_id) if task.status_id else None + status = status_crud.get_status(db, task.status_id) if task.status_id else None # Get priority details priority = get_type_lookup(db, task.priority_id) if task.priority_id else None diff --git a/tests/backend/security/test_organization_filtering.py b/tests/backend/security/test_organization_filtering.py index 360ea4397e..e7aa7b3f4f 100644 --- a/tests/backend/security/test_organization_filtering.py +++ b/tests/backend/security/test_organization_filtering.py @@ -657,11 +657,11 @@ def test_task_organization_constraint_validation(self, test_db: Session): ) # Create a status in organization1 - from rhesis.backend.app import crud + from rhesis.backend.app.crud import status as status_crud from rhesis.backend.app.schemas.status import StatusCreate status_data = StatusCreate(name=f"Test Status {unique_id}") - status = crud.create_status( + status = status_crud.create_status( test_db, status_data, organization_id=str(organization1.id), user_id=str(user1.id) )