Added admin page

This commit is contained in:
gulimabr
2025-12-01 12:39:13 -03:00
parent 74454c7b6b
commit a52a669521
13 changed files with 1430 additions and 65 deletions

View File

@@ -11,8 +11,9 @@ from src.models import (
RequirementCreateRequest, RequirementUpdateRequest,
ProjectResponse, ProjectCreateRequest, ProjectUpdateRequest, ProjectMemberRequest,
ValidationStatusResponse, ValidationHistoryResponse, ValidationCreateRequest,
RelationshipTypeResponse, RelationshipTypeCreateRequest,
RequirementLinkResponse, RequirementLinkCreateRequest, RequirementSearchResult
RelationshipTypeResponse, RelationshipTypeCreateRequest, RelationshipTypeUpdateRequest,
RequirementLinkResponse, RequirementLinkCreateRequest, RequirementSearchResult,
RoleResponse, ProjectMemberResponse, UserRoleUpdateRequest, ROLE_DISPLAY_NAMES
)
from src.controller import AuthController
from src.config import get_openid, get_settings
@@ -521,6 +522,240 @@ async def remove_project_member(
await db.commit()
# ===========================================
# Admin Endpoints (Role Management, Project Admin)
# ===========================================
@app.get("/api/roles", response_model=List[RoleResponse])
async def get_all_roles(
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Get all available roles with their display names.
Returns:
List of roles with id, role_name, and display_name.
"""
# Ensure user is authenticated
await _get_current_user_db(request, db)
role_repo = RoleRepository(db)
roles = await role_repo.get_all()
return [RoleResponse.from_role(r) for r in roles]
@app.get("/api/projects/{project_id}/members", response_model=List[ProjectMemberResponse])
async def get_project_members(
project_id: int,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Get all members of a project with their role information.
User must be a member of the project.
Args:
project_id: The project ID
Returns:
List of project members with role info.
"""
user = await _get_current_user_db(request, db)
await _verify_project_membership(project_id, user.id, db)
project_repo = ProjectRepository(db)
members = await project_repo.get_members(project_id)
return [
ProjectMemberResponse(
id=member.id,
sub=member.sub,
role_id=member.role_id,
role_name=member.role.role_name if member.role else "unknown",
role_display_name=ROLE_DISPLAY_NAMES.get(member.role.role_name, member.role.role_name.title()) if member.role else "Unknown",
created_at=member.created_at
)
for member in members
]
@app.put("/api/projects/{project_id}/members/{user_id}/role", response_model=ProjectMemberResponse)
async def update_member_role(
project_id: int,
user_id: int,
request: Request,
role_data: UserRoleUpdateRequest,
db: AsyncSession = Depends(get_db)
):
"""
Update a project member's role.
Only project admins (role_id=3) can update roles.
Admin cannot demote themselves.
Args:
project_id: The project ID
user_id: The user ID to update
role_data: The new role ID
Returns:
The updated member info.
"""
current_user = await _get_current_user_db(request, db)
# Only admins (role_id=3) can update roles
_require_role(current_user, [3], "update member roles")
await _verify_project_membership(project_id, current_user.id, db)
# Check target user is a member of the project
project_repo = ProjectRepository(db)
if not await project_repo.is_member(project_id, user_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User is not a member of this project"
)
# Prevent self-demotion
if current_user.id == user_id and role_data.role_id != 3:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="You cannot demote yourself. Ask another admin to change your role."
)
# Verify role exists
role_repo = RoleRepository(db)
role = await role_repo.get_by_id(role_data.role_id)
if not role:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid role id {role_data.role_id}"
)
# Update the user's role
from src.repositories import UserRepository
user_repo = UserRepository(db)
updated_user = await user_repo.update_role(user_id, role_data.role_id)
if not updated_user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with id {user_id} not found"
)
await db.commit()
return ProjectMemberResponse(
id=updated_user.id,
sub=updated_user.sub,
role_id=updated_user.role_id,
role_name=role.role_name,
role_display_name=ROLE_DISPLAY_NAMES.get(role.role_name, role.role_name.title()),
created_at=updated_user.created_at
)
@app.put("/api/projects/{project_id}/relationship-types/{type_id}", response_model=RelationshipTypeResponse)
async def update_relationship_type(
project_id: int,
type_id: int,
request: Request,
type_data: RelationshipTypeUpdateRequest,
db: AsyncSession = Depends(get_db)
):
"""
Update a relationship type.
Only project admins (role_id=3) can update relationship types.
Args:
project_id: The project ID
type_id: The relationship type ID to update
type_data: The updated relationship type data
Returns:
The updated relationship type.
"""
user = await _get_current_user_db(request, db)
# Only admins (role_id=3) can update relationship types
_require_role(user, [3], "update relationship types")
await _verify_project_membership(project_id, user.id, db)
rel_type_repo = RelationshipTypeRepository(db)
# Check if relationship type exists and belongs to the project
existing_type = await rel_type_repo.get_by_id(type_id)
if not existing_type:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Relationship type with id {type_id} not found"
)
if existing_type.project_id != project_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Relationship type does not belong to this project"
)
updated_type = await rel_type_repo.update(
relationship_type_id=type_id,
type_name=type_data.type_name,
type_description=type_data.type_description,
inverse_type_name=type_data.inverse_type_name
)
await db.commit()
return RelationshipTypeResponse.model_validate(updated_type)
@app.delete("/api/projects/{project_id}/relationship-types/{type_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_relationship_type(
project_id: int,
type_id: int,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
Delete a relationship type.
Only project admins (role_id=3) can delete relationship types.
This will also delete all links using this relationship type.
Args:
project_id: The project ID
type_id: The relationship type ID to delete
"""
user = await _get_current_user_db(request, db)
# Only admins (role_id=3) can delete relationship types
_require_role(user, [3], "delete relationship types")
await _verify_project_membership(project_id, user.id, db)
rel_type_repo = RelationshipTypeRepository(db)
# Check if relationship type exists and belongs to the project
existing_type = await rel_type_repo.get_by_id(type_id)
if not existing_type:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Relationship type with id {type_id} not found"
)
if existing_type.project_id != project_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Relationship type does not belong to this project"
)
deleted = await rel_type_repo.delete(type_id)
if not deleted:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Relationship type with id {type_id} not found"
)
await db.commit()
# ===========================================
# Requirements Endpoints
# ===========================================
@@ -968,7 +1203,7 @@ async def create_relationship_type(
):
"""
Create a new relationship type for a project.
Only admins (role_id=1) can create relationship types.
Only project admins (role_id=3) can create relationship types.
Args:
project_id: The project ID
@@ -979,8 +1214,8 @@ async def create_relationship_type(
"""
user = await _get_current_user_db(request, db)
# Only admins can create relationship types
_require_role(user, [1], "create relationship types")
# Only admins (role_id=3) can create relationship types
_require_role(user, [3], "create relationship types")
await _verify_project_membership(project_id, user.id, db)