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
177 lines
6.8 KiB
Python
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')) |