This commit is contained in:
parent
e02bf1aa62
commit
d1ef5be5d8
19
src/app.py
19
src/app.py
@ -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__":
|
||||||
|
@ -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
27
src/errorhandlers.py
Normal 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))
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user