all repos — myprecious @ 5acbe103e332613faff043d102cc215dc13380d2

A lightweight web service to backup precious game saves.

myprecious/__main__.py (view raw)

  1from flask import Flask, request, render_template, url_for, redirect
  2from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user
  3from werkzeug.utils import secure_filename
  4from contextlib import suppress
  5import os, json, base64, sqlite3
  6import hashlib, uuid
  7import Constants as c
  8if c.DEBUG_SWITCH:
  9    from GamesApiTest import search_game
 10else:
 11    from GamesApi import search_game
 12
 13app = Flask(__name__)
 14login_manager = LoginManager(app)
 15
 16def hash(password: str):
 17    salt = uuid.uuid4().hex
 18    return hashlib.sha512(password + salt).hexdigest(), salt
 19
 20def db_query_one(query, parameters):
 21    with sqlite3.connect(c.DB_PATH) as db_connection:
 22        curs = db_connection.cursor()
 23        curs.execute(query, parameters)
 24        try:
 25            return list(curs.fetchone())
 26        except TypeError:
 27            return None
 28        
 29def run_sql(sql_path: str):
 30    with open(sql_path, 'r', encoding="utf-8") as f:
 31        with sqlite3.connect(c.DB_PATH) as con:
 32            curs = con.cursor()
 33            sql = f.read()
 34            curs.executescript(sql)
 35
 36def add_user(username, password, email):
 37    query_str = "insert or ignore into login (username, password, email) values (?,?,?);"
 38    query_param = [username, password, email]
 39    return db_query_one(query_str, query_param)
 40
 41def init_db():
 42    run_sql(c.MIGRATIONS_INIT_PATH)
 43    add_user(c.DEFAULT_ADMIN_USER, c.DEFAULT_ADMIN_PW, c.DEFAULT_ADMIN_EMAIL)
 44
 45
 46class User(UserMixin):
 47    def __init__(self, user_id, username, password, email):
 48        self.id = user_id
 49        self.username = username
 50        self.password = password
 51        self.email = email
 52
 53def construct_user(id, username, password, email):
 54    try:
 55        return User(int(id), username, password, email)
 56    except TypeError:
 57        return None
 58    
 59def render(template):
 60    return render_template(template, user=current_user)
 61
 62
 63def handle_platform(game, platform):
 64    try:
 65        game_cover = "https:" + game["cover"]["url"]
 66    except KeyError:
 67        game_cover = c.MISSING_COVER_URL
 68    temp_obj = {
 69        "game_id": game["id"],
 70        "platform_id": platform["id"],
 71        "cover": game_cover,
 72        "title": game["name"],
 73        "platform": platform["name"]
 74    }
 75    temp_json = json.dumps(temp_obj)
 76    temp_bytes = temp_json.encode("utf-8")
 77    temp_base64 = base64.b64encode(temp_bytes)
 78    temp_obj["info"] = temp_base64.decode("utf-8")
 79    return temp_obj
 80
 81def handle_game(game):
 82    return [ handle_platform(game, platform) for platform in game["platforms"] ]
 83
 84def collapse_list_of_lists(l):
 85    return [ item for sublist in l for item in sublist ]
 86
 87@login_manager.user_loader
 88def load_user(user_id):
 89    lu = db_query_one("SELECT * from login where user_id = (?)", [user_id])
 90    if lu is None:
 91        return None
 92    return construct_user(lu[0], lu[1], lu[2], lu[3])
 93
 94@app.route('/')
 95def route_index():
 96    return render("index.html")
 97
 98@app.route('/login', methods=['GET', 'POST'])
 99def route_login():
100    if current_user.is_authenticated:
101        return redirect('/')
102    
103    if request.method == "GET":
104        return render("login.html")
105    
106    form = request.form
107    username = form["username"].lower()
108    password = form["password"]
109    try:
110        remember = bool(form["remember"])
111    except KeyError:
112        remember = False
113
114    r = db_query_one("SELECT * FROM login where username = (?)", [username])
115    if r is None:
116        return render_template("login.html", user=current_user, last_user=username)
117    user = construct_user(r[0], r[1], r[2], r[3])
118
119    if user is None:
120        return redirect(url_for("login"))
121    
122    if user.password == password:
123        login_user(user, remember=remember)
124        return redirect("/")
125    else:
126        return render_template("login.html", user=current_user, last_user=username)
127
128@app.route('/logout')
129def route_logout():
130    logout_user()
131    return redirect("/")
132
133@app.route('/search', methods=['GET', 'POST'])
134def route_search():
135    if not current_user.is_authenticated:
136        return redirect('/login')
137    if request.method == 'GET':
138        return render("search.html")
139    
140    query = request.form["query"]
141    search_response = search_game(query)
142    
143    games = collapse_list_of_lists([ handle_game(x) for x in search_response ])
144    return render_template("search.html", user=current_user, games=games, query=query)
145
146@app.route('/upload', methods=['GET', 'POST'])
147def route_upload():
148    if not current_user.is_authenticated:
149        return redirect('/login')
150    if request.method == 'GET':
151        info = request.args.get("info")
152        if info is None:
153            return render_template("upload.html", user=current_user, game=c.NO_GAME)
154        # info = base64
155        base64_bytes = info.encode('utf-8')
156        message_bytes = base64.b64decode(base64_bytes)
157        message = message_bytes.decode('utf-8')
158        game = json.loads(message)
159        return render_template("upload.html", user=current_user, game=game)
160        
161    f = request.files['file']
162    try:
163        game_id = int(request.form['game_id'])
164        platform_id = int(request.form['platform_id'])
165    except ValueError:
166        return redirect("/upload")
167    
168    # TODO: use IGDB api to validate game_id, platform_id and title before adding
169    # TODO: save game in DB
170
171    save_folder = os.path.join(c.BASE_DIRECTORY, c.CONTENT_DIRECTORY, str(current_user.id), str(game_id), str(platform_id))
172    with suppress(FileExistsError):
173        os.makedirs(save_folder)
174    if f.filename is None:
175        return redirect("/upload")
176    save_file = os.path.join(save_folder, secure_filename(f.filename))
177    f.save(save_file)
178    return render("index.html")
179
180if __name__ == "__main__":
181    app.debug=c.DEBUG_SWITCH
182    app.secret_key = c.SECRET_KEY
183    app.config['SESSION_TYPE'] = 'filesystem'
184    init_db()
185    app.run(port=1111)