snorklerjoe/CubeServer

View on GitHub
src/CubeServer-api/cubeserver_api/beacon_resources.py

Summary

Maintainability
A
0 mins
Test Coverage
"""This package contains the resource classes for the api used by the beacon
"""

from datetime import datetime
from time import time
import logging

from flask import request
from flask_restful import Resource
from json import dumps, loads, decoder
from base64 import encodebytes

from cubeserver_common.models.config.rules import Rules
from cubeserver_common.models.config.conf import Conf
from cubeserver_common.models.team import Team, TeamStatus
from cubeserver_common.models.beaconmessage import BeaconMessage, SentStatus
from cubeserver_common.models.datapoint import DataClass, DataPoint
from cubeserver_common import config
from cubeserver_common.metadata import VERSION

from .auth import auth, internal, check_secret_header


class NextMessage(Resource):
    """
    For the beacon API, a GET request to /beacon/message/next_queued with proper credentials should yield a json response along the lines of
    {"id": <hex string representing the object id>, "timestamp": <epoch timestamp as a decimal>, "offset": <seconds from request until instant>, "destination": <"Infrared"|"Visible">, "intensity": <8-bit integer>, "message": <the message as a UTF-8 str>}
    I think the message for the purposes of simplicity in the short term can be conveyed as a string but this might be changed in the long term for sending other data over the beacon.
    Other than the timestamp and offset, the other items are the same values as what would be transferred by the command system.
    The response will only have information for the next soonest beacon message that has the status of "queued" in the database.
    """

    decorators = [auth.login_required, check_secret_header]

    @internal
    def get(self):
        logging.debug(f"Next message get req from {auth.username()}")
        team: Team = Team.find_by_name(auth.username())
        # data_str = request.get_json()
        # logging.debug(f"Request: {data_str}")
        # if data_str is None:
        #     data_str = loads(request.form['data'])

        message: BeaconMessage = BeaconMessage.find_one_queued()
        if message is None:
            return None, 404

        return {
            "id": str(message.id),
            "timestamp": message.send_at.timestamp(),
            "offset": (message.send_at - datetime.now()).total_seconds(),
            "destination": message.destination.value,
            "intensity": message.intensity,
            "message": message.full_message_bytes.decode("utf-8"),
            "status": message.status.value,
        }, 200


class Message(Resource):
    """
    For the beacon API, a POST request to /beacon/message with proper credentials should yield a json response along the lines of
    {"id": <hex string representing the object id>, "timestamp": <epoch timestamp as a decimal>, "offset": <seconds from request until instant>, "destination": <"Infrared"|"Visible">, "intensity": <8-bit integer>, "message": <the message as a UTF-8 str>}
    I think the message for the purposes of simplicity in the short term can be conveyed as a string but this might be changed in the long term for sending other data over the beacon.
    Other than the timestamp and offset, the other items are the same values as what would be transferred by the command system.
    The response will only have information for the next soonest beacon message that has the status of "queued" in the database.

    A particular message (endpoint /beacon/message/<id>) can be updated with a PUT request with proper credentials. The request should have a json body with the following format:
    {"status": <"Queued"|"Scheduled"|"Transmitting..."|"Transmitted"|"Failed">}
    The response will be a json object with the same format as the GET request.
    """

    decorators = [auth.login_required, check_secret_header]

    @internal
    def put(self, message_id: str):
        logging.debug(f"Message put req from {auth.username()}")
        data_str = request.get_json()
        logging.debug(f"Request: {data_str}")
        if data_str is None:
            data_str = loads(request.form["data"])
        message: BeaconMessage = BeaconMessage.find_by_id(message_id)
        try:
            message.status = SentStatus(
                data_str["status"]
            )  # TODO: Support other fields
        except ValueError:
            logging.debug("Attempted to set invalid status")
            return None, 400
        message.save()
        return {
            "id": str(message.id),
            "timestamp": message.send_at.timestamp(),
            "offset": (message.send_at - datetime.now()).total_seconds(),
            "destination": message.destination.value,
            "intensity": message.intensity,
            "message": message.full_message_bytes.decode("utf-8"),
            "status": message.status.value,
        }, 200

    @internal
    def get(self, message_id: str):
        logging.debug(f"Message get req from {auth.username()}")
        message: BeaconMessage = BeaconMessage.find_by_id(message_id)
        return {
            "id": str(message.id),
            "timestamp": message.send_at.timestamp(),
            "offset": (message.send_at - datetime.now()).total_seconds(),
            "destination": message.destination.value,
            "intensity": message.intensity,
            "message": message.full_message_bytes.decode("utf-8"),
            "status": message.status.value,
        }, 200