Files
kServers-kList/src/ver/0.0.3/webapp/routes.py
Unstable Kitsune 4063deba42 feat: implement kList webapp with Flask framework
Adds complete Flask-based web application for server listing service, including routing, database integration, Discord OAuth, templates, static assets, and configuration.

Handles user authentication, server browsing, and admin dashboard functionality. BROKEN
2025-09-03 05:44:20 -04:00

177 lines
6.8 KiB
Python

# kList-v2/webapp/routes.py
import json
import requests
import logging
from flask import Blueprint, render_template, abort, session, redirect, url_for, request, jsonify
from flask_dance.contrib.discord import discord
# Use relative imports because this file is part of the 'webapp' package
from .database import query_db
from .config import RED_CONFIG_TABLE, ADMIN_USER_IDS, BOT_TOKEN
# A single Blueprint for all application routes
bp = Blueprint('main', __name__)
log = logging.getLogger(__name__)
# --- HELPER FUNCTION ---
DISCORD_API_URL = "https://discord.com/api/v10"
def get_discord_guild_data(server_id):
"""Fetches guild (server) information from the Discord API."""
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
url = f"{DISCORD_API_URL}/guilds/{server_id}?with_counts=true"
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
response.json()
json_data = response.json()
log.info(f"OUT: {response}")
return json_data
except requests.exceptions.RequestException:
log.error(f"Discord API request failed for server ID {server_id}. Error: {e}")
return None
# --- API ROUTE ---
@bp.route("/api/v1/server/<int:server_id>")
def get_server_info(server_id):
sql_query = f"SELECT json_data FROM {RED_CONFIG_TABLE} WHERE primary_key_1 = %s;"
log.info(f"QUERY: {sql_query}")
raw_data = query_db(sql_query, (server_id,), fetch="one")
if not raw_data:
log.info(f"RAW DATA: {raw_data}")
return jsonify({"error": "Server not found"}), 404
return jsonify(raw_data['json_data'])
# --- ERROR HANDLER ROUTES ---
@bp.errorhandler(404)
def page_not_found(err):
return render_template('404.html'), 404
@bp.errorhandler(500)
def internal_server_error(err):
return render_template('500.html'), 500
@bp.errorhandler(502)
def service_error(err):
return render_template('502.html'), 502
@bp.errorhandler(503)
def service_unavailable(err):
return render_template('503.html'), 503
# --- WEB PAGE ROUTES ---
@bp.route("/")
def index():
sql_query = f"SELECT primary_key_1, json_data FROM {RED_CONFIG_TABLE} WHERE (json_data->>'status') = 'complete';"
servers_from_db = query_db(sql_query)
log.info(f"QUERY: {sql_query}")
servers_for_template = []
for row in servers_from_db:
server_info = row['json_data']
server_info['_id'] = row['primary_key_1']
servers_for_template.append(server_info)
log.info(f"INFO: {server_info}")
return render_template('index.html', servers=servers_for_template)
@bp.route("/server/<string:server_name>")
def view_server(server_name):
original_server_name = server_name.replace('-', ' ')
log.info(f"ORIGINAL NAME: {original_server_name}")
sql_query = f"SELECT primary_key_1, json_data FROM {RED_CONFIG_TABLE} WHERE (json_data->>'server_name') = %s;"
log.info(f"QUERY: {sql_query}")
raw_data = query_db(sql_query, (original_server_name,), fetch="one")
log.info(f"QUERY: {raw_data}")
if not raw_data:
abort(404)
server_db_data = raw_data['json_data']
log.info(f"SERVER DATA: {server_db_data}")
server_id = raw_data['primary_key_1']
server_db_data['server_id'] = server_id
log.info(f"SERVER ID: {server_id}")
discord_guild_data = get_discord_guild_data(server_id)
log.info(f"GUILD DATA: {get_discord_guild_data}")
if not discord_guild_data:
log.info(f"GUILD DATA: {discord_guild_data}")
abort(503)
if server_db_data.get('vanity_code'):
discord_guild_data["invite"] = f"https://discord.gg/{server_db_data.get('vanity_code')}"
log.info(f"GUILD DATA: {discord_guild_data}")
elif server_db_data.get('invite_code'):
discord_guild_data["invite"] = f"https://discord.gg/{server_db_data.get('invite_code')}"
log.info(f"GUILD DATA: {discord_guild_data}")
else:
discord_guild_data["invite"] = None
log.info(f"TEMPLATE: {render_template}")
return render_template('view_server.html', server=server_db_data, discord=discord_guild_data)
@bp.route('/browse/', defaults={'tag_name': None})
@bp.route('/browse/<string:tag_name>')
def browse(tag_name):
"""Renders a page for browsing servers by tag."""
log.info(f"Browse page requested for tag: {tag_name or 'All'}")
# 1. Get all unique tags from the database
tag_query = f"SELECT json_data->>'tags' AS tags FROM {RED_CONFIG_TABLE} WHERE (json_data->>'status') = 'complete';"
all_servers_tags = query_db(tag_query)
all_tags = set()
for server in all_servers_tags:
tags_string = server.get('tags')
if isinstance(tags_string, str):
tags_list = [tag.strip() for tag in tags_string.split(',') if tag.strip()]
all_tags.update(tags_list)
sorted_tags = sorted(list(all_tags), key=str.lower)
log.info(f"Found {len(sorted_tags)} unique tags to display.")
# 2. Filter the servers based on the selected tag
if tag_name:
log.info(f"Filtering servers by tag: '{tag_name}'")
sql_query = f"SELECT primary_key_1, json_data FROM {RED_CONFIG_TABLE} WHERE (json_data->>'status') = 'complete' AND (json_data->>'tags') ILIKE %s;"
filtered_servers_raw = query_db(sql_query, (f'%{tag_name}%',))
else:
log.info("No tag selected, fetching all servers.")
sql_query = f"SELECT primary_key_1, json_data FROM {RED_CONFIG_TABLE} WHERE (json_data->>'status') = 'complete';"
filtered_servers_raw = query_db(sql_query)
filtered_servers = []
for row in filtered_servers_raw:
server_info = row['json_data']
server_info['_id'] = row['primary_key_1']
filtered_servers.append(server_info)
log.info(f"Found {len(filtered_servers)} servers to display.")
return render_template('browse.html', all_tags=sorted_tags, servers=filtered_servers, current_tag=tag_name)
# --- AUTH & ADMIN ROUTES ---
@bp.route("/auth")
def auth():
if not discord.authorized:
return redirect(url_for(".index")) # Use dot prefix for Blueprints
user_info = discord.get("/api/users/@me").json()
user_id = int(user_info.get("id"))
if user_id in ADMIN_USER_IDS:
session["is_admin"] = True
session["user_id"] = user_id
session["username"] = user_info.get("username")
else:
session.clear()
return "<h1>Access Denied</h1><p>You are not authorized to view this page.</p>", 403
return redirect(url_for(".dashboard"))
@bp.route('/admin')
def admin():
if session.get("is_admin"):
return redirect(url_for(".dashboard"))
return render_template('admin.html')
@bp.route('/dashboard')
def dashboard():
if not session.get("is_admin"):
return redirect(url_for(".admin"))
return render_template('dashboard.html')
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('.index'))