470 lines
17 KiB
Python
470 lines
17 KiB
Python
from flask import Flask, request, jsonify
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from flask_bcrypt import Bcrypt
|
|
import uuid
|
|
import jwt
|
|
from datetime import datetime, timedelta
|
|
|
|
app = Flask(__name__)
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///game_save.db'
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
app.config['SECRET_KEY'] = 'your_secret_key_here'
|
|
|
|
db = SQLAlchemy(app)
|
|
bcrypt = Bcrypt(app)
|
|
|
|
# Models
|
|
class Developer(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
username = db.Column(db.String(80), nullable=True)
|
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
|
password = db.Column(db.String(120), nullable=False)
|
|
|
|
class User(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
username = db.Column(db.String(80), nullable=False)
|
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
|
password = db.Column(db.String(120), nullable=False)
|
|
game_password = db.Column(db.String(120), nullable=False)
|
|
|
|
class GameSave(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
|
game_id = db.Column(db.String(80), nullable=False)
|
|
slot = db.Column(db.Integer, nullable=False) # Save slot (1-10)
|
|
save_data = db.Column(db.Text, nullable=False)
|
|
|
|
class Game(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
ugi = db.Column(db.String(80), unique=True, nullable=False)
|
|
name = db.Column(db.String(100), nullable=False)
|
|
developer_id = db.Column(db.Integer, db.ForeignKey('developer.id'), nullable=False)
|
|
|
|
class Log(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
|
game_id = db.Column(db.String(80), nullable=False)
|
|
action = db.Column(db.String(20), nullable=False) # 'add_points' or 'remove_points'
|
|
points = db.Column(db.Integer, nullable=False)
|
|
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
|
|
|
class UserStats(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
|
games_played = db.Column(db.String(200), nullable=False, default='')
|
|
points_earned = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
class GamePlayed(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
|
game_id = db.Column(db.String(200), nullable=False, default='')
|
|
|
|
class AuthToken(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
token = db.Column(db.String(200), nullable=False)
|
|
game_id = db.Column(db.String(80), nullable=False)
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
|
|
|
class Session(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
session_token = db.Column(db.String(200), nullable=False)
|
|
user_type = db.Column(db.String(20), nullable=False) # 'user' or 'developer'
|
|
user_id = db.Column(db.Integer, nullable=False)
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
|
|
|
# Routes
|
|
|
|
@app.route('/api/user/login', methods=['POST'])
|
|
def login_user():
|
|
data = request.json
|
|
user = User.query.filter_by(email=data['email']).first()
|
|
if user and bcrypt.check_password_hash(user.password, data['password']):
|
|
session_token = str(uuid.uuid4())
|
|
new_session = Session(
|
|
session_token=session_token,
|
|
user_type='user',
|
|
user_id=user.id
|
|
)
|
|
db.session.add(new_session)
|
|
db.session.commit()
|
|
return jsonify({'session_token': session_token}), 200
|
|
else:
|
|
return jsonify({'error': 'Invalid email or password'}), 401
|
|
|
|
@app.route('/api/user/register', methods=['POST'])
|
|
def register_user():
|
|
data = request.json
|
|
if 'username' not in data or 'email' not in data or 'password' not in data or 'game_password' not in data:
|
|
return jsonify({'error': 'Username, email, password, and game password are required'}), 400
|
|
|
|
existing_user = User.query.filter_by(email=data['email']).first()
|
|
if existing_user:
|
|
return jsonify({'error': 'Email already in use'}), 400
|
|
|
|
hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8')
|
|
new_user = User(
|
|
username=data['username'],
|
|
email=data['email'],
|
|
password=hashed_password,
|
|
game_password=data['game_password']
|
|
)
|
|
db.session.add(new_user)
|
|
db.session.commit()
|
|
|
|
new_user_stats = UserStats(user_id=new_user.id)
|
|
db.session.add(new_user_stats)
|
|
db.session.commit()
|
|
return jsonify({'message': 'User registered successfully'}), 201
|
|
|
|
@app.route('/api/developer/login', methods=['POST'])
|
|
def login_developer():
|
|
data = request.json
|
|
developer = Developer.query.filter_by(email=data['email']).first()
|
|
if developer and bcrypt.check_password_hash(developer.password, data['password']):
|
|
session_token = str(uuid.uuid4())
|
|
new_session = Session(
|
|
session_token=session_token,
|
|
user_type='developer',
|
|
user_id=developer.id
|
|
)
|
|
db.session.add(new_session)
|
|
db.session.commit()
|
|
return jsonify({'session_token': session_token, 'id': developer.id}), 200
|
|
else:
|
|
return jsonify({'error': 'Invalid email or password'}), 401
|
|
|
|
@app.route('/api/developer/register', methods=['POST'])
|
|
def register_developer():
|
|
data = request.json
|
|
if 'email' not in data or 'password' not in data:
|
|
return jsonify({'error': 'Email and password are required'}), 400
|
|
|
|
existing_dev = Developer.query.filter_by(email=data['email']).first()
|
|
if existing_dev:
|
|
return jsonify({'error': 'Email already in use'}), 400
|
|
|
|
hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8')
|
|
new_dev = Developer(
|
|
username=data.get('username'),
|
|
email=data['email'],
|
|
password=hashed_password
|
|
)
|
|
db.session.add(new_dev)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Developer registered successfully'}), 201
|
|
|
|
@app.route('/api/game/register', methods=['POST'])
|
|
def register_game():
|
|
data = request.json
|
|
if 'session_token' not in data:
|
|
return jsonify({'error': 'Session token is required'}), 401
|
|
|
|
session = Session.query.filter_by(session_token=data['session_token']).first()
|
|
if not session or session.user_type != 'developer':
|
|
return jsonify({'error': 'Invalid session or not a developer'}), 401
|
|
|
|
if 'name' not in data:
|
|
return jsonify({'error': 'Game name is required'}), 400
|
|
|
|
gameexist = Game.query.filter_by(name=data['name']).first()
|
|
if gameexist:
|
|
return jsonify({'error': 'Game name already taken.'}), 400
|
|
|
|
ugi = str(uuid.uuid4())
|
|
new_game = Game(
|
|
ugi=ugi,
|
|
name=data['name'],
|
|
developer_id=session.user_id
|
|
)
|
|
db.session.add(new_game)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Game registered successfully', 'ugi': ugi}), 201
|
|
|
|
@app.route('/api/game/delete', methods=['POST'])
|
|
def delete_game():
|
|
data = request.json
|
|
if 'session_token' not in data:
|
|
return jsonify({'error': 'Session token is required'}), 401
|
|
|
|
session = Session.query.filter_by(session_token=data['session_token']).first()
|
|
if not session or session.user_type != 'developer':
|
|
return jsonify({'error': 'Invalid session or not a developer'}), 401
|
|
|
|
if 'ugi' not in data:
|
|
return jsonify({'error': 'Game UGI is required'}), 400
|
|
|
|
game_to_delete = Game.query.filter_by(ugi=data['ugi'], developer_id=session.user_id).first()
|
|
if not game_to_delete:
|
|
return jsonify({'error': 'Game not found or not owned by you'}), 404
|
|
game_saves_to_delete = GameSave.query.filter_by(game_id=data['ugi']).all()
|
|
for save in game_saves_to_delete:
|
|
db.session.delete(save)
|
|
|
|
db.session.delete(game_to_delete)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Game deleted successfully'}), 200
|
|
|
|
@app.route('/api/game/authenticate', methods=['POST'])
|
|
def authenticate_game():
|
|
data = request.json
|
|
if 'username' not in data or 'game_password' not in data or 'game_id' not in data:
|
|
return jsonify({'error': 'Username, game password, and game ID are required'}), 400
|
|
|
|
user = User.query.filter_by(username=data['username']).first()
|
|
if not user:
|
|
return jsonify({'error': 'User not found'}), 404
|
|
|
|
if user.game_password != data['game_password']:
|
|
return jsonify({'error': 'Incorrect game password'}), 401
|
|
|
|
payload = {
|
|
'exp': datetime.utcnow() + timedelta(minutes=30),
|
|
'iat': datetime.utcnow(),
|
|
'sub': user.id
|
|
}
|
|
token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
|
|
# data['game_id'] is the game's UGI. When storing it in db, first, find the ugi and then find the id of the game then store that id in the db
|
|
game_id = Game.query.filter_by(ugi=data['game_id']).first().id
|
|
if not game_id:
|
|
return jsonify({'error': 'Game not found'}), 404
|
|
new_auth_token = AuthToken(
|
|
token=token,
|
|
game_id=game_id,
|
|
user_id=user.id
|
|
)
|
|
db.session.add(new_auth_token)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Game authenticated successfully', 'token': token}), 200
|
|
|
|
@app.route('/api/game/save-data', methods=['POST'])
|
|
def save_game_data():
|
|
data = request.json
|
|
if 'save_data' not in data or 'token' not in data or 'slot' not in data:
|
|
return jsonify({'error': 'Save data, token, and slot are required'}), 400
|
|
|
|
auth_token = AuthToken.query.filter_by(token=data['token']).first()
|
|
if not auth_token:
|
|
return jsonify({'error': 'Invalid token'}), 401
|
|
|
|
if not 1 <= int(data['slot']) <= 10:
|
|
return jsonify({'error': 'Invalid save slot'}), 400
|
|
|
|
existing_save = GameSave.query.filter_by(user_id=auth_token.user_id, game_id=auth_token.game_id, slot=int(data['slot'])).first()
|
|
if existing_save:
|
|
existing_save.save_data = data['save_data']
|
|
db.session.commit()
|
|
return jsonify({'message': 'Game save data updated successfully'}), 200
|
|
else:
|
|
new_save = GameSave(
|
|
user_id=auth_token.user_id,
|
|
game_id=auth_token.game_id,
|
|
slot=data['slot'],
|
|
save_data=data['save_data']
|
|
)
|
|
db.session.add(new_save)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Game save data saved successfully'}), 201
|
|
|
|
@app.route('/api/game/read-data', methods=['POST'])
|
|
def read_game_data():
|
|
data = request.json
|
|
if 'token' not in data or 'slot' not in data:
|
|
return jsonify({'error': 'Token and slot are required'}), 400
|
|
|
|
auth_token = AuthToken.query.filter_by(token=data['token']).first()
|
|
if not auth_token:
|
|
return jsonify({'error': 'Invalid token'}), 401
|
|
|
|
if not 1 <= int(data['slot']) <= 10:
|
|
return jsonify({'error': 'Invalid save slot'}), 400
|
|
|
|
save_data = GameSave.query.filter_by(user_id=auth_token.user_id, game_id=auth_token.game_id, slot=int(data['slot'])).first()
|
|
if save_data:
|
|
return jsonify({'save_data': save_data.save_data}), 200
|
|
else:
|
|
return jsonify({'error': 'No save data found for this user and game'}), 404
|
|
|
|
@app.route('/api/user/stats', methods=['POST'])
|
|
def get_user_stats():
|
|
data = request.json
|
|
if 'token' not in data:
|
|
return jsonify({'error': 'Token is required'}), 400
|
|
|
|
auth_token = AuthToken.query.filter_by(token=data['token']).first()
|
|
auth_token2 = Session.query.filter_by(session_token=data['token']).first()
|
|
if not auth_token and not auth_token2:
|
|
return jsonify({'error': 'Invalid token'}), 401
|
|
if auth_token:
|
|
user_id = auth_token.user_id
|
|
elif auth_token2:
|
|
user_id = auth_token2.user_id
|
|
|
|
user_stats = UserStats.query.filter_by(user_id=user_id).first()
|
|
gamed = GamePlayed.query.filter_by(user_id=user_id).all()
|
|
gamesPld = []
|
|
for game in gamed:
|
|
gamesPld.append(game.game_id)
|
|
if user_stats:
|
|
return jsonify({
|
|
'games_played': gamesPld,
|
|
'points_earned': user_stats.points_earned
|
|
}), 200
|
|
else:
|
|
return jsonify({'error': 'User stats not found'}), 404
|
|
|
|
@app.route('/api/user/add-game-played', methods=['POST'])
|
|
def add_game_played():
|
|
data = request.json
|
|
if 'token' not in data:
|
|
return jsonify({'error': 'Token is required'}), 400
|
|
|
|
auth_token = AuthToken.query.filter_by(token=data['token']).first()
|
|
if not auth_token:
|
|
return jsonify({'error': 'Invalid token'}), 401
|
|
user_id = auth_token.user_id
|
|
|
|
user_stats = UserStats.query.filter_by(user_id=user_id).first()
|
|
if user_stats:
|
|
game_already_played = GamePlayed.query.filter_by(game_id=auth_token.game_id).first()
|
|
if not game_already_played:
|
|
new_game_played = GamePlayed(
|
|
user_id=user_id,
|
|
game_id=auth_token.game_id
|
|
)
|
|
db.session.add(new_game_played)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Game added to played games successfully'}), 200
|
|
else:
|
|
return jsonify({'error': 'Game is already marked as played'}), 200
|
|
else:
|
|
return jsonify({'error': 'User stats not found'}), 404
|
|
|
|
@app.route('/api/user/points/add', methods=['POST'])
|
|
def add_points():
|
|
data = request.json
|
|
if 'points' not in data or 'token' not in data:
|
|
return jsonify({'error': 'Points and token are required'}), 400
|
|
|
|
auth_token = AuthToken.query.filter_by(token=data['token']).first()
|
|
auth_token2 = Session.query.filter_by(session_token=data['token']).first()
|
|
if not auth_token and not auth_token2:
|
|
return jsonify({'error': 'Invalid token'}), 401
|
|
|
|
if auth_token is not None:
|
|
user_id = auth_token.user_id
|
|
elif auth_token2:
|
|
user_id = auth_token2.user_id
|
|
|
|
|
|
user_stats = UserStats.query.filter_by(user_id=user_id).first()
|
|
if user_stats:
|
|
user_stats.points_earned += data['points']
|
|
db.session.commit()
|
|
|
|
new_log = Log(
|
|
user_id=user_id,
|
|
game_id="game_id",
|
|
action='add_points',
|
|
points=data['points']
|
|
)
|
|
db.session.add(new_log)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Points added successfully'}), 200
|
|
else:
|
|
return jsonify({'error': 'User stats not found'}), 404
|
|
|
|
@app.route('/api/user/points/remove', methods=['POST'])
|
|
def remove_points():
|
|
data = request.json
|
|
if 'points' not in data or 'token' not in data:
|
|
return jsonify({'error': 'Points and token are required'}), 400
|
|
|
|
auth_token = AuthToken.query.filter_by(token=data['token']).first()
|
|
auth_token2 = Session.query.filter_by(session_token=data['token']).first()
|
|
if not auth_token and not auth_token2:
|
|
return jsonify({'error': 'Invalid token'}), 401
|
|
if auth_token:
|
|
user_id = auth_token.user_id
|
|
elif auth_token2:
|
|
user_id = auth_token2.user_id
|
|
|
|
user_stats = UserStats.query.filter_by(user_id=user_id).first()
|
|
if user_stats:
|
|
if user_stats.points_earned >= data['points']:
|
|
user_stats.points_earned -= data['points']
|
|
db.session.commit()
|
|
|
|
new_log = Log(
|
|
user_id=user_id,
|
|
game_id="game_id",
|
|
action='remove_points',
|
|
points=data['points']
|
|
)
|
|
db.session.add(new_log)
|
|
db.session.commit()
|
|
return jsonify({'message': 'Points removed successfully'}), 200
|
|
else:
|
|
return jsonify({'error': 'Not enough points to remove'}), 400
|
|
else:
|
|
return jsonify({'error': 'User stats not found'}), 404
|
|
|
|
@app.route('/api/logs', methods=['GET'])
|
|
def get_logs():
|
|
session_token = request.cookies.get('session_token')
|
|
if not session_token:
|
|
return jsonify({'error': 'Session token is required'}), 401
|
|
|
|
session = Session.query.filter_by(session_token=session_token).first()
|
|
if not session:
|
|
return jsonify({'error': 'Invalid session'}), 401
|
|
|
|
logs = Log.query.filter_by(user_id=session.user_id).all()
|
|
log_data = []
|
|
for log in logs:
|
|
log_data.append({
|
|
'id': log.id,
|
|
'user_id': log.user_id,
|
|
'game_id': log.game_id,
|
|
'action': log.action,
|
|
'points': log.points,
|
|
'timestamp': log.timestamp
|
|
})
|
|
return jsonify(log_data), 200
|
|
|
|
@app.route('/api/developerstats/games', methods=['POST'])
|
|
def games_count():
|
|
data = request.json
|
|
if 'token' not in data:
|
|
return jsonify({'error': 'token is required'}), 401
|
|
|
|
session = Session.query.filter_by(session_token=data['token']).first()
|
|
if not session:
|
|
return jsonify({'error': 'Invalid session'}), 401
|
|
|
|
if session.user_type != "developer":
|
|
return jsonify({'error': 'Invalid User type. Requested developer, got user.'})
|
|
|
|
total_games = Game.query.filter_by(developer_id=session.user_id).all()
|
|
games_data = [
|
|
{'name': game.name, 'ugi': game.ugi}
|
|
for game in total_games
|
|
]
|
|
|
|
return jsonify({"len": len(total_games), "games": games_data}), 200
|
|
|
|
|
|
# Purge old tokens
|
|
#@app.before_first_request
|
|
#def purge_old_tokens():
|
|
# old_tokens = AuthToken.query.filter(AuthToken.created_at < datetime.utcnow() - timedelta(days=10)).all()
|
|
# for token in old_tokens:
|
|
# db.session.delete(token)
|
|
# db.session.commit()
|
|
with app.app_context():
|
|
db.create_all()
|
|
#if __name__ == '__main__':
|
|
# with app.app_context():
|
|
# db.create_all()
|
|
#app.run(debug=True, host='0.0.0.0', port=5034)
|