From 44b6cc61e64e8f372ffb47229abc7ce25f080c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Wed, 15 Nov 2023 14:54:21 +0100 Subject: [PATCH] add new firmware_upload api definition, not implemented yet --- src/c3nav/api/newauth.py | 2 +- src/c3nav/mesh/newapi.py | 75 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/c3nav/api/newauth.py b/src/c3nav/api/newauth.py index 3d5606b5..774acccc 100644 --- a/src/c3nav/api/newauth.py +++ b/src/c3nav/api/newauth.py @@ -5,7 +5,7 @@ from django.db.models import Q from ninja.security import HttpBearer from c3nav import settings -from c3nav.api.exceptions import APITokenInvalid, APIPermissionDenied +from c3nav.api.exceptions import APIPermissionDenied, APITokenInvalid from c3nav.api.schema import APIErrorSchema from c3nav.control.models import UserPermissions diff --git a/src/c3nav/mesh/newapi.py b/src/c3nav/mesh/newapi.py index c30ff48d..04aef977 100644 --- a/src/c3nav/mesh/newapi.py +++ b/src/c3nav/mesh/newapi.py @@ -1,11 +1,15 @@ +import base64 from datetime import datetime +from django.db import transaction from ninja import Field as APIField +from ninja import ModelSchema from ninja import Router as APIRouter -from ninja import Schema +from ninja import Schema, UploadedFile from ninja.pagination import paginate +from pydantic import validator -from c3nav.api.newauth import auth_responses +from c3nav.api.newauth import BearerAuth, auth_permission_responses, auth_responses from c3nav.mesh.dataformats import BoardType from c3nav.mesh.messages import ChipType from c3nav.mesh.models import FirmwareVersion @@ -15,6 +19,7 @@ api_router = APIRouter(tags=["mesh"]) class FirmwareBuildSchema(Schema): id: int + variant: str = APIField(..., example="c3uart") chip: ChipType = APIField(..., example=ChipType.ESP32_C3.name) sha256_hash: str = APIField(..., regex=r"^[0-9a-f]{64}$") url: str = APIField(..., alias="binary", example="/media/firmware/012345/firmware.bin") @@ -34,6 +39,12 @@ class FirmwareSchema(Schema): created: datetime builds: list[FirmwareBuildSchema] + @validator('builds') + def builds_variants_must_be_unique(cls, builds): + if len(set(build.variant for build in builds)) != len(builds): + raise ValueError("builds must have unique variant identifiers") + return builds + class Error(Schema): detail: str @@ -53,3 +64,63 @@ def firmware_detail(request, firmware_id: int): return FirmwareVersion.objects.get(id=firmware_id) except FirmwareVersion.DoesNotExist: return 404, {"detail": "firmware not found"} + + +class Base64Bytes(bytes): + @classmethod + def __get_validators__(cls): + # one or more validators may be yielded which will be called in the + # order to validate the input, each validator will receive as an input + # the value returned from the previous validator + yield cls.validate + + @classmethod + def __modify_schema__(cls, field_schema): + # __modify_schema__ should mutate the dict it receives in place, + # the returned value will be ignored + field_schema.update( + # simplified regex here for brevity, see the wikipedia link above + pattern='^[A-Z]{1,2}[0-9][A-Z0-9]? ?[0-9][A-Z]{2}$', + # some example postcodes + examples=['SP11 9DG', 'w1j7bu'], + ) + + @classmethod + def validate(cls, v): + if not isinstance(v, str): + raise TypeError('string required') + return cls(base64.b64decode(v.encode("ascii"))) + + def __repr__(self): + return f'PostCode({super().__repr__()})' + + +class UploadFirmwareBuildSchema(Schema): + variant: str = APIField(..., example="c3uart") + chip: ChipType = APIField(..., example=ChipType.ESP32_C3.name) + sha256_hash: str = APIField(..., regex=r"^[0-9a-f]{64}$") + boards: list[BoardType] = APIField(..., example=[BoardType.C3NAV_LOCATION_PCB_REV_0_2.name, ]) + binary: bytes = APIField(..., example="base64", contentEncoding="base64") + + @validator('binary') + def get_binary_base64(cls, binary): + return base64.b64decode(binary.encode()) + + +class UploadFirmwareSchema(Schema): + project_name: str = APIField(..., example="c3nav_positioning") + version: str = APIField(..., example="499837d-dirty") + idf_version: str = APIField(..., example="v5.1-476-g3187b8b326") + builds: list[UploadFirmwareBuildSchema] = APIField(..., min_items=1) + + @validator('builds') + def builds_variants_must_be_unique(cls, builds): + if len(set(build.variant for build in builds)) != len(builds): + raise ValueError("builds must have unique variant identifiers") + return builds + + +@api_router.post('/firmwares/upload', summary="Upload firmware", auth=BearerAuth(superuser=True), + response={200: FirmwareSchema, **auth_permission_responses}) +def firmware_upload(request, firmware_data: UploadFirmwareSchema): + raise NotImplementedError