diff options
Diffstat (limited to 'src/home/web_api/web_api.py')
-rw-r--r-- | src/home/web_api/web_api.py | 213 |
1 files changed, 0 insertions, 213 deletions
diff --git a/src/home/web_api/web_api.py b/src/home/web_api/web_api.py deleted file mode 100644 index 6b8c54e..0000000 --- a/src/home/web_api/web_api.py +++ /dev/null @@ -1,213 +0,0 @@ -import logging -import json -import os.path - -from datetime import datetime, timedelta -from typing import Optional - -from werkzeug.exceptions import HTTPException -from flask import Flask, request, Response - -from ..config import config, is_development_mode -from ..database import BotsDatabase, SensorsDatabase -from ..util import stringify, format_tb -from ..api.types import BotType, TemperatureSensorLocation, SoundSensorLocation -from ..media import SoundRecordStorage - -db: Optional[BotsDatabase] = None -sensors_db: Optional[SensorsDatabase] = None -app = Flask(__name__) -logger = logging.getLogger(__name__) - - -class AuthError(Exception): - def __init__(self, message: str): - super().__init__() - self.message = message - - -# api methods -# ----------- - -@app.route("/") -def hello(): - message = "nothing here, keep lurking" - if is_development_mode(): - message += ' (dev mode)' - return message - - -@app.route('/api/sensors/data/', methods=['GET']) -def sensors_data(): - hours = request.args.get('hours', type=int, default=1) - sensor = TemperatureSensorLocation(request.args.get('sensor', type=int)) - - if hours < 1 or hours > 24: - raise ValueError('invalid hours value') - - dt_to = datetime.now() - dt_from = dt_to - timedelta(hours=hours) - - data = sensors_db.get_temperature_recordings(sensor, (dt_from, dt_to)) - return ok(data) - - -@app.route('/api/sound_sensors/hits/', methods=['GET']) -def get_sound_sensors_hits(): - location = SoundSensorLocation(request.args.get('location', type=int)) - - after = request.args.get('after', type=int) - kwargs = {} - if after is None: - last = request.args.get('last', type=int) - if last is None: - raise ValueError('you must pass `after` or `last` params') - else: - if not 0 < last < 100: - raise ValueError('invalid last value: must be between 0 and 100') - kwargs['last'] = last - else: - kwargs['after'] = datetime.fromtimestamp(after) - - data = db.get_sound_hits(location, **kwargs) - return ok(data) - - -@app.route('/api/sound_sensors/hits/', methods=['POST']) -def post_sound_sensors_hits(): - hits = [] - for hit, count in json.loads(request.form.get('hits', type=str)): - if not hasattr(SoundSensorLocation, hit.upper()): - raise ValueError('invalid sensor location') - if count < 1: - raise ValueError(f'invalid count: {count}') - hits.append((SoundSensorLocation[hit.upper()], count)) - - db.add_sound_hits(hits, datetime.now()) - return ok() - - -@app.route('/api/logs/bot-request/', methods=['POST']) -def log_bot_request(): - user_id = request.form.get('user_id', type=int, default=0) - message = request.form.get('message', type=str, default='') - bot = BotType(request.form.get('bot', type=int)) - - # validate message - if message.strip() == '': - raise ValueError('message can\'t be empty') - - # add record to the database - db.add_request(bot, user_id, message) - - return ok() - - -@app.route('/api/logs/openwrt/', methods=['POST']) -def log_openwrt(): - logs = request.form.get('logs', type=str, default='') - - # validate it - logs = json.loads(logs) - assert type(logs) is list, "invalid json data (list expected)" - - lines = [] - for line in logs: - assert type(line) is list, "invalid line type (list expected)" - assert len(line) == 2, f"expected 2 items in line, got {len(line)}" - assert type(line[0]) is int, "invalid line[0] type (int expected)" - assert type(line[1]) is str, "invalid line[1] type (str expected)" - - lines.append(( - datetime.fromtimestamp(line[0]), - line[1] - )) - - db.add_openwrt_logs(lines) - return ok() - - -@app.route('/api/recordings/list/', methods=['GET']) -def recordings_list(): - extended = request.args.get('extended', type=bool, default=False) - node = request.args.get('node', type=str) - - root = os.path.join(config['recordings']['directory'], node) - if not os.path.isdir(root): - raise ValueError(f'invalid node {node}: no such directory') - - storage = SoundRecordStorage(root) - files = storage.getfiles(as_objects=extended) - if extended: - files = list(map(lambda file: file.__dict__(), files)) - - return ok(files) - - -# internal functions -# ------------------ - -def ok(data=None) -> Response: - response = {'result': 'ok'} - if data is not None: - response['data'] = data - return Response(stringify(response), - mimetype='application/json') - - -def err(e) -> Response: - error = { - 'type': e.__class__.__name__, - 'message': e.message if hasattr(e, 'message') else str(e) - } - if is_development_mode(): - tb = format_tb(e) - if tb: - error['stacktrace'] = tb - data = { - 'result': 'error', - 'error': error - } - return Response(stringify(data), mimetype='application/json') - - -def get_token() -> Optional[str]: - name = 'X-Token' - if name in request.headers: - return request.headers[name] - - token = request.args.get('token', default='', type=str) - if token != '': - return token - - return None - - -@app.errorhandler(Exception) -def handle_exception(e): - if isinstance(e, HTTPException): - return e - return err(e), 500 - - -@app.before_request -def validate_token() -> None: - if request.path.startswith('/api/') and not is_development_mode(): - token = get_token() - if not token: - raise AuthError(f'token is missing') - - if token != config['api']['token']: - raise AuthError('invalid token') - - -def get_app(): - global db, sensors_db - - config.load('web_api') - app.config.from_mapping(**config['flask']) - - db = BotsDatabase() - sensors_db = SensorsDatabase() - - return app |