IMPROVE: cho phép lưu dự án và phục hồi dự án vừa tắt
This commit is contained in:
+118
-6
@@ -2,9 +2,9 @@ import time
|
||||
import json
|
||||
import uuid
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException, Depends, Header
|
||||
from fastapi import APIRouter, HTTPException, Depends, Header, Query
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Any, Dict
|
||||
from typing import Optional, Any, Dict, List
|
||||
from jsonschema import validate, ValidationError
|
||||
from app.models.user import get_db_connection
|
||||
from app.api.v1.auth import get_current_user, decode_token
|
||||
@@ -228,9 +228,11 @@ async def list_cloud_projects(current_user: dict = Depends(get_current_user)):
|
||||
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
|
||||
SELECT p.id, p.name, p.size_bytes, p.updated_at,
|
||||
(SELECT COUNT(*) FROM project_backups pb WHERE pb.project_id = p.id AND pb.user_id = p.user_id) as backup_count
|
||||
FROM projects p
|
||||
WHERE p.user_id = ? AND p.is_temp = 0
|
||||
ORDER BY p.updated_at DESC
|
||||
""", (user_id,))
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
@@ -240,7 +242,8 @@ async def list_cloud_projects(current_user: dict = Depends(get_current_user)):
|
||||
"id": r["id"],
|
||||
"name": r["name"],
|
||||
"size_mb": round(r["size_bytes"] / (1024 * 1024), 2),
|
||||
"updated_at": r["updated_at"]
|
||||
"updated_at": r["updated_at"],
|
||||
"backup_count": r["backup_count"]
|
||||
} for r in rows
|
||||
]
|
||||
|
||||
@@ -298,6 +301,115 @@ async def update_cloud_project(project_id: str, req: SaveProjectRequest, current
|
||||
conn.close()
|
||||
return {"success": True, "message": "Đã cập nhật dự án thành công"}
|
||||
|
||||
class BackupConfigRequest(BaseModel):
|
||||
max_count: int = 10
|
||||
|
||||
@router.post("/cloud/{project_id}/backup")
|
||||
async def create_project_backup(project_id: str, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name, data_json FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
raise HTTPException(status_code=404, detail="Không tìm thấy dự án")
|
||||
|
||||
backup_id = f"backup_{uuid.uuid4().hex[:12]}"
|
||||
now = time.time()
|
||||
size_bytes = len(row["data_json"].encode("utf-8"))
|
||||
backup_name = f"[Backup] {row['name']} ({time.strftime('%Y-%m-%d %H:%M', time.localtime(now))})"
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO project_backups (id, user_id, project_id, name, data_json, size_bytes, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (backup_id, user_id, project_id, backup_name, row["data_json"], size_bytes, now))
|
||||
|
||||
# Enforce retention limit: lấy max_count từ query param mặc định 10
|
||||
cursor.execute("SELECT COUNT(*) as cnt FROM project_backups WHERE project_id = ? AND user_id = ?", (project_id, user_id))
|
||||
count = cursor.fetchone()["cnt"]
|
||||
max_backup = 10 # default
|
||||
if count > max_backup:
|
||||
excess = count - max_backup
|
||||
cursor.execute("""
|
||||
DELETE FROM project_backups WHERE id IN (
|
||||
SELECT id FROM project_backups WHERE project_id = ? AND user_id = ?
|
||||
ORDER BY created_at ASC LIMIT ?
|
||||
)
|
||||
""", (project_id, user_id, excess))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {
|
||||
"backup_id": backup_id,
|
||||
"name": backup_name,
|
||||
"created_at": now,
|
||||
"max_backups": max_backup
|
||||
}
|
||||
|
||||
@router.get("/cloud/{project_id}/backups")
|
||||
async def list_project_backups(project_id: str, 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, created_at FROM project_backups
|
||||
WHERE project_id = ? AND user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
""", (project_id, user_id))
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
return [
|
||||
{
|
||||
"id": r["id"],
|
||||
"name": r["name"],
|
||||
"size_mb": round(r["size_bytes"] / (1024 * 1024), 2),
|
||||
"created_at": r["created_at"]
|
||||
} for r in rows
|
||||
]
|
||||
|
||||
@router.delete("/cloud/backups/{backup_id}")
|
||||
async def delete_project_backup(backup_id: str, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM project_backups WHERE id = ? AND user_id = ?", (backup_id, user_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"success": True, "message": "Đã xóa bản backup"}
|
||||
|
||||
class CleanupBackupsRequest(BaseModel):
|
||||
keep: int = 10
|
||||
|
||||
@router.post("/cloud/backups/cleanup")
|
||||
async def cleanup_all_backups(req: CleanupBackupsRequest, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
keep = max(5, min(20, req.keep))
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Lấy tất cả project_id của user
|
||||
cursor.execute("SELECT DISTINCT project_id FROM project_backups WHERE user_id = ?", (user_id,))
|
||||
projects = cursor.fetchall()
|
||||
total_deleted = 0
|
||||
for p in projects:
|
||||
pid = p["project_id"]
|
||||
cursor.execute("SELECT COUNT(*) as cnt FROM project_backups WHERE project_id = ? AND user_id = ?", (pid, user_id))
|
||||
cnt = cursor.fetchone()["cnt"]
|
||||
if cnt > keep:
|
||||
excess = cnt - keep
|
||||
cursor.execute("""
|
||||
DELETE FROM project_backups WHERE id IN (
|
||||
SELECT id FROM project_backups WHERE project_id = ? AND user_id = ?
|
||||
ORDER BY created_at ASC LIMIT ?
|
||||
)
|
||||
""", (pid, user_id, excess))
|
||||
total_deleted += excess
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"success": True, "deleted": total_deleted, "keep": keep}
|
||||
|
||||
class RenderProjectRequest(BaseModel):
|
||||
sample_rate: Optional[int] = 44100
|
||||
|
||||
|
||||
Reference in New Issue
Block a user