diff --git a/backend/app.py b/backend/app.py index 18c3409..60bf373 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,60 +1,147 @@ +import os + import flask import flask_cors +import flask_restful +import flask_restful.fields +import flask_restful.reqparse +import flask_sqlalchemy +import sqlalchemy -from quote import engine, Base, Session, Quote, QuoteSchema +DB_PATH = os.path.expandvars('$HOME/quotes/quotes.db') app = flask.Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_PATH}' +app.config['ERROR_404_HELP'] = False + +db = flask_sqlalchemy.SQLAlchemy(app) +db.create_all() + +api = flask_restful.Api(app) + flask_cors.CORS(app) -Base.metadata.create_all(engine) - - -@app.route('/quotes') -def get_quotes(): - args = flask.request.args - filter = args.get('filter', type=str) - sort_by = args.get('sort_by', type=str) - sort_order = args.get('sort_order', type=str) - page_number = args.get('page_number', type=int) - page_size = args.get('page_size', type=int) - session = Session() - try: - q = session.query(Quote) - if filter: - q = q.filter(Quote.text.ilike('%{}%'.format(filter))) + +class Quote(db.Model): + __tablename__ = 'quotes' + + id = db.Column(db.Integer, primary_key=True) + video_id = db.Column(db.Integer) + date = db.Column(db.Date) + game = db.Column(db.String) + text = db.Column(db.String) + created_at = db.Column(db.DateTime) + updated_at = db.Column(db.DateTime) + + +quote_fields = { + 'id': flask_restful.fields.Integer(), + 'video_id': flask_restful.fields.Integer(), + 'date': flask_restful.fields.DateTime(dt_format='iso8601'), + 'game': flask_restful.fields.String(), + 'text': flask_restful.fields.String(), + 'created_at': flask_restful.fields.DateTime(dt_format='iso8601'), + 'updated_at': flask_restful.fields.DateTime(dt_format='iso8601'), +} + + +quote_parser = flask_restful.reqparse.RequestParser() +quote_parser.add_argument('id', type=int) +quote_parser.add_argument('video_id', type=int) +quote_parser.add_argument('date', type=flask_restful.inputs.date, required=True) +quote_parser.add_argument('game', type=str, required=True) +quote_parser.add_argument('text', type=str, required=True) + + +filter_parser = flask_restful.reqparse.RequestParser() +filter_parser.add_argument('filter', type=str) +filter_parser.add_argument('sort_by', type=str) +filter_parser.add_argument('sort_order', type=str) +filter_parser.add_argument('page_number', type=int) +filter_parser.add_argument('page_size', type=int) + + +class QuoteResource(flask_restful.Resource): + @flask_restful.marshal_with(quote_fields) + def get(self, id): + q = db.session.query(Quote).filter(Quote.id == id) + quote = q.first() + if not quote: + flask_restful.abort(404, message='Quote {0} does not exist'.format(id)) + return quote, 200 + + @flask_restful.marshal_with(quote_fields) + def put(self, id): + args = quote_parser.parse_args() + now = sqlalchemy.func.now() + q = db.session.query(Quote).filter(Quote.id == id) + quote = q.first() + if not quote: + quote = Quote(id=id, created_at=now) + quote.video_id = args['video_id'] # FIXME: NULL + quote.date = args['date'] + quote.game = args['game'] + quote.text = args['text'] + quote.updated_at = now + db.session.add(quote) + db.session.commit() + return quote, 200 + + def delete(self, id): + q = db.session.query(Quote).filter(Quote.id == id) + quote = q.first() + if not quote: + flask_restful.abort(404, message='Quote {0} does not exist'.format(id)) + db.session.delete(quote) + db.session.commit() + return None, 204 + + +class QuotesResource(flask_restful.Resource): + @flask_restful.marshal_with(quote_fields) + def get(self): + args = filter_parser.parse_args() + q = db.session.query(Quote) + if args['filter']: + q = q.filter(Quote.text.ilike('%{}%'.format(args['filter']))) count = q.count() - if sort_by: - col = getattr(Quote, sort_by, None) + if args['sort_by']: + col = getattr(Quote, args['sort_by'], None) if col: - if sort_order: - order_by = getattr(col, sort_order, None) + if args['sort_order']: + order_by = getattr(col, args['sort_order'], None) if order_by: q = q.order_by(order_by()) else: q = q.order_by(col) - if page_size: - q = q.limit(page_size) - if page_number and page_size: - q = q.offset(page_number * page_size) - data = QuoteSchema(many=True).dump(q).data - finally: - session.close() - return flask.jsonify(data), 200, {'X-Total-Count': count} - - -@app.route('/quotes', methods=['POST']) -def add_quote(): - q = QuoteSchema().load(flask.request.get_json()) - quote = Quote(**q.data) - session = Session() - try: - session.add(quote) - session.commit() - data = QuoteSchema().dump(quote).data - except: - session.rollback() - raise - finally: - session.close() - return flask.jsonify(data), 201 + if args['page_size']: + q = q.limit(args['page_size']) + if args['page_number'] and args['page_size']: + q = q.offset(args['page_number'] * args['page_size']) + quotes = q.all() + return quotes, 200, {'X-Total-Count': count} + + @flask_restful.marshal_with(quote_fields) + def post(self): + args = quote_parser.parse_args() + if not args['id']: + flask_restful.abort(400, message='Missing required parameter id') + now = sqlalchemy.func.now() + q = db.session.query(Quote).filter(Quote.id == args['id']) + quote = q.first() + if not quote: + quote = Quote(id=args['id'], created_at=now) + quote.video_id = args['video_id'] # FIXME: NULL + quote.date = args['date'] + quote.game = args['game'] + quote.text = args['text'] + quote.updated_at = now + db.session.add(quote) + db.session.commit() + url = api.url_for(QuoteResource, id=quote.id, _external=True, _scheme='https') + return quote, 201, {'Location': url} + + +api.add_resource(QuoteResource, '/quotes/') +api.add_resource(QuotesResource, '/quotes') diff --git a/backend/quote.py b/backend/quote.py deleted file mode 100644 index c6e5274..0000000 --- a/backend/quote.py +++ /dev/null @@ -1,48 +0,0 @@ -import os - -import marshmallow -import sqlalchemy -import sqlalchemy.orm -import sqlalchemy.ext.declarative - -from datetime import datetime - - -DB_PATH = os.path.expandvars('$HOME/quotes/quotes.db') - -engine = sqlalchemy.create_engine(f'sqlite:///{DB_PATH}') -Session = sqlalchemy.orm.sessionmaker(bind=engine) -Base = sqlalchemy.ext.declarative.declarative_base() - - -class Quote(Base): - - __tablename__ = 'quotes' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) - video_id = sqlalchemy.Column(sqlalchemy.Integer) - date = sqlalchemy.Column(sqlalchemy.Date) - game = sqlalchemy.Column(sqlalchemy.String) - text = sqlalchemy.Column(sqlalchemy.String) - created_at = sqlalchemy.Column(sqlalchemy.DateTime) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime) - - def __init__(self, id, video_id, date, game, text): - self.id = id - self.video_id = video_id - self.date = date - self.game = game - self.text = text - self.created_at = datetime.now() - self.updated_at = datetime.now() - - -class QuoteSchema(marshmallow.Schema): - - id = marshmallow.fields.Integer() - video_id = marshmallow.fields.Integer() - date = marshmallow.fields.Date() - game = marshmallow.fields.Str() - text = marshmallow.fields.Str() - created_at = marshmallow.fields.DateTime() - updated_at = marshmallow.fields.DateTime()