feat: Бэкапы и загрузка бэкапов админом
This commit is contained in:
parent
c70b109851
commit
b52c0d16fa
@ -71,7 +71,11 @@ async def upload_backup(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(require_admin),
|
user=Depends(require_admin),
|
||||||
):
|
):
|
||||||
backup_service = BackupService(db)
|
backup_service = BackupService(
|
||||||
|
db,
|
||||||
|
backup_dir=settings.BACKUP_DIR,
|
||||||
|
app_files_dir=settings.FILE_UPLOAD_DIR,
|
||||||
|
)
|
||||||
return await backup_service.upload_backup(file, user)
|
return await backup_service.upload_backup(file, user)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -89,8 +89,7 @@ class BackupService:
|
|||||||
file.file.seek(0)
|
file.file.seek(0)
|
||||||
self.validate_file_type(file)
|
self.validate_file_type(file)
|
||||||
|
|
||||||
if not self.validate_backup_archive(file_bytes,
|
if not self.validate_backup_archive(file_bytes, self.app_files_dir):
|
||||||
expected_app_files_dir_name=os.path.basename(self.app_files_dir)):
|
|
||||||
raise HTTPException(400, "Неверная структура архива резервной копии")
|
raise HTTPException(400, "Неверная структура архива резервной копии")
|
||||||
|
|
||||||
filename = self.generate_filename(file)
|
filename = self.generate_filename(file)
|
||||||
@ -102,6 +101,7 @@ class BackupService:
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
path=backup_path,
|
path=backup_path,
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
|
is_by_user=True,
|
||||||
)
|
)
|
||||||
await self.backup_repository.create(backup_record)
|
await self.backup_repository.create(backup_record)
|
||||||
return self.model_to_entity(backup_record)
|
return self.model_to_entity(backup_record)
|
||||||
@ -132,12 +132,15 @@ class BackupService:
|
|||||||
try:
|
try:
|
||||||
with tarfile.open(fileobj=io.BytesIO(file_bytes), mode="r:gz") as tar:
|
with tarfile.open(fileobj=io.BytesIO(file_bytes), mode="r:gz") as tar:
|
||||||
members = tar.getnames()
|
members = tar.getnames()
|
||||||
|
|
||||||
if "db_dump.sql" not in members:
|
if "db_dump.sql" not in members:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not any(name.startswith(expected_app_files_dir_name) for name in members):
|
if not any(name.startswith(expected_app_files_dir_name) for name in members):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception as e:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -156,9 +159,7 @@ class BackupService:
|
|||||||
mime = magic.Magic(mime=True)
|
mime = magic.Magic(mime=True)
|
||||||
file_type = mime.from_buffer(file.file.read(1024))
|
file_type = mime.from_buffer(file.file.read(1024))
|
||||||
file.file.seek(0)
|
file.file.seek(0)
|
||||||
content = file.file.read()
|
if file_type not in ["application/zip", "application/gzip", "application/x-gzip"]:
|
||||||
print(content.decode('utf-8'))
|
|
||||||
if file_type not in ["application/zip", "application/gzip"]:
|
|
||||||
raise HTTPException(400, "Неправильный формат файла")
|
raise HTTPException(400, "Неправильный формат файла")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -9,8 +9,7 @@ export const baseQuery = fetchBaseQuery({
|
|||||||
if (token) {
|
if (token) {
|
||||||
headers.set('Authorization', `Bearer ${token}`);
|
headers.set('Authorization', `Bearer ${token}`);
|
||||||
}
|
}
|
||||||
|
if (endpoint === 'uploadAppointmentFile' || endpoint === 'uploadBackup') {
|
||||||
if (endpoint === 'uploadAppointmentFile') {
|
|
||||||
const mutation = getState()?.api?.mutations?.[Object.keys(getState()?.api?.mutations || {})[0]];
|
const mutation = getState()?.api?.mutations?.[Object.keys(getState()?.api?.mutations || {})[0]];
|
||||||
if (mutation?.body instanceof FormData) {
|
if (mutation?.body instanceof FormData) {
|
||||||
headers.delete('Content-Type');
|
headers.delete('Content-Type');
|
||||||
|
|||||||
@ -202,7 +202,7 @@ const AppointmentFormModal = () => {
|
|||||||
fileList={appointmentFormModalUI.draftFiles}
|
fileList={appointmentFormModalUI.draftFiles}
|
||||||
beforeUpload={(file) => {
|
beforeUpload={(file) => {
|
||||||
appointmentFormModalUI.handleAddFile(file);
|
appointmentFormModalUI.handleAddFile(file);
|
||||||
return false; // Prevent auto-upload
|
return false;
|
||||||
}}
|
}}
|
||||||
onRemove={(file) => appointmentFormModalUI.handleRemoveFile(file)}
|
onRemove={(file) => appointmentFormModalUI.handleRemoveFile(file)}
|
||||||
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
|
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const BackupManageTab = () => {
|
|||||||
renderItem={(backup) => (
|
renderItem={(backup) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Typography.Text>{backup.filename}</Typography.Text>
|
<Typography.Text>{backup.filename}</Typography.Text>
|
||||||
<Typography.Text>Создан: {new Date(backup.timestamp).toLocaleString()}</Typography.Text>
|
<Typography.Text>{backup.is_by_user ? "Загружена" : "Создана"}: {new Date(backup.timestamp).toLocaleString()}</Typography.Text>
|
||||||
<Tooltip title="Скачать резервную копию">
|
<Tooltip title="Скачать резервную копию">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user