feat: SAC 0.6.0 user edit UI, login to dashboard, daily report timer sync

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-06-01 09:55:12 +10:00
parent 56c469ddbd
commit 8de5d14cfb
17 changed files with 449 additions and 56 deletions
+15
View File
@@ -32,6 +32,7 @@ class CreateUserRequest(BaseModel):
class UpdateUserRequest(BaseModel):
username: str | None = Field(default=None, min_length=2, max_length=64)
role: str | None = None
password: str | None = Field(default=None, min_length=8, max_length=128)
is_active: bool | None = None
@@ -91,6 +92,20 @@ def update_user(
if user is None:
raise HTTPException(status_code=404, detail="User not found")
if body.username is not None:
normalized = body.username.strip()
if not normalized:
raise HTTPException(status_code=422, detail="username is required")
existing = db.scalar(
select(User).where(
func.lower(User.username) == normalized.lower(),
User.id != user.id,
)
)
if existing is not None:
raise HTTPException(status_code=400, detail="username already exists")
user.username = normalized
if body.role is not None:
if body.role not in USER_ROLES:
raise HTTPException(status_code=422, detail=f"role must be one of: {', '.join(sorted(USER_ROLES))}")
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center"
APP_VERSION = "0.5.0"
APP_VERSION = "0.6.0"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+2 -2
View File
@@ -4,6 +4,6 @@ from app.version import APP_NAME, APP_VERSION, APP_VERSION_LABEL
def test_version_constants():
assert APP_VERSION == "0.5.0"
assert APP_VERSION == "0.6.0"
assert APP_NAME == "Security Alert Center"
assert APP_VERSION_LABEL == "Security Alert Center v.0.5.0"
assert APP_VERSION_LABEL == "Security Alert Center v.0.6.0"
+72
View File
@@ -62,3 +62,75 @@ def test_admin_create_monitor_user(client, jwt_headers):
def test_monitor_cannot_list_users(client, jwt_monitor_headers):
response = client.get("/api/v1/users", headers=jwt_monitor_headers)
assert response.status_code == 403
def test_admin_update_user_role_and_password(client, jwt_headers):
create = client.post(
"/api/v1/users",
headers=jwt_headers,
json={"username": "patch-me", "password": "longpassword1", "role": "monitor"},
)
assert create.status_code == 201
user_id = create.json()["id"]
response = client.patch(
f"/api/v1/users/{user_id}",
headers=jwt_headers,
json={"role": "admin", "password": "newpassword9"},
)
assert response.status_code == 200
body = response.json()
assert body["role"] == "admin"
login = client.post(
"/api/v1/auth/login",
json={"username": "patch-me", "password": "newpassword9"},
)
assert login.status_code == 200
assert login.json()["role"] == "admin"
def test_admin_update_username(client, jwt_headers):
create = client.post(
"/api/v1/users",
headers=jwt_headers,
json={"username": "old-name", "password": "longpassword1", "role": "monitor"},
)
user_id = create.json()["id"]
response = client.patch(
f"/api/v1/users/{user_id}",
headers=jwt_headers,
json={"username": "new-name"},
)
assert response.status_code == 200
assert response.json()["username"] == "new-name"
login = client.post(
"/api/v1/auth/login",
json={"username": "new-name", "password": "longpassword1"},
)
assert login.status_code == 200
def test_admin_deactivate_user(client, jwt_headers):
create = client.post(
"/api/v1/users",
headers=jwt_headers,
json={"username": "to-disable", "password": "longpassword1", "role": "monitor"},
)
user_id = create.json()["id"]
response = client.patch(
f"/api/v1/users/{user_id}",
headers=jwt_headers,
json={"is_active": False},
)
assert response.status_code == 200
assert response.json()["is_active"] is False
login = client.post(
"/api/v1/auth/login",
json={"username": "to-disable", "password": "longpassword1"},
)
assert login.status_code == 401