fix: refactor
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from app.models.user import get_db_connection
|
||||
from app.api.v1.auth import get_current_user
|
||||
from app.core.auth import hash_password
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def require_admin(current_user: dict = Depends(get_current_user)):
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Chỉ Admin hệ thống mới có quyền truy cập tính năng này")
|
||||
return current_user
|
||||
|
||||
class UpdateUserQuotaRequest(BaseModel):
|
||||
storage_limit_mb: int
|
||||
max_tracks: Optional[int] = 16
|
||||
|
||||
class UpdateUserRoleRequest(BaseModel):
|
||||
role: str # 'admin', 'standard', 'premium'
|
||||
is_active: Optional[bool] = True
|
||||
|
||||
@router.get("/users")
|
||||
async def list_users(admin: dict = Depends(require_admin)):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT u.id, u.username, u.email, u.role, u.is_active, u.must_change_password, u.created_at,
|
||||
q.storage_limit_mb, q.max_tracks,
|
||||
(SELECT COALESCE(SUM(p.size_bytes), 0) FROM projects p WHERE p.user_id = u.id) as used_bytes
|
||||
FROM users u
|
||||
LEFT JOIN user_quotas q ON u.id = q.user_id
|
||||
ORDER BY u.created_at DESC
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
users = []
|
||||
for r in rows:
|
||||
used_mb = round((r["used_bytes"] or 0) / (1024 * 1024), 2)
|
||||
users.append({
|
||||
"id": r["id"],
|
||||
"username": r["username"],
|
||||
"email": r["email"],
|
||||
"role": r["role"],
|
||||
"is_active": bool(r["is_active"]),
|
||||
"must_change_password": bool(r["must_change_password"]),
|
||||
"created_at": r["created_at"],
|
||||
"quota_mb": r["storage_limit_mb"] or 500,
|
||||
"used_mb": used_mb,
|
||||
"max_tracks": r["max_tracks"] or 16
|
||||
})
|
||||
return users
|
||||
|
||||
@router.put("/users/{user_id}/role")
|
||||
async def update_user_role(user_id: str, req: UpdateUserRoleRequest, admin: dict = Depends(require_admin)):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("UPDATE users SET role = ?, is_active = ? WHERE id = ?", (req.role, int(req.is_active), user_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"message": "Cập nhật vai trò người dùng thành công"}
|
||||
|
||||
@router.put("/quotas/{user_id}")
|
||||
async def update_user_quota(user_id: str, req: UpdateUserQuotaRequest, admin: dict = Depends(require_admin)):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
INSERT INTO user_quotas (user_id, storage_limit_mb, max_tracks)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(user_id) DO UPDATE SET storage_limit_mb = excluded.storage_limit_mb, max_tracks = excluded.max_tracks
|
||||
""", (user_id, req.storage_limit_mb, req.max_tracks))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"message": "Cập nhật hạn mức Quota thành công"}
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def delete_user(user_id: str, admin: dict = Depends(require_admin)):
|
||||
if user_id == admin["user_id"]:
|
||||
raise HTTPException(status_code=400, detail="Không thể xóa chính tài khoản Admin đang đăng nhập")
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
|
||||
cursor.execute("DELETE FROM user_quotas WHERE id = ?", (user_id,))
|
||||
cursor.execute("DELETE FROM projects WHERE user_id = ?", (user_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"message": "Đã xóa người dùng thành công"}
|
||||
@@ -0,0 +1,188 @@
|
||||
import uuid
|
||||
import time
|
||||
from fastapi import APIRouter, HTTPException, Header, Depends
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional
|
||||
from app.models.user import get_db_connection
|
||||
from app.core.auth import hash_password, verify_password, create_token, decode_token, seed_admin
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
username: Optional[str] = "admin"
|
||||
password: str
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
username: str
|
||||
email: str
|
||||
password: str
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
old_password: str
|
||||
new_password: str
|
||||
|
||||
def get_current_user(authorization: Optional[str] = Header(None)):
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="Thiếu Token xác thực hoặc Token không hợp lệ")
|
||||
token = authorization.split(" ")[1]
|
||||
payload = decode_token(token)
|
||||
if not payload:
|
||||
raise HTTPException(status_code=401, detail="Token đã hết hạn hoặc không hợp lệ")
|
||||
return payload
|
||||
|
||||
@router.post("/login")
|
||||
async def login(req: LoginRequest):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
username = (req.username or "").strip()
|
||||
if not username:
|
||||
username = "admin"
|
||||
|
||||
password = (req.password or "").strip()
|
||||
|
||||
# Case-insensitive search by username or email
|
||||
cursor.execute("SELECT * FROM users WHERE LOWER(username) = LOWER(?) OR LOWER(email) = LOWER(?)", (username, username))
|
||||
user = cursor.fetchone()
|
||||
|
||||
# Auto-heal seed_admin if admin record missing
|
||||
if not user and username.lower() == "admin":
|
||||
conn.close()
|
||||
seed_admin()
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE username = 'admin'")
|
||||
user = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
if not user or not user["is_active"]:
|
||||
raise HTTPException(status_code=400, detail="Tài khoản hoặc mật khẩu không chính xác")
|
||||
|
||||
if not verify_password(password, user["hashed_password"]):
|
||||
raise HTTPException(status_code=400, detail="Tài khoản hoặc mật khẩu không chính xác")
|
||||
|
||||
token = create_token(user["id"], user["username"], user["role"], user["must_change_password"])
|
||||
|
||||
return {
|
||||
"access_token": token,
|
||||
"user": {
|
||||
"id": user["id"],
|
||||
"username": user["username"],
|
||||
"email": user["email"],
|
||||
"role": user["role"],
|
||||
"must_change_password": bool(user["must_change_password"])
|
||||
}
|
||||
}
|
||||
|
||||
@router.post("/register")
|
||||
async def register(req: RegisterRequest):
|
||||
username = req.username.strip()
|
||||
email = req.email.strip()
|
||||
password = req.password.strip()
|
||||
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT id FROM users WHERE LOWER(username) = LOWER(?) OR LOWER(email) = LOWER(?)", (username, email))
|
||||
if cursor.fetchone():
|
||||
conn.close()
|
||||
raise HTTPException(status_code=400, detail="Tên người dùng hoặc Email đã tồn tại")
|
||||
|
||||
user_id = str(uuid.uuid4())
|
||||
hashed_pwd = hash_password(password)
|
||||
now = time.time()
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO users (id, username, email, hashed_password, role, must_change_password, created_at, is_active)
|
||||
VALUES (?, ?, ?, ?, 'standard', 0, ?, 1)
|
||||
""", (user_id, username, email, hashed_pwd, now))
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO user_quotas (user_id, storage_limit_mb, max_tracks)
|
||||
VALUES (?, 500, 16)
|
||||
""", (user_id,))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
token = create_token(user_id, username, "standard", False)
|
||||
return {
|
||||
"access_token": token,
|
||||
"user": {
|
||||
"id": user_id,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"role": "standard",
|
||||
"must_change_password": False
|
||||
}
|
||||
}
|
||||
|
||||
@router.post("/change-password")
|
||||
async def change_password(req: ChangePasswordRequest, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
old_pwd = req.old_password.strip()
|
||||
new_pwd = req.new_password.strip()
|
||||
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT hashed_password FROM users WHERE id = ?", (user_id,))
|
||||
user = cursor.fetchone()
|
||||
if not user or not verify_password(old_pwd, user["hashed_password"]):
|
||||
conn.close()
|
||||
raise HTTPException(status_code=400, detail="Mật khẩu hiện tại không chính xác")
|
||||
|
||||
new_hashed = hash_password(new_pwd)
|
||||
cursor.execute("""
|
||||
UPDATE users SET hashed_password = ?, must_change_password = 0 WHERE id = ?
|
||||
""", (new_hashed, user_id))
|
||||
|
||||
conn.commit()
|
||||
|
||||
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
|
||||
updated_user = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
new_token = create_token(updated_user["id"], updated_user["username"], updated_user["role"], False)
|
||||
return {
|
||||
"message": "Đổi mật khẩu thành công!",
|
||||
"access_token": new_token
|
||||
}
|
||||
|
||||
@router.get("/profile")
|
||||
async def get_profile(current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT u.id, u.username, u.email, u.role, u.must_change_password, q.storage_limit_mb, q.max_tracks
|
||||
FROM users u
|
||||
LEFT JOIN user_quotas q ON u.id = q.user_id
|
||||
WHERE u.id = ?
|
||||
""", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.execute("SELECT SUM(size_bytes) as total_used FROM projects WHERE user_id = ?", (user_id,))
|
||||
used_row = cursor.fetchone()
|
||||
used_bytes = used_row["total_used"] if used_row and used_row["total_used"] else 0
|
||||
used_mb = round(used_bytes / (1024 * 1024), 2)
|
||||
|
||||
conn.close()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Không tìm thấy thông tin tài khoản")
|
||||
|
||||
return {
|
||||
"id": row["id"],
|
||||
"username": row["username"],
|
||||
"email": row["email"],
|
||||
"role": row["role"],
|
||||
"must_change_password": bool(row["must_change_password"]),
|
||||
"quota": {
|
||||
"storage_limit_mb": row["storage_limit_mb"] or 500,
|
||||
"used_mb": used_mb,
|
||||
"max_tracks": row["max_tracks"] or 16
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import time
|
||||
import json
|
||||
import uuid
|
||||
from fastapi import APIRouter, HTTPException, Depends, Header
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Any, Dict
|
||||
from app.models.user import get_db_connection
|
||||
from app.api.v1.auth import get_current_user, decode_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class SaveProjectRequest(BaseModel):
|
||||
name: str
|
||||
data_json: str
|
||||
|
||||
class SaveTempProjectRequest(BaseModel):
|
||||
data_json: str
|
||||
|
||||
def get_optional_user(authorization: Optional[str] = Header(None)) -> Optional[dict]:
|
||||
if authorization and authorization.startswith("Bearer "):
|
||||
token = authorization.split(" ")[1]
|
||||
return decode_token(token)
|
||||
return None
|
||||
|
||||
@router.post("/temp")
|
||||
async def save_temp_project(req: SaveTempProjectRequest, current_user: Optional[dict] = Depends(get_optional_user)):
|
||||
user_id = current_user["user_id"] if current_user else "anonymous"
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
size_bytes = len(req.data_json.encode("utf-8"))
|
||||
now = time.time()
|
||||
temp_id = f"temp_{user_id}"
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at)
|
||||
VALUES (?, ?, 'Dự án tạm chưa lưu', ?, 1, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET data_json = excluded.data_json, size_bytes = excluded.size_bytes, updated_at = excluded.updated_at
|
||||
""", (temp_id, user_id, req.data_json, size_bytes, now))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"message": "Đã lưu dự án tạm tự động", "updated_at": now}
|
||||
|
||||
@router.get("/temp")
|
||||
async def get_temp_project(current_user: Optional[dict] = Depends(get_optional_user)):
|
||||
user_id = current_user["user_id"] if current_user else "anonymous"
|
||||
temp_id = f"temp_{user_id}"
|
||||
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT data_json, updated_at FROM projects WHERE id = ? AND is_temp = 1", (temp_id,))
|
||||
row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not row:
|
||||
return {"has_temp": False}
|
||||
|
||||
return {
|
||||
"has_temp": True,
|
||||
"data_json": row["data_json"],
|
||||
"updated_at": row["updated_at"]
|
||||
}
|
||||
|
||||
@router.post("/cloud")
|
||||
async def save_cloud_project(req: SaveProjectRequest, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT storage_limit_mb FROM user_quotas WHERE user_id = ?", (user_id,))
|
||||
quota_row = cursor.fetchone()
|
||||
storage_limit_mb = quota_row["storage_limit_mb"] if quota_row else 500
|
||||
|
||||
cursor.execute("SELECT SUM(size_bytes) as total_used FROM projects WHERE user_id = ? AND is_temp = 0", (user_id,))
|
||||
used_row = cursor.fetchone()
|
||||
used_bytes = used_row["total_used"] if used_row and used_row["total_used"] else 0
|
||||
|
||||
new_size_bytes = len(req.data_json.encode("utf-8"))
|
||||
max_bytes = storage_limit_mb * 1024 * 1024
|
||||
|
||||
if used_bytes + new_size_bytes > max_bytes:
|
||||
conn.close()
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Dung lượng dự án vượt quá hạn mức Quota ({storage_limit_mb}MB). Vui lòng dọn dẹp hoặc nâng cấp tài khoản."
|
||||
)
|
||||
|
||||
project_id = str(uuid.uuid4())
|
||||
now = time.time()
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at)
|
||||
VALUES (?, ?, ?, ?, 0, ?, ?)
|
||||
""", (project_id, user_id, req.name, req.data_json, new_size_bytes, now))
|
||||
|
||||
temp_id = f"temp_{user_id}"
|
||||
cursor.execute("DELETE FROM projects WHERE id = ? AND is_temp = 1", (temp_id,))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"message": "Đã lưu dự án lên Cloud thành công!",
|
||||
"project_id": project_id
|
||||
}
|
||||
|
||||
@router.get("/cloud")
|
||||
async def list_cloud_projects(current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT id, name, size_bytes, updated_at FROM projects
|
||||
WHERE user_id = ? AND is_temp = 0
|
||||
ORDER BY updated_at DESC
|
||||
""", (user_id,))
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": r["id"],
|
||||
"name": r["name"],
|
||||
"size_mb": round(r["size_bytes"] / (1024 * 1024), 2),
|
||||
"updated_at": r["updated_at"]
|
||||
} for r in rows
|
||||
]
|
||||
Reference in New Issue
Block a user