Added project separation logic

This commit is contained in:
gulimabr
2025-12-01 11:01:13 -03:00
parent 6d02736cba
commit 07005788ed
17 changed files with 1337 additions and 115 deletions

View File

@@ -52,6 +52,11 @@ class User(Base):
foreign_keys="Requirement.last_editor_id"
)
validations: Mapped[List["Validation"]] = relationship("Validation", back_populates="user")
projects: Mapped[List["Project"]] = relationship(
"Project",
secondary="project_members",
back_populates="members"
)
class Tag(Base):
@@ -94,6 +99,54 @@ class Priority(Base):
requirements: Mapped[List["Requirement"]] = relationship("Requirement", back_populates="priority")
class Project(Base):
"""Projects - containers for requirements that can have multiple members."""
__tablename__ = "projects"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
project_name: Mapped[str] = mapped_column(Text, nullable=False)
project_desc: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=datetime.utcnow,
nullable=True
)
# Relationships
members: Mapped[List["User"]] = relationship(
"User",
secondary="project_members",
back_populates="projects"
)
requirements: Mapped[List["Requirement"]] = relationship("Requirement", back_populates="project")
class ProjectMember(Base):
"""Join table for many-to-many relationship between projects and users."""
__tablename__ = "project_members"
project_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("projects.id", ondelete="CASCADE"),
primary_key=True
)
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="CASCADE"),
primary_key=True
)
joined_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=datetime.utcnow,
nullable=True
)
# Indexes
__table_args__ = (
Index("idx_pm_user", "user_id"),
)
class ValidationStatus(Base):
"""Validation status options."""
__tablename__ = "validation_statuses"
@@ -110,6 +163,7 @@ class Requirement(Base):
__tablename__ = "requirements"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
project_id: Mapped[int] = mapped_column(Integer, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
tag_id: Mapped[int] = mapped_column(Integer, ForeignKey("tags.id"), nullable=False)
last_editor_id: Mapped[Optional[int]] = mapped_column(
@@ -138,6 +192,7 @@ class Requirement(Base):
)
# Relationships
project: Mapped["Project"] = relationship("Project", back_populates="requirements")
user: Mapped["User"] = relationship(
"User",
back_populates="requirements",
@@ -159,6 +214,7 @@ class Requirement(Base):
# Indexes
__table_args__ = (
Index("idx_req_project", "project_id"),
Index("idx_req_tag", "tag_id"),
Index("idx_req_priority", "priority_id"),
Index("idx_req_user", "user_id"),
@@ -220,6 +276,7 @@ class RequirementHistory(Base):
history_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
original_req_id: Mapped[int] = mapped_column(Integer, nullable=False)
project_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
req_name: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
req_desc: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
priority_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)

View File

@@ -8,12 +8,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.models import (
TokenResponse, UserInfo, GroupResponse,
TagResponse, RequirementResponse, PriorityResponse,
RequirementCreateRequest, RequirementUpdateRequest
RequirementCreateRequest, RequirementUpdateRequest,
ProjectResponse, ProjectCreateRequest, ProjectUpdateRequest, ProjectMemberRequest
)
from src.controller import AuthController
from src.config import get_openid, get_settings
from src.database import init_db, close_db, get_db
from src.repositories import RoleRepository, GroupRepository, TagRepository, RequirementRepository, PriorityRepository
from src.repositories import RoleRepository, GroupRepository, TagRepository, RequirementRepository, PriorityRepository, ProjectRepository
import logging
# Configure logging
@@ -237,6 +238,233 @@ async def get_priorities(db: AsyncSession = Depends(get_db)):
return [PriorityResponse.model_validate(p) for p in priorities]
# ===========================================
# Projects Endpoints
# ===========================================
async def _get_current_user_db(request: Request, db: AsyncSession):
"""Helper to get the current authenticated user from the database."""
user_info = AuthController.get_current_user(request)
from src.repositories import UserRepository
user_repo = UserRepository(db)
user = await user_repo.get_by_sub(user_info.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found in database"
)
return user
async def _verify_project_membership(project_id: int, user_id: int, db: AsyncSession):
"""Helper to verify user is a member of a project."""
project_repo = ProjectRepository(db)
# Check if project exists
project = await project_repo.get_by_id(project_id)
if not project:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Project with id {project_id} not found"
)
# Check if user is a member
is_member = await project_repo.is_member(project_id, user_id)
if not is_member:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not a member of this project"
)
return project
@app.get("/api/projects", response_model=List[ProjectResponse])
async def get_my_projects(
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Get all projects the authenticated user is a member of.
Returns:
List of projects the user belongs to.
"""
user = await _get_current_user_db(request, db)
project_repo = ProjectRepository(db)
projects = await project_repo.get_by_user_id(user.id)
return [ProjectResponse.model_validate(p) for p in projects]
@app.get("/api/projects/{project_id}", response_model=ProjectResponse)
async def get_project(
project_id: int,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Get a specific project by ID.
User must be a member of the project.
Args:
project_id: The project ID
Returns:
The project if found and user is a member.
"""
user = await _get_current_user_db(request, db)
project = await _verify_project_membership(project_id, user.id, db)
return ProjectResponse.model_validate(project)
@app.post("/api/projects", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
async def create_project(
request: Request,
project_data: ProjectCreateRequest,
db: AsyncSession = Depends(get_db)
):
"""
Create a new project.
The creating user will automatically be added as a member.
Args:
project_data: The project data
Returns:
The created project.
"""
user = await _get_current_user_db(request, db)
project_repo = ProjectRepository(db)
project = await project_repo.create(
project_name=project_data.project_name,
project_desc=project_data.project_desc,
creator_id=user.id,
)
await db.commit()
return ProjectResponse.model_validate(project)
@app.put("/api/projects/{project_id}", response_model=ProjectResponse)
async def update_project(
project_id: int,
request: Request,
project_data: ProjectUpdateRequest,
db: AsyncSession = Depends(get_db)
):
"""
Update an existing project.
User must be a member of the project.
Args:
project_id: The project ID to update
project_data: The updated project data
Returns:
The updated project.
"""
user = await _get_current_user_db(request, db)
await _verify_project_membership(project_id, user.id, db)
project_repo = ProjectRepository(db)
project = await project_repo.update(
project_id=project_id,
project_name=project_data.project_name,
project_desc=project_data.project_desc,
)
await db.commit()
return ProjectResponse.model_validate(project)
@app.delete("/api/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_project(
project_id: int,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Delete a project.
User must be a member of the project.
Args:
project_id: The project ID to delete
"""
user = await _get_current_user_db(request, db)
await _verify_project_membership(project_id, user.id, db)
project_repo = ProjectRepository(db)
await project_repo.delete(project_id)
await db.commit()
@app.post("/api/projects/{project_id}/members", status_code=status.HTTP_201_CREATED)
async def add_project_member(
project_id: int,
request: Request,
member_data: ProjectMemberRequest,
db: AsyncSession = Depends(get_db)
):
"""
Add a member to a project.
User must be a member of the project.
Args:
project_id: The project ID
member_data: The user to add
Returns:
Success message.
"""
user = await _get_current_user_db(request, db)
await _verify_project_membership(project_id, user.id, db)
project_repo = ProjectRepository(db)
added = await project_repo.add_member(project_id, member_data.user_id)
if not added:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="User is already a member of this project"
)
await db.commit()
return {"message": "Member added successfully"}
@app.delete("/api/projects/{project_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def remove_project_member(
project_id: int,
user_id: int,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Remove a member from a project.
User must be a member of the project.
Args:
project_id: The project ID
user_id: The user ID to remove
"""
current_user = await _get_current_user_db(request, db)
await _verify_project_membership(project_id, current_user.id, db)
project_repo = ProjectRepository(db)
removed = await project_repo.remove_member(project_id, user_id)
if not removed:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User is not a member of this project"
)
await db.commit()
# ===========================================
# Requirements Endpoints
# ===========================================
@@ -252,6 +480,7 @@ def _build_requirement_response(req) -> RequirementResponse:
return RequirementResponse(
id=req.id,
project_id=req.project_id,
req_name=req.req_name,
req_desc=req.req_desc,
version=req.version,
@@ -264,44 +493,79 @@ def _build_requirement_response(req) -> RequirementResponse:
)
@app.get("/api/requirements", response_model=List[RequirementResponse])
async def get_requirements(
@app.get("/api/projects/{project_id}/requirements", response_model=List[RequirementResponse])
async def get_project_requirements(
project_id: int,
request: Request,
group_id: Optional[int] = None,
tag_id: Optional[int] = None,
db: AsyncSession = Depends(get_db)
):
"""
Get all requirements for the authenticated user, optionally filtered by group or tag.
Get all requirements for a specific project, optionally filtered by group or tag.
User must be a member of the project.
Args:
project_id: The project ID
group_id: Optional group ID to filter by
tag_id: Optional tag ID to filter by
Returns:
List of requirements owned by the authenticated user.
List of requirements in the project.
"""
# Get the current user from cookie
user_info = AuthController.get_current_user(request)
# Get the user's database ID
from src.repositories import UserRepository
user_repo = UserRepository(db)
user = await user_repo.get_by_sub(user_info.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found in database"
)
user = await _get_current_user_db(request, db)
await _verify_project_membership(project_id, user.id, db)
req_repo = RequirementRepository(db)
if group_id:
requirements = await req_repo.get_by_group_id(group_id, user_id=user.id)
requirements = await req_repo.get_by_group_id(group_id, project_id=project_id)
elif tag_id:
requirements = await req_repo.get_by_tag_id(tag_id, user_id=user.id)
requirements = await req_repo.get_by_tag_id(tag_id, project_id=project_id)
else:
requirements = await req_repo.get_by_user_id(user.id)
requirements = await req_repo.get_by_project_id(project_id)
return [_build_requirement_response(req) for req in requirements]
@app.get("/api/requirements", response_model=List[RequirementResponse])
async def get_requirements(
request: Request,
project_id: Optional[int] = None,
group_id: Optional[int] = None,
tag_id: Optional[int] = None,
db: AsyncSession = Depends(get_db)
):
"""
Get requirements. If project_id is provided, returns requirements for that project.
User must be a member of the project.
Args:
project_id: Required project ID to filter by
group_id: Optional group ID to filter by
tag_id: Optional tag ID to filter by
Returns:
List of requirements.
"""
user = await _get_current_user_db(request, db)
if not project_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="project_id is required"
)
await _verify_project_membership(project_id, user.id, db)
req_repo = RequirementRepository(db)
if group_id:
requirements = await req_repo.get_by_group_id(group_id, project_id=project_id)
elif tag_id:
requirements = await req_repo.get_by_tag_id(tag_id, project_id=project_id)
else:
requirements = await req_repo.get_by_project_id(project_id)
return [_build_requirement_response(req) for req in requirements]
@@ -314,25 +578,15 @@ async def get_requirement(
):
"""
Get a specific requirement by ID.
User must be a member of the requirement's project.
Args:
requirement_id: The requirement ID
Returns:
The requirement if found and owned by the authenticated user.
The requirement if found and user has access.
"""
# Get the current user from cookie
user_info = AuthController.get_current_user(request)
# Get the user's database ID
from src.repositories import UserRepository
user_repo = UserRepository(db)
user = await user_repo.get_by_sub(user_info.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found in database"
)
user = await _get_current_user_db(request, db)
req_repo = RequirementRepository(db)
requirement = await req_repo.get_by_id(requirement_id)
@@ -342,12 +596,8 @@ async def get_requirement(
detail=f"Requirement with id {requirement_id} not found"
)
# Verify user owns this requirement
if requirement.user_id != user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to access this requirement"
)
# Verify user is a member of the requirement's project
await _verify_project_membership(requirement.project_id, user.id, db)
return _build_requirement_response(requirement)
@@ -360,28 +610,22 @@ async def create_requirement(
):
"""
Create a new requirement.
User must be a member of the project.
Args:
req_data: The requirement data
req_data: The requirement data (must include project_id)
Returns:
The created requirement.
"""
# Get the current user from cookie
user_info = AuthController.get_current_user(request)
user = await _get_current_user_db(request, db)
# Get the user's database ID
from src.repositories import UserRepository
user_repo = UserRepository(db)
user = await user_repo.get_by_sub(user_info.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found in database"
)
# Verify user is a member of the project
await _verify_project_membership(req_data.project_id, user.id, db)
req_repo = RequirementRepository(db)
requirement = await req_repo.create(
project_id=req_data.project_id,
user_id=user.id,
tag_id=req_data.tag_id,
req_name=req_data.req_name,
@@ -403,6 +647,7 @@ async def update_requirement(
):
"""
Update an existing requirement.
User must be a member of the requirement's project.
Args:
requirement_id: The requirement ID to update
@@ -411,22 +656,11 @@ async def update_requirement(
Returns:
The updated requirement.
"""
# Get the current user from cookie
user_info = AuthController.get_current_user(request)
# Get the user's database ID
from src.repositories import UserRepository
user_repo = UserRepository(db)
user = await user_repo.get_by_sub(user_info.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found in database"
)
user = await _get_current_user_db(request, db)
req_repo = RequirementRepository(db)
# First check if requirement exists and user owns it
# First check if requirement exists
existing_req = await req_repo.get_by_id(requirement_id)
if not existing_req:
raise HTTPException(
@@ -434,11 +668,8 @@ async def update_requirement(
detail=f"Requirement with id {requirement_id} not found"
)
if existing_req.user_id != user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to update this requirement"
)
# Verify user is a member of the requirement's project
await _verify_project_membership(existing_req.project_id, user.id, db)
requirement = await req_repo.update(
requirement_id=requirement_id,
@@ -462,26 +693,16 @@ async def delete_requirement(
):
"""
Delete a requirement.
User must be a member of the requirement's project.
Args:
requirement_id: The requirement ID to delete
"""
# Get the current user from cookie
user_info = AuthController.get_current_user(request)
# Get the user's database ID
from src.repositories import UserRepository
user_repo = UserRepository(db)
user = await user_repo.get_by_sub(user_info.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found in database"
)
user = await _get_current_user_db(request, db)
req_repo = RequirementRepository(db)
# First check if requirement exists and user owns it
# First check if requirement exists
existing_req = await req_repo.get_by_id(requirement_id)
if not existing_req:
raise HTTPException(
@@ -489,11 +710,8 @@ async def delete_requirement(
detail=f"Requirement with id {requirement_id} not found"
)
if existing_req.user_id != user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to delete this requirement"
)
# Verify user is a member of the requirement's project
await _verify_project_membership(existing_req.project_id, user.id, db)
await req_repo.delete(requirement_id)
await db.commit()

View File

@@ -22,6 +22,46 @@ class UserInfo(BaseModel):
role: Optional[str] = None # User role name
# Project schemas
class ProjectBase(BaseModel):
"""Base schema for projects."""
project_name: str
project_desc: Optional[str] = None
class ProjectResponse(BaseModel):
"""Response schema for a single project."""
id: int
project_name: str
project_desc: Optional[str] = None
created_at: Optional[datetime] = None
class Config:
from_attributes = True
class ProjectCreateRequest(BaseModel):
"""Request schema for creating a project."""
project_name: str
project_desc: Optional[str] = None
class ProjectUpdateRequest(BaseModel):
"""Request schema for updating a project."""
project_name: Optional[str] = None
project_desc: Optional[str] = None
class ProjectListResponse(BaseModel):
"""Response schema for list of projects."""
projects: List[ProjectResponse]
class ProjectMemberRequest(BaseModel):
"""Request schema for adding/removing project members."""
user_id: int
# Group schemas
class GroupResponse(BaseModel):
"""Response schema for a single group."""
@@ -82,6 +122,7 @@ class ValidationResponse(BaseModel):
class RequirementResponse(BaseModel):
"""Response schema for a single requirement."""
id: int
project_id: int
req_name: str
req_desc: Optional[str] = None
version: int
@@ -103,6 +144,7 @@ class RequirementListResponse(BaseModel):
class RequirementCreateRequest(BaseModel):
"""Request schema for creating a requirement."""
project_id: int
tag_id: int
req_name: str
req_desc: Optional[str] = None

View File

@@ -6,6 +6,7 @@ from src.repositories.group_repository import GroupRepository
from src.repositories.tag_repository import TagRepository
from src.repositories.requirement_repository import RequirementRepository
from src.repositories.priority_repository import PriorityRepository
from src.repositories.project_repository import ProjectRepository
__all__ = [
"UserRepository",
@@ -14,4 +15,5 @@ __all__ = [
"TagRepository",
"RequirementRepository",
"PriorityRepository",
"ProjectRepository",
]

View File

@@ -0,0 +1,235 @@
"""
Repository layer for Project database operations.
"""
from typing import List, Optional
from sqlalchemy import select, exists
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession
from src.db_models import Project, ProjectMember, User
import logging
logger = logging.getLogger(__name__)
class ProjectRepository:
"""Repository for Project-related database operations."""
def __init__(self, session: AsyncSession):
self.session = session
async def get_all(self) -> List[Project]:
"""
Get all projects.
Returns:
List of all projects
"""
result = await self.session.execute(
select(Project)
.options(selectinload(Project.members))
.order_by(Project.created_at.desc())
)
return list(result.scalars().all())
async def get_by_id(self, project_id: int) -> Optional[Project]:
"""
Get a project by ID with its members.
Args:
project_id: The project ID
Returns:
Project if found, None otherwise
"""
result = await self.session.execute(
select(Project)
.options(selectinload(Project.members))
.where(Project.id == project_id)
)
return result.scalar_one_or_none()
async def get_by_user_id(self, user_id: int) -> List[Project]:
"""
Get all projects that a user is a member of.
Args:
user_id: The user's database ID
Returns:
List of projects the user belongs to
"""
result = await self.session.execute(
select(Project)
.options(selectinload(Project.members))
.join(ProjectMember, Project.id == ProjectMember.project_id)
.where(ProjectMember.user_id == user_id)
.order_by(Project.created_at.desc())
)
return list(result.scalars().all())
async def is_member(self, project_id: int, user_id: int) -> bool:
"""
Check if a user is a member of a project.
Args:
project_id: The project ID
user_id: The user's database ID
Returns:
True if user is a member, False otherwise
"""
result = await self.session.execute(
select(
exists()
.where(ProjectMember.project_id == project_id)
.where(ProjectMember.user_id == user_id)
)
)
return result.scalar()
async def create(
self,
project_name: str,
project_desc: Optional[str] = None,
creator_id: Optional[int] = None,
) -> Project:
"""
Create a new project.
Optionally add the creator as the first member.
Args:
project_name: The project name
project_desc: The project description (optional)
creator_id: The user ID of the creator to add as first member (optional)
Returns:
The created Project
"""
project = Project(
project_name=project_name,
project_desc=project_desc,
)
self.session.add(project)
await self.session.flush()
# Add creator as first member if provided
if creator_id:
await self.add_member(project.id, creator_id)
await self.session.refresh(project)
return await self.get_by_id(project.id)
async def update(
self,
project_id: int,
project_name: Optional[str] = None,
project_desc: Optional[str] = None,
) -> Optional[Project]:
"""
Update an existing project.
Args:
project_id: The project ID to update
project_name: New project name (optional)
project_desc: New project description (optional)
Returns:
The updated Project, or None if not found
"""
project = await self.get_by_id(project_id)
if not project:
return None
if project_name is not None:
project.project_name = project_name
if project_desc is not None:
project.project_desc = project_desc
await self.session.flush()
await self.session.refresh(project)
return await self.get_by_id(project_id)
async def delete(self, project_id: int) -> bool:
"""
Delete a project (cascades to requirements and memberships).
Args:
project_id: The project ID to delete
Returns:
True if deleted, False if not found
"""
project = await self.get_by_id(project_id)
if not project:
return False
await self.session.delete(project)
await self.session.flush()
return True
async def add_member(self, project_id: int, user_id: int) -> bool:
"""
Add a user as a member of a project.
Args:
project_id: The project ID
user_id: The user ID to add
Returns:
True if added, False if already a member
"""
# Check if already a member
if await self.is_member(project_id, user_id):
return False
member = ProjectMember(
project_id=project_id,
user_id=user_id,
)
self.session.add(member)
await self.session.flush()
return True
async def remove_member(self, project_id: int, user_id: int) -> bool:
"""
Remove a user from a project.
Args:
project_id: The project ID
user_id: The user ID to remove
Returns:
True if removed, False if not a member
"""
result = await self.session.execute(
select(ProjectMember)
.where(ProjectMember.project_id == project_id)
.where(ProjectMember.user_id == user_id)
)
member = result.scalar_one_or_none()
if not member:
return False
await self.session.delete(member)
await self.session.flush()
return True
async def get_members(self, project_id: int) -> List[User]:
"""
Get all members of a project.
Args:
project_id: The project ID
Returns:
List of users who are members of the project
"""
result = await self.session.execute(
select(User)
.join(ProjectMember, User.id == ProjectMember.user_id)
.where(ProjectMember.project_id == project_id)
)
return list(result.scalars().all())

View File

@@ -36,15 +36,38 @@ class RequirementRepository:
)
return list(result.scalars().all())
async def get_by_project_id(self, project_id: int) -> List[Requirement]:
"""
Get all requirements for a specific project.
Args:
project_id: The project's database ID
Returns:
List of requirements belonging to the project
"""
result = await self.session.execute(
select(Requirement)
.options(
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.validations).selectinload(Validation.status),
)
.where(Requirement.project_id == project_id)
.order_by(Requirement.created_at.desc())
)
return list(result.scalars().all())
async def get_by_user_id(self, user_id: int) -> List[Requirement]:
"""
Get all requirements for a specific user.
Get all requirements created by a specific user.
Args:
user_id: The user's database ID
Returns:
List of requirements owned by the user
List of requirements created by the user
"""
result = await self.session.execute(
select(Requirement)
@@ -83,13 +106,14 @@ class RequirementRepository:
)
return result.scalar_one_or_none()
async def get_by_group_id(self, group_id: int, user_id: Optional[int] = None) -> List[Requirement]:
async def get_by_group_id(self, group_id: int, project_id: Optional[int] = None, user_id: Optional[int] = None) -> List[Requirement]:
"""
Get all requirements belonging to a specific group.
Args:
group_id: The group ID
user_id: Optional user ID to filter by
project_id: Optional project ID to filter by
user_id: Optional user ID to filter by (deprecated, use project_id)
Returns:
List of requirements in the group
@@ -106,6 +130,9 @@ class RequirementRepository:
.where(Group.id == group_id)
)
if project_id is not None:
query = query.where(Requirement.project_id == project_id)
if user_id is not None:
query = query.where(Requirement.user_id == user_id)
@@ -113,13 +140,14 @@ class RequirementRepository:
result = await self.session.execute(query)
return list(result.scalars().all())
async def get_by_tag_id(self, tag_id: int, user_id: Optional[int] = None) -> List[Requirement]:
async def get_by_tag_id(self, tag_id: int, project_id: Optional[int] = None, user_id: Optional[int] = None) -> List[Requirement]:
"""
Get all requirements with a specific tag.
Args:
tag_id: The tag ID
user_id: Optional user ID to filter by
project_id: Optional project ID to filter by
user_id: Optional user ID to filter by (deprecated, use project_id)
Returns:
List of requirements with the tag
@@ -135,6 +163,9 @@ class RequirementRepository:
.where(Requirement.tag_id == tag_id)
)
if project_id is not None:
query = query.where(Requirement.project_id == project_id)
if user_id is not None:
query = query.where(Requirement.user_id == user_id)
@@ -144,6 +175,7 @@ class RequirementRepository:
async def create(
self,
project_id: int,
user_id: int,
tag_id: int,
req_name: str,
@@ -155,6 +187,7 @@ class RequirementRepository:
Create a new requirement.
Args:
project_id: The project ID this requirement belongs to
user_id: The creating user's ID
tag_id: The tag ID
req_name: The requirement name
@@ -166,6 +199,7 @@ class RequirementRepository:
The created Requirement
"""
requirement = Requirement(
project_id=project_id,
user_id=user_id,
tag_id=tag_id,
req_name=req_name,