feat: export state corp package from backup endpoint
Some checks failed
CI/CD Pipeline / Quality Gate (push) Successful in 33s
CI/CD Pipeline / Build and Push Images (push) Successful in 10s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev in Dokploy (push) Failing after 9s

This commit is contained in:
2026-05-12 15:12:56 +02:00
parent 15360a3c8e
commit 75c1d4cf1a
11 changed files with 925 additions and 301 deletions

View File

@@ -1,8 +1,11 @@
"""API views экспорта защищённых backup архивов."""
from apps.backups.serializers import BackupExportRequestSerializer
from apps.backups.services import BackupExportError, BackupExportJobService
from apps.core.openapi import CommonResponses, ErrorResponses, swagger_tag
from apps.exchange.state_corp_services import (
StateCorpExchangeError,
StateCorpExchangeService,
)
from django.http import HttpResponse
from django.utils import timezone
from drf_yasg import openapi
@@ -10,14 +13,13 @@ from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
BACKUPS_TAG = swagger_tag("Резервные копии", "backups")
class BackupExportView(APIView):
"""Асинхронный экспорт защищённого backup архива."""
"""Синхронный экспорт защищённого ZIP-пакета для state-corp."""
permission_classes = [IsAdminUser]
@@ -25,16 +27,12 @@ class BackupExportView(APIView):
tags=[BACKUPS_TAG],
operation_summary="Сформировать backup архив",
operation_description=(
"Асинхронное формирование архива `.zip` на дату актуальности.\n"
"Логика endpoint:\n"
"- если задача на дату уже выполняется: вернуть 'подождите'\n"
"- если задачи нет: запустить формирование\n"
"- если задача завершена успешно: отдать архив и удалить его\n\n"
"Синхронное формирование архива `.zip` на дату актуальности.\n"
"Внутри архива:\n"
"- бинарный зашифрованный backup `.bin`\n"
"- бинарный зашифрованный пакет `.bin`\n"
"- контрольная сумма `.sha256`\n\n"
"Экспортирует только актуальные организации из реестров на указанную дату "
"и все связанные записи из parser-таблиц."
"Экспортирует организации из реестров Росатом и Роскосмос на указанную "
"дату и связанные записи из parser-таблиц в контракте импорта state-corp."
),
request_body=BackupExportRequestSerializer,
responses={
@@ -42,21 +40,6 @@ class BackupExportView(APIView):
description="Готовый backup-архив (application/zip)",
schema=openapi.Schema(type=openapi.TYPE_FILE),
),
202: openapi.Response(
description="Задача запущена или выполняется",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_STRING),
"message": openapi.Schema(type=openapi.TYPE_STRING),
"actual_date": openapi.Schema(
type=openapi.TYPE_STRING,
format=openapi.FORMAT_DATE,
),
"task_id": openapi.Schema(type=openapi.TYPE_STRING),
},
),
),
400: CommonResponses.BAD_REQUEST,
**ErrorResponses.ADMIN,
},
@@ -69,40 +52,23 @@ class BackupExportView(APIView):
)
try:
result = BackupExportJobService.check_or_start_job(
actual_date=actual_date,
requested_by_id=request.user.id
if request.user.is_authenticated
else None,
)
except BackupExportError as exc:
package = StateCorpExchangeService.build_package(actual_date=actual_date)
except StateCorpExchangeError as exc:
raise ValidationError({"backup": str(exc)}) from exc
if result.action in {"started", "wait"}:
return Response(
{
"status": result.action,
"message": result.message,
"actual_date": result.actual_date.isoformat(),
"task_id": result.task_id,
},
status=status.HTTP_202_ACCEPTED,
)
try:
artifact = BackupExportJobService.consume_ready_archive(
actual_date=result.actual_date
)
except BackupExportError as exc:
raise ValidationError({"backup": str(exc)}) from exc
response = HttpResponse(artifact.archive_bytes, content_type="application/zip")
response = HttpResponse(package.archive_bytes, content_type="application/zip")
response.status_code = status.HTTP_200_OK
response[
"Content-Disposition"
] = f'attachment; filename="{artifact.archive_filename}"'
response["X-Backup-SHA256"] = artifact.checksum_sha256
response["X-Backup-Checksum-File"] = artifact.checksum_filename
response["X-Backup-Organizations"] = str(artifact.organizations_count)
response["X-Backup-Actual-Date"] = artifact.actual_date.isoformat()
] = f'attachment; filename="{package.archive_name}"'
response["Content-Length"] = str(len(package.archive_bytes))
response["X-State-Corp-Package-Id"] = package.package_id
response["X-State-Corp-Bin-File"] = package.bin_name
response["X-State-Corp-Organizations"] = str(
package.payload_counts.get("organizations", 0)
)
response["X-Backup-Organizations"] = str(
package.payload_counts.get("organizations", 0)
)
response["X-Backup-Actual-Date"] = actual_date.isoformat()
return response