commit
c14688f1e8
42 changed files with 1097 additions and 7 deletions
1
Procfile
Normal file
1
Procfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
web: gunicorn app:app
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
### Login
|
||||||
|
|
||||||
|
|
||||||
### Users
|
### Users
|
||||||
|
|
||||||
###### User Endpoints
|
###### User Endpoints
|
||||||
|
|
0
api/__init__.py
Normal file
0
api/__init__.py
Normal file
23
api/api.py
Normal file
23
api/api.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from flask import Blueprint, request, jsonify, session
|
||||||
|
from .users.api_users import api_users
|
||||||
|
from .rooms.api_rooms import api_rooms
|
||||||
|
from .games.api_games import api_games
|
||||||
|
|
||||||
|
from auth.auth import auth
|
||||||
|
|
||||||
|
api = Blueprint('api', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
def register_api_endpoints(app):
|
||||||
|
app.register_blueprint(api_users)
|
||||||
|
app.register_blueprint(api_rooms)
|
||||||
|
app.register_blueprint(api_games)
|
||||||
|
app.register_blueprint(api)
|
||||||
|
app.register_blueprint(auth)
|
||||||
|
return app
|
||||||
|
|
||||||
|
@api.route('/home', methods=['GET'])
|
||||||
|
def api_home():
|
||||||
|
response = {"message": "home page"}
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
|
18
api/decorators.py
Normal file
18
api/decorators.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from flask import Blueprint, request, jsonify, session, abort
|
||||||
|
import os
|
||||||
|
import jwt
|
||||||
|
|
||||||
|
def jwt_required():
|
||||||
|
def decorator(func):
|
||||||
|
def authorized(*args, **kwargs):
|
||||||
|
auth_header = request.headers.get('Authorization') or None
|
||||||
|
if auth_header:
|
||||||
|
auth_token = auth_header.split(" ")[1]
|
||||||
|
if jwt.decode(auth_token, os.environ.get('SECRET_KEY')):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
abort(401)
|
||||||
|
else:
|
||||||
|
abort(401)
|
||||||
|
return authorized
|
||||||
|
return decorator
|
0
api/games/__init__.py
Normal file
0
api/games/__init__.py
Normal file
59
api/games/api_games.py
Normal file
59
api/games/api_games.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
from flask import Blueprint, request, jsonify, session
|
||||||
|
from models.User import User, user_schema, users_schema
|
||||||
|
from models.GameRoom import GameRoom, rooms_schema, room_schema
|
||||||
|
from models.Game import Game, game_schema
|
||||||
|
from database import db
|
||||||
|
from ..decorators import jwt_required
|
||||||
|
import jwt
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from websockets.roomSocket import new_game_notice, join_game_notice
|
||||||
|
|
||||||
|
api_games = Blueprint('api_games', __name__, url_prefix='/api/games')
|
||||||
|
|
||||||
|
@api_games.route('/<game_id>', methods=['GET'])
|
||||||
|
def get_room(game_id):
|
||||||
|
print(game_id)
|
||||||
|
game = Game.query.filter_by(id=game_id).first()
|
||||||
|
response = game_schema.dumps(game)
|
||||||
|
# TODO create decorator that returns user from header
|
||||||
|
auth_header = request.headers.get('Authorization')
|
||||||
|
user = jwt.decode(auth_header.split(" ")[1], os.environ.get('SECRET_KEY'))['user']
|
||||||
|
print(user)
|
||||||
|
join_game_notice(game_id, user)
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
@api_games.route('/', methods=['POST'])
|
||||||
|
@jwt_required()
|
||||||
|
def post_game():
|
||||||
|
data = request.get_json()
|
||||||
|
# TODO create decorator that returns user from header
|
||||||
|
auth_header = request.headers.get('Authorization')
|
||||||
|
user = jwt.decode(auth_header.split(" ")[1], os.environ.get('SECRET_KEY'))['user']
|
||||||
|
user_id = json.loads(user)['id']
|
||||||
|
try:
|
||||||
|
game = Game(
|
||||||
|
name = data['name'],
|
||||||
|
description = data['description'],
|
||||||
|
board_size = data['boardSize'],
|
||||||
|
game_room = data['gameRoom'],
|
||||||
|
player_white = user_id
|
||||||
|
)
|
||||||
|
db.session.add(game)
|
||||||
|
db.session.commit()
|
||||||
|
new_game_notice(room=game.game_room, game=game_schema.dumps(game))
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Game created',
|
||||||
|
'game': game.id
|
||||||
|
}
|
||||||
|
return jsonify(response), 201
|
||||||
|
except Exception as e:
|
||||||
|
print('error')
|
||||||
|
print(e)
|
||||||
|
print(e.__dict__)
|
||||||
|
response = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'There was an error. Please try again.'
|
||||||
|
}
|
||||||
|
return jsonify(response), 401
|
0
api/home/__init__.py
Normal file
0
api/home/__init__.py
Normal file
7
api/home/home.py
Normal file
7
api/home/home.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
home = Blueprint('home', __name__)
|
||||||
|
|
||||||
|
@home.route('/home')
|
||||||
|
def func():
|
||||||
|
pass
|
0
api/rooms/__init__.py
Normal file
0
api/rooms/__init__.py
Normal file
55
api/rooms/api_rooms.py
Normal file
55
api/rooms/api_rooms.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
from flask import Blueprint, request, jsonify, session
|
||||||
|
from models.User import User, user_schema, users_schema
|
||||||
|
from models.GameRoom import GameRoom, rooms_schema, room_schema
|
||||||
|
from models.Game import Game, games_schema
|
||||||
|
from database import db
|
||||||
|
from ..decorators import jwt_required
|
||||||
|
from websockets.roomSocket import new_room_notice, join_room_notice
|
||||||
|
|
||||||
|
api_rooms = Blueprint('api_rooms', __name__, url_prefix='/api/rooms')
|
||||||
|
|
||||||
|
@api_rooms.route('/<room_id>', methods=['GET'])
|
||||||
|
def get_room(room_id):
|
||||||
|
print(room_id)
|
||||||
|
games = Game.query.filter_by(game_room=room_id).all()
|
||||||
|
response = games_schema.dumps(games)
|
||||||
|
join_room_notice(room_id)
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
@api_rooms.route('/', methods=['GET'])
|
||||||
|
def get_rooms():
|
||||||
|
rooms = GameRoom.query.all()
|
||||||
|
response = rooms_schema.dumps(rooms)
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
# protected route
|
||||||
|
@api_rooms.route('/', methods=['POST'])
|
||||||
|
@jwt_required()
|
||||||
|
def post_room():
|
||||||
|
print('Hey it\'s a POST request')
|
||||||
|
data = request.get_json()
|
||||||
|
print(data)
|
||||||
|
try:
|
||||||
|
room = GameRoom(
|
||||||
|
name = data['name'],
|
||||||
|
description = data['description'],
|
||||||
|
# TODO add support for private rooms and multiple languages
|
||||||
|
# private = data['private'],
|
||||||
|
# language = data['language']
|
||||||
|
)
|
||||||
|
db.session.add(room)
|
||||||
|
db.session.commit()
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Succesfully registered.',
|
||||||
|
}
|
||||||
|
new_room_notice(room_schema.dumps(room))
|
||||||
|
return jsonify(response), 201
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print(e.__dict__)
|
||||||
|
response = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'There was an error. Please try again.'
|
||||||
|
}
|
||||||
|
return jsonify(response), 401
|
0
api/users/__init__.py
Normal file
0
api/users/__init__.py
Normal file
28
api/users/api_users.py
Normal file
28
api/users/api_users.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from models.User import User, user_schema, users_schema
|
||||||
|
from flask import Blueprint, request, json, session, jsonify
|
||||||
|
from ..decorators import jwt_required
|
||||||
|
|
||||||
|
api_users = Blueprint('api_users', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
@api_users.route('/users/', methods=['GET'])
|
||||||
|
def api_get_users():
|
||||||
|
print('called one')
|
||||||
|
users = User.query.all()
|
||||||
|
response = users_schema.dumps(users)
|
||||||
|
return jsonify(response), 200
|
||||||
|
|
||||||
|
|
||||||
|
@api_users.route('/users/account', methods=['GET'])
|
||||||
|
@jwt_required()
|
||||||
|
def api_get_user():
|
||||||
|
print('called')
|
||||||
|
auth_header = request.headers.get('Authorization') or None
|
||||||
|
if auth_header:
|
||||||
|
auth_token = auth_header.split(" ")[1]
|
||||||
|
user = User.decode_auth_token(auth_token) or None
|
||||||
|
response = json.dumps(user)
|
||||||
|
else:
|
||||||
|
response = {
|
||||||
|
'status': 'failed',
|
||||||
|
'message': 'Please Log In'}
|
||||||
|
return jsonify(response)
|
28
app.py
28
app.py
|
@ -1,15 +1,29 @@
|
||||||
import os
|
import os
|
||||||
|
from database import db, ma
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
from flask_cors import CORS
|
||||||
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
bcrypt = Bcrypt(app)
|
||||||
|
app.config['CORS_HEADERS'] = 'Content-Type'
|
||||||
|
|
||||||
DEBUG = True
|
# ! Environment Variable
|
||||||
PORT = 8000
|
# TODO export CONFIGURATION_OBJECT='configuration.config.ProductionConfig'
|
||||||
|
app.config.from_object(os.getenv('CONFIGURATION_OBJECT'))
|
||||||
|
|
||||||
@app.route('/')
|
# ! Environment Variable
|
||||||
def hello_world():
|
# TODO export ALLOWED_ORIGIN= whatever the react server is
|
||||||
return 'Hello World'
|
socketio = SocketIO(app, cors_allowed_origins=os.getenv('ALLOWED_ORIGIN'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def create_app():
|
||||||
app.run(debug=DEBUG, port=PORT)
|
CORS(app, resources={
|
||||||
|
r"/api/*": {"origins": os.getenv('ALLOWED_ORIGIN')},
|
||||||
|
r"/auth/*": {"origins": os.getenv('ALLOWED_ORIGIN')},
|
||||||
|
})
|
||||||
|
db.init_app(app)
|
||||||
|
ma.init_app(app)
|
||||||
|
return app
|
||||||
|
|
0
auth/__init__.py
Normal file
0
auth/__init__.py
Normal file
73
auth/auth.py
Normal file
73
auth/auth.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
from flask import Blueprint, request, jsonify, session
|
||||||
|
from database import db
|
||||||
|
from models.User import User
|
||||||
|
|
||||||
|
auth = Blueprint('auth', __name__, url_prefix='/auth')
|
||||||
|
|
||||||
|
@auth.route('/signup', methods=['POST'])
|
||||||
|
def auth_signup():
|
||||||
|
data = request.get_json()
|
||||||
|
user = User.query.filter_by(email=data.get('email')).first()
|
||||||
|
if not user:
|
||||||
|
user = User.query.filter_by(username=data.get('username')).first()
|
||||||
|
if not user:
|
||||||
|
try:
|
||||||
|
user = User(
|
||||||
|
username = data['username'],
|
||||||
|
email = data['email'],
|
||||||
|
password = data['password'],
|
||||||
|
)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
auth_token = user.encode_auth_token(user.id)
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Succesfully registered.',
|
||||||
|
'token': auth_token.decode()
|
||||||
|
}
|
||||||
|
return jsonify(response), 201
|
||||||
|
except Exception as e:
|
||||||
|
print(e.__dict__)
|
||||||
|
response = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'There was an error. Please try again.'
|
||||||
|
}
|
||||||
|
return jsonify(response), 401
|
||||||
|
else: # username is taken
|
||||||
|
response = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'User already exists. Please login.'
|
||||||
|
}
|
||||||
|
else: # email is taken
|
||||||
|
response = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'User already exists. Please login.'
|
||||||
|
}
|
||||||
|
return jsonify(response), 202
|
||||||
|
|
||||||
|
@auth.route('/login', methods=['POST'])
|
||||||
|
def auth_login():
|
||||||
|
# get the post data
|
||||||
|
data = request.get_json()
|
||||||
|
try:
|
||||||
|
# fetch the user data
|
||||||
|
print('getting here')
|
||||||
|
print(data)
|
||||||
|
user = User.query.filter_by(email=data['email']).first()
|
||||||
|
print(user.username)
|
||||||
|
auth_token = user.encode_auth_token(user.id)
|
||||||
|
print(auth_token)
|
||||||
|
if auth_token:
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Successfully logged in.',
|
||||||
|
'token': auth_token.decode()
|
||||||
|
}
|
||||||
|
return jsonify(response), 200
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
response = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'Try again'
|
||||||
|
}
|
||||||
|
return jsonify(response), 500
|
0
configuration/__init__.py
Normal file
0
configuration/__init__.py
Normal file
34
configuration/config.py
Normal file
34
configuration/config.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import os
|
||||||
|
# local db
|
||||||
|
# ! Environment Variable
|
||||||
|
DATABASE = 'postgresql://localhost/browser-go'
|
||||||
|
|
||||||
|
class BaseConfig:
|
||||||
|
"""Base configuration."""
|
||||||
|
SECRET_KEY = os.getenv('SECRET_KEY')
|
||||||
|
DEBUG = False
|
||||||
|
BCRYPT_LOG_ROUNDS = 13
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
|
class DevelopmentConfig(BaseConfig):
|
||||||
|
"""Development configuration."""
|
||||||
|
DEBUG = True
|
||||||
|
BCRYPT_LOG_ROUNDS = 4
|
||||||
|
SQLALCHEMY_DATABASE_URI = DATABASE
|
||||||
|
PORT = 5000
|
||||||
|
|
||||||
|
|
||||||
|
class TestingConfig(BaseConfig):
|
||||||
|
"""Testing configuration."""
|
||||||
|
DEBUG = True
|
||||||
|
TESTING = True
|
||||||
|
BCRYPT_LOG_ROUNDS = 4
|
||||||
|
SQLALCHEMY_DATABASE_URI = DATABASE
|
||||||
|
PRESERVE_CONTEXT_ON_EXCEPTION = False
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionConfig(BaseConfig):
|
||||||
|
"""Production configuration."""
|
||||||
|
SECRET_KEY = ''
|
||||||
|
DEBUG = False
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'postgresql:///'
|
6
configuration/models_mount.py
Normal file
6
configuration/models_mount.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from ..models.User import User
|
||||||
|
from ..models.GameRoom import GameRoom
|
||||||
|
from ..models.Game import Game
|
||||||
|
from ..models.Move import Move
|
||||||
|
from ..models.Message import Message
|
10
database.py
Normal file
10
database.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
# ! SQLAlchemy > Marshmallow - these must be imported in this order
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_marshmallow import Marshmallow
|
||||||
|
|
||||||
|
# init database
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
# init marshmallow
|
||||||
|
ma = Marshmallow()
|
47
manage.py
Normal file
47
manage.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from flask_script import Manager
|
||||||
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
|
|
||||||
|
from database import db
|
||||||
|
from app import create_app
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
migrate = Migrate(app, db)
|
||||||
|
manager = Manager(app)
|
||||||
|
|
||||||
|
from models.Game import Game
|
||||||
|
from models.GameRoom import GameRoom
|
||||||
|
from models.Message import Message
|
||||||
|
from models.Move import Move
|
||||||
|
from models.User import User
|
||||||
|
|
||||||
|
# migrations
|
||||||
|
manager.add_command('db', MigrateCommand)
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def test():
|
||||||
|
"""Runs the unit tests without test coverage."""
|
||||||
|
tests = unittest.TestLoader().discover('browser-go-api/tests', pattern='test*.py')
|
||||||
|
result = unittest.TextTestRunner(verbosity=2).run(tests)
|
||||||
|
if result.wasSuccessful():
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def create_db():
|
||||||
|
"""Creates the db tables."""
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def drop_db():
|
||||||
|
"""Drops the db tables."""
|
||||||
|
db.drop_all()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
manager.run()
|
1
migrations/README
Normal file
1
migrations/README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Generic single-database configuration.
|
45
migrations/alembic.ini
Normal file
45
migrations/alembic.ini
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
96
migrations/env.py
Normal file
96
migrations/env.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
from flask import current_app
|
||||||
|
config.set_main_option(
|
||||||
|
'sqlalchemy.url', current_app.config.get(
|
||||||
|
'SQLALCHEMY_DATABASE_URI').replace('%', '%%'))
|
||||||
|
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url, target_metadata=target_metadata, literal_binds=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this callback is used to prevent an auto-migration from being generated
|
||||||
|
# when there are no changes to the schema
|
||||||
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
|
connectable = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section),
|
||||||
|
prefix='sqlalchemy.',
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
process_revision_directives=process_revision_directives,
|
||||||
|
**current_app.extensions['migrate'].configure_args
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
122
migrations/versions/45f01fb15e26_.py
Normal file
122
migrations/versions/45f01fb15e26_.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 45f01fb15e26
|
||||||
|
Revises:
|
||||||
|
Create Date: 2019-10-10 17:50:40.846864
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '45f01fb15e26'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('game_rooms',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=40), nullable=False),
|
||||||
|
sa.Column('description', sa.String(length=200), nullable=False),
|
||||||
|
sa.Column('private', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('language', sa.Enum('EN', name='languages'), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('users',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('username', sa.String(length=255), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('password', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('registered_on', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('admin', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('rank', sa.Enum('D7', 'D6', 'D5', 'D4', 'D3', 'D2', 'D1', 'K1', 'K2', 'K3', 'K4', 'K5', 'K6', 'K7', 'K8', 'K9', 'K10', 'K11', 'K12', 'K13', 'K14', 'K15', 'K16', 'K17', 'K18', 'K19', 'K20', 'K21', 'K22', 'K23', 'K24', 'K25', 'K26', 'K27', 'K28', 'K29', 'K30', 'UR', name='ranks'), nullable=True),
|
||||||
|
sa.Column('elo', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('rank_certainty', sa.Boolean(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('email'),
|
||||||
|
sa.UniqueConstraint('username')
|
||||||
|
)
|
||||||
|
op.create_table('game_rooms_users',
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('game_rooms_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['game_rooms_id'], ['game_rooms.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('user_id', 'game_rooms_id')
|
||||||
|
)
|
||||||
|
op.create_table('games',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('komi', sa.Numeric(precision=2, scale=1), nullable=False),
|
||||||
|
sa.Column('handicap', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('board_size', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('win_type', sa.Enum('DRAW', 'RESIGN', 'SCORE', 'TIME', 'VOID', name='wintype'), nullable=True),
|
||||||
|
sa.Column('winner', sa.Enum('BLACK', 'WHITE', 'VOID', name='players'), nullable=True),
|
||||||
|
sa.Column('score', sa.Numeric(precision=2, scale=1), nullable=True),
|
||||||
|
sa.Column('white_captures', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('black_captures', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('application', sa.String(length=40), nullable=True),
|
||||||
|
sa.Column('application_version', sa.String(length=20), nullable=True),
|
||||||
|
sa.Column('event', sa.String(length=40), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=40), nullable=True),
|
||||||
|
sa.Column('description', sa.String(length=200), nullable=True),
|
||||||
|
sa.Column('round', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('main_time', sa.Enum('BYOYOMI', 'ABSOLUTE', 'HOURGLASS', 'NONE', name='timetypes'), nullable=False),
|
||||||
|
sa.Column('time_period', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('period_length', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('overtime', sa.Enum('BYOYOMI', 'ABSOLUTE', 'HOURGLASS', 'NONE', name='timetypes'), nullable=False),
|
||||||
|
sa.Column('overtime_period', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('overtime_length', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('game_room', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('player_black', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('player_white', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['game_room'], ['game_rooms.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['player_black'], ['users.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['player_white'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('games_users',
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('game_rooms_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['game_rooms_id'], ['games.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('user_id', 'game_rooms_id')
|
||||||
|
)
|
||||||
|
op.create_table('moves',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('player', sa.Enum('BLACK', 'WHITE', name='players'), nullable=True),
|
||||||
|
sa.Column('x_point', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('y_point', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('move_number', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('is_pass', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('is_main', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('game', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('preceding_move', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['game'], ['games.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['preceding_move'], ['moves.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('messages',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('date', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('content', sa.String(length=200), nullable=False),
|
||||||
|
sa.Column('move', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['move'], ['moves.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('messages')
|
||||||
|
op.drop_table('moves')
|
||||||
|
op.drop_table('games_users')
|
||||||
|
op.drop_table('games')
|
||||||
|
op.drop_table('game_rooms_users')
|
||||||
|
op.drop_table('users')
|
||||||
|
op.drop_table('game_rooms')
|
||||||
|
# ### end Alembic commands ###
|
85
models/Game.py
Normal file
85
models/Game.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
from app import db, ma
|
||||||
|
from marshmallow import fields
|
||||||
|
import enum
|
||||||
|
from models.User import user_schema
|
||||||
|
|
||||||
|
# ! Games >-< Users join table
|
||||||
|
games_users = db.Table('games_users',
|
||||||
|
db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
|
||||||
|
db.Column('game_rooms_id', db.Integer, db.ForeignKey('games.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
class Game(db.Model):
|
||||||
|
__tablename__ = "games"
|
||||||
|
__table_args__ = {'extend_existing': True}
|
||||||
|
|
||||||
|
class Players(enum.Enum):
|
||||||
|
BLACK = "The player taking black stones"
|
||||||
|
WHITE = "The player taking white stones"
|
||||||
|
VOID = "The game was a draw or voided"
|
||||||
|
|
||||||
|
class WinType(enum.Enum):
|
||||||
|
DRAW = "The game is a draw"
|
||||||
|
RESIGN = "The game ended in resignation"
|
||||||
|
SCORE = "The game ended by counting points"
|
||||||
|
TIME = "The game ended in loss by time out"
|
||||||
|
VOID = "The game was suspended"
|
||||||
|
|
||||||
|
class TimeTypes(enum.Enum):
|
||||||
|
BYOYOMI = "Counting by time period"
|
||||||
|
ABSOLUTE = "One period to use time"
|
||||||
|
HOURGLASS = "Absolute time for both players"
|
||||||
|
NONE = "Untimed"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
date = db.Column(db.DateTime())
|
||||||
|
komi = db.Column(db.Numeric(2,1), nullable=False)
|
||||||
|
handicap = db.Column(db.Integer, nullable=False)
|
||||||
|
board_size = db.Column(db.Integer, nullable=False)
|
||||||
|
win_type = db.Column(db.Enum(WinType))
|
||||||
|
winner = db.Column(db.Enum(Players))
|
||||||
|
score = db.Column(db.Numeric(2,1))
|
||||||
|
white_captures = db.Column(db.Integer)
|
||||||
|
black_captures = db.Column(db.Integer)
|
||||||
|
application = db.Column(db.String(40))
|
||||||
|
application_version = db.Column(db.String(20))
|
||||||
|
event = db.Column(db.String(40))
|
||||||
|
name = db.Column(db.String(40))
|
||||||
|
description = db.Column(db.String(200))
|
||||||
|
round = db.Column(db.Integer)
|
||||||
|
main_time = db.Column(db.Enum(TimeTypes), nullable=False)
|
||||||
|
time_period = db.Column(db.Integer) # number of periods
|
||||||
|
period_length = db.Column(db.Integer) # seconds
|
||||||
|
overtime = db.Column(db.Enum(TimeTypes), nullable=False)
|
||||||
|
overtime_period = db.Column(db.Integer) # number of overtime periods
|
||||||
|
overtime_length = db.Column(db.Integer) # seconds
|
||||||
|
|
||||||
|
# foreign keys
|
||||||
|
game_room = db.Column(db.Integer, db.ForeignKey("game_rooms.id"))
|
||||||
|
player_black = db.Column(db.Integer, db.ForeignKey("users.id"))
|
||||||
|
player_white = db.Column(db.Integer, db.ForeignKey("users.id"))
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, name, description, board_size, game_room, player_white,
|
||||||
|
komi=0.5, handicap=0, main_time=TimeTypes.NONE, overtime=TimeTypes.NONE
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.board_size = board_size
|
||||||
|
self.game_room = game_room
|
||||||
|
self.player_white = player_white
|
||||||
|
self.komi = komi
|
||||||
|
self.handicap = handicap
|
||||||
|
self.main_time = main_time
|
||||||
|
self.overtime = overtime
|
||||||
|
|
||||||
|
class GameSchema(ma.ModelSchema):
|
||||||
|
id = fields.Int()
|
||||||
|
name = fields.Str()
|
||||||
|
description = fields.Str()
|
||||||
|
board_size = fields.Int()
|
||||||
|
player = fields.Nested(user_schema)
|
||||||
|
game_room = fields.Int()
|
||||||
|
|
||||||
|
game_schema = GameSchema()
|
||||||
|
games_schema = GameSchema(many=True)
|
40
models/GameRoom.py
Normal file
40
models/GameRoom.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from app import db, ma
|
||||||
|
from marshmallow import fields
|
||||||
|
import enum
|
||||||
|
# TODO User >---< GameRoom
|
||||||
|
|
||||||
|
# ! Game Rooms >-< Users join table
|
||||||
|
game_rooms_users = db.Table('game_rooms_users',
|
||||||
|
db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
|
||||||
|
db.Column('game_rooms_id', db.Integer, db.ForeignKey('game_rooms.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
class GameRoom(db.Model):
|
||||||
|
__tablename__ = "game_rooms"
|
||||||
|
__table_args__ = {'extend_existing': True}
|
||||||
|
|
||||||
|
class Languages(enum.Enum):
|
||||||
|
EN = "English"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = db.Column(db.String(40), nullable=False)
|
||||||
|
description = db.Column(db.String(200), nullable=False)
|
||||||
|
private = db.Column(db.Boolean(), nullable=False, default=False)
|
||||||
|
language = db.Column(db.Enum(Languages), nullable=False, default=Languages.EN)
|
||||||
|
|
||||||
|
def __init__(self, name, description, private=False, language=Languages.EN):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.private = private
|
||||||
|
self.language = language
|
||||||
|
|
||||||
|
class RoomSchema(ma.ModelSchema):
|
||||||
|
id = fields.Int()
|
||||||
|
name = fields.Str()
|
||||||
|
description = fields.Str()
|
||||||
|
private = fields.Bool()
|
||||||
|
language = fields.Str()
|
||||||
|
|
||||||
|
|
||||||
|
room_schema = RoomSchema()
|
||||||
|
rooms_schema = RoomSchema(many=True)
|
21
models/Message.py
Normal file
21
models/Message.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from app import db, ma
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class Message(db.Model):
|
||||||
|
__tablename__ = "messages"
|
||||||
|
__table_args__ = {'extend_existing': True}
|
||||||
|
|
||||||
|
class Players(enum.Enum):
|
||||||
|
BLACK = "The player taking black stones"
|
||||||
|
WHITE = "The player taking white stones"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
date = db.Column(db.DateTime(), nullable=False)
|
||||||
|
content = db.Column(db.String(200), nullable=False)
|
||||||
|
|
||||||
|
# foreign key
|
||||||
|
move = db.Column(db.Integer, db.ForeignKey("moves.id"), nullable=False)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
32
models/Move.py
Normal file
32
models/Move.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from app import db, ma
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class Move(db.Model):
|
||||||
|
__tablename__ = "moves"
|
||||||
|
__table_args__ = {'extend_existing': True}
|
||||||
|
|
||||||
|
class Players(enum.Enum):
|
||||||
|
BLACK = "The player taking black stones"
|
||||||
|
WHITE = "The player taking white stones"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
player = db.Column(db.Enum(Players))
|
||||||
|
x_point = db.Column(db.Integer)
|
||||||
|
y_point = db.Column(db.Integer)
|
||||||
|
move_number = db.Column(db.Integer)
|
||||||
|
is_pass = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
is_main = db.Column(db.Boolean, nullable=False, default=True)
|
||||||
|
|
||||||
|
# foreign keys
|
||||||
|
game = db.Column(db.Integer, db.ForeignKey("games.id"), nullable=False)
|
||||||
|
preceding_move = db.Column(db.Integer, db.ForeignKey("moves.id"))
|
||||||
|
|
||||||
|
succeeding_moves = db.relationship(
|
||||||
|
'Move',
|
||||||
|
lazy='subquery',
|
||||||
|
backref=db.backref('moves', lazy=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
121
models/User.py
Normal file
121
models/User.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
from database import db, ma
|
||||||
|
from marshmallow import fields
|
||||||
|
from app import bcrypt
|
||||||
|
from configuration import config
|
||||||
|
import datetime
|
||||||
|
import enum
|
||||||
|
import json
|
||||||
|
import jwt
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
class Ranks(enum.Enum): # with minimal Elo rating
|
||||||
|
D7 = "Seven Dan" # Elo 2700+
|
||||||
|
D6 = "Six Dan"
|
||||||
|
D5 = "Five Dan" # Elo 2500
|
||||||
|
D4 = "Four Dan"
|
||||||
|
D3 = "Three Dan"
|
||||||
|
D2 = "Two Dan"
|
||||||
|
D1 = "One Dan"
|
||||||
|
K1 = "One Kyu" # Elo 2000
|
||||||
|
K2 = "Two Kyu"
|
||||||
|
K3 = "Three Kyu"
|
||||||
|
K4 = "Four Kyu"
|
||||||
|
K5 = "Five Kyu"
|
||||||
|
K6 = "Six Kyu" # Elo 1500
|
||||||
|
K7 = "Seven Kyu"
|
||||||
|
K8 = "Eight Kyu"
|
||||||
|
K9 = "Nine Kyu"
|
||||||
|
K10 = "Ten Kyu"
|
||||||
|
K11 = "Eleven Kyu" # ELo 1000
|
||||||
|
K12 = "Twelve Kyu"
|
||||||
|
K13 = "Thirteen Kyu"
|
||||||
|
K14 = "Fourteen Kyu"
|
||||||
|
K15 = "Fifteen Kyu"
|
||||||
|
K16 = "Sixteen Kyu" # Elo 500
|
||||||
|
K17 = "Seventeen Kyu"
|
||||||
|
K18 = "Eighteen Kyu"
|
||||||
|
K19 = "Nineteen Kyu"
|
||||||
|
K20 = "Twenty Kyu"
|
||||||
|
K21 = "Twenty-One Kyu" # Elo 0
|
||||||
|
K22 = "Twenty-Two Kyu"
|
||||||
|
K23 = "Twenty-Three Kyu"
|
||||||
|
K24 = "Twenty-Four Kyu"
|
||||||
|
K25 = "Twenty-Five Kyu"
|
||||||
|
K26 = "Twenty-Six Kyu" # Elo -500
|
||||||
|
K27 = "Twenty-Seven Kyu"
|
||||||
|
K28 = "Twenty-Eight Kyu"
|
||||||
|
K29 = "Twenty-Nine Kyu"
|
||||||
|
K30 = "Thirty Kyu" # Elo -900
|
||||||
|
UR = "Unknown Rank"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
username = db.Column(db.String(255), unique=True, nullable=False, autoincrement=True)
|
||||||
|
email = db.Column(db.String(255), unique=True, nullable=False)
|
||||||
|
password = db.Column(db.String(255), nullable=False)
|
||||||
|
registered_on = db.Column(db.DateTime, nullable=False)
|
||||||
|
admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
rank = db.Column(db.Enum(Ranks))
|
||||||
|
elo = db.Column(db.Integer)
|
||||||
|
rank_certainty = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, username, email, password, rank=Ranks.K1, admin=False):
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.password = bcrypt.generate_password_hash(
|
||||||
|
password, 13
|
||||||
|
).decode()
|
||||||
|
self.rank = rank
|
||||||
|
self.registered_on = datetime.datetime.now()
|
||||||
|
self.admin = admin
|
||||||
|
|
||||||
|
def encode_auth_token(self, user_id):
|
||||||
|
"""
|
||||||
|
Generates the Auth Token
|
||||||
|
:return: string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
payload = {
|
||||||
|
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=4),
|
||||||
|
'iat': datetime.datetime.utcnow(),
|
||||||
|
'user': user_schema.dumps(self)
|
||||||
|
}
|
||||||
|
return jwt.encode(
|
||||||
|
payload,
|
||||||
|
os.environ.get('SECRET_KEY'),
|
||||||
|
algorithm='HS256'
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_auth_token(auth_token):
|
||||||
|
"""
|
||||||
|
Decodes the auth token
|
||||||
|
:param auth_token:
|
||||||
|
:return: integer|string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(auth_token, os.environ.get('SECRET_KEY'))
|
||||||
|
return payload['user']
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
return 'Signature expired. Please log in again.'
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
return 'Invalid token. Please log in again.'
|
||||||
|
|
||||||
|
class UserSchema(ma.ModelSchema):
|
||||||
|
id = fields.Int()
|
||||||
|
username = fields.Str()
|
||||||
|
email = fields.Str()
|
||||||
|
registered_on = fields.Date()
|
||||||
|
rank = fields.Str()
|
||||||
|
rank_certainty = fields.Bool()
|
||||||
|
elo = fields.Int()
|
||||||
|
|
||||||
|
|
||||||
|
user_schema = UserSchema()
|
||||||
|
users_schema = UserSchema(many=True)
|
0
models/__init__.py
Normal file
0
models/__init__.py
Normal file
|
@ -1,13 +1,41 @@
|
||||||
|
alembic==1.2.1
|
||||||
|
astroid==2.3.1
|
||||||
|
bcrypt==3.1.7
|
||||||
|
cffi==1.12.3
|
||||||
Click==7.0
|
Click==7.0
|
||||||
|
dnspython==1.16.0
|
||||||
|
eventlet==0.25.1
|
||||||
Flask==1.1.1
|
Flask==1.1.1
|
||||||
|
Flask-Bcrypt==0.7.1
|
||||||
Flask-Cors==3.0.8
|
Flask-Cors==3.0.8
|
||||||
flask-marshmallow==0.10.1
|
flask-marshmallow==0.10.1
|
||||||
|
Flask-Migrate==2.5.2
|
||||||
|
Flask-Script==2.0.6
|
||||||
|
Flask-SocketIO==4.2.1
|
||||||
Flask-SQLAlchemy==2.4.1
|
Flask-SQLAlchemy==2.4.1
|
||||||
|
Flask-Testing==0.7.1
|
||||||
|
greenlet==0.4.15
|
||||||
|
isort==4.3.21
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.10.1
|
Jinja2==2.10.1
|
||||||
|
lazy-object-proxy==1.4.2
|
||||||
|
Mako==1.1.0
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
marshmallow==3.2.0
|
marshmallow==3.2.0
|
||||||
marshmallow-sqlalchemy==0.19.0
|
marshmallow-sqlalchemy==0.19.0
|
||||||
|
mccabe==0.6.1
|
||||||
|
monotonic==1.5
|
||||||
|
numpy==1.17.2
|
||||||
|
psycopg2==2.8.3
|
||||||
|
pycparser==2.19
|
||||||
|
PyJWT==1.7.1
|
||||||
|
pylint==2.4.2
|
||||||
|
python-dateutil==2.8.0
|
||||||
|
python-editor==1.0.4
|
||||||
|
python-engineio==3.9.3
|
||||||
|
python-socketio==4.3.1
|
||||||
six==1.12.0
|
six==1.12.0
|
||||||
SQLAlchemy==1.3.8
|
SQLAlchemy==1.3.8
|
||||||
|
typed-ast==1.4.0
|
||||||
Werkzeug==0.16.0
|
Werkzeug==0.16.0
|
||||||
|
wrapt==1.11.2
|
||||||
|
|
1
runtime.txt
Normal file
1
runtime.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
python-3.7.4
|
17
server.py
Normal file
17
server.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from app import create_app, db
|
||||||
|
|
||||||
|
# Blueprints
|
||||||
|
from api.api import register_api_endpoints
|
||||||
|
from auth.auth import auth
|
||||||
|
|
||||||
|
# Web sockets
|
||||||
|
from websockets.socket import socketio
|
||||||
|
|
||||||
|
import configuration.models_mount
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = create_app()
|
||||||
|
register_api_endpoints(app)
|
||||||
|
migrate = Migrate(app, db)
|
||||||
|
socketio.run(app, debug=True)
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
14
tests/base.py
Normal file
14
tests/base.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from flask_testing import TestCase
|
||||||
|
from ..app import app, db
|
||||||
|
|
||||||
|
class BaseTestCase(TestCase):
|
||||||
|
""" Base Tests """
|
||||||
|
|
||||||
|
def create_app(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
14
tests/test_config.py
Normal file
14
tests/test_config.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
from flask_testing import TestCase
|
||||||
|
|
||||||
|
from ..app import app
|
||||||
|
|
||||||
|
class TestDevelopmentConfig(TestCase):
|
||||||
|
def create_app(self):
|
||||||
|
app.config.from_object('browser-go-api.config.DevelopmentConfig')
|
||||||
|
return app
|
||||||
|
|
||||||
|
def test_app_is_development(self):
|
||||||
|
self.assertFalse(app.config['SECRET_KEY'] is 'my_precious')
|
0
websockets/__init__.py
Normal file
0
websockets/__init__.py
Normal file
24
websockets/roomSocket.py
Normal file
24
websockets/roomSocket.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from app import socketio
|
||||||
|
from flask_socketio import send, emit, join_room, leave_room
|
||||||
|
import json
|
||||||
|
|
||||||
|
def join_room_notice(room):
|
||||||
|
@socketio.on('join room', namespace=f'/{room}')
|
||||||
|
def connect_room(message):
|
||||||
|
print(f'connected with ${message}')
|
||||||
|
emit('connected', {'roomspace': f'/{room}'})
|
||||||
|
|
||||||
|
def new_game_notice(room, game):
|
||||||
|
print('sending new game notice')
|
||||||
|
socketio.emit('new game', game, broadcast=True, namespace=f'/{room}')
|
||||||
|
|
||||||
|
|
||||||
|
def new_room_notice(room):
|
||||||
|
socketio.emit('new room', room, broadcast=True)
|
||||||
|
|
||||||
|
def join_game_notice(game_id, user):
|
||||||
|
@socketio.on('join game')
|
||||||
|
def return_join_game_notice(data):
|
||||||
|
game = data['game']
|
||||||
|
join_room(game)
|
||||||
|
emit('join game', data, room=f'game')
|
27
websockets/socket.py
Normal file
27
websockets/socket.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from app import socketio
|
||||||
|
from flask_socketio import send, emit, join_room, leave_room
|
||||||
|
import json
|
||||||
|
|
||||||
|
# ! Basic Connection
|
||||||
|
@socketio.on('connect')
|
||||||
|
def handle_connection():
|
||||||
|
print('''
|
||||||
|
|
||||||
|
wow, there was a socketio connection!
|
||||||
|
|
||||||
|
cool
|
||||||
|
''')
|
||||||
|
emit('message', {'data':'connection'}, broadcast=True)
|
||||||
|
|
||||||
|
@socketio.on('send message')
|
||||||
|
def handle_message(message):
|
||||||
|
print(message)
|
||||||
|
emit('init namespace', {'namespace':'newroom'})
|
||||||
|
|
||||||
|
@socketio.on('connect', namespace='/newroom')
|
||||||
|
def handle_connection():
|
||||||
|
print('''
|
||||||
|
|
||||||
|
look cool a namespaced socketio connection!
|
||||||
|
|
||||||
|
''')
|
Loading…
Reference in a new issue