From e0136a35175ae5447144c2a481710ac218e2c99e Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 1 Feb 2020 14:29:56 -0800 Subject: [PATCH] Add initial server implementation --- fgs/__init__.py | 13 +++++++++++++ fgs/jwt.py | 32 ++++++++++++++++++++++++++++++ fgs/model.py | 48 +++++++++++++++++++++++++++++++++++++++++++++ fgs/views.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 fgs/__init__.py create mode 100644 fgs/jwt.py create mode 100644 fgs/model.py create mode 100644 fgs/views.py diff --git a/fgs/__init__.py b/fgs/__init__.py new file mode 100644 index 0000000..4dff75a --- /dev/null +++ b/fgs/__init__.py @@ -0,0 +1,13 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +import os + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./data.sqlite' +app.secret_key = os.environ.get('FGS_SECRET_KEY') or 'default secret key' + +db = SQLAlchemy(app) + +from .views import * +from .model import * +from .jwt import * diff --git a/fgs/jwt.py b/fgs/jwt.py new file mode 100644 index 0000000..cedb98b --- /dev/null +++ b/fgs/jwt.py @@ -0,0 +1,32 @@ +import jwt +from flask import request, g, abort +from . import db, app +from .model import User +from functools import wraps + +def get_jwt(self): + return jwt.encode({'id': self.id}, app.secret_key, algorithm='HS256') + +User.get_jwt = get_jwt + +def jwt_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'Authorization' not in request.headers: + abort(400) + authorization = request.headers['Authorization'].split(' ') + if len(authorization) < 2: + abort(400) + + try: + decoded = jwt.decode( + authorization[1].encode(), + app.secret_key, + algorithms = ['HS256']) + g.user = User.query.filter_by(id=decoded['id']).first() + if g.user is None: abort(400) + return f(*args, **kwargs) + except Exception as e: + print(e) + abort(500) + return decorated_function diff --git a/fgs/model.py b/fgs/model.py new file mode 100644 index 0000000..34b9e1a --- /dev/null +++ b/fgs/model.py @@ -0,0 +1,48 @@ +from . import db +import bcrypt + +class Collar(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + active = db.Column(db.Boolean) + data_points = db.relationship('DataPoint') + +class DataPoint(db.Model): + id = db.Column(db.Integer, primary_key=True) + collar_id = db.Column(db.Integer, db.ForeignKey('collar.id')) + longitude = db.Column(db.Float(precision=10)) + latitude = db.Column(db.Float(precision=10)) + battery_level = db.Column(db.Integer) + datetime = db.Column(db.DateTime) + outside = db.Column(db.Boolean) + + def to_dict(self): + return {'longitude': self.longitude, + 'latitude': self.latitude, + 'battery_level': self.battery_level, + 'datetime': self.datetime, + 'outside': self.outside + } + +class StimulusActivation(db.Model): + id = db.Column(db.Integer, primary_key=True) + longitude = db.Column(db.Float(precision=10)) + latitude = db.Column(db.Float(precision=10)) + volume_level = db.Column(db.Integer) + voltage_level = db.Column(db.Integer) + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String) + password_hash = db.Column(db.String) + + def get_password(self): + return self.password_hash + + def set_password(self, password): + self.password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) + + def check_password(self, password): + return bcrypt.checkpw(password.encode(), self.password_hash) + + password = property(get_password, set_password) diff --git a/fgs/views.py b/fgs/views.py new file mode 100644 index 0000000..ba4f721 --- /dev/null +++ b/fgs/views.py @@ -0,0 +1,52 @@ +from . import app +from .jwt import jwt_required +from .model import * +from flask import g, jsonify, request, abort +from sqlalchemy import func + +@app.route('/') +def index(): + return 'Hello, world!' + +@app.route('/login', methods=['POST']) +def login(): + username = request.form.get('username') + password = request.form.get('password') + if username is None or password is None: abort(400) + user = User.query.filter_by(username=username).first() + if user is None or not user.check_password(password): abort(400) + return jsonify({ 'token': user.get_jwt().decode() }) + +@app.route('/me') +@jwt_required +def me(): + return jsonify({ 'username' : g.user.username }) + +@app.route('/collars') +@jwt_required +def collars(): + active_collars = Collar.query.\ + filter_by(active=True).\ + join(DataPoint, isouter=True).\ + order_by(DataPoint.datetime.desc()).\ + group_by(Collar.id).\ + with_entities( + Collar.id, Collar.name, + DataPoint.longitude, DataPoint.latitude).\ + all() + active_collars = [ + {'id': id, 'name': name, 'pos': {'longitude': lo, 'latitude': la}} + for (id, name, lo, la) in active_collars] + return jsonify(active_collars) + +@app.route('/collars//history') +@jwt_required +def collar_history(id): + collar = Collar.query.filter_by(id=id).first() + if collar is None: abort(404) + + data_points = DataPoint.query.\ + filter_by(collar_id=id).\ + order_by(DataPoint.datetime.desc()).\ + all() + return jsonify([point.to_dict() for point in data_points])