from flask import Flask, render_template, request, redirect, url_for import sqlite3 app = Flask(__name__) DB_PATH = "tarkov.db" def get_db(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn @app.route("/") def index(): conn = get_db() maps = conn.execute(""" SELECT id, name FROM maps ORDER BY name """).fetchall() map_filter = request.args.get("map_id", type=int) sort = request.args.get("sort", "priority_desc") show = request.args.get("show", "all") key_map_rows = conn.execute(""" SELECT key_id, map_id FROM key_maps """).fetchall() key_maps = {} for row in key_map_rows: key_maps.setdefault(row["key_id"], set()).add(row["map_id"]) key_query = """ SELECT k.id, k.name, k.icon_url, k.grid_image_url, k.wiki_url, r.priority, r.reason, COALESCE(r.used_in_quest, 0) AS used_in_quest FROM keys k """ params = [] if map_filter: key_query += """ JOIN key_maps kmf ON k.id = kmf.key_id AND kmf.map_id = ? """ params.append(map_filter) key_query += """ LEFT JOIN key_ratings r ON k.id = r.key_id """ if show == "rated": key_query += " WHERE r.priority IS NOT NULL " elif show == "unrated": key_query += " WHERE r.priority IS NULL " elif show == "quest": key_query += " WHERE COALESCE(r.used_in_quest, 0) = 1 " if sort == "name_asc": order_by = "k.name ASC" elif sort == "name_desc": order_by = "k.name DESC" elif sort == "priority_asc": order_by = "CASE WHEN r.priority IS NULL THEN 1 ELSE 0 END, r.priority ASC, k.name" else: order_by = "CASE WHEN r.priority IS NULL THEN 1 ELSE 0 END, r.priority DESC, k.name" key_query += f" ORDER BY {order_by} " keys = conn.execute(key_query, params).fetchall() conn.close() key_maps = {k: sorted(v) for k, v in key_maps.items()} return render_template( "index.html", keys=keys, maps=maps, key_maps=key_maps, map_filter=map_filter, sort=sort, show=show, ) @app.route("/rate", methods=["POST"]) def rate_key(): key_id = request.form["key_id"] priority = request.form.get("priority") if priority == "": priority = None reason = request.form.get("reason", "") used_in_quest = 1 if request.form.get("used_in_quest") == "on" else 0 map_filter = request.form.get("map_id") sort = request.form.get("sort") show = request.form.get("show") map_ids = [] for value in request.form.getlist("map_ids"): try: map_ids.append(int(value)) except ValueError: continue conn = get_db() conn.execute(""" INSERT INTO key_ratings (key_id, priority, reason, used_in_quest) VALUES (?, ?, ?, ?) ON CONFLICT(key_id) DO UPDATE SET priority = excluded.priority, reason = excluded.reason, used_in_quest = excluded.used_in_quest, updated_at = CURRENT_TIMESTAMP """, (key_id, priority, reason, used_in_quest)) conn.execute("DELETE FROM key_maps WHERE key_id = ?", (key_id,)) if map_ids: conn.executemany( "INSERT OR IGNORE INTO key_maps (key_id, map_id) VALUES (?, ?)", [(key_id, map_id) for map_id in map_ids], ) conn.commit() conn.close() redirect_args = {} if map_filter: redirect_args["map_id"] = map_filter if sort: redirect_args["sort"] = sort if show: redirect_args["show"] = show base_url = url_for("index", **redirect_args) return redirect(f"{base_url}#key-{key_id}") def _update_key(conn, key_id, priority, reason, used_in_quest, map_ids): conn.execute(""" INSERT INTO key_ratings (key_id, priority, reason, used_in_quest) VALUES (?, ?, ?, ?) ON CONFLICT(key_id) DO UPDATE SET priority = excluded.priority, reason = excluded.reason, used_in_quest = excluded.used_in_quest, updated_at = CURRENT_TIMESTAMP """, (key_id, priority, reason, used_in_quest)) conn.execute("DELETE FROM key_maps WHERE key_id = ?", (key_id,)) if map_ids: conn.executemany( "INSERT OR IGNORE INTO key_maps (key_id, map_id) VALUES (?, ?)", [(key_id, map_id) for map_id in map_ids], ) @app.route("/rate_all", methods=["POST"]) def rate_all(): key_ids = request.form.getlist("key_ids") save_one = request.form.get("save_one") map_filter = request.form.get("map_id") sort = request.form.get("sort") show = request.form.get("show") if save_one: key_ids = [save_one] conn = get_db() for key_id in key_ids: priority = request.form.get(f"priority_{key_id}") if priority is None: continue if priority == "": priority = None reason = request.form.get(f"reason_{key_id}", "") used_in_quest = 1 if request.form.get(f"used_in_quest_{key_id}") == "on" else 0 map_ids = [] for value in request.form.getlist(f"map_ids_{key_id}"): try: map_ids.append(int(value)) except ValueError: continue _update_key(conn, key_id, priority, reason, used_in_quest, map_ids) conn.commit() conn.close() redirect_args = {} if map_filter: redirect_args["map_id"] = map_filter if sort: redirect_args["sort"] = sort if show: redirect_args["show"] = show base_url = url_for("index", **redirect_args) if save_one: return redirect(f"{base_url}#key-{save_one}") return redirect(base_url) @app.route("/collector") def collector(): conn = get_db() collector = conn.execute( "SELECT id FROM quests WHERE name = 'Collector'" ).fetchone() if not collector: conn.close() return "Run import_quests.py first to populate quest data.", 503 # Recursive CTE to get all transitive prerequisites prereqs = conn.execute(""" WITH RECURSIVE deps(quest_id) AS ( SELECT depends_on FROM quest_deps WHERE quest_id = ? UNION SELECT qd.depends_on FROM quest_deps qd JOIN deps d ON qd.quest_id = d.quest_id ) SELECT q.id, q.name, q.trader, q.wiki_link, COALESCE(qp.done, 0) AS done FROM quests q JOIN deps d ON q.id = d.quest_id LEFT JOIN quest_progress qp ON q.id = qp.quest_id ORDER BY q.trader, q.name """, (collector["id"],)).fetchall() conn.close() total = len(prereqs) done = sum(1 for q in prereqs if q["done"]) return render_template("collector.html", quests=prereqs, total=total, done=done) @app.route("/collector/toggle", methods=["POST"]) def collector_toggle(): quest_id = request.form["quest_id"] done = 1 if request.form.get("done") == "1" else 0 conn = get_db() conn.execute(""" INSERT INTO quest_progress (quest_id, done) VALUES (?, ?) ON CONFLICT(quest_id) DO UPDATE SET done = excluded.done """, (quest_id, done)) conn.commit() conn.close() return redirect(url_for("collector")) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)