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)