parent
78a527dc61
commit
8a6b2bb602
@ -0,0 +1,96 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
@ -0,0 +1,14 @@
|
||||
FROM python:alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
RUN pip install --no-cache-dir --requirement requirements.txt
|
||||
|
||||
RUN addgroup -g 9999 lilia
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
USER nobody:lilia
|
||||
|
||||
ENTRYPOINT ["python", "app.py"]
|
@ -0,0 +1,174 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import flask
|
||||
import flask_login
|
||||
import flask_restful
|
||||
import flask_restful.fields
|
||||
import flask_restful.reqparse
|
||||
import itsdangerous
|
||||
import sqlalchemy
|
||||
import sqlalchemy.engine
|
||||
|
||||
from db import db, GiftedSub
|
||||
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.config.update(
|
||||
ERROR_404_HELP=False,
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS=False,
|
||||
SQLALCHEMY_DATABASE_URI=os.getenv('SQLALCHEMY_DATABASE_URI'),
|
||||
SECRET_KEY=os.getenv('SECRET_KEY'))
|
||||
|
||||
if app.config.get('SQLALCHEMY_DATABASE_URI', '').startswith('sqlite://'):
|
||||
@sqlalchemy.event.listens_for(sqlalchemy.engine.Engine, 'connect')
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
dbapi_connection.execute('PRAGMA journal_mode=WAL')
|
||||
dbapi_connection.execute('PRAGMA synchronous=NORMAL')
|
||||
|
||||
db.init_app(app)
|
||||
db.create_all(app=app)
|
||||
|
||||
login_manager = flask_login.LoginManager()
|
||||
login_manager.init_app(app)
|
||||
|
||||
api = flask_restful.Api(app)
|
||||
|
||||
|
||||
gifted_sub_fields = {
|
||||
'id': flask_restful.fields.Integer(),
|
||||
'giver': flask_restful.fields.String(),
|
||||
'receiver': flask_restful.fields.String(),
|
||||
'time': flask_restful.fields.DateTime(dt_format='iso8601'),
|
||||
'created_at': flask_restful.fields.DateTime(dt_format='iso8601'),
|
||||
'updated_at': flask_restful.fields.DateTime(dt_format='iso8601'),
|
||||
}
|
||||
|
||||
|
||||
gifted_sub_parser = flask_restful.reqparse.RequestParser()
|
||||
gifted_sub_parser.add_argument('id', type=int)
|
||||
gifted_sub_parser.add_argument('giver', type=str, required=True)
|
||||
gifted_sub_parser.add_argument('receiver', type=str, required=True)
|
||||
gifted_sub_parser.add_argument('time', type=flask_restful.inputs.datetime_from_iso8601, 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)
|
||||
|
||||
|
||||
@login_manager.request_loader
|
||||
def load_user(request):
|
||||
key = request.headers.get('X-Gifted-Subs-API-Key')
|
||||
if not key:
|
||||
return None
|
||||
s = itsdangerous.TimedJSONWebSignatureSerializer(app.config['SECRET_KEY'])
|
||||
try:
|
||||
user = flask_login.UserMixin()
|
||||
user.id = s.loads(key)
|
||||
return user
|
||||
except (itsdangerous.SignatureExpired, itsdangerous.BadSignature):
|
||||
return None
|
||||
|
||||
|
||||
class GiftedSubResource(flask_restful.Resource):
|
||||
@flask_restful.marshal_with(gifted_sub_fields)
|
||||
def get(self, id):
|
||||
q = db.session.query(GiftedSub).filter(GiftedSub.id == id)
|
||||
gifted_sub = q.first()
|
||||
if not gifted_sub:
|
||||
flask_restful.abort(404, message='Gifted sub {0} does not exist'.format(id))
|
||||
return gifted_sub, 200
|
||||
|
||||
@flask_login.login_required
|
||||
@flask_restful.marshal_with(gifted_sub_fields)
|
||||
def put(self, id):
|
||||
args = gifted_sub_parser.parse_args()
|
||||
now = sqlalchemy.func.now()
|
||||
q = db.session.query(GiftedSub).filter(GiftedSub.id == id)
|
||||
gifted_sub = q.first()
|
||||
if not gifted_sub:
|
||||
gifted_sub = GiftedSub(id=id, created_at=now)
|
||||
gifted_sub.giver = args['giver']
|
||||
gifted_sub.receiver = args['receiver']
|
||||
gifted_sub.time = args['time']
|
||||
gifted_sub.updated_at = now
|
||||
db.session.add(gifted_sub)
|
||||
db.session.commit()
|
||||
return gifted_sub, 200
|
||||
|
||||
@flask_login.login_required
|
||||
def delete(self, id):
|
||||
q = db.session.query(GiftedSub).filter(GiftedSub.id == id)
|
||||
gifted_sub = q.first()
|
||||
if not gifted_sub:
|
||||
flask_restful.abort(404, message='Gifted sub {0} does not exist'.format(id))
|
||||
db.session.delete(gifted_sub)
|
||||
db.session.commit()
|
||||
return None, 204
|
||||
|
||||
|
||||
class GiftedSubsResource(flask_restful.Resource):
|
||||
@flask_restful.marshal_with(gifted_sub_fields)
|
||||
def get(self):
|
||||
args = filter_parser.parse_args()
|
||||
q = db.session.query(GiftedSub)
|
||||
if args['filter']:
|
||||
q = q.filter(GiftedSub.giver.ilike('%{}%'.format(args['filter'])) |\
|
||||
GiftedSub.receiver.ilike('%{}%'.format(args['filter'])))
|
||||
count = q.count()
|
||||
if args['sort_order'] == 'random':
|
||||
q = q.order_by(sqlalchemy.func.random())
|
||||
elif args['sort_by']:
|
||||
col = getattr(GiftedSub, args['sort_by'], None)
|
||||
if col:
|
||||
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 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'])
|
||||
gifted_subs = q.all()
|
||||
return gifted_subs, 200, {'X-Total-Count': count}
|
||||
|
||||
@flask_login.login_required
|
||||
@flask_restful.marshal_with(gifted_sub_fields)
|
||||
def post(self):
|
||||
args = gifted_sub_parser.parse_args()
|
||||
if not args['giver']:
|
||||
flask_restful.abort(400, message='Missing required parameter giver')
|
||||
if not args['receiver']:
|
||||
flask_restful.abort(400, message='Missing required parameter receiver')
|
||||
if not args['time']:
|
||||
flask_restful.abort(400, message='Missing required parameter time')
|
||||
now = sqlalchemy.func.now()
|
||||
q = db.session.query(GiftedSub).filter((GiftedSub.giver == args['giver']) &\
|
||||
(GiftedSub.receiver == args['receiver']) &\
|
||||
(sqlalchemy.func.DATE(GiftedSub.time) == args['time'].date()))
|
||||
gifted_sub = q.first()
|
||||
if not gifted_sub:
|
||||
gifted_sub = GiftedSub(created_at=now)
|
||||
gifted_sub.giver = args['giver']
|
||||
gifted_sub.receiver = args['receiver']
|
||||
gifted_sub.time = args['time']
|
||||
gifted_sub.updated_at = now
|
||||
db.session.add(gifted_sub)
|
||||
db.session.commit()
|
||||
url = api.url_for(GiftedSubResource, id=gifted_sub.id, _external=True, _scheme='https')
|
||||
return gifted_sub, 201, {'Location': url}
|
||||
|
||||
|
||||
api.add_resource(GiftedSubResource, '/gifted-subs/<int:id>')
|
||||
api.add_resource(GiftedSubsResource, '/gifted-subs')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', threaded=True, debug=False)
|
@ -0,0 +1,15 @@
|
||||
import flask_sqlalchemy
|
||||
|
||||
|
||||
db = flask_sqlalchemy.SQLAlchemy(session_options=dict(autoflush=False))
|
||||
|
||||
|
||||
class GiftedSub(db.Model):
|
||||
__tablename__ = 'giftedsubs'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
giver = db.Column(db.String)
|
||||
receiver = db.Column(db.String)
|
||||
time = db.Column(db.DateTime)
|
||||
created_at = db.Column(db.DateTime)
|
||||
updated_at = db.Column(db.DateTime)
|
@ -0,0 +1,24 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import itsdangerous
|
||||
|
||||
|
||||
def generate(secret_key, username, expiration):
|
||||
s = itsdangerous.TimedJSONWebSignatureSerializer(secret_key, expires_in=expiration)
|
||||
return s.dumps(username)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('username', metavar='USERNAME', help='user name')
|
||||
parser.add_argument('expiration', metavar='EXPIRATION', type=int,
|
||||
help='expiration time in seconds')
|
||||
args = parser.parse_args()
|
||||
secret_key = os.getenv('SECRET_KEY')
|
||||
api_key = generate(secret_key, args.username, args.expiration)
|
||||
print(api_key.decode('utf-8'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,5 @@
|
||||
Flask
|
||||
Flask-Login
|
||||
Flask-RESTful
|
||||
Flask-SQLAlchemy
|
||||
itsdangerous
|
Loading…
Reference in new issue