feat: Бэкапы: Удалено восстановление бэкапов.
This commit is contained in:
parent
8042460557
commit
89bfb07a23
@ -79,27 +79,6 @@ async def upload_backup(
|
||||
return await backup_service.upload_backup(file, user)
|
||||
|
||||
|
||||
@router.post(
|
||||
'/{backup_id}/restore/',
|
||||
response_model=BackupResponseEntity,
|
||||
summary='Restore a backup',
|
||||
description='Restore a backup by ID, overwriting existing data',
|
||||
)
|
||||
async def restore_backup(
|
||||
backup_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user=Depends(require_admin),
|
||||
):
|
||||
backup_service = BackupService(
|
||||
db,
|
||||
settings.BACKUP_DB_URL,
|
||||
settings.FILE_UPLOAD_DIR,
|
||||
settings.BACKUP_DIR,
|
||||
settings.PG_DUMP_PATH,
|
||||
)
|
||||
return await backup_service.restore_backup(backup_id, engine)
|
||||
|
||||
|
||||
@router.delete(
|
||||
'/{backup_id}/',
|
||||
response_model=BackupResponseEntity,
|
||||
|
||||
@ -71,58 +71,6 @@ class BackupService:
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise HTTPException(500, f"Ошибка создания бэкапа: {e}")
|
||||
|
||||
async def restore_backup(self, backup_id: int, engine: AsyncEngine) -> BackupResponseEntity:
|
||||
try:
|
||||
async with maintenance_mode_on():
|
||||
backup = await self.backup_repository.get_by_id(backup_id)
|
||||
if not backup:
|
||||
raise HTTPException(404, 'Резервная копия с таким id не найдена')
|
||||
|
||||
if not os.path.exists(backup.path):
|
||||
raise HTTPException(404, 'Файл не найден на диске')
|
||||
|
||||
with tarfile.open(backup.path, "r:gz") as tar:
|
||||
members = tar.getnames()
|
||||
if "db_dump.sql" not in members or not any(
|
||||
name.startswith(os.path.basename(self.app_files_dir)) for name in members):
|
||||
raise HTTPException(400, "Неверная структура архива резервной копии")
|
||||
|
||||
if os.path.exists(self.app_files_dir):
|
||||
shutil.rmtree(self.app_files_dir)
|
||||
os.makedirs(self.app_files_dir, exist_ok=True)
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
psql_path = self.pg_dump_path.replace("pg_dump", "psql")
|
||||
drop_cmd = (
|
||||
f'"{psql_path}" '
|
||||
f'-d "{self.db_url}" '
|
||||
f'-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"'
|
||||
)
|
||||
subprocess.run(drop_cmd, shell=True, check=True)
|
||||
|
||||
temp_dir = os.path.join(self.backup_dir, f"temp_restore_{backup_id}")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
with tarfile.open(backup.path, "r:gz") as tar:
|
||||
tar.extractall(temp_dir)
|
||||
|
||||
db_dump_path = os.path.join(temp_dir, "db_dump.sql")
|
||||
restore_cmd = f'"{self.pg_dump_path.replace("pg_dump", "pg_restore")}" -d {self.db_url} --no-owner --no-privileges -Fc "{db_dump_path}"'
|
||||
subprocess.run(restore_cmd, shell=True, check=True)
|
||||
|
||||
extracted_app_files_dir = os.path.join(temp_dir, os.path.basename(self.app_files_dir))
|
||||
if os.path.exists(extracted_app_files_dir):
|
||||
shutil.copytree(extracted_app_files_dir, self.app_files_dir, dirs_exist_ok=True)
|
||||
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
return self.model_to_entity(backup)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise HTTPException(500, f"Ошибка восстановления бэкапа: {e}")
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"Ошибка сервера: {str(e)}")
|
||||
|
||||
async def get_backup_file_by_id(self, backup_id: int) -> FileResponse:
|
||||
backup = await self.backup_repository.get_by_id(backup_id)
|
||||
|
||||
|
||||
@ -40,12 +40,6 @@ export const backupsApi = createApi({
|
||||
},
|
||||
invalidatesTags: ['Backup'],
|
||||
}),
|
||||
restoreBackup: builder.mutation({
|
||||
query: (backupId) => ({
|
||||
url: `/backups/${backupId}/restore/`,
|
||||
method: 'POST',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -54,5 +48,4 @@ export const {
|
||||
useCreateBackupMutation,
|
||||
useDeleteBackupMutation,
|
||||
useUploadBackupMutation,
|
||||
useRestoreBackupMutation,
|
||||
} = backupsApi;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {Button, Divider, List, Popconfirm, Result, Space, Tooltip, Typography, Upload} from "antd";
|
||||
import {CloudDownloadOutlined, DeleteOutlined, UploadOutlined, ReloadOutlined} from "@ant-design/icons";
|
||||
import {Button, Divider, List, Result, Space, Tooltip, Typography, Upload} from "antd";
|
||||
import {CloudDownloadOutlined, DeleteOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
import useBackupManageTab from "./useBackupManageTab.js";
|
||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
|
||||
@ -58,18 +58,6 @@ const BackupManageTab = () => {
|
||||
Скачать
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Восстановить резервную копию">
|
||||
<Popconfirm title={"Вы действительно хотите восстановить резервную копию? Существующие данные будут утеряны навсегда."}
|
||||
onConfirm={() => backupManageTabData.restoreBackupHandler(backup.id)}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined/>}
|
||||
loading={backupManageTabData.isRestoringBackup}
|
||||
>
|
||||
Восстановить
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Tooltip>
|
||||
<Tooltip title="Удалить резервную копию">
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
useUploadBackupMutation,
|
||||
useRestoreBackupMutation,
|
||||
} from "../../../../../Api/backupsApi.js";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {notification} from "antd";
|
||||
import {useState} from "react";
|
||||
import CONFIG from "../../../../../Core/сonfig.js";
|
||||
@ -18,7 +17,6 @@ const useBackupManageTab = () => {
|
||||
const [createBackup, {isLoading: isCreatingBackup}] = useCreateBackupMutation();
|
||||
const [deleteBackup, {isLoading: isDeletingBackup}] = useDeleteBackupMutation();
|
||||
const [uploadBackup, {isLoading: isUploadingBackup}] = useUploadBackupMutation();
|
||||
const [restoreBackup, {isLoading: isRestoringBackup}] = useRestoreBackupMutation();
|
||||
|
||||
const [isDownloadingBackup, setDownloadingFiles] = useState(false);
|
||||
|
||||
@ -109,21 +107,6 @@ const useBackupManageTab = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const restoreBackupHandler = async (backupId) => {
|
||||
try {
|
||||
await restoreBackup(backupId).unwrap();
|
||||
notification.success({
|
||||
message: "Успех", description: "Резервная копия успешно восстановлена", placement: "topRight",
|
||||
});
|
||||
} catch (e) {
|
||||
notification.error({
|
||||
message: "Ошибка",
|
||||
description: e?.data?.detail || "Не удалось восстановить резервную копию",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteBackupHandler = async (backupId) => {
|
||||
try {
|
||||
await deleteBackup(backupId).unwrap();
|
||||
@ -175,10 +158,8 @@ const useBackupManageTab = () => {
|
||||
isDownloadingBackup,
|
||||
isDeletingBackup,
|
||||
isUploadingBackup,
|
||||
isRestoringBackup,
|
||||
createBackupHandler,
|
||||
downloadBackupHandler,
|
||||
restoreBackupHandler,
|
||||
deleteBackupHandler,
|
||||
uploadBackupHandler,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user