refactor bumper_confserver_api_appsvr plugin
This commit is contained in:
parent
23d79e8ea1
commit
89f4b56859
9 changed files with 202 additions and 243 deletions
|
|
@ -5,7 +5,7 @@ import pkgutil
|
|||
import socket
|
||||
import sys
|
||||
|
||||
from bumper.confserver import ConfServer, WebServerBinding
|
||||
from bumper.confserver import ConfServer, WebserverBinding
|
||||
from bumper.db import *
|
||||
from bumper.models import *
|
||||
from bumper.mqttserver import MQTTHelperBot, MQTTServer
|
||||
|
|
@ -76,8 +76,8 @@ conf_server_https_port = os.environ.get("WEB_SERVER_HTTPS_PORT") or 443
|
|||
mqtt_listen_port = 8883
|
||||
xmpp_listen_port = 5223
|
||||
conf_server_bindings = [
|
||||
WebServerBinding(bumper_listen, conf_server_https_port, True),
|
||||
WebServerBinding(bumper_listen, 8007, False),
|
||||
WebserverBinding(bumper_listen, conf_server_https_port, True),
|
||||
WebserverBinding(bumper_listen, 8007, False),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ from aiohttp.web_response import Response, StreamResponse
|
|||
|
||||
import bumper
|
||||
|
||||
from .plugins import ConfServerApp
|
||||
from .plugins import ConfServerApp, WebserverPlugin, WebserverSubApi
|
||||
from .util import get_logger
|
||||
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ logging.getLogger("aiohttp.access").addFilter(_aiohttp_filter())
|
|||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class WebServerBinding:
|
||||
"""Web server binding."""
|
||||
class WebserverBinding:
|
||||
"""Webserver binding."""
|
||||
|
||||
host: str
|
||||
port: int
|
||||
|
|
@ -59,10 +59,10 @@ class ConfServer:
|
|||
|
||||
_EXCLUDE_FROM_LOGGING = ["base", "remove-bot", "remove-client", "restart-service"]
|
||||
|
||||
def __init__(self, bindings: Union[list[WebServerBinding], WebServerBinding]):
|
||||
def __init__(self, bindings: Union[list[WebserverBinding], WebserverBinding]):
|
||||
self._runners: list[web.AppRunner] = []
|
||||
|
||||
if isinstance(bindings, WebServerBinding):
|
||||
if isinstance(bindings, WebserverBinding):
|
||||
bindings = [bindings]
|
||||
self._bindings = bindings
|
||||
|
||||
|
|
@ -109,10 +109,10 @@ class ConfServer:
|
|||
upload_api = {"prefix": "/upload/", "app": web.Application()} # for /upload/
|
||||
|
||||
apis = {
|
||||
"api_v1": api_v1,
|
||||
"api_v2": api_v2,
|
||||
"portal_api": portal_api,
|
||||
"upload_api": upload_api,
|
||||
WebserverSubApi.V1: api_v1,
|
||||
WebserverSubApi.V2: api_v2,
|
||||
WebserverSubApi.API: portal_api,
|
||||
WebserverSubApi.UPLOAD: upload_api,
|
||||
}
|
||||
|
||||
# Load plugins
|
||||
|
|
@ -122,22 +122,31 @@ class ConfServer:
|
|||
for m in inspect.getmembers(module, inspect.isclass)
|
||||
if m[1].__module__ == module.__name__
|
||||
]
|
||||
for plugin_type in plugins:
|
||||
if not issubclass(plugin_type, ConfServerApp):
|
||||
continue
|
||||
|
||||
plugin = plugin_type()
|
||||
if plugin.plugin_type == "sub_api": # app or sub_api
|
||||
if plugin.sub_api in apis:
|
||||
if plugin.routes:
|
||||
logging.debug(f"Adding confserver sub_api ({plugin.name})")
|
||||
for plugin_class in plugins:
|
||||
if issubclass(plugin_class, WebserverPlugin):
|
||||
plugin = plugin_class()
|
||||
logging.debug(
|
||||
f"Adding confserver sub_api ({plugin.__class__.__name__})"
|
||||
)
|
||||
apis[plugin.sub_api]["app"].add_routes(plugin.routes)
|
||||
elif issubclass(plugin_class, ConfServerApp):
|
||||
plugin = plugin_class()
|
||||
if plugin.plugin_type == "sub_api": # app or sub_api
|
||||
convert_api = {
|
||||
"api_v1": WebserverSubApi.V1,
|
||||
"api_v2": WebserverSubApi.V2,
|
||||
"portal_api": WebserverSubApi.API,
|
||||
"upload_api": WebserverSubApi.UPLOAD,
|
||||
}
|
||||
api = convert_api.get(plugin.sub_api, None)
|
||||
if api and plugin.routes:
|
||||
logging.debug(f"Adding confserver sub_api ({plugin.name})")
|
||||
apis[api]["app"].add_routes(plugin.routes)
|
||||
|
||||
elif plugin.plugin_type == "app":
|
||||
if plugin.path_prefix and plugin.app:
|
||||
logging.debug(f"Adding confserver plugin ({plugin.name})")
|
||||
self._app.add_subapp(plugin.path_prefix, plugin.app)
|
||||
|
||||
for api in apis:
|
||||
self._app.add_subapp(apis[api]["prefix"], apis[api]["app"])
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ def user_add_oauth(userid) -> OAuth:
|
|||
return oauth
|
||||
|
||||
|
||||
def token_by_authcode(authcode):
|
||||
def token_by_authcode(authcode: str):
|
||||
tokens = db_get().table("tokens")
|
||||
return tokens.get(Query().authcode == authcode)
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class OAuth:
|
|||
def toDB(self):
|
||||
return self.__dict__
|
||||
|
||||
def toResponse(self):
|
||||
def toResponse(self) -> dict:
|
||||
data = self.__dict__
|
||||
data["expire_at"] = convert_to_millis(
|
||||
datetime.fromisoformat(self.expire_at).timestamp()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
from abc import abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Iterable
|
||||
|
||||
from aiohttp.web_routedef import AbstractRouteDef
|
||||
|
||||
|
||||
class ConfServerApp:
|
||||
name = None
|
||||
plugin_type = None
|
||||
|
|
@ -5,3 +12,28 @@ class ConfServerApp:
|
|||
app = None
|
||||
sub_api = None
|
||||
routes = None
|
||||
|
||||
|
||||
class WebserverSubApi(str, Enum):
|
||||
"""Enum with all different sub apis."""
|
||||
|
||||
V1 = "v1"
|
||||
V2 = "v2"
|
||||
API = "api"
|
||||
UPLOAD = "upload"
|
||||
|
||||
|
||||
class WebserverPlugin:
|
||||
"""Abstract webserver plugin."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def sub_api(self) -> WebserverSubApi:
|
||||
"""Sub api."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def routes(self) -> Iterable[AbstractRouteDef]:
|
||||
"""Plugin routes."""
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
127
bumper/plugins/bumper_confserver_api_appsvr.py
Normal file
127
bumper/plugins/bumper_confserver_api_appsvr.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Api appsvr plugin module."""
|
||||
import json
|
||||
import logging
|
||||
from typing import Iterable
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.web_exceptions import HTTPInternalServerError
|
||||
from aiohttp.web_request import Request
|
||||
from aiohttp.web_response import Response
|
||||
from aiohttp.web_routedef import AbstractRouteDef
|
||||
|
||||
import bumper
|
||||
from bumper.plugins import WebserverPlugin, WebserverSubApi
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class ApiAppsvrPlugin(WebserverPlugin):
|
||||
"""Api appsvr plugin."""
|
||||
|
||||
sub_api = WebserverSubApi.API
|
||||
|
||||
@property
|
||||
def routes(self) -> Iterable[AbstractRouteDef]:
|
||||
"""Plugin routes."""
|
||||
return [
|
||||
web.route(
|
||||
"*",
|
||||
"/appsvr/app.do",
|
||||
self._handle_appsvr_app,
|
||||
),
|
||||
web.route(
|
||||
"*",
|
||||
"/appsvr/service/list",
|
||||
self._handle_appsvr_service_list,
|
||||
),
|
||||
web.route(
|
||||
"*",
|
||||
"/appsvr/oauth_callback",
|
||||
self._handle_appsvr_oauth_callback,
|
||||
),
|
||||
]
|
||||
|
||||
async def _handle_appsvr_app(self, request: Request) -> Response:
|
||||
if request.method == "GET":
|
||||
# Skip GET for now
|
||||
return web.json_response({"result": "fail", "todo": "result"})
|
||||
|
||||
try:
|
||||
if request.content_type == "application/x-www-form-urlencoded":
|
||||
postbody = await request.post()
|
||||
else:
|
||||
postbody = json.loads(await request.text())
|
||||
|
||||
todo = postbody["todo"]
|
||||
|
||||
if todo == "GetGlobalDeviceList": # EcoVacs Home
|
||||
bots = bumper.db_get().table("bots").all()
|
||||
devices = []
|
||||
for bot in bots:
|
||||
if bot["class"] != "":
|
||||
device = bumper.include_EcoVacsHomeProducts_info(bot)
|
||||
# Happens if the bot isn't on the EcoVacs Home list
|
||||
if device is not None:
|
||||
devices.append(device)
|
||||
|
||||
body = {
|
||||
"code": 0,
|
||||
"devices": devices,
|
||||
"ret": "ok",
|
||||
"todo": "result",
|
||||
}
|
||||
|
||||
return web.json_response(body)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
logging.error("Unexpected exception occurred", exc_info=True)
|
||||
|
||||
raise HTTPInternalServerError
|
||||
|
||||
async def _handle_appsvr_service_list(self, _: Request) -> Response:
|
||||
try:
|
||||
# original urls comment out as they are sub sub domain, which the current certificate is not valid
|
||||
# using url, where the certs is valid
|
||||
# data = {
|
||||
# "account": "users-base.dc-eu.ww.ecouser.net",
|
||||
# "jmq": "jmq-ngiot-eu.dc.ww.ecouser.net",
|
||||
# "lb": "lbo.ecouser.net",
|
||||
# "magw": "api-app.dc-eu.ww.ecouser.net",
|
||||
# "msgcloud": "msg-eu.ecouser.net:5223",
|
||||
# "ngiotLb": "jmq-ngiot-eu.area.ww.ecouser.net",
|
||||
# "rop": "api-rop.dc-eu.ww.ecouser.net"
|
||||
# }
|
||||
|
||||
data = {
|
||||
"account": "users-base.ecouser.net",
|
||||
"jmq": "jmq-ngiot-eu.ecouser.net",
|
||||
"lb": "lbo.ecouser.net",
|
||||
"magw": "api-app.ecouser.net",
|
||||
"msgcloud": "msg-eu.ecouser.net:5223",
|
||||
"ngiotLb": "jmq-ngiot-eu.ecouser.net",
|
||||
"rop": "api-rop.ecouser.net",
|
||||
}
|
||||
|
||||
body = {"code": 0, "data": data, "ret": "ok", "todo": "result"}
|
||||
|
||||
return web.json_response(body)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
logging.error("Unexpected exception occurred", exc_info=True)
|
||||
|
||||
raise HTTPInternalServerError
|
||||
|
||||
async def _handle_appsvr_oauth_callback(self, request: Request) -> Response:
|
||||
try:
|
||||
token = bumper.token_by_authcode(request.query["code"])
|
||||
oauth = bumper.user_add_oauth(token["userid"])
|
||||
body = {
|
||||
"code": 0,
|
||||
"data": oauth.toResponse(),
|
||||
"ret": "ok",
|
||||
"todo": "result",
|
||||
}
|
||||
|
||||
return web.json_response(body)
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
logging.error("Unexpected exception occurred", exc_info=True)
|
||||
|
||||
raise HTTPInternalServerError
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
import bumper
|
||||
from bumper.plugins import ConfServerApp
|
||||
|
||||
|
||||
class portal_api_appsvr(ConfServerApp):
|
||||
def __init__(self):
|
||||
self.name = "portal_api_appsvr"
|
||||
self.plugin_type = "sub_api"
|
||||
self.sub_api = "portal_api"
|
||||
|
||||
self.routes = [
|
||||
web.route(
|
||||
"*",
|
||||
"/appsvr/app.do",
|
||||
self.handle_appsvr_app,
|
||||
name="portal_api_appsvr_app",
|
||||
),
|
||||
web.route(
|
||||
"*",
|
||||
"/appsvr/service/list",
|
||||
self.handle_appsvr_service_list,
|
||||
name="portal_api_appsvr_service_list",
|
||||
),
|
||||
web.route(
|
||||
"*",
|
||||
"/appsvr/oauth_callback",
|
||||
self.handle_appsvr_oauth_callback,
|
||||
name="portal_api_appsvr_oauth_callback",
|
||||
),
|
||||
]
|
||||
|
||||
async def handle_appsvr_app(self, request):
|
||||
if not request.method == "GET": # Skip GET for now
|
||||
try:
|
||||
if request.content_type == "application/x-www-form-urlencoded":
|
||||
postbody = await request.post()
|
||||
else:
|
||||
postbody = json.loads(await request.text())
|
||||
|
||||
todo = postbody["todo"]
|
||||
|
||||
if todo == "GetGlobalDeviceList": # EcoVacs Home
|
||||
bots = bumper.db_get().table("bots").all()
|
||||
botlist = []
|
||||
for bot in bots:
|
||||
if bot["class"] != "":
|
||||
b = bumper.include_EcoVacsHomeProducts_info(bot)
|
||||
# Happens if the bot isn't on the EcoVacs Home list
|
||||
if b is not None:
|
||||
botlist.append(b)
|
||||
|
||||
body = {
|
||||
"code": 0,
|
||||
"devices": botlist,
|
||||
"ret": "ok",
|
||||
"todo": "result",
|
||||
}
|
||||
|
||||
return web.json_response(body)
|
||||
|
||||
# elif todo == "GetShareDeviceList":
|
||||
# example response
|
||||
# {
|
||||
# "code": 0,
|
||||
# "devices": [
|
||||
# {
|
||||
# "deviceName": "DEEBOT 900 Series (Cleaner Cleaner)",
|
||||
# "did": "did",
|
||||
# "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839",
|
||||
# "mid": "ls1ok3",
|
||||
# "ownUsers": {
|
||||
# "isMe": true,
|
||||
# "nickname": "user@gmail.com",
|
||||
# "user": "cg****"
|
||||
# },
|
||||
# "resource": "wC3g",
|
||||
# "share": true,
|
||||
# "shareUsers": []
|
||||
# }
|
||||
# ],
|
||||
# "ret": "ok",
|
||||
# "todo": "result"
|
||||
# }
|
||||
|
||||
# if shared shareUsers
|
||||
# "shareUsers": [
|
||||
# {
|
||||
# "isMe": false,
|
||||
# "nickname": "user@gmail.com",
|
||||
# "status": "sharing",
|
||||
# "user": "eafg****"
|
||||
# }
|
||||
# ]
|
||||
|
||||
# elif todo == "ShareDevice":
|
||||
# example post
|
||||
# {
|
||||
# "todo": "ShareDevice",
|
||||
# "accountType": "EMAIL",
|
||||
# "auth": {
|
||||
# "realm": "ecouser.net",
|
||||
# "resource": "res",
|
||||
# "token": "token***",
|
||||
# "userid": "cg***",
|
||||
# "with": "users"
|
||||
# },
|
||||
# "country": "US",
|
||||
# "did": "did",
|
||||
# "resource": "wC3g",
|
||||
# "username": "email to share to"
|
||||
# }
|
||||
|
||||
# fail response (no user)
|
||||
# {
|
||||
# "todo": "result",
|
||||
# "code": -3,
|
||||
# "errno": -3,
|
||||
# "ret": "fail"
|
||||
# }
|
||||
|
||||
# success response
|
||||
# {"ret":"ok","code":0,"todo":"result"}
|
||||
|
||||
# elif todo == "ShareUnRegisterDevice":
|
||||
# example post
|
||||
# {
|
||||
# "todo": "ShareUnRegisterDevice",
|
||||
# "account": "email to share to",
|
||||
# "auth": {
|
||||
# "realm": "ecouser.net",
|
||||
# "resource": "res",
|
||||
# "token": "token",
|
||||
# "userid": "userid",
|
||||
# "with": "users"
|
||||
# },
|
||||
# "country": "US",
|
||||
# "did": "did",
|
||||
# "lang": "EN",
|
||||
# "mid": "ls1ok3"
|
||||
# }
|
||||
# example response
|
||||
# {
|
||||
# "todo": "result",
|
||||
# "code": 0,
|
||||
# "data": {
|
||||
# "mailContent": "<!-- 邮件代码 --><div style=\"position: relative;\"><div style=\"font-size: 14px;font-family: Helvetica;padding: 65px 0;line-height: 150%;\"><p style=\"margin-bottom: 15px;\">Hey there,</p><p style=\"margin-bottom: 15px;\">\n\t\t\tCheck out my new awesome robot vacuum: <a href=\"#\" style=\"color: #1c95ea;text-decoration: none;\">DEEBOT 900 Series</a>!<br/>\n\t\t\tDownload the <strong>ECOVACS HOME</strong> App and sign up with your email: <a href=\"#\" style=\"color: #1c95ea;text-decoration: none;\">brian@bmartin.net</a>, so you and I can control this kick-ass robot together.</p><p style=\"margin-bottom: 15px;\">\n\t\t\tIOS: <span style=\"font-size:15px\"><a href=\"https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&ls=1&mt=8\">https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&ls=1&mt=8</a></span><br/>\n\t\t\tAndroid: <span style=\"font-size:15px\"><a href=\"https://play.google.com/store/apps/details?id=com.eco.global.app\">https://play.google.com/store/apps/details?id=com.eco.global.app</a></span></p><p style=\"margin-bottom: 15px;\">\n\t\t\tThis invitation is valid within 7 days.<br/>\n\t\t\tIf I'm sending to the wrong person, please ignore this email.</p><p style=\"margin-bottom: 15px;\">Thank you.</p></div></div><!-- 邮件代码 -->",
|
||||
# "mailTitle": "I'm sharing my DEEBOT and you're invited!"
|
||||
# },
|
||||
# "ret": "ok"
|
||||
# }
|
||||
|
||||
except Exception as e:
|
||||
logging.exception(f"{e}")
|
||||
|
||||
# Return fail for GET
|
||||
body = {"result": "fail", "todo": "result"}
|
||||
return web.json_response(body)
|
||||
|
||||
async def handle_appsvr_service_list(self, request):
|
||||
try:
|
||||
# original urls comment out as they are sub sub domain, which the current certificate is not valid
|
||||
# using url, where the certs is valid
|
||||
# data = {
|
||||
# "account": "users-base.dc-eu.ww.ecouser.net",
|
||||
# "jmq": "jmq-ngiot-eu.dc.ww.ecouser.net",
|
||||
# "lb": "lbo.ecouser.net",
|
||||
# "magw": "api-app.dc-eu.ww.ecouser.net",
|
||||
# "msgcloud": "msg-eu.ecouser.net:5223",
|
||||
# "ngiotLb": "jmq-ngiot-eu.area.ww.ecouser.net",
|
||||
# "rop": "api-rop.dc-eu.ww.ecouser.net"
|
||||
# }
|
||||
|
||||
data = {
|
||||
"account": "users-base.ecouser.net",
|
||||
"jmq": "jmq-ngiot-eu.ecouser.net",
|
||||
"lb": "lbo.ecouser.net",
|
||||
"magw": "api-app.ecouser.net",
|
||||
"msgcloud": "msg-eu.ecouser.net:5223",
|
||||
"ngiotLb": "jmq-ngiot-eu.ecouser.net",
|
||||
"rop": "api-rop.ecouser.net",
|
||||
}
|
||||
|
||||
body = {"code": 0, "data": data, "ret": "ok", "todo": "result"}
|
||||
|
||||
return web.json_response(body)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception(f"{e}")
|
||||
|
||||
async def handle_appsvr_oauth_callback(self, request):
|
||||
try:
|
||||
token = bumper.token_by_authcode(request.query["code"])
|
||||
oauth = bumper.user_add_oauth(token["userid"])
|
||||
body = {
|
||||
"code": 0,
|
||||
"data": oauth.toResponse(),
|
||||
"ret": "ok",
|
||||
"todo": "result",
|
||||
}
|
||||
|
||||
return web.json_response(body)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception(f"{e}")
|
||||
|
|
@ -4,7 +4,7 @@ import pytest
|
|||
from amqtt.client import MQTTClient
|
||||
|
||||
import bumper
|
||||
from bumper import WebServerBinding
|
||||
from bumper import WebserverBinding
|
||||
from tests import CONF_SERVER_PORT, HOST, MQTT_PORT
|
||||
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ async def mqtt_client():
|
|||
|
||||
@pytest.fixture
|
||||
async def conf_server_client(aiohttp_client):
|
||||
confserver = bumper.ConfServer(WebServerBinding(HOST, CONF_SERVER_PORT, False))
|
||||
confserver = bumper.ConfServer(WebserverBinding(HOST, CONF_SERVER_PORT, False))
|
||||
client = await aiohttp_client(confserver._app)
|
||||
|
||||
yield client
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ from unittest import mock
|
|||
import pytest
|
||||
|
||||
import bumper
|
||||
from bumper import WebServerBinding
|
||||
from bumper import WebserverBinding
|
||||
from tests import CONF_SERVER_PORT, HOST, MQTT_PORT
|
||||
|
||||
|
||||
def create_confserver():
|
||||
return bumper.ConfServer(WebServerBinding(HOST, CONF_SERVER_PORT, False))
|
||||
return bumper.ConfServer(WebserverBinding(HOST, CONF_SERVER_PORT, False))
|
||||
|
||||
|
||||
def async_return(result):
|
||||
|
|
@ -27,12 +27,12 @@ def remove_existing_db():
|
|||
|
||||
|
||||
async def test_confserver_ssl():
|
||||
conf_server = bumper.ConfServer(WebServerBinding(HOST, CONF_SERVER_PORT, True))
|
||||
conf_server = bumper.ConfServer(WebserverBinding(HOST, CONF_SERVER_PORT, True))
|
||||
await conf_server.start()
|
||||
|
||||
|
||||
async def test_confserver_no_ssl():
|
||||
conf_server = bumper.ConfServer(WebServerBinding(HOST, 11112, False))
|
||||
conf_server = bumper.ConfServer(WebserverBinding(HOST, 11112, False))
|
||||
await conf_server.start()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue