Skip to content

Commit a658034

Browse files
committed
Refactor Code & Add Data Modifying #5
- Add data modifying - Add types - Separate to folders and files - Add very simple admin auth
1 parent 246832b commit a658034

14 files changed

+221
-68
lines changed

.env.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
DATABASE_URL="mongodb://<dbuser>:<dbpassword>@<ip>:<port>/ir-commands"
1+
DATABASE_URL="mongodb://<dbuser>:<dbpassword>@<ip>:<port>/ir-commands"
2+
ADMIN_API_KEY="some admin key"

.vscode/settings.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"python.linting.pylintEnabled": true,
3-
"python.linting.enabled": true
3+
"python.linting.enabled": true,
4+
"cSpell.words": [
5+
"jsonify"
6+
]
47
}

README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ Simple, light-weight server for RF commands (such as: IR, 433 MHz etc.) for appl
33

44
# running server
55

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

1314
# technologies
1415
The Server is Build with Python, with the Flask framework for the HTTP Routing, MongoDB for the data storing.

app.py

+4-27
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,10 @@
1-
from flask import Flask, jsonify
1+
from flask import Flask, jsonify, request, abort
22
import os
33
import settings
4-
from data import get_devices, get_device_commands
4+
import route.rf_route
5+
import route.device_route
6+
from route.rest_common import app
57

6-
app = Flask(__name__)
7-
8-
@app.after_request
9-
def remove_header(response):
10-
# Hide server info from possilbe attakers.
11-
response.headers['Server'] = ''
12-
return response
13-
14-
# Get all supported devices in system
15-
@app.route('/devices')
16-
def models_list():
17-
return jsonify(get_devices())
18-
19-
# Get RF commands of a devices.
20-
@app.route('/rf/<brand>/<model>')
21-
def model_rf_commands(brand, model):
22-
return jsonify(get_device_commands(brand=brand, model=model))
23-
24-
25-
# Get security help info
26-
@app.route('/.well-known/security.txt')
27-
def security_info():
28-
return app.send_static_file('.well-known/security.txt')
29-
30-
318
# Get PORT from env.
329
PORT = os.getenv("PORT")
3310
if not PORT:

data.py

-32
This file was deleted.

data/devices.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from data.models import RfDevice
2+
3+
4+
def is_device_exists(brand: str, model: str) -> bool:
5+
try:
6+
RfDevice.objects.get(brand=brand, model=model)
7+
return True
8+
except:
9+
return False
10+
11+
12+
def get_devices() -> list:
13+
devices = []
14+
for device in RfDevice.objects:
15+
record = {
16+
'brand': device.brand,
17+
'model': device.model,
18+
'category': device.category,
19+
}
20+
devices.append(record)
21+
return devices
22+
23+
24+
def create_device(device) -> None:
25+
brand = device['brand']
26+
model = device['model']
27+
if is_device_exists(brand=brand, model=model):
28+
raise Exception("Sorry, the device already exists")
29+
30+
newDevice = RfDevice(brand=brand, model=model,
31+
category=device['category'], commands=device['commands'])
32+
newDevice.save()
33+
34+
35+
def edit_device(brand: str, model: str, device: dict) -> None:
36+
if not is_device_exists(brand=brand, model=model):
37+
raise Exception("Sorry, the device is not exists")
38+
device = RfDevice.objects.get(brand=brand, model=model)
39+
device['brand'] = newName['brand']
40+
device['model'] = newName['model']
41+
device['category'] = newName['category']
42+
device.save()
43+
44+
45+
def delete_device(brand: str, model: str) -> None:
46+
device = RfDevice.objects.get(brand=brand, model=model)
47+
device.delete()

data/models.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from mongoengine import *
2+
import os
3+
4+
DATABASE_URL = os.getenv("DATABASE_URL")
5+
6+
# Connect mongoengine driver to the database
7+
connect(host=DATABASE_URL)
8+
9+
10+
class RfDevice(Document):
11+
"""Devices document objects model."""
12+
meta = {
13+
'collection': 'commands'
14+
}
15+
brand = StringField(required=True)
16+
model = StringField(required=True)
17+
category = StringField(required=True)
18+
commands = DynamicField(required=True)

data/rf.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
from data.models import RfDevice
3+
4+
5+
def get_device_commands(brand: str, model: str) -> dict:
6+
device = RfDevice.objects.get(brand=brand, model=model)
7+
return device.commands
8+
9+
10+
def set_device_commands(brand: str, model: str, data: dict) -> None:
11+
device = RfDevice.objects.get(brand=brand, model=model)
12+
device.commands = data['commands']
13+
device.save()

requirements.txt

-157 Bytes
Binary file not shown.

route/device_route.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from flask import Flask, jsonify, request, abort
2+
from route.rest_common import admin_scope, commands_schema, device_schema, editDevice_schema, app
3+
from flask_expects_json import expects_json
4+
from data.devices import edit_device, create_device, delete_device, get_devices
5+
6+
7+
@app.route('/devices', methods=['GET'])
8+
def models_list_route():
9+
return jsonify(get_devices())
10+
11+
12+
@app.route('/device', methods=['POST'])
13+
@expects_json(device_schema)
14+
@admin_scope()
15+
def create_device_route():
16+
create_device(request.json)
17+
resp = jsonify(success=True)
18+
return resp
19+
20+
21+
@app.route('/device/<brand>/<model>', methods=['PUT'])
22+
@admin_scope()
23+
@expects_json(editDevice_schema)
24+
def set_device_route(brand: str, model: str):
25+
edit_device(brand=brand, model=model, newDevice=request.json)
26+
resp = jsonify(success=True)
27+
return resp
28+
29+
30+
@app.route('/device/<brand>/<model>', methods=['DELETE'])
31+
@admin_scope()
32+
def delete_device_route(brand: str, model: str):
33+
delete_device(brand=brand, model=model)
34+
resp = jsonify(success=True)
35+
return resp

route/rest_common.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from flask import Flask, jsonify, request, abort
2+
from functools import wraps
3+
from settings import app_name
4+
import os
5+
import sys
6+
7+
app = Flask(app_name)
8+
9+
ADMIN_API_KEY = os.getenv("ADMIN_API_KEY")
10+
if not ADMIN_API_KEY:
11+
msg = 'ADMIN_API_KEY not exists... exiting'
12+
print(msg)
13+
sys.exit(msg)
14+
15+
device_schema = {
16+
"type": "object",
17+
"properties": {
18+
"brand": {"type": "string"},
19+
"model": {"type": "string"},
20+
"category": {"type": "string"},
21+
"commands": {"type": "object"},
22+
},
23+
'required': ['brand', 'model', 'category', 'commands']
24+
}
25+
26+
commands_schema = {
27+
"type": "object",
28+
"properties": {
29+
"commands": {"type": "object"},
30+
},
31+
'required': ['commands']
32+
}
33+
34+
editDevice_schema = {
35+
"type": "object",
36+
"properties": {
37+
"brand": {"type": "string"},
38+
"model": {"type": "string"},
39+
"category": {"type": "string"},
40+
},
41+
'required': ['brand', 'model', 'category']
42+
}
43+
44+
45+
@app.after_request
46+
def remove_header(response):
47+
# Hide server info from possilbe attakers.
48+
response.headers['Server'] = ''
49+
return response
50+
51+
52+
def admin_scope():
53+
"""Very simple admin scope authorazed"""
54+
def _admin_scope(f):
55+
@wraps(f)
56+
def __admin_scope(*args, **kwargs):
57+
try:
58+
if request.headers['api-key'] != ADMIN_API_KEY:
59+
raise Exception('Invalid api-key')
60+
except:
61+
abort(403, 'Invalid api-key')
62+
return f(*args, **kwargs)
63+
return __admin_scope
64+
return _admin_scope
65+
66+
67+
@app.route('/.well-known/security.txt')
68+
def security_info_route():
69+
""" Get security help info """
70+
return app.send_static_file('.well-known/security.txt')

route/rf_route.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from flask import Flask, jsonify, request, abort
2+
from route.rest_common import app, admin_scope, commands_schema
3+
from flask_expects_json import expects_json
4+
from data.rf import set_device_commands, get_device_commands
5+
6+
7+
@app.route('/rf/<brand>/<model>', methods=['GET'])
8+
def model_rf_commands_route(brand: str, model: str):
9+
return jsonify(get_device_commands(brand=brand, model=model))
10+
11+
12+
@app.route('/rf/<brand>/<model>', methods=['PUT'])
13+
@admin_scope()
14+
@expects_json(commands_schema)
15+
def set_device_commans_route(brand: str, model: str):
16+
set_device_commands(brand=brand, model=model, data=request.json)
17+
resp = jsonify(success=True)
18+
return resp

runtime.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
python-3.7.3
1+
python-3.8.3

settings.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from dotenv import load_dotenv
22
import os
33
load_dotenv()
4+
5+
app_name = 'rf-commands-repo'

0 commit comments

Comments
 (0)