From 7bacaeb66f829a6be628bb0021c4ed7255406462 Mon Sep 17 00:00:00 2001 From: Georgy Okrokvertskhov Date: Fri, 26 Jul 2019 18:38:57 +0800 Subject: [PATCH] Add /device/../skill API support and Device info information This commit adds support for /skill API calls. Skills will be able to store and retrieve skill related information. This commit also changes the way how device information is passed from API server. It adds setting and user fields to the device information response. --- personal_mycroft_backend/backend/device.py | 29 +++++- personal_mycroft_backend/database/devices.py | 94 +++++++++++++++++++- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/personal_mycroft_backend/backend/device.py b/personal_mycroft_backend/backend/device.py index 32095ee..8ebab8c 100644 --- a/personal_mycroft_backend/backend/device.py +++ b/personal_mycroft_backend/backend/device.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import json + from flask_mail import Message from flask import request, Response @@ -172,7 +174,7 @@ def get_uuid(uuid): accessToken=result.get("accessToken"), refreshToken=result.get("refreshToken")) - result = model_to_dict(device) + result = device.as_dict else: result = {} return nice_json(result) @@ -284,4 +286,29 @@ def get_subscriber_voice_url(uuid=""): device.arch = arch return nice_json({"link": ""}) + @app.route("/" + API_VERSION + "/device//skill", methods=['GET', 'PUT']) + @noindex + @donation + @requires_auth + def skill(uuid=""): + with DeviceDatabase(SQL_DEVICES_URI, debug=DEBUG) as device_db: + device = device_db.get_device_by_uuid(uuid) + if device is not None: + if request.method == 'GET': + skills = [] + for skill in device.skills_info: + skills.append(skill.as_dict) + + return nice_json(skills) + if request.method == 'PUT': + data = request.json + skill_metadata = u'' + # Replace parsed json structure with text representation + if 'skillMetadata' in data.keys(): + skill_metadata = str(json.dumps(data['skillMetadata'])) + + data['skillMetadata'] = skill_metadata + device_db.add_skill_info(uuid, data) + + return nice_json({"link": ""}) return app diff --git a/personal_mycroft_backend/database/devices.py b/personal_mycroft_backend/database/devices.py index f42bf18..cfe0f9c 100644 --- a/personal_mycroft_backend/database/devices.py +++ b/personal_mycroft_backend/database/devices.py @@ -13,7 +13,7 @@ # limitations under the License. # from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, Text, \ - Table, Float, create_engine + Table, Float, Unicode, create_engine from sqlalchemy.orm import relationship, sessionmaker from sqlalchemy.exc import IntegrityError @@ -88,6 +88,13 @@ primary_key=True) ) +skill_info_devices = Table('skill_info_devices', Base.metadata, + Column('skill_id', ForeignKey('skillinfo.identifier'), + primary_key=True), + Column('device_id', ForeignKey('devices.uuid'), + primary_key=True) + ) + skill_configs = Table('skill_configs', Base.metadata, Column('skill_id', ForeignKey('skills.id'), primary_key=True), @@ -143,6 +150,8 @@ ) + + # classes @@ -213,12 +222,27 @@ class Device(Base): skills = relationship("Skill", back_populates="device", secondary=skill_devices) + skills_info = relationship("SkillInfo", + back_populates="device", + secondary=skill_info_devices) + metrics = relationship("Metric", back_populates="device", secondary=metrics_devices) hotwords = relationship("Hotword", back_populates="device", secondary=hotword_devices) + @property + def as_dict(self): + bucket = model_to_dict(self) + bucket['location'] = model_to_dict(self.location) + bucket['user'] = model_to_dict(self.user) + bucket['setting'] = model_to_dict(self.config) + # TODO: check API definition document + bucket['user']['uuid'] = self.user.id + return bucket + + class Metric(Base): __tablename__ = "metrics" @@ -261,6 +285,40 @@ class Skill(Base): blacklisted = Column(Boolean, default=False) +class SkillInfo(Base): + __tablename__ = "skillinfo" + identifier = Column(String, primary_key=True, nullable=False) + name = Column(String) + description = Column(String) + contributor = Column(String) + display_name = Column(String) + color = Column(String) + skill_gid = Column(String) + icon = Column(String) + skillMetadata = Column(String) + + device = relationship("Device", # order_by="devices.last_seen", + back_populates="skills_info", + secondary=skill_info_devices, uselist=False) + + @property + def as_dict(self): + # this is a placeholder + bucket = { + 'identifier': str(self.identifier), + 'name': str(self.name), + 'description': str(self.description), + 'contributor': str(self.contributor), + 'icon': str(self.icon), + 'display_name': str(self.display_name), + 'color': str(self.color), + 'skill_gid': str(self.skill_gid), + } + if self.skillMetadata is not None and len(self.skillMetadata) > 0: + bucket['skillMetadata'] = json.loads(str(self.skillMetadata)) + return bucket + + class IPAddress(Base): __tablename__ = "ips" created_at = Column(Integer, default=time.time()) @@ -570,6 +628,9 @@ def get_device_by_token(self, token): token).first() return device + def get_skill_info_by_id(self, skill_id): + return self.session.query(SkillInfo).filter(SkillInfo.identifier == skill_id).first() + def add_location(self, uuid, location_data=None): device = self.get_device_by_uuid(uuid) if device is None: @@ -736,6 +797,37 @@ def add_device(self, uuid, name=None, expires_at=None, accessToken=None, return self.commit() + def add_skill_info(self, uuid, skill_info_data): + device = self.get_device_by_uuid(uuid) + if device is None: + return False + skill_id = skill_info_data['identifier'] + skill = self.get_skill_info_by_id(str(skill_id)) + if skill is None: + skill_info = SkillInfo(identifier=str(skill_info_data['identifier']), + name=str(skill_info_data.get('name', u'')), + description=str(skill_info_data.get('description', u'')), + contributor=str(skill_info_data.get('contributor', u'')), + display_name=str(skill_info_data.get('display_name', u'')), + color=str(skill_info_data.get('color', u'')), + skill_gid=str(skill_info_data.get('skill_gid', u'')), + icon=str(skill_info_data.get('icon', u'')), + skillMetadata=str(skill_info_data.get('skillMetadata', u'')) + ) + skill_info.device = device + device.skills_info.append(skill_info) + self.session.add(skill_info) + else: + skill.name = str(skill_info_data.get('name', '')).encode('utf8') + skill.description = str(skill_info_data.get('description', u'')) + skill.contributor = str(skill_info_data.get('contributor', u'')) + skill.display_name = str(skill_info_data.get('display_name', u'')) + skill.color = str(skill_info_data.get('color', u'')) + skill.skill_gid = str(skill_info_data.get('skill_gid', u'')) + skill.icon = str(skill_info_data.get('icon', u'')) + skill.skillMetadata = str(skill_info_data.get('skillMetadata', u'')) + return self.commit() + def total_users(self): return self.session.query(User).count()