commit 38ab1578b0e0e99061fe380a5acf0f6f8542a27f Author: Tobias Date: Wed Mar 6 16:36:57 2019 +0100 added rest-API, sqlalchemy and nice structure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..1c20633 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +flask = "*" +flask-httpauth = "*" +flask-restplus-patched = "*" +flask-sqlalchemy = "*" + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..4005e69 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,203 @@ +{ + "_meta": { + "hash": { + "sha256": "b3e3f1a3dbe6801a4a969a2b5c9b73167ea078c1916db723fdc282bc9af814a7" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aniso8601": { + "hashes": [ + "sha256:29ad6be3828ab6ac2a31fd2876fd84477cde11890ffca7e8a9434aad5d4acec8", + "sha256:a5c7595bb65d3919a9944a759d907b57c4d050abaa0e5cf845e84c26cdfd1218" + ], + "version": "==5.1.0" + }, + "apispec": { + "hashes": [ + "sha256:57a7b81fd19fff0663a7e5ffd196eaea79b5364151ed2b65533be36d55e0229c", + "sha256:b45def53903516e67e8584ee41f34bc60c3e4acace6892b69340293ea20f3caa" + ], + "version": "==1.0.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "flask": { + "hashes": [ + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "flask-httpauth": { + "hashes": [ + "sha256:c08b69b302f1aa7ecd0db327809132ef6ca9486a36a9174776da146d1a4adc18", + "sha256:f71b7611f385fbdf350e8c430eed17b41c3b2200dc35eae19c1734264b68e31d" + ], + "index": "pypi", + "version": "==3.2.4" + }, + "flask-marshmallow": { + "hashes": [ + "sha256:75c9d80f22af982b1e8ccec109d3b75c14bb5570602ae3705a4ff775badd2816", + "sha256:db7aff4130eb99fd05ab78fd2e2c58843ba0f208899aeb1c14aff9cd98ae8c80" + ], + "version": "==0.9.0" + }, + "flask-restplus": { + "hashes": [ + "sha256:3fad697e1d91dfc13c078abcb86003f438a751c5a4ff41b84c9050199d2eab62", + "sha256:cdc27b5be63f12968a7f762eaa355e68228b0c904b4c96040a314ba7dc6d0e69" + ], + "version": "==0.12.1" + }, + "flask-restplus-patched": { + "hashes": [ + "sha256:36342775f9e0990dfc000dbe61133dfe56f9ef32c9b4c6293ba7f2c128d16efc" + ], + "index": "pypi", + "version": "==0.1.10" + }, + "flask-sqlalchemy": { + "hashes": [ + "sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b", + "sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53" + ], + "index": "pypi", + "version": "==2.3.2" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "jsonschema": { + "hashes": [ + "sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d", + "sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a" + ], + "version": "==3.0.1" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, + "marshmallow": { + "hashes": [ + "sha256:6eeaf1301a5f5942bfe8ab2c2eaf03feb793072b56d5fae563638bddd7bb62e6", + "sha256:f72a206432a3369dd72824564d18d915761e07805c05f00d0dcc7885fac1e385" + ], + "version": "==2.18.1" + }, + "pyrsistent": { + "hashes": [ + "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2" + ], + "version": "==0.14.11" + }, + "pytz": { + "hashes": [ + "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", + "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + ], + "version": "==2018.9" + }, + "relativetimebuilder": { + "hashes": [ + "sha256:5cc415b539d18a20e09a600cf7ba7199eda7b365d13aaaf9ffbbaa26cfb8062a", + "sha256:8b11e6fa6d6d4a09c61cfa9dadae4ea640bf10818e0991874d33452c0aeff2d7" + ], + "version": "==0.2.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:11ead7047ff3f394ed0d4b62aded6c5d970a9b718e1dc6add9f5e79442cc5b14" + ], + "version": "==1.3.0" + }, + "webargs": { + "hashes": [ + "sha256:10438164b41b81abe45b299eb182580f7bc6bcdbc864b0cbd62845bb6bab424d", + "sha256:3bed01136ea4a7d1468a54f6c3925d133872a83a2144e83a94f484731576bc58", + "sha256:494044344b5673e3624621d0e9d14d5dc01dd05c0b5b8952febc80a4f80181f6" + ], + "version": "==5.1.2" + }, + "werkzeug": { + "hashes": [ + "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", + "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + ], + "version": "==0.14.1" + } + }, + "develop": {} +} diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..aaaefc9 --- /dev/null +++ b/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +import os +from flask import Flask +from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth +from flask_sqlalchemy import SQLAlchemy + +from .serve_frontend import fe_bp +from .api import api_bp + +jwt_auth = HTTPTokenAuth('Bearer') +basic_auth = HTTPBasicAuth() + +app = Flask(__name__) +app.register_blueprint(api_bp) +app.register_blueprint(fe_bp) + +db = SQLAlchemy(app) diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..66653b1 --- /dev/null +++ b/api/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from flask import Blueprint +from flask_restplus import Api + +api_authorizations = { + 'apikey': { + 'type': 'apiKey', + 'in': 'header', + 'name': 'X-API-KEY' + }, + 'basicAuth': { + 'type': 'basic', + 'scheme': 'basic' + }, + 'bearerAuth': { + 'type': 'apiKey', + 'scheme': 'bearer', + 'name': 'Authorization', + 'in': 'header' + } +} + +api_bp = Blueprint('api', __name__, url_prefix='/api') +api_v1 = Api(api_bp, prefix="/v1", version='0.1', title='Vue Test API', + description='The Vue Test API', doc='/v1/doc/', authorizations=api_authorizations, security='bearerAuth') + +from .example_api import * diff --git a/api/example_api.py b/api/example_api.py new file mode 100644 index 0000000..41b4896 --- /dev/null +++ b/api/example_api.py @@ -0,0 +1,118 @@ +import logging +from random import * +from flask import jsonify, Blueprint +from flask_restplus import Resource, reqparse + +from backend import basic_auth +from backend.api import api_v1, api_bp + + +@api_bp.route('/random') +def random_number(): + """ + :return: a random number + """ + response = { + 'randomNumber': randint(1, 100) + } + return jsonify(response) + + +class HelloWorld(Resource): + """ + This is a test class. + """ + def get(self): + """ + just a test! + :return: Hello: World + """ + print(str(self)) + return {'hello': 'world'} + + +api_v1.add_resource(HelloWorld, '/') + + +class SensorData_Handler(Resource): + parser = reqparse.RequestParser() + parser.add_argument('values', type=str, required=True, help="sensor data values are required (as JSON list)") + + @basic_auth.login_required + def get(self, mac): + sensor = Sensor.get_by_mac_user(mac, g.user) + if not sensor: + return "sensor not found", 404 + return jsonify(sensor.get_sensor_data()) + # return {'task': 'id: ' + mac} + + @multi_auth.login_required + def post(self, mac): + sensor = Sensor.get_by_mac(mac) + if not sensor: + return "sensor not found", 404 + + print("JSON") + print(request.json) + print(request.json['values']) + args = SensorData_Handler.parser.parse_args() + print("values...") + print(args['values']) + values = json.loads(args['values']) + wasss_app.logger.info("vals: " + str(values) + " (len: " + str(len(values)) + ")") + + rough_geo_location = None + try: + rough_geo_location_info = None + ip = ipaddress.ip_address(request.remote_addr) + if request.remote_addr == "127.0.0.1": + ip = ipaddress.ip_address("89.245.37.108") + if isinstance(ip, ipaddress.IPv4Address): + rough_geo_location_info = geoip4.record_by_addr(str(ip)) + elif isinstance(ip, ipaddress.IPv6Address): + rough_geo_location_info = geoip6.record_by_addr(str(ip)) + + if rough_geo_location_info is not None: + rough_geo_location = json.dumps({key: rough_geo_location_info[key] for key in ['country_code', + 'country_name', + 'postal_code', + 'city', + 'time_zone', + 'latitude', + 'longitude'] + }) + except ValueError: + pass + + for values_at_time in values: + wasss_app.logger.info("values_at_time: " + str(values_at_time) + " (len: " + str(len(values_at_time)) + ")") + sensor_data = SensorData(sensor=sensor, rough_geo_location=rough_geo_location) + val_cycle = cycle(values_at_time) + for sensor_data_header in sensor.get_sensor_data_headers(): + value = next(val_cycle) + if sensor_data_header == SensorData.HEADER_TIMESTAMP: + sensor_data.timestamp = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z") + wasss_app.logger.info(sensor_data.timestamp) + elif sensor_data_header == SensorData.HEADER_VOLTAGE: + sensor_data.voltage = value + elif sensor_data_header == SensorData.HEADER_TEMPERATURE: + sensor_data.temp = value + elif sensor_data_header == SensorData.HEADER_HUMIDITY: + sensor_data.humidity = value + elif sensor_data_header == SensorData.HEADER_PRESSURE: + sensor_data.pressure = value + elif sensor_data_header == SensorData.HEADER_BRIGHTNESS: + sensor_data.brightness = value + elif sensor_data_header == SensorData.HEADER_SPEED: + sensor_data.speed = value + elif sensor_data_header == SensorData.HEADER_MOTION: + sensor_data.motion = value + else: + gen_sen_data = GenericSensorData(sensor=sensor, name=sensor_data_header, value=value) + db.session.add(sensor_data) + + db.session.commit() + return jsonify("ok") + + +api_v1.add_resource(SensorData_Handler, '/sensor//data') diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/example_model.py b/models/example_model.py new file mode 100644 index 0000000..c26eb28 --- /dev/null +++ b/models/example_model.py @@ -0,0 +1,10 @@ + + +class Sensor(db.Model): + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + mac = db.Column(db.String(32), nullable=False, unique=True, index=True) + uuid = db.Column(db.String(36), nullable=False, unique=True, index=True, default=str(uuid.uuid4())) + tags = db.relationship("SensorTag", secondary=sensor_tag_relations, back_populates="sensors") + rough_geo_location = db.Column(db.String, nullable=True, index=True) + name = db.Column(db.String(128), default="", nullable=False, index=True, unique=False) + description = db.Column(db.String(4096), nullable=True, unique=False) diff --git a/serve_frontend.py b/serve_frontend.py new file mode 100644 index 0000000..a125ddc --- /dev/null +++ b/serve_frontend.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# -*- coding: utf-8 -*- + +import os +from flask import render_template, send_from_directory, Blueprint + +fe_path = os.path.join(os.getcwd(), "frontend", "dist") +fe_bp = Blueprint('frontend', __name__, url_prefix='/', template_folder=os.path.join(fe_path, "")) + + +@fe_bp.route('/js/') +def send_js(path): + return send_from_directory(os.path.join(fe_path, "js"), path) + + +@fe_bp.route('/css/') +def send_css(path): + return send_from_directory(os.path.join(fe_path, "css"), path) + + +@fe_bp.route('/img/') +def send_img(path): + return send_from_directory(os.path.join(fe_path, "img"), path) + + +@fe_bp.route('/', defaults={'path': ''}) +@fe_bp.route('/') +def catch_all(path): + return render_template("index.html")