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 sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from healthcheck import HealthCheck from healthcheck import HealthCheck
from errorhandlers import register_all_error_handlers
from config import * from config import *
from db import db from db import db
from jwtman import jwtman from jwtman import jwtman
from fbcrypt import bcrypt from fbcrypt import bcrypt
from marshm import ma from marshm import ma
from healthchecks import health_database_status from healthchecks import health_database_status
from resources import SignupApi, LoginApi from resources import SignupApi, LoginApi, UsersApi, UserParameterApi
""" """
Main Flask RESTful API Main Flask RESTful API
@ -42,15 +43,12 @@ app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
api = Api(app) api = Api(app)
health = HealthCheck(app, "/healthz") health = HealthCheck()
db.init_app(app) db.init_app(app)
ma.init_app(app) ma.init_app(app)
bcrypt.init_app(app) bcrypt.init_app(app)
jwtman.init_app(app) jwtman.init_app(app)
with app.app_context():
db.create_all()
formatter = logging.Formatter( formatter = logging.Formatter(
fmt="%(asctime)s - %(levelname)s - %(module)s - %(message)s" fmt="%(asctime)s - %(levelname)s - %(module)s - %(message)s"
) )
@ -65,13 +63,18 @@ logger.addHandler(handler)
# api.add_resource(SampleResource, "/sample") # api.add_resource(SampleResource, "/sample")
api.add_resource(SignupApi, '/api/auth/signup') api.add_resource(SignupApi, '/api/auth/signup')
api.add_resource(LoginApi, '/api/auth/login') 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) 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) @app.before_first_request
def page_not_found(e): def init_db():
return {'status': 'error', 'message': 'page not found'}, 404 db.create_all()
if __name__ == "__main__": 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") POSTGRES_DB = os.getenv("VIDEON_POSTGRES_DB", "videon")
JWT_SECRET_KEY = os.getenv("VIDEON_POSTGRES_DB", str(uuid4())) 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 #!/usr/bin/env python3
from uuid import uuid4
from enum import Enum
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func from sqlalchemy.sql import func
from flask_bcrypt import generate_password_hash, check_password_hash from flask_bcrypt import generate_password_hash, check_password_hash
@ -29,3 +33,27 @@ class User(db.Model):
def check_password(self, password): def check_password(self, password):
return check_password_hash(self.password, 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_jwt_extended import create_access_token, jwt_required
from flask_restful import Resource from flask_restful import Resource
from flask import request, current_app from flask import request, current_app, abort
from db import db from db import db
from models import User from models import User
from schemas import UserSchema, UserMetadataSchema from schemas import UserSchema, UserMetadataSchema
from config import REGISTER_DISABLED
""" """
Flask Restful endpoints Flask Restful endpoints
@ -29,13 +29,16 @@ class SignupApi(Resource):
usermetadataschema = UserMetadataSchema(many=False) usermetadataschema = UserMetadataSchema(many=False)
def post(self): def post(self):
if REGISTER_DISABLED:
abort(401, "register disabled")
body = request.get_json() body = request.get_json()
try: try:
userobj = self.userschema.load(body) userobj = self.userschema.load(body)
except Exception as e: except Exception as e:
current_app.logger.exception(e) current_app.logger.warning(e)
return {'status': 'error', 'message': 'Input JSON schema invalid'}, 417 abort(417, "invalid json schema")
user = User(name=userobj['name'], password=userobj['password']) user = User(name=userobj['name'], password=userobj['password'])
try: try:
@ -44,8 +47,8 @@ class SignupApi(Resource):
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
current_app.logger.exception(e) current_app.logger.warning(e)
return {'status': 'error', 'message': 'db transaction error'}, 503 abort(503, "user already exists")
return self.usermetadataschema.dump(user), 200 return self.usermetadataschema.dump(user), 200
@ -64,13 +67,13 @@ class LoginApi(Resource):
try: try:
userobj = self.userschema.load(body) userobj = self.userschema.load(body)
except Exception as e: except Exception as e:
current_app.logger.exception(e) current_app.logger.warning(e)
return {'status': 'error', 'message': 'Input JSON schema invalid'}, 417 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']) authorized = user.check_password(userobj['password'])
if not authorized: if not authorized:
return {'status': 'error', 'message': 'username or password invalid'}, 401 abort(401, "username or password incorrect")
try: try:
user.last_logon = datetime.datetime.now() user.last_logon = datetime.datetime.now()
@ -79,8 +82,76 @@ class LoginApi(Resource):
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
current_app.logger.exception(e) current_app.logger.exception(e)
return {'status': 'error', 'message': 'db transaction error'}, 503 abort(503, "db session error")
expires = datetime.timedelta(days=7) expires = datetime.timedelta(days=7)
access_token = create_access_token(identity=str(user.name), expires_delta=expires) access_token = create_access_token(identity=str(user.name), expires_delta=expires)
return {'token': access_token}, 200 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 #!/usr/bin/env python3
from flask_marshmallow.sqla import auto_field from flask_marshmallow.sqla import auto_field
from models import User from models import User, StreamResource
from marshm import ma from marshm import ma
from marshmallow import fields from marshmallow import fields
@ -34,5 +34,34 @@ class UserMetadataSchema(ma.SQLAlchemyAutoSchema):
""" """
class Meta: class Meta:
model = User model = User
exclude = ('timestamp', 'password',) exclude = ('timestamp', 'password', 'id',)
creation_date = auto_field("timestamp", dump_only=False) 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)