feat(platform): add audit logs and activity feed
This commit is contained in:
parent
92cb8d1286
commit
c816e9869d
34 changed files with 1592 additions and 43 deletions
80
backend/hermes/app/api/audit.py
Normal file
80
backend/hermes/app/api/audit.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.rbac import get_user_permission_names, require_permission
|
||||
from app.db.database import get_db
|
||||
from app.models.user import User
|
||||
from app.repositories.audit_repository import AuditRepository
|
||||
from app.schemas.api_response import ApiSuccess
|
||||
from app.schemas.audit import ActivityFeedItem, AuditLogListResponse
|
||||
from app.services.audit_service import audit_to_activity
|
||||
|
||||
router = APIRouter(tags=["Audit"])
|
||||
|
||||
|
||||
@router.get("/audit-logs", response_model=ApiSuccess)
|
||||
def get_audit_logs(
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=25, ge=1, le=100),
|
||||
entity_type: str | None = None,
|
||||
actor_user_id: int | None = None,
|
||||
action: str | None = None,
|
||||
date_from: datetime | None = None,
|
||||
date_to: datetime | None = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("audit_logs.read")),
|
||||
):
|
||||
items, total = AuditRepository.list(
|
||||
db,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
entity_type=entity_type,
|
||||
actor_user_id=actor_user_id,
|
||||
action=action,
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
)
|
||||
return ApiSuccess(
|
||||
data=AuditLogListResponse(
|
||||
items=items,
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
),
|
||||
message="Audit Logs geladen",
|
||||
)
|
||||
|
||||
|
||||
def can_read_activity(action: str, permissions: set[str]) -> bool:
|
||||
if action.startswith("users."):
|
||||
return "users.read" in permissions
|
||||
if action.startswith("customers.") or action.startswith("customer_contacts."):
|
||||
return "customers.read" in permissions
|
||||
if action.startswith("roles."):
|
||||
return "roles.read" in permissions
|
||||
if action.startswith("audit_logs."):
|
||||
return "audit_logs.read" in permissions
|
||||
if action.startswith("auth."):
|
||||
return "audit_logs.read" in permissions
|
||||
return False
|
||||
|
||||
|
||||
@router.get("/activity-feed", response_model=ApiSuccess)
|
||||
def get_activity_feed(
|
||||
limit: int = Query(default=10, ge=1, le=50),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("dashboard.read")),
|
||||
):
|
||||
permissions = get_user_permission_names(current_user)
|
||||
activities: list[ActivityFeedItem] = []
|
||||
|
||||
for audit_log in AuditRepository.latest(db, limit=100):
|
||||
if not can_read_activity(audit_log.action, permissions):
|
||||
continue
|
||||
activities.append(ActivityFeedItem(**audit_to_activity(audit_log)))
|
||||
if len(activities) >= limit:
|
||||
break
|
||||
|
||||
return ApiSuccess(data=activities, message="Aktivitäten geladen")
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.core.security import (
|
||||
create_access_token,
|
||||
|
|
@ -12,6 +13,7 @@ from app.models.user import User
|
|||
from app.repositories.user_repository import UserRepository
|
||||
from app.schemas.rbac import CurrentUserResponse
|
||||
from app.schemas.user import LoginRequest, LoginResponse
|
||||
from app.services.audit_service import write_audit_log
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/auth",
|
||||
|
|
@ -22,23 +24,50 @@ router = APIRouter(
|
|||
@router.post("/login", response_model=LoginResponse)
|
||||
def login(
|
||||
login: LoginRequest,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
user = UserRepository.get_by_username(db, login.username)
|
||||
|
||||
if not user:
|
||||
write_audit_log(
|
||||
db,
|
||||
action="auth.login.failed",
|
||||
entity_type="auth",
|
||||
entity_label=login.username,
|
||||
actor_username=login.username,
|
||||
request=request,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Ungültige Anmeldedaten",
|
||||
)
|
||||
|
||||
if not verify_password(login.password, user.password_hash):
|
||||
write_audit_log(
|
||||
db,
|
||||
action="auth.login.failed",
|
||||
entity_type="auth",
|
||||
entity_label=login.username,
|
||||
actor_user_id=user.id,
|
||||
actor_username=user.username,
|
||||
request=request,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Ungültige Anmeldedaten",
|
||||
)
|
||||
|
||||
token = create_access_token(user.username)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="auth.login.success",
|
||||
entity_type="auth",
|
||||
entity_id=user.id,
|
||||
entity_label=user.username,
|
||||
actor=user,
|
||||
request=request,
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": token,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import logging
|
|||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.core.rbac import require_permission
|
||||
from app.db.database import get_db
|
||||
|
|
@ -16,6 +17,7 @@ from app.schemas.customer import (
|
|||
CustomerResponse,
|
||||
CustomerUpdate,
|
||||
)
|
||||
from app.services.audit_service import sanitize, write_audit_log
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -94,42 +96,81 @@ def get_customer(
|
|||
)
|
||||
def create_customer(
|
||||
customer: CustomerCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("customers.create")),
|
||||
):
|
||||
ensure_unique_customer_number(db, customer.customer_number)
|
||||
logger.info("customers.create", extra={"actor_user_id": current_user.id})
|
||||
return CustomerRepository.create(db, customer)
|
||||
created_customer = CustomerRepository.create(db, customer)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="customers.create",
|
||||
entity_type="customers",
|
||||
entity_id=created_customer.id,
|
||||
entity_label=created_customer.company_name,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
after_data=created_customer,
|
||||
)
|
||||
return created_customer
|
||||
|
||||
|
||||
@router.put("/{customer_id}", response_model=CustomerResponse)
|
||||
def update_customer(
|
||||
customer_id: int,
|
||||
customer: CustomerUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("customers.update")),
|
||||
):
|
||||
db_customer = get_customer_or_404(db, customer_id)
|
||||
before_data = sanitize(db_customer)
|
||||
ensure_unique_customer_number(db, customer.customer_number, customer_id)
|
||||
logger.info(
|
||||
"customers.update",
|
||||
extra={"actor_user_id": current_user.id, "target_customer_id": customer_id},
|
||||
)
|
||||
return CustomerRepository.update(db, db_customer, customer)
|
||||
updated_customer = CustomerRepository.update(db, db_customer, customer)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="customers.update",
|
||||
entity_type="customers",
|
||||
entity_id=updated_customer.id,
|
||||
entity_label=updated_customer.company_name,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated_customer,
|
||||
)
|
||||
return updated_customer
|
||||
|
||||
|
||||
@router.delete("/{customer_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_customer(
|
||||
customer_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("customers.delete")),
|
||||
):
|
||||
db_customer = get_customer_or_404(db, customer_id)
|
||||
before_data = sanitize(db_customer)
|
||||
entity_label = db_customer.company_name
|
||||
logger.info(
|
||||
"customers.delete",
|
||||
extra={"actor_user_id": current_user.id, "target_customer_id": customer_id},
|
||||
)
|
||||
CustomerRepository.delete(db, db_customer)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="customers.delete",
|
||||
entity_type="customers",
|
||||
entity_id=customer_id,
|
||||
entity_label=entity_label,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
|
@ -155,15 +196,28 @@ def get_customer_contacts(
|
|||
def create_customer_contact(
|
||||
customer_id: int,
|
||||
contact: CustomerContactCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("customers.update")),
|
||||
):
|
||||
get_customer_or_404(db, customer_id)
|
||||
customer = get_customer_or_404(db, customer_id)
|
||||
logger.info(
|
||||
"customers.contacts.create",
|
||||
extra={"actor_user_id": current_user.id, "target_customer_id": customer_id},
|
||||
)
|
||||
return CustomerRepository.create_contact(db, customer_id, contact)
|
||||
created_contact = CustomerRepository.create_contact(db, customer_id, contact)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="customer_contacts.create",
|
||||
entity_type="customer_contacts",
|
||||
entity_id=created_contact.id,
|
||||
entity_label=f"{created_contact.first_name} {created_contact.last_name}".strip(),
|
||||
actor=current_user,
|
||||
request=request,
|
||||
after_data=created_contact,
|
||||
metadata={"customer_id": customer_id, "customer_label": customer.company_name},
|
||||
)
|
||||
return created_contact
|
||||
|
||||
|
||||
@router.put("/{customer_id}/contacts/{contact_id}", response_model=CustomerContactResponse)
|
||||
|
|
@ -171,11 +225,13 @@ def update_customer_contact(
|
|||
customer_id: int,
|
||||
contact_id: int,
|
||||
contact: CustomerContactUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("customers.update")),
|
||||
):
|
||||
get_customer_or_404(db, customer_id)
|
||||
customer = get_customer_or_404(db, customer_id)
|
||||
db_contact = get_contact_or_404(db, customer_id, contact_id)
|
||||
before_data = sanitize(db_contact)
|
||||
logger.info(
|
||||
"customers.contacts.update",
|
||||
extra={
|
||||
|
|
@ -184,18 +240,34 @@ def update_customer_contact(
|
|||
"target_contact_id": contact_id,
|
||||
},
|
||||
)
|
||||
return CustomerRepository.update_contact(db, db_contact, contact)
|
||||
updated_contact = CustomerRepository.update_contact(db, db_contact, contact)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="customer_contacts.update",
|
||||
entity_type="customer_contacts",
|
||||
entity_id=updated_contact.id,
|
||||
entity_label=f"{updated_contact.first_name} {updated_contact.last_name}".strip(),
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated_contact,
|
||||
metadata={"customer_id": customer_id, "customer_label": customer.company_name},
|
||||
)
|
||||
return updated_contact
|
||||
|
||||
|
||||
@router.delete("/{customer_id}/contacts/{contact_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_customer_contact(
|
||||
customer_id: int,
|
||||
contact_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("customers.update")),
|
||||
):
|
||||
get_customer_or_404(db, customer_id)
|
||||
customer = get_customer_or_404(db, customer_id)
|
||||
db_contact = get_contact_or_404(db, customer_id, contact_id)
|
||||
before_data = sanitize(db_contact)
|
||||
entity_label = f"{db_contact.first_name} {db_contact.last_name}".strip()
|
||||
logger.info(
|
||||
"customers.contacts.delete",
|
||||
extra={
|
||||
|
|
@ -205,4 +277,15 @@ def delete_customer_contact(
|
|||
},
|
||||
)
|
||||
CustomerRepository.delete_contact(db, db_contact)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="customer_contacts.delete",
|
||||
entity_type="customer_contacts",
|
||||
entity_id=contact_id,
|
||||
entity_label=entity_label,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
metadata={"customer_id": customer_id, "customer_label": customer.company_name},
|
||||
)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import logging
|
|||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.core.rbac import require_permission
|
||||
from app.db.database import get_db
|
||||
from app.models.user import User
|
||||
from app.repositories.rbac_repository import RbacRepository
|
||||
from app.schemas.rbac import RoleCreate, RolePermissionUpdate, RoleResponse, RoleUpdate
|
||||
from app.services.audit_service import sanitize, write_audit_log
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -80,6 +82,7 @@ def get_role(
|
|||
)
|
||||
def create_role(
|
||||
role: RoleCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("roles.create")),
|
||||
):
|
||||
|
|
@ -87,17 +90,30 @@ def create_role(
|
|||
ensure_permission_ids_exist(db, role.permission_ids)
|
||||
|
||||
logger.info("roles.create", extra={"actor_user_id": current_user.id})
|
||||
return RbacRepository.create_role(db, role)
|
||||
created_role = RbacRepository.create_role(db, role)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="roles.create",
|
||||
entity_type="roles",
|
||||
entity_id=created_role.id,
|
||||
entity_label=created_role.name,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
after_data=created_role,
|
||||
)
|
||||
return created_role
|
||||
|
||||
|
||||
@router.put("/{role_id}", response_model=RoleResponse)
|
||||
def update_role(
|
||||
role_id: int,
|
||||
role: RoleUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("roles.update")),
|
||||
):
|
||||
db_role = get_role_or_404(db, role_id)
|
||||
before_data = sanitize(db_role)
|
||||
ensure_unique_role_name(db, role.name, role_id)
|
||||
ensure_permission_ids_exist(db, role.permission_ids)
|
||||
|
||||
|
|
@ -105,16 +121,31 @@ def update_role(
|
|||
"roles.update",
|
||||
extra={"actor_user_id": current_user.id, "target_role_id": role_id},
|
||||
)
|
||||
return RbacRepository.update_role(db, db_role, role)
|
||||
updated_role = RbacRepository.update_role(db, db_role, role)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="roles.update",
|
||||
entity_type="roles",
|
||||
entity_id=updated_role.id,
|
||||
entity_label=updated_role.name,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated_role,
|
||||
)
|
||||
return updated_role
|
||||
|
||||
|
||||
@router.delete("/{role_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_role(
|
||||
role_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("roles.delete")),
|
||||
):
|
||||
db_role = get_role_or_404(db, role_id)
|
||||
before_data = sanitize(db_role)
|
||||
entity_label = db_role.name
|
||||
if db_role.is_system:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -133,6 +164,16 @@ def delete_role(
|
|||
extra={"actor_user_id": current_user.id, "target_role_id": role_id},
|
||||
)
|
||||
RbacRepository.delete_role(db, db_role)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="roles.delete",
|
||||
entity_type="roles",
|
||||
entity_id=role_id,
|
||||
entity_label=entity_label,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
|
@ -140,14 +181,28 @@ def delete_role(
|
|||
def update_role_permissions(
|
||||
role_id: int,
|
||||
permission_update: RolePermissionUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("roles.assign")),
|
||||
):
|
||||
db_role = get_role_or_404(db, role_id)
|
||||
before_data = sanitize(db_role)
|
||||
ensure_permission_ids_exist(db, permission_update.permission_ids)
|
||||
|
||||
logger.info(
|
||||
"roles.permissions_update",
|
||||
extra={"actor_user_id": current_user.id, "target_role_id": role_id},
|
||||
)
|
||||
return RbacRepository.update_role_permissions(db, db_role, permission_update)
|
||||
updated_role = RbacRepository.update_role_permissions(db, db_role, permission_update)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="roles.permissions_update",
|
||||
entity_type="roles",
|
||||
entity_id=updated_role.id,
|
||||
entity_label=updated_role.name,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated_role,
|
||||
)
|
||||
return updated_role
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import logging
|
|||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.core.rbac import get_user_permission_names, require_permission
|
||||
from app.db.database import get_db
|
||||
|
|
@ -9,6 +10,7 @@ from app.models.user import User
|
|||
from app.repositories.rbac_repository import RbacRepository
|
||||
from app.repositories.user_repository import UserRepository
|
||||
from app.schemas.user import UserCreate, UserPasswordUpdate, UserResponse, UserUpdate
|
||||
from app.services.audit_service import sanitize, write_audit_log
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -98,6 +100,7 @@ def get_user(
|
|||
)
|
||||
def create_user(
|
||||
user: UserCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("users.create")),
|
||||
):
|
||||
|
|
@ -113,17 +116,30 @@ def create_user(
|
|||
raise_conflict(conflict[0])
|
||||
|
||||
logger.info("users.create", extra={"actor_user_id": current_user.id})
|
||||
return UserRepository.create(db, user)
|
||||
created_user = UserRepository.create(db, user)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="users.create",
|
||||
entity_type="users",
|
||||
entity_id=created_user.id,
|
||||
entity_label=created_user.username,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
after_data=created_user,
|
||||
)
|
||||
return created_user
|
||||
|
||||
|
||||
@router.put("/{user_id}", response_model=UserResponse)
|
||||
def update_user(
|
||||
user_id: int,
|
||||
user: UserUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("users.update")),
|
||||
):
|
||||
db_user = get_user_or_404(db, user_id)
|
||||
before_data = sanitize(db_user)
|
||||
apply_role(db, user)
|
||||
if user.role_id != db_user.role_id:
|
||||
require_actor_permission(current_user, "roles.assign")
|
||||
|
|
@ -141,28 +157,56 @@ def update_user(
|
|||
"users.update",
|
||||
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
||||
)
|
||||
return UserRepository.update(db, db_user, user)
|
||||
updated_user = UserRepository.update(db, db_user, user)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="users.update",
|
||||
entity_type="users",
|
||||
entity_id=updated_user.id,
|
||||
entity_label=updated_user.username,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated_user,
|
||||
)
|
||||
return updated_user
|
||||
|
||||
|
||||
@router.put("/{user_id}/password", response_model=UserResponse)
|
||||
def update_user_password(
|
||||
user_id: int,
|
||||
password_update: UserPasswordUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("users.password.update")),
|
||||
):
|
||||
db_user = get_user_or_404(db, user_id)
|
||||
before_data = sanitize(db_user)
|
||||
|
||||
logger.info(
|
||||
"users.password_update",
|
||||
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
||||
)
|
||||
return UserRepository.update_password(db, db_user, password_update)
|
||||
updated_user = UserRepository.update_password(db, db_user, password_update)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="users.password_update",
|
||||
entity_type="users",
|
||||
entity_id=updated_user.id,
|
||||
entity_label=updated_user.username,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated_user,
|
||||
metadata={"password_changed": True},
|
||||
)
|
||||
return updated_user
|
||||
|
||||
|
||||
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_user(
|
||||
user_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("users.delete")),
|
||||
):
|
||||
|
|
@ -173,10 +217,22 @@ def delete_user(
|
|||
)
|
||||
|
||||
db_user = get_user_or_404(db, user_id)
|
||||
before_data = sanitize(db_user)
|
||||
entity_label = db_user.username
|
||||
|
||||
logger.info(
|
||||
"users.delete",
|
||||
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
||||
)
|
||||
UserRepository.delete(db, db_user)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="users.delete",
|
||||
entity_type="users",
|
||||
entity_id=user_id,
|
||||
entity_label=entity_label,
|
||||
actor=current_user,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue