|
|
|
|
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
from app.api.deps import get_current_user, get_current_user_or_apikey, get_password_hash
|
|
|
|
|
from app.core.config import get_db
|
|
|
|
|
from app.init_wizard import DELETED_USER_USERNAME
|
|
|
|
|
from app.models import models
|
|
|
|
|
from app.models.agent import Agent
|
|
|
|
|
from app.models.role_permission import Permission, Role, RolePermission
|
|
|
|
|
@@ -212,6 +213,86 @@ def update_user(
|
|
|
|
|
return _user_response(user)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_BUILTIN_USERNAMES = {"acc-mgr", DELETED_USER_USERNAME}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _reassign_user_references(db: Session, old_id: int, new_id: int) -> None:
|
|
|
|
|
"""Reassign all foreign key references from old_id to new_id, then delete
|
|
|
|
|
records that would be meaningless under deleted-user (api_keys, notifications,
|
|
|
|
|
project memberships)."""
|
|
|
|
|
from app.models.apikey import APIKey
|
|
|
|
|
from app.models.notification import Notification
|
|
|
|
|
from app.models.activity import ActivityLog as Activity
|
|
|
|
|
from app.models.worklog import WorkLog as WorkLogModel
|
|
|
|
|
from app.models.meeting import Meeting, MeetingParticipant
|
|
|
|
|
from app.models.task import Task
|
|
|
|
|
from app.models.support import Support
|
|
|
|
|
from app.models.proposal import Proposal
|
|
|
|
|
from app.models.milestone import Milestone
|
|
|
|
|
from app.models.calendar import TimeSlot, SchedulePlan
|
|
|
|
|
from app.models.minimum_workload import MinimumWorkload
|
|
|
|
|
from app.models.essential import Essential
|
|
|
|
|
|
|
|
|
|
# Delete records that are meaningless without the real user
|
|
|
|
|
db.query(APIKey).filter(APIKey.user_id == old_id).delete()
|
|
|
|
|
db.query(Notification).filter(Notification.user_id == old_id).delete()
|
|
|
|
|
db.query(models.ProjectMember).filter(models.ProjectMember.user_id == old_id).delete()
|
|
|
|
|
|
|
|
|
|
# Reassign ownership/authorship references
|
|
|
|
|
db.query(models.Project).filter(models.Project.owner_id == old_id).update(
|
|
|
|
|
{"owner_id": new_id})
|
|
|
|
|
db.query(models.Comment).filter(models.Comment.author_id == old_id).update(
|
|
|
|
|
{"author_id": new_id})
|
|
|
|
|
db.query(Activity).filter(Activity.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
db.query(WorkLogModel).filter(WorkLogModel.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Tasks
|
|
|
|
|
db.query(Task).filter(Task.reporter_id == old_id).update(
|
|
|
|
|
{"reporter_id": new_id})
|
|
|
|
|
db.query(Task).filter(Task.assignee_id == old_id).update(
|
|
|
|
|
{"assignee_id": new_id})
|
|
|
|
|
db.query(Task).filter(Task.created_by_id == old_id).update(
|
|
|
|
|
{"created_by_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Meetings
|
|
|
|
|
db.query(Meeting).filter(Meeting.reporter_id == old_id).update(
|
|
|
|
|
{"reporter_id": new_id})
|
|
|
|
|
db.query(MeetingParticipant).filter(MeetingParticipant.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Support
|
|
|
|
|
db.query(Support).filter(Support.reporter_id == old_id).update(
|
|
|
|
|
{"reporter_id": new_id})
|
|
|
|
|
db.query(Support).filter(Support.assignee_id == old_id).update(
|
|
|
|
|
{"assignee_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Proposals
|
|
|
|
|
db.query(Proposal).filter(Proposal.created_by_id == old_id).update(
|
|
|
|
|
{"created_by_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Milestones
|
|
|
|
|
db.query(Milestone).filter(Milestone.created_by_id == old_id).update(
|
|
|
|
|
{"created_by_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Calendar
|
|
|
|
|
db.query(TimeSlot).filter(TimeSlot.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
db.query(SchedulePlan).filter(SchedulePlan.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Minimum workload / Essential
|
|
|
|
|
db.query(MinimumWorkload).filter(MinimumWorkload.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
db.query(Essential).filter(Essential.created_by_id == old_id).update(
|
|
|
|
|
{"created_by_id": new_id})
|
|
|
|
|
|
|
|
|
|
# Agent profile
|
|
|
|
|
db.query(Agent).filter(Agent.user_id == old_id).update(
|
|
|
|
|
{"user_id": new_id})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{identifier}", status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
def delete_user(
|
|
|
|
|
identifier: str,
|
|
|
|
|
@@ -223,17 +304,26 @@ def delete_user(
|
|
|
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
|
if current_user.id == user.id:
|
|
|
|
|
raise HTTPException(status_code=400, detail="You cannot delete your own account")
|
|
|
|
|
# Protect built-in accounts from deletion
|
|
|
|
|
if user.is_admin:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Admin accounts cannot be deleted")
|
|
|
|
|
if user.username == "acc-mgr":
|
|
|
|
|
raise HTTPException(status_code=400, detail="The acc-mgr account is a built-in account and cannot be deleted")
|
|
|
|
|
try:
|
|
|
|
|
if user.username in _BUILTIN_USERNAMES:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400,
|
|
|
|
|
detail=f"The {user.username} account is a built-in account and cannot be deleted",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
deleted_user = db.query(models.User).filter(
|
|
|
|
|
models.User.username == DELETED_USER_USERNAME
|
|
|
|
|
).first()
|
|
|
|
|
if not deleted_user:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=500,
|
|
|
|
|
detail="Built-in deleted-user account not found. Run init_wizard first.",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_reassign_user_references(db, user.id, deleted_user.id)
|
|
|
|
|
db.delete(user)
|
|
|
|
|
db.commit()
|
|
|
|
|
except IntegrityError:
|
|
|
|
|
db.rollback()
|
|
|
|
|
raise HTTPException(status_code=400, detail="User has related records. Deactivate the account instead.")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|