all repos — myprecious @ 4f12dee46f1c595f99cb80b659909d4ce88d7dd5

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
 62def handle_platform(game, platform):
 63    try:
 64        game_cover = "https:" + game["cover"]["url"]
 65    except KeyError:
 66        game_cover = c.MISSING_COVER_URL
 67    temp_obj = {
 68        "game_id": game["id"],
 69        "platform_id": platform["id"],
 70        "cover": game_cover,
 71        "title": game["name"],
 72        "platform": platform["name"]
 73    }
 74    temp_json = json.dumps(temp_obj)
 75    temp_bytes = temp_json.encode("utf-8")
 76    temp_base64 = base64.b64encode(temp_bytes)
 77    temp_obj["info"] = temp_base64.decode("utf-8")
 78    return temp_obj
 79
 80def handle_game(game):
 81    return [ handle_platform(game, platform) for platform in game["platforms"] ]
 82
 83def collapse_list_of_lists(l):
 84    return [ item for sublist in l for item in sublist ]
 85
 86@login_manager.user_loader
 87def load_user(user_id):
 88    lu = db_query_one("SELECT * from login where user_id = (?)", [user_id])
 89    if lu is None:
 90        return None
 91    return construct_user(lu[0], lu[1], lu[2], lu[3])
 92
 93@app.route('/')
 94def route_index():
 95    return render("index.html")
 96
 97@app.route('/login', methods=['GET', 'POST'])
 98def route_login():
 99    if current_user.is_authenticated:
100        return redirect('/')
101    
102    if request.method == "GET":
103        return render("login.html")
104    
105    form = request.form
106    username = form["username"].lower()
107    password = form["password"]
108    try:
109        remember = bool(form["remember"])
110    except KeyError:
111        remember = False
112
113    r = db_query_one("SELECT * FROM login where username = (?)", [username])
114    if r is None:
115        return render_template("login.html", user=current_user, last_user=username)
116    user = construct_user(r[0], r[1], r[2], r[3])
117
118    if user is None:
119        return redirect(url_for("login"))
120    
121    if user.password == password:
122        login_user(user, remember=remember)
123        return redirect("/")
124    else:
125        return render_template("login.html", user=current_user, last_user=username)
126
127@app.route('/register', methods=['GET', 'POST'])
128def route_register():
129    if current_user.is_authenticated:
130        return redirect('/')
131    
132    if request.method == "GET":
133        return render("register.html")
134    
135    return render("register.html")
136
137@app.route('/logout')
138def route_logout():
139    logout_user()
140    return redirect("/")
141
142@app.route('/search', methods=['GET', 'POST'])
143def route_search():
144    if not current_user.is_authenticated:
145        return redirect('/login')
146    if request.method == 'GET':
147        return render("search.html")
148    
149    query = request.form["query"]
150    search_response = search_game(query)
151    
152    games = collapse_list_of_lists([ handle_game(x) for x in search_response ])
153    return render_template("search.html", user=current_user, games=games, query=query)
154
155@app.route('/upload', methods=['GET', 'POST'])
156def route_upload():
157    if not current_user.is_authenticated:
158        return redirect('/login')
159    if request.method == 'GET':
160        info = request.args.get("info")
161        if info is None:
162            return render_template("upload.html", user=current_user, game=c.NO_GAME)
163        # info = base64
164        base64_bytes = info.encode('utf-8')
165        message_bytes = base64.b64decode(base64_bytes)
166        message = message_bytes.decode('utf-8')
167        game = json.loads(message)
168        return render_template("upload.html", user=current_user, game=game)
169        
170    f = request.files['file']
171    try:
172        game_id = int(request.form['game_id'])
173        platform_id = int(request.form['platform_id'])
174    except ValueError:
175        return redirect("/upload")
176    
177    # TODO: use IGDB api to validate game_id, platform_id and title before adding
178    # TODO: save game in DB
179
180    save_folder = os.path.join(c.BASE_DIRECTORY, c.CONTENT_DIRECTORY, str(current_user.id), str(game_id), str(platform_id))
181    with suppress(FileExistsError):
182        os.makedirs(save_folder)
183    if f.filename is None:
184        return redirect("/upload")
185    save_file = os.path.join(save_folder, secure_filename(f.filename))
186    f.save(save_file)
187    return render("index.html")
188
189
190@app.route('/admin', methods=['GET', 'POST'])
191def route_admin():
192    if not current_user.is_authenticated:
193        return redirect('/')
194    
195    if current_user.id != 1:
196        return redirect('/')
197    
198    if request.method == "GET":
199        return render("admin.html")
200    
201    return render("admin.html")
202
203@app.route('/about')
204def route_about():
205    return render("about.html")
206
207if __name__ == "__main__":
208    app.debug=c.DEBUG_SWITCH
209    app.secret_key = c.SECRET_KEY
210    app.config['SESSION_TYPE'] = 'filesystem'
211    init_db()
212    app.run(port=1111)