Skip to content

Commit

Permalink
Refactor Code & Add Data Modifying #5
Browse files Browse the repository at this point in the history
- Add data modifying
- Add types
- Separate to folders and files
- Add very simple admin auth
  • Loading branch information
haimkastner committed Jun 13, 2020
1 parent 246832b commit a658034
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 68 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
DATABASE_URL="mongodb://<dbuser>:<dbpassword>@<ip>:<port>/ir-commands"
DATABASE_URL="mongodb://<dbuser>:<dbpassword>@<ip>:<port>/ir-commands"
ADMIN_API_KEY="some admin key"
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true
"python.linting.enabled": true,
"cSpell.words": [
"jsonify"
]
}
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ Simple, light-weight server for RF commands (such as: IR, 433 MHz etc.) for appl

# running server

* in project directory press `pip install -r requirements.txt`
* create MongoDB database named `ir-commands`.
* create collection named `commands`.
* set `DATABASE_URL` environment variable the MongoDB URL,
* run it using `python app.py`.
* in production run is recomended using `gunicorn`.
* To clean current dependencies in the machine uninstall all current system packages `pip uninstall -r requirements.txt -y && pip freeze > requirements.txt`
* In project directory press `pip install -r requirements.txt`
* Create MongoDB database named `ir-commands`.
* Create collection named `commands`.
* Set `DATABASE_URL` environment variable the MongoDB URL,
* Run it using `python app.py`.
* In production run is recomended using `gunicorn`.

# technologies
The Server is Build with Python, with the Flask framework for the HTTP Routing, MongoDB for the data storing.
Expand Down
31 changes: 4 additions & 27 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
from flask import Flask, jsonify
from flask import Flask, jsonify, request, abort
import os
import settings
from data import get_devices, get_device_commands
import route.rf_route
import route.device_route
from route.rest_common import app

app = Flask(__name__)

@app.after_request
def remove_header(response):
# Hide server info from possilbe attakers.
response.headers['Server'] = ''
return response

# Get all supported devices in system
@app.route('/devices')
def models_list():
return jsonify(get_devices())

# Get RF commands of a devices.
@app.route('/rf/<brand>/<model>')
def model_rf_commands(brand, model):
return jsonify(get_device_commands(brand=brand, model=model))


# Get security help info
@app.route('/.well-known/security.txt')
def security_info():
return app.send_static_file('.well-known/security.txt')


# Get PORT from env.
PORT = os.getenv("PORT")
if not PORT:
Expand Down
32 changes: 0 additions & 32 deletions data.py

This file was deleted.

47 changes: 47 additions & 0 deletions data/devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from data.models import RfDevice


def is_device_exists(brand: str, model: str) -> bool:
try:
RfDevice.objects.get(brand=brand, model=model)
return True
except:
return False


def get_devices() -> list:
devices = []
for device in RfDevice.objects:
record = {
'brand': device.brand,
'model': device.model,
'category': device.category,
}
devices.append(record)
return devices


def create_device(device) -> None:
brand = device['brand']
model = device['model']
if is_device_exists(brand=brand, model=model):
raise Exception("Sorry, the device already exists")

newDevice = RfDevice(brand=brand, model=model,
category=device['category'], commands=device['commands'])
newDevice.save()


def edit_device(brand: str, model: str, device: dict) -> None:
if not is_device_exists(brand=brand, model=model):
raise Exception("Sorry, the device is not exists")
device = RfDevice.objects.get(brand=brand, model=model)
device['brand'] = newName['brand']
device['model'] = newName['model']
device['category'] = newName['category']
device.save()


def delete_device(brand: str, model: str) -> None:
device = RfDevice.objects.get(brand=brand, model=model)
device.delete()
18 changes: 18 additions & 0 deletions data/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from mongoengine import *
import os

DATABASE_URL = os.getenv("DATABASE_URL")

# Connect mongoengine driver to the database
connect(host=DATABASE_URL)


class RfDevice(Document):
"""Devices document objects model."""
meta = {
'collection': 'commands'
}
brand = StringField(required=True)
model = StringField(required=True)
category = StringField(required=True)
commands = DynamicField(required=True)
13 changes: 13 additions & 0 deletions data/rf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

from data.models import RfDevice


def get_device_commands(brand: str, model: str) -> dict:
device = RfDevice.objects.get(brand=brand, model=model)
return device.commands


def set_device_commands(brand: str, model: str, data: dict) -> None:
device = RfDevice.objects.get(brand=brand, model=model)
device.commands = data['commands']
device.save()
Binary file modified requirements.txt
Binary file not shown.
35 changes: 35 additions & 0 deletions route/device_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from flask import Flask, jsonify, request, abort
from route.rest_common import admin_scope, commands_schema, device_schema, editDevice_schema, app
from flask_expects_json import expects_json
from data.devices import edit_device, create_device, delete_device, get_devices


@app.route('/devices', methods=['GET'])
def models_list_route():
return jsonify(get_devices())


@app.route('/device', methods=['POST'])
@expects_json(device_schema)
@admin_scope()
def create_device_route():
create_device(request.json)
resp = jsonify(success=True)
return resp


@app.route('/device/<brand>/<model>', methods=['PUT'])
@admin_scope()
@expects_json(editDevice_schema)
def set_device_route(brand: str, model: str):
edit_device(brand=brand, model=model, newDevice=request.json)
resp = jsonify(success=True)
return resp


@app.route('/device/<brand>/<model>', methods=['DELETE'])
@admin_scope()
def delete_device_route(brand: str, model: str):
delete_device(brand=brand, model=model)
resp = jsonify(success=True)
return resp
70 changes: 70 additions & 0 deletions route/rest_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from flask import Flask, jsonify, request, abort
from functools import wraps
from settings import app_name
import os
import sys

app = Flask(app_name)

ADMIN_API_KEY = os.getenv("ADMIN_API_KEY")
if not ADMIN_API_KEY:
msg = 'ADMIN_API_KEY not exists... exiting'
print(msg)
sys.exit(msg)

device_schema = {
"type": "object",
"properties": {
"brand": {"type": "string"},
"model": {"type": "string"},
"category": {"type": "string"},
"commands": {"type": "object"},
},
'required': ['brand', 'model', 'category', 'commands']
}

commands_schema = {
"type": "object",
"properties": {
"commands": {"type": "object"},
},
'required': ['commands']
}

editDevice_schema = {
"type": "object",
"properties": {
"brand": {"type": "string"},
"model": {"type": "string"},
"category": {"type": "string"},
},
'required': ['brand', 'model', 'category']
}


@app.after_request
def remove_header(response):
# Hide server info from possilbe attakers.
response.headers['Server'] = ''
return response


def admin_scope():
"""Very simple admin scope authorazed"""
def _admin_scope(f):
@wraps(f)
def __admin_scope(*args, **kwargs):
try:
if request.headers['api-key'] != ADMIN_API_KEY:
raise Exception('Invalid api-key')
except:
abort(403, 'Invalid api-key')
return f(*args, **kwargs)
return __admin_scope
return _admin_scope


@app.route('/.well-known/security.txt')
def security_info_route():
""" Get security help info """
return app.send_static_file('.well-known/security.txt')
18 changes: 18 additions & 0 deletions route/rf_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from flask import Flask, jsonify, request, abort
from route.rest_common import app, admin_scope, commands_schema
from flask_expects_json import expects_json
from data.rf import set_device_commands, get_device_commands


@app.route('/rf/<brand>/<model>', methods=['GET'])
def model_rf_commands_route(brand: str, model: str):
return jsonify(get_device_commands(brand=brand, model=model))


@app.route('/rf/<brand>/<model>', methods=['PUT'])
@admin_scope()
@expects_json(commands_schema)
def set_device_commans_route(brand: str, model: str):
set_device_commands(brand=brand, model=model, data=request.json)
resp = jsonify(success=True)
return resp
2 changes: 1 addition & 1 deletion runtime.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-3.7.3
python-3.8.3
2 changes: 2 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from dotenv import load_dotenv
import os
load_dotenv()

app_name = 'rf-commands-repo'

0 comments on commit a658034

Please sign in to comment.