diff options
Diffstat (limited to 'src/web_api.py')
-rwxr-xr-x | src/web_api.py | 239 |
1 files changed, 0 insertions, 239 deletions
diff --git a/src/web_api.py b/src/web_api.py deleted file mode 100755 index 0ddc6bd..0000000 --- a/src/web_api.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python3 -import asyncio -import json -import os - -from datetime import datetime, timedelta - -from aiohttp import web -from home import http -from home.config import config, is_development_mode -from home.database import BotsDatabase, SensorsDatabase, InverterDatabase -from home.database.inverter_time_formats import * -from home.api.types import BotType, TemperatureSensorLocation, SoundSensorLocation -from home.media import SoundRecordStorage - - -def strptime_auto(s: str) -> datetime: - e = None - for fmt in (FormatTime, FormatDate): - try: - return datetime.strptime(s, fmt) - except ValueError as _e: - e = _e - raise e - - -class AuthError(Exception): - def __init__(self, message: str): - super().__init__() - self.message = message - - -class WebAPIServer(http.HTTPServer): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.app.middlewares.append(self.validate_auth) - - self.get('/', self.get_index) - self.get('/sensors/data/', self.GET_sensors_data) - self.get('/sound_sensors/hits/', self.GET_sound_sensors_hits) - self.post('/sound_sensors/hits/', self.POST_sound_sensors_hits) - - self.post('/log/bot_request/', self.POST_bot_request_log) - self.post('/log/openwrt/', self.POST_openwrt_log) - - self.get('/inverter/consumed_energy/', self.GET_consumed_energy) - self.get('/inverter/grid_consumed_energy/', self.GET_grid_consumed_energy) - - self.get('/recordings/list/', self.GET_recordings_list) - - @staticmethod - @web.middleware - async def validate_auth(req: http.Request, handler): - def get_token() -> str: - name = 'X-Token' - if name in req.headers: - return req.headers[name] - - return req.query['token'] - - try: - token = get_token() - except KeyError: - raise AuthError('no token') - - if token != config['api']['token']: - raise AuthError('invalid token') - - return await handler(req) - - @staticmethod - async def get_index(req: http.Request): - message = "nothing here, keep lurking" - if is_development_mode(): - message += ' (dev mode)' - return http.Response(text=message, content_type='text/plain') - - async def GET_sensors_data(self, req: http.Request): - try: - hours = int(req.query['hours']) - if hours < 1 or hours > 24: - raise ValueError('invalid hours value') - except KeyError: - hours = 1 - - sensor = TemperatureSensorLocation(int(req.query['sensor'])) - - dt_to = datetime.now() - dt_from = dt_to - timedelta(hours=hours) - - db = SensorsDatabase() - data = db.get_temperature_recordings(sensor, (dt_from, dt_to)) - return self.ok(data) - - async def GET_sound_sensors_hits(self, req: http.Request): - location = SoundSensorLocation(int(req.query['location'])) - - after = int(req.query['after']) - kwargs = {} - if after is None: - last = int(req.query['last']) - 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 = BotsDatabase().get_sound_hits(location, **kwargs) - return self.ok(data) - - async def POST_sound_sensors_hits(self, req: http.Request): - hits = [] - data = await req.post() - for hit, count in json.loads(data['hits']): - 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)) - - BotsDatabase().add_sound_hits(hits, datetime.now()) - return self.ok() - - async def POST_bot_request_log(self, req: http.Request): - data = await req.post() - - try: - user_id = int(data['user_id']) - except KeyError: - user_id = 0 - - try: - message = data['message'] - except KeyError: - message = '' - - bot = BotType(int(data['bot'])) - - # validate message - if message.strip() == '': - raise ValueError('message can\'t be empty') - - # add record to the database - BotsDatabase().add_request(bot, user_id, message) - - return self.ok() - - async def POST_openwrt_log(self, req: http.Request): - data = await req.post() - - try: - logs = data['logs'] - ap = int(data['ap']) - except KeyError: - logs = '' - ap = 0 - - # 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] - )) - - BotsDatabase().add_openwrt_logs(lines, ap) - return self.ok() - - async def GET_recordings_list(self, req: http.Request): - data = await req.post() - - try: - extended = bool(int(data['extended'])) - except KeyError: - extended = False - - node = data['node'] - - 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 self.ok(files) - - @staticmethod - def _get_inverter_from_to(req: http.Request): - s_from = req.query['from'] - s_to = req.query['to'] - - dt_from = strptime_auto(s_from) - - if s_to == 'now': - dt_to = datetime.now() - else: - dt_to = strptime_auto(s_to) - - return dt_from, dt_to - - async def GET_consumed_energy(self, req: http.Request): - dt_from, dt_to = self._get_inverter_from_to(req) - wh = InverterDatabase().get_consumed_energy(dt_from, dt_to) - return self.ok(wh) - - async def GET_grid_consumed_energy(self, req: http.Request): - dt_from, dt_to = self._get_inverter_from_to(req) - wh = InverterDatabase().get_grid_consumed_energy(dt_from, dt_to) - return self.ok(wh) - - -# start of the program -# -------------------- - -if __name__ == '__main__': - _app_name = 'web_api' - if is_development_mode(): - _app_name += '_dev' - config.load(_app_name) - - loop = asyncio.get_event_loop() - - server = WebAPIServer(config.get_addr('server.listen')) - server.run() |