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 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__":
|
||||
|
@ -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
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
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user