database schema done
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Torma Kristóf 2020-11-24 22:39:43 +01:00
parent e02bf1aa62
commit d1ef5be5d8
6 changed files with 181 additions and 21 deletions

View File

@ -7,13 +7,14 @@ from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from healthcheck import HealthCheck
from errorhandlers import register_all_error_handlers
from config import *
from db import db
from jwtman import jwtman
from fbcrypt import bcrypt
from marshm import ma
from healthchecks import health_database_status
from resources import SignupApi, LoginApi
from resources import SignupApi, LoginApi, UsersApi, UserParameterApi
"""
Main Flask RESTful API
@ -42,15 +43,12 @@ app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
api = Api(app)
health = HealthCheck(app, "/healthz")
health = HealthCheck()
db.init_app(app)
ma.init_app(app)
bcrypt.init_app(app)
jwtman.init_app(app)
with app.app_context():
db.create_all()
formatter = logging.Formatter(
fmt="%(asctime)s - %(levelname)s - %(module)s - %(message)s"
)
@ -65,13 +63,18 @@ logger.addHandler(handler)
# api.add_resource(SampleResource, "/sample")
api.add_resource(SignupApi, '/api/auth/signup')
api.add_resource(LoginApi, '/api/auth/login')
api.add_resource(UsersApi, '/api/users')
api.add_resource(UserParameterApi, '/api/users/<username>')
health.add_check(health_database_status)
app.add_url_rule("/healthz", "healthcheck", view_func=lambda: health.run())
register_all_error_handlers(app)
@app.errorhandler(404)
def page_not_found(e):
return {'status': 'error', 'message': 'page not found'}, 404
@app.before_first_request
def init_db():
db.create_all()
if __name__ == "__main__":

View File

@ -27,3 +27,5 @@ POSTGRES_PASSWORD = os.getenv("VIDEON_POSTGRES_PASSWORD", "videon")
POSTGRES_DB = os.getenv("VIDEON_POSTGRES_DB", "videon")
JWT_SECRET_KEY = os.getenv("VIDEON_POSTGRES_DB", str(uuid4()))
REGISTER_DISABLED = os.getenv("VIDEON_REGISTER_DISABLED", "FALSE").upper() == "TRUE"

27
src/errorhandlers.py Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""
Flask error handler functions
"""
__author__ = '@tormakris'
__copyright__ = "Copyright 2020, videON Team"
__module_name__ = "errorhandlers"
__version__text__ = "1"
def get_standard_error_handler(code: int):
def error_handler(err):
return {"msg": str(err)}, code
return error_handler
# function to register all handlers
def register_all_error_handlers(app):
error_codes_to_override = [404, 403, 401, 405, 400, 409, 422]
for code in error_codes_to_override:
app.register_error_handler(code, get_standard_error_handler(code))

View File

@ -1,4 +1,8 @@
#!/usr/bin/env python3
from uuid import uuid4
from enum import Enum
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from flask_bcrypt import generate_password_hash, check_password_hash
@ -29,3 +33,27 @@ class User(db.Model):
def check_password(self, password):
return check_password_hash(self.password, password)
class StreamResourceTypeEnum(Enum):
INGEST = 1
ENCODE = 2
RESTREAM = 3
class StreamResource(db.Model):
id = db.Column(UUID(as_uuid=True), primary_key=True, unique=True, nullable=False, default=uuid4)
resource_type = db.Column(db.Enum(StreamResourceTypeEnum), nullable=False)
url = db.Column(db.String, nullable=True)
output_urls = db.relationship('OuputUrls', backref='streamresource', lazy=False)
bitrate = db.Column(db.Integer, nullable=True)
width = db.Column(db.Integer, nullable=True)
height = db.Column(db.Integer, nullable=True)
parent_id = db.Column(db.Integer, db.ForeignKey('stream_resource.id'), nullable=True)
children = db.relationship('StreamResource', lazy=False)
class OuputUrls(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
output_url = db.Column(db.String, nullable=False)
streamresource_id = db.Column(db.Integer, db.ForeignKey('stream_resource.id'), nullable=False)

View File

@ -3,12 +3,12 @@ import datetime
from flask_jwt_extended import create_access_token, jwt_required
from flask_restful import Resource
from flask import request, current_app
from flask import request, current_app, abort
from db import db
from models import User
from schemas import UserSchema, UserMetadataSchema
from config import REGISTER_DISABLED
"""
Flask Restful endpoints
@ -29,13 +29,16 @@ class SignupApi(Resource):
usermetadataschema = UserMetadataSchema(many=False)
def post(self):
if REGISTER_DISABLED:
abort(401, "register disabled")
body = request.get_json()
try:
userobj = self.userschema.load(body)
except Exception as e:
current_app.logger.exception(e)
return {'status': 'error', 'message': 'Input JSON schema invalid'}, 417
current_app.logger.warning(e)
abort(417, "invalid json schema")
user = User(name=userobj['name'], password=userobj['password'])
try:
@ -44,8 +47,8 @@ class SignupApi(Resource):
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.exception(e)
return {'status': 'error', 'message': 'db transaction error'}, 503
current_app.logger.warning(e)
abort(503, "user already exists")
return self.usermetadataschema.dump(user), 200
@ -64,13 +67,13 @@ class LoginApi(Resource):
try:
userobj = self.userschema.load(body)
except Exception as e:
current_app.logger.exception(e)
return {'status': 'error', 'message': 'Input JSON schema invalid'}, 417
current_app.logger.warning(e)
abort(417, "invalid json schema")
user = User.query.filter_by(name=userobj['name']).first()
user = User.query.filter_by(name=userobj['name']).first_or_404()
authorized = user.check_password(userobj['password'])
if not authorized:
return {'status': 'error', 'message': 'username or password invalid'}, 401
abort(401, "username or password incorrect")
try:
user.last_logon = datetime.datetime.now()
@ -79,8 +82,76 @@ class LoginApi(Resource):
except Exception as e:
db.session.rollback()
current_app.logger.exception(e)
return {'status': 'error', 'message': 'db transaction error'}, 503
abort(503, "db session error")
expires = datetime.timedelta(days=7)
access_token = create_access_token(identity=str(user.name), expires_delta=expires)
return {'token': access_token}, 200
class UsersApi(Resource):
"""
See: https://swagger.kmlabz.com/?urls.primaryName=videON%20Backend#/backend/getall
"""
usermetadataschema = UserMetadataSchema(many=True)
def get(self):
users = User.query.all()
return self.usermetadataschema.dump(users), 200
class UserParameterApi(Resource):
userschema = UserSchema(many=False)
usermetadataschema = UserMetadataSchema(many=False)
def get(self, username: str):
"""
See: https://swagger.kmlabz.com/?urls.primaryName=videON%20Backend#/backend/getauser
:param username: Username of user (url parameter)
:return:
"""
user = User.query.filter_by(name=username).first_or_404()
return self.usermetadataschema.dump(user), 200
def delete(self, username: str):
"""
See: https://swagger.kmlabz.com/?urls.primaryName=videON%20Backend#/backend/deleteuser
:param username: Username of user (url parameter)
:return:
"""
user = User.query.filter_by(name=username).first_or_404()
try:
db.session.delete(user)
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.exception(e)
abort(503, "db session error")
return self.usermetadataschema.dump(user), 200
def put(self, username: str):
"""
See: https://swagger.kmlabz.com/?urls.primaryName=videON%20Backend#/backend/modifyUser
:param username: Username of user (url parameter)
:return:
"""
body = request.get_json()
user = User.query.filter_by(name=username).first_or_404()
try:
userobj = self.userschema.load(body)
except Exception as e:
current_app.logger.warning(e)
abort(417, "invalid json schema")
try:
user.password = userobj['password']
user.hash_password()
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.exception(e)
abort(503, "db session error")
return self.usermetadataschema.dump(user), 200

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from flask_marshmallow.sqla import auto_field
from models import User
from models import User, StreamResource
from marshm import ma
from marshmallow import fields
@ -34,5 +34,34 @@ class UserMetadataSchema(ma.SQLAlchemyAutoSchema):
"""
class Meta:
model = User
exclude = ('timestamp', 'password',)
exclude = ('timestamp', 'password', 'id',)
creation_date = auto_field("timestamp", dump_only=False)
class IngestInputSchema(ma.Schema):
outputNeighbours = fields.List(fields.UUID(), required=True)
class EncodeInputSchema(ma.Schema):
inputNeighbour = fields.UUID(required=True)
outputNeighbours = fields.List(fields.UUID(), required=True)
bitrate = fields.Integer(required=False)
width = fields.Integer(required=False)
height = fields.Integer(required=False)
class RestreamInputSchema(ma.Schema):
inputNeighbour = fields.UUID(required=True)
outputURLs = fields.List(fields.String(), required=True)
class StreamResourceSchema(ma.SQLAlchemyAutoSchema):
"""
Marshmallow schema generated
"""
class Meta:
model = StreamResource
exclude = ('output_urls', 'parent_id', 'children',)
outputURLs = auto_field('output_urls', dump_only=False)
inputNeighbour = auto_field('parent_id', dump_only=False)
outputNeighbours = auto_field('children', dump_only=False)