Added admin page
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user