feat: add device registration observation
This commit is contained in:
@@ -4,6 +4,7 @@ from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.billing.models import RechargeOrder
|
||||
from apps.licensing.models import ClientDevice
|
||||
|
||||
|
||||
class GenerateTitleRequestSerializer(serializers.Serializer):
|
||||
@@ -129,3 +130,28 @@ class RechargeStatusRequestSerializer(serializers.Serializer):
|
||||
allow_blank=False,
|
||||
max_length=64,
|
||||
)
|
||||
|
||||
|
||||
class DeviceRegistrationRequestSerializer(serializers.Serializer):
|
||||
product_code = serializers.ChoiceField(choices=ClientDevice.ProductCode.values)
|
||||
device_id = serializers.CharField(
|
||||
trim_whitespace=True,
|
||||
allow_blank=False,
|
||||
max_length=256,
|
||||
)
|
||||
device_id_version = serializers.CharField(
|
||||
trim_whitespace=True,
|
||||
allow_blank=False,
|
||||
max_length=32,
|
||||
)
|
||||
installation_public_key = serializers.CharField(
|
||||
trim_whitespace=True,
|
||||
allow_blank=False,
|
||||
max_length=4096,
|
||||
)
|
||||
platform = serializers.ChoiceField(choices=ClientDevice.Platform.values)
|
||||
client_version = serializers.CharField(
|
||||
trim_whitespace=True,
|
||||
allow_blank=False,
|
||||
max_length=64,
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@ from .views import (
|
||||
AnalyzeImagesView,
|
||||
BalanceView,
|
||||
ClientLatestReleaseView,
|
||||
DeviceHeartbeatView,
|
||||
DeviceRegistrationView,
|
||||
GenerateImageTaskDetailView,
|
||||
GenerateImageTaskSubmitView,
|
||||
GenerateImageView,
|
||||
@@ -18,6 +20,16 @@ from .views import (
|
||||
urlpatterns = [
|
||||
path("v1/balance", BalanceView.as_view(), name="api-balance"),
|
||||
path("v1/models", ModelsView.as_view(), name="api-models"),
|
||||
path(
|
||||
"v1/client/devices/register",
|
||||
DeviceRegistrationView.as_view(),
|
||||
name="api-client-device-register",
|
||||
),
|
||||
path(
|
||||
"v1/client/devices/heartbeat",
|
||||
DeviceHeartbeatView.as_view(),
|
||||
name="api-client-device-heartbeat",
|
||||
),
|
||||
path(
|
||||
"v1/client/releases/latest",
|
||||
ClientLatestReleaseView.as_view(),
|
||||
|
||||
@@ -27,6 +27,7 @@ from apps.api.image_tasks import (
|
||||
from apps.api.models import ImageGenerationTask
|
||||
from apps.api.serializers import (
|
||||
AnalyzeImagesRequestSerializer,
|
||||
DeviceRegistrationRequestSerializer,
|
||||
GenerateImageRequestSerializer,
|
||||
GenerateTitleRequestSerializer,
|
||||
RechargeCreateRequestSerializer,
|
||||
@@ -63,6 +64,12 @@ from apps.billing.services import (
|
||||
query_and_apply_recharge_payment,
|
||||
)
|
||||
from apps.portal.models import DownloadRelease
|
||||
from apps.licensing.authentication import DeviceSessionAuthentication
|
||||
from apps.licensing.services import (
|
||||
DeviceRegistrationError,
|
||||
record_device_heartbeat,
|
||||
register_device,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -263,6 +270,82 @@ class ModelsView(ExternalApiView):
|
||||
)
|
||||
|
||||
|
||||
def _device_response(device) -> dict:
|
||||
return {
|
||||
"product_code": device.product_code,
|
||||
"platform": device.platform,
|
||||
"client_version": device.client_version,
|
||||
"status": device.status,
|
||||
"first_seen_at": device.first_seen_at.isoformat(),
|
||||
"last_seen_at": device.last_seen_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
class DeviceRegistrationView(ExternalApiView):
|
||||
def post(self, request):
|
||||
serializer = DeviceRegistrationRequestSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(
|
||||
api_error("bad_request", "参数错误"),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
data = serializer.validated_data
|
||||
try:
|
||||
result = register_device(
|
||||
user=request.user,
|
||||
api_key=request.auth,
|
||||
product_code=data["product_code"],
|
||||
device_id_version=data["device_id_version"],
|
||||
device_id=data["device_id"],
|
||||
public_key=data["installation_public_key"],
|
||||
platform=data["platform"],
|
||||
client_version=data["client_version"],
|
||||
)
|
||||
except DeviceRegistrationError as exc:
|
||||
return Response(
|
||||
api_error(exc.code, exc.message),
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"device": _device_response(result.device),
|
||||
"device_session_token": result.session_token,
|
||||
"expires_at": result.session.expires_at.isoformat(),
|
||||
},
|
||||
status=status.HTTP_201_CREATED if result.created else status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class DeviceSessionApiView(APIView):
|
||||
authentication_classes = (DeviceSessionAuthentication,)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def permission_denied(self, request, message=None, code=None):
|
||||
if request.authenticators and not request.successful_authenticator:
|
||||
raise DeviceSessionAuthentication.authentication_failed(request)
|
||||
super().permission_denied(request, message=message, code=code)
|
||||
|
||||
|
||||
class DeviceHeartbeatView(DeviceSessionApiView):
|
||||
def post(self, request):
|
||||
try:
|
||||
updated = record_device_heartbeat(request.auth)
|
||||
except DeviceRegistrationError as exc:
|
||||
return Response(
|
||||
api_error(exc.code, exc.message),
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
request.auth.device.refresh_from_db()
|
||||
return Response(
|
||||
{
|
||||
"device": _device_response(request.auth.device),
|
||||
"activity_updated": updated,
|
||||
"expires_at": request.auth.expires_at.isoformat(),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
def _release_unpublished_response(platform: str) -> dict:
|
||||
return {
|
||||
"platform": platform,
|
||||
|
||||
Reference in New Issue
Block a user