feat: bổ sung phần profile của user
This commit is contained in:
+28
-1
@@ -6,6 +6,7 @@ from fastapi import APIRouter, UploadFile, File, HTTPException, Query, Depends
|
|||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
import json
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.api.v1.auth import get_current_user
|
from app.api.v1.auth import get_current_user
|
||||||
from app.api.v1.projects import get_optional_user
|
from app.api.v1.projects import get_optional_user
|
||||||
@@ -67,7 +68,16 @@ async def upload_audio(file: UploadFile = File(...), current_user: Optional[dict
|
|||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
content = await file.read()
|
content = await file.read()
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
# Save original filename as sidecar metadata
|
||||||
|
import json
|
||||||
|
meta_path = os.path.join(settings.UPLOADS_DIR, file_id + ".meta")
|
||||||
|
try:
|
||||||
|
with open(meta_path, "w") as mf:
|
||||||
|
json.dump({"original_name": file.filename}, mf)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Trigger celery task
|
# Trigger celery task
|
||||||
from app.tasks.worker import analyze_audio_task
|
from app.tasks.worker import analyze_audio_task
|
||||||
task = analyze_audio_task.delay(file_id)
|
task = analyze_audio_task.delay(file_id)
|
||||||
@@ -357,8 +367,18 @@ async def list_user_files(req: MyFilesRequest, current_user: dict = Depends(get_
|
|||||||
if filename in files_map:
|
if filename in files_map:
|
||||||
files_map[filename]["size_mb"] = round(files_map[filename]["size_mb"] + stat.st_size / (1024 * 1024), 2)
|
files_map[filename]["size_mb"] = round(files_map[filename]["size_mb"] + stat.st_size / (1024 * 1024), 2)
|
||||||
else:
|
else:
|
||||||
|
original_name = filename
|
||||||
|
meta_path = os.path.join(directory, filename + ".meta")
|
||||||
|
if os.path.isfile(meta_path):
|
||||||
|
try:
|
||||||
|
with open(meta_path, "r") as mf:
|
||||||
|
meta = json.load(mf)
|
||||||
|
original_name = meta.get("original_name", filename)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
files_map[filename] = {
|
files_map[filename] = {
|
||||||
"file_id": filename,
|
"file_id": filename,
|
||||||
|
"original_name": original_name,
|
||||||
"size_mb": round(stat.st_size / (1024 * 1024), 2),
|
"size_mb": round(stat.st_size / (1024 * 1024), 2),
|
||||||
"created_at": stat.st_mtime,
|
"created_at": stat.st_mtime,
|
||||||
"type": type_label,
|
"type": type_label,
|
||||||
@@ -390,6 +410,13 @@ async def delete_user_file(file_id: str, current_user: dict = Depends(get_curren
|
|||||||
deleted = True
|
deleted = True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Clean up sidecar metadata file
|
||||||
|
meta_path = os.path.join(directory, file_id + ".meta")
|
||||||
|
if os.path.isfile(meta_path):
|
||||||
|
try:
|
||||||
|
os.remove(meta_path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if not deleted:
|
if not deleted:
|
||||||
raise HTTPException(status_code=404, detail="Không tìm thấy tệp trên server")
|
raise HTTPException(status_code=404, detail="Không tìm thấy tệp trên server")
|
||||||
|
|||||||
+35
-11
@@ -2752,6 +2752,9 @@ const ProfileModal = ({
|
|||||||
const [filesList, setFilesList] = useState([]);
|
const [filesList, setFilesList] = useState([]);
|
||||||
const [loadingFiles, setLoadingFiles] = useState(false);
|
const [loadingFiles, setLoadingFiles] = useState(false);
|
||||||
|
|
||||||
|
// Confirmation modal state
|
||||||
|
const [confirmModal, setConfirmModal] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
@@ -2821,15 +2824,20 @@ const ProfileModal = ({
|
|||||||
|
|
||||||
const handleDeleteProject = async (projectId, e) => {
|
const handleDeleteProject = async (projectId, e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!confirm("Bạn có chắc chắn muốn xóa dự án này khỏi Cloud?")) return;
|
setConfirmModal({
|
||||||
try {
|
title: "Xóa dự án Cloud",
|
||||||
await window.SonicAPI.deleteCloudProject(projectId);
|
message: "Bạn có chắc chắn muốn xóa dự án này khỏi Cloud? Hành động này không thể hoàn tác.",
|
||||||
showToast("Đã xóa dự án thành công!", "success");
|
onConfirm: async () => {
|
||||||
fetchProjects();
|
try {
|
||||||
fetchProfile();
|
await window.SonicAPI.deleteCloudProject(projectId);
|
||||||
} catch (err) {
|
showToast("Đã xóa dự án thành công!", "success");
|
||||||
showToast(err.message || "Lỗi khi xóa dự án", "error");
|
fetchProjects();
|
||||||
}
|
fetchProfile();
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message || "Lỗi khi xóa dự án", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteFile = async (fileId) => {
|
const handleDeleteFile = async (fileId) => {
|
||||||
@@ -2883,8 +2891,24 @@ const ProfileModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return /*#__PURE__*/React.createElement("div", {
|
return /*#__PURE__*/React.createElement("div", {
|
||||||
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm relative"
|
||||||
|
}, confirmModal && /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "absolute inset-0 z-[60] flex items-center justify-center bg-black/50"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200"
|
||||||
|
}, /*#__PURE__*/React.createElement("h4", {
|
||||||
|
className: "text-sm font-bold text-rose-400 mb-2"
|
||||||
|
}, confirmModal.title), /*#__PURE__*/React.createElement("p", {
|
||||||
|
className: "text-xs text-slate-300 mb-4"
|
||||||
|
}, confirmModal.message), /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "flex justify-end gap-2"
|
||||||
|
}, /*#__PURE__*/React.createElement("button", {
|
||||||
|
onClick: () => setConfirmModal(null),
|
||||||
|
className: "px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-slate-300 rounded text-xs font-semibold"
|
||||||
|
}, "Hủy"), /*#__PURE__*/React.createElement("button", {
|
||||||
|
onClick: () => { const fn = confirmModal.onConfirm; setConfirmModal(null); fn(); },
|
||||||
|
className: "px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold"
|
||||||
|
}, "Xác nhận xóa")))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]"
|
className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0"
|
className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0"
|
||||||
@@ -3024,7 +3048,7 @@ const ProfileModal = ({
|
|||||||
className: "flex items-center justify-between p-3 bg-[#1e1e1e] rounded border border-zinc-800 text-xs"
|
className: "flex items-center justify-between p-3 bg-[#1e1e1e] rounded border border-zinc-800 text-xs"
|
||||||
}, [
|
}, [
|
||||||
/*#__PURE__*/React.createElement("div", { key: "meta", className: "max-w-[70%]" }, [
|
/*#__PURE__*/React.createElement("div", { key: "meta", className: "max-w-[70%]" }, [
|
||||||
/*#__PURE__*/React.createElement("div", { className: "font-mono font-semibold text-slate-300 truncate" }, file.file_id.replace(/^user_[^_]+_/, '')),
|
/*#__PURE__*/React.createElement("div", { className: "font-semibold text-slate-300 truncate" }, file.original_name || file.file_id),
|
||||||
/*#__PURE__*/React.createElement("div", { className: "text-[10px] text-zinc-500 mt-0.5" }, `Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at * 1000).toLocaleString()}`)
|
/*#__PURE__*/React.createElement("div", { className: "text-[10px] text-zinc-500 mt-0.5" }, `Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at * 1000).toLocaleString()}`)
|
||||||
]),
|
]),
|
||||||
/*#__PURE__*/React.createElement("div", { key: "actions", className: "flex items-center gap-2" }, [
|
/*#__PURE__*/React.createElement("div", { key: "actions", className: "flex items-center gap-2" }, [
|
||||||
|
|||||||
@@ -2788,6 +2788,9 @@ const ProfileModal = ({
|
|||||||
// Files list state
|
// Files list state
|
||||||
const [filesList, setFilesList] = useState([]);
|
const [filesList, setFilesList] = useState([]);
|
||||||
const [loadingFiles, setLoadingFiles] = useState(false);
|
const [loadingFiles, setLoadingFiles] = useState(false);
|
||||||
|
|
||||||
|
// Confirmation modal state
|
||||||
|
const [confirmModal, setConfirmModal] = useState(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
@@ -2852,15 +2855,20 @@ const ProfileModal = ({
|
|||||||
};
|
};
|
||||||
const handleDeleteProject = async (projectId, e) => {
|
const handleDeleteProject = async (projectId, e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!confirm("Bạn có chắc chắn muốn xóa dự án này khỏi Cloud?")) return;
|
setConfirmModal({
|
||||||
try {
|
title: "Xóa dự án Cloud",
|
||||||
await window.SonicAPI.deleteCloudProject(projectId);
|
message: "Bạn có chắc chắn muốn xóa dự án này khỏi Cloud? Hành động này không thể hoàn tác.",
|
||||||
showToast("Đã xóa dự án thành công!", "success");
|
onConfirm: async () => {
|
||||||
fetchProjects();
|
try {
|
||||||
fetchProfile();
|
await window.SonicAPI.deleteCloudProject(projectId);
|
||||||
} catch (err) {
|
showToast("Đã xóa dự án thành công!", "success");
|
||||||
showToast(err.message || "Lỗi khi xóa dự án", "error");
|
fetchProjects();
|
||||||
}
|
fetchProfile();
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message || "Lỗi khi xóa dự án", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const handleDeleteFile = async fileId => {
|
const handleDeleteFile = async fileId => {
|
||||||
if (!confirm(`Bạn có chắc chắn muốn xóa tệp tin ${fileId}?`)) return;
|
if (!confirm(`Bạn có chắc chắn muốn xóa tệp tin ${fileId}?`)) return;
|
||||||
@@ -2910,8 +2918,28 @@ const ProfileModal = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return /*#__PURE__*/React.createElement("div", {
|
return /*#__PURE__*/React.createElement("div", {
|
||||||
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm relative"
|
||||||
|
}, confirmModal && /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "absolute inset-0 z-[60] flex items-center justify-center bg-black/50"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200"
|
||||||
|
}, /*#__PURE__*/React.createElement("h4", {
|
||||||
|
className: "text-sm font-bold text-rose-400 mb-2"
|
||||||
|
}, confirmModal.title), /*#__PURE__*/React.createElement("p", {
|
||||||
|
className: "text-xs text-slate-300 mb-4"
|
||||||
|
}, confirmModal.message), /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "flex justify-end gap-2"
|
||||||
|
}, /*#__PURE__*/React.createElement("button", {
|
||||||
|
onClick: () => setConfirmModal(null),
|
||||||
|
className: "px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-slate-300 rounded text-xs font-semibold"
|
||||||
|
}, "Hủy"), /*#__PURE__*/React.createElement("button", {
|
||||||
|
onClick: () => {
|
||||||
|
const fn = confirmModal.onConfirm;
|
||||||
|
setConfirmModal(null);
|
||||||
|
fn();
|
||||||
|
},
|
||||||
|
className: "px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold"
|
||||||
|
}, "Xác nhận xóa")))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]"
|
className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0"
|
className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0"
|
||||||
@@ -3067,8 +3095,8 @@ const ProfileModal = ({
|
|||||||
key: "meta",
|
key: "meta",
|
||||||
className: "max-w-[70%]"
|
className: "max-w-[70%]"
|
||||||
}, [/*#__PURE__*/React.createElement("div", {
|
}, [/*#__PURE__*/React.createElement("div", {
|
||||||
className: "font-mono font-semibold text-slate-300 truncate"
|
className: "font-semibold text-slate-300 truncate"
|
||||||
}, file.file_id.replace(/^user_[^_]+_/, '')), /*#__PURE__*/React.createElement("div", {
|
}, file.original_name || file.file_id), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "text-[10px] text-zinc-500 mt-0.5"
|
className: "text-[10px] text-zinc-500 mt-0.5"
|
||||||
}, `Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at * 1000).toLocaleString()}`)]), /*#__PURE__*/React.createElement("div", {
|
}, `Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at * 1000).toLocaleString()}`)]), /*#__PURE__*/React.createElement("div", {
|
||||||
key: "actions",
|
key: "actions",
|
||||||
|
|||||||
Reference in New Issue
Block a user