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
This commit is contained in:
2025-09-03 05:23:30 -04:00
parent ba286278cf
commit 4063deba42
26 changed files with 2767 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
<title>{{ error.code }} | {{site_name}}</title>
</head>
<body>
<div class="container">
<div class="center-container">
<div class="card shadow-lg p-4 text-light" style="background-color: rgba(44, 47, 51, 0.8);">
<h1 class="display-1">{{ error.code }}</h1>
<a href="{{ url_for('index') }}" class="btn btn-primary mt-3">Go Home</a>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
</html>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
<title>{{ error.code }} | {{site_name}}</title>
</head>
<body>
<div class="container">
<div class="center-container">
<div class="card shadow-lg p-4 text-light" style="background-color: rgba(44, 47, 51, 0.8);">
<h1 class="display-1">{{ error.code }}</h1>
<h2>Page Not Found</h2>
<p class="lead">Sorry, the page you are looking for does not exist.</p>
<a href="{{ url_for('index') }}" class="btn btn-primary mt-3">Go Home</a>
<hr class="my-4">
<p class="text-muted small">
<strong>Note for server owners:</strong> If you're seeing this page for your server, its name may have recently changed on Discord. You can fix the link by running the `{p}sync` command in your server.
</p>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
</html>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
<title>{{ error.code }} | {{site_name}}</title>
</head>
<body>
<div class="container">
<div class="center-container">
<div class="card shadow-lg p-4 text-light" style="background-color: rgba(44, 47, 51, 0.8);">
<h1 class="display-1">{{ error.code }}</h1>
<h2>Page Not Found</h2>
<p class="lead">Sorry, the page you are looking for does not exist.</p>
<a href="{{ url_for('index') }}" class="btn btn-primary mt-3">Go Home</a>
<hr class="my-4">
<p class="text-muted small">
<strong>Note for server owners:</strong> If you're seeing this page for your server, its name may have recently changed on Discord. You can fix the link by running the `{p}sync` command in your server.
</p>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
</html>

View File

@@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block title %}{{ site_name }} | Admin Login{% endblock %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
{% block content %}
<div class="container my-4 text-light">
<div class="row justify-content-center text-center">
<div class="col-md-6">
<div class="card bg-dark p-4">
<h2>Admin Access Required</h2>
<p class="lead">Please log in with an authorized Discord account to continue.</p>
<hr>
<a href="{{ url_for('discord.login') }}" class="btn btn-primary btn-lg">
Login with Discord
</a>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
{% endblock %}

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
{% block links %}{% endblock %}
<title>{% block title %}{{ site_name }}{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
<div id="particles-js"></div>
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
<script src="{{url_for('static', filename='js/app.js')}}"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
<script>
$(document).ready(() => {
const md = window.markdownit();
const elements = document.getElementsByClassName("server-description");
for (var i = 0; i < elements.length; i++) {
elements[i].innerHTML = md.render(elements[i].innerHTML);
}
$(".servers").animate({
opacity: "100%"
}, 600)
})
</script>
{% block scripts %}
<a id="hidden-admin-button" href="{{ url_for('main.admin') }}" title="">👀</a>
<script>
const adminButton = document.getElementById('hidden-admin-button');
if (adminButton) {
adminButton.addEventListener('click', function(event) {
// Always prevent the default link action first
event.preventDefault();
if (!event.ctrlKey && !event.metaKey) {
// Denied case
alert("❌ Access Denied! ❌|⚠Only authorized personnel! ⚠️");
} else {
// --- GRANTED CASE ---
// 1. Create the notification element
const toast = document.createElement('div');
toast.textContent = '✅ Access Granted! Redirecting...';
toast.className = 'access-toast';
document.body.appendChild(toast);
// 2. Animate it into view
// A tiny delay is needed for the CSS transition to work
setTimeout(() => {
toast.classList.add('show');
}, 10);
// 3. Redirect to the admin page after a short delay
setTimeout(() => {
window.location.href = adminButton.href;
}, 1000); // Redirect after 1 second
}
});
}
</script>
{% endblock %}
</body>
<footer>
<nav class="navbar sticky-bottom bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Sticky bottom</a>
</div>
</nav>
</footer>
</html>

View File

@@ -0,0 +1,91 @@
{% extends "base.html" %}
{% block title %}Browse Servers | {{ site_name }}{% endblock %}
{% block links %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/index.css')}}">
{% endblock %}
{% block content %}
<div class="container my-4">
<div class="d-flex justify-content-between align-items-center">
<a href="{{ url_for('.index') }}" class="btn btn-outline-light">&larr; Back to Home</a>
<h1 class="text-light">Browse Servers</h1>
</div>
<hr class="text-light">
</div>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-3">
<h4 class="text-light">Tags</h4>
<div class="list-group">
<a href="{{ url_for('.browse') }}" class="list-group-item list-group-item-action {% if not current_tag %}active{% endif %}">
All Servers
</a>
{% for tag in all_tags %}
<a href="{{ url_for('.browse', tag_name=tag) }}" class="list-group-item list-group-item-action {% if tag == current_tag %}active{% endif %}">
{{ tag }}
</a>
{% endfor %}
</div>
</div>
<div class="col-md-9">
<div class="servers">
{% if servers %}
{% for server in servers %}
<div class="card shadow-lg" style="width: 18rem; margin:5px;">
<img class="card-img-top" src="{{server['icon_url']}}">
<div class="card-body">
<div class="card-header bg-danger bg-gradient text-light" style="margin-bottom:5px;">
<strong>{{server['server_name']}}</strong>
</div>
<p class="card-text server-description">{{ server['description'] }}</p>
<p class="card-text">
<strong>Tags:</strong> {{ server['tags'] }}
</p>
<a class="btn btn-outline-success d-block mx-auto" href="/server/{{ server['server_name']|replace(' ', '-') }}">View Server</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-light text-center mt-5">
<h4>No servers found with the tag "{{ current_tag }}".</h4>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
</body>
</html>
{% block scripts %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
<script>
$(document).ready(() => {
// This is the part that renders markdown
const md = window.markdownit();
const elements = document.getElementsByClassName("server-description");
for (var i = 0; i < elements.length; i++) {
elements[i].innerHTML = md.render(elements[i].innerHTML);
}
// --- ADD THIS ANIMATION ---
// This fades in the server list, making it visible
$(".servers").animate({
opacity: "100%"
}, 600)
})
</script>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}Dashboard | {{ site_name }}{% endblock %}
{% block content %}
<!DOCTYPE html>
<html>
<body>
<div class="container my-4 text-light">
<div class="d-flex justify-content-between align-items-center">
<h1>Admin Dashboard</h1>
<div>
<span class="me-3">Welcome, {{ session.username }}!</span>
<a href="{{ url_for('main.logout') }}" class="btn btn-secondary">Logout</a>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-9">
<h2>Overview</h2>
<p>You are successfully logged in. This is the main content area for your dashboard.</p>
</div>
<div class="col-md-3">
<div class="card bg-dark">
<div class="card-header">
Menu
</div>
<div class="list-group list-group-flush">
<a href="#" class="list-group-item list-group-item-action bg-dark text-light">Dashboard</a>
<a href="#" class="list-group-item list-group-item-action bg-dark text-light">Manage Servers</a>
<a href="#" class="list-group-item list-group-item-action bg-dark text-light">Site Settings</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,94 @@
{% extends base.html %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
<link rel="stylesheet" href="{{url_for('static', filename='styles/index.css')}}">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>Home | kServers</title>
</head>
<body>
<div class="container text-center my-4">
<h1><a href="{{ url_for('.index') }}" class="text-light text-decoration-none">{{ site_name }}</a></h1>
<a href="{{ url_for('.browse') }}" class="btn btn-secondary mt-2">Browse by Tag</a>
<a href="{{ bot_invite_url }}" class="btn btn-primary invite-button" target="https://discord.com/oauth2/authorize?client_id=1285545069591527466&permissions=537012969&integration_type=0&scope=bot">🤖 Invite Bot</a>
</div>
<div class="servers">
{% for server in servers %}
<div class="card shadow-lg" style="width: 18rem; margin:5px;">
<img class="card-img-top" src="{{server['icon_url']}}">
<div class="card-body">
<div class="card-header bg-danger bg-gradient text-light" style="margin-bottom:5px;">
<strong>{{server['server_name']}}</strong>
</div>
<p class="card-text server-description">{{ server['description'] }}</p>
<p class="card-text">
<strong>Tags:</strong> {{ server['tags'] }}
</p>
<a class="btn btn-success d-block mx-auto" href="/server/{{ server['server_name']|replace(' ', '-') }}">View Server</a>
</div>
</div>
{% endfor %}
</div>
<div id="particles-js"></div>
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
<script src="{{url_for('static', filename='js/app.js')}}"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
<script>
$(document).ready(() => {
const md = window.markdownit();
const elements = document.getElementsByClassName("server-description");
for (var i = 0; i < elements.length; i++) {
elements[i].innerHTML = md.render(elements[i].innerHTML);
}
$(".servers").animate({
opacity: "100%"
}, 600)
})
</script>
<a id="hidden-admin-button" href="{{ url_for('main.admin') }}" title="">👀</a>
<script>
const adminButton = document.getElementById('hidden-admin-button');
if (adminButton) {
adminButton.addEventListener('click', function(event) {
// Always prevent the default link action first
event.preventDefault();
if (!event.ctrlKey && !event.metaKey) {
// Denied case
alert("❌ Access Denied! ❌|⚠Only authorized personnel! ⚠️");
} else {
// --- GRANTED CASE ---
// 1. Create the notification element
const toast = document.createElement('div');
toast.textContent = '✅ Access Granted! Redirecting...';
toast.className = 'access-toast';
document.body.appendChild(toast);
// 2. Animate it into view
// A tiny delay is needed for the CSS transition to work
setTimeout(() => {
toast.classList.add('show');
}, 10);
// 3. Redirect to the admin page after a short delay
setTimeout(() => {
window.location.href = adminButton.href;
}, 1000); // Redirect after 1 second
}
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
<link rel="stylesheet" href="{{url_for('static', filename='styles/index.css')}}">
<title>{{ discord['name'] }} | {{site_name}}</title>
</head>
<body>
<div class="container my-4">
<a href="{{ url_for('index') }}" class="btn btn-outline-light">&larr; Back to Server List</a>
</div>
<div class="container">
<div class="card shadow-lg text-light">
<div class="card-header bg-danger bg-gradient text-light text-center h3">
{{ discord.name }}
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 text-center">
{% if discord.icon %}
<img src="https://cdn.discordapp.com/icons/{{ discord.id }}/{{ discord.icon }}.png?size=256" class="img-fluid rounded-circle border border-5 border-danger mb-3">
{% else %}
<img src="https://placehold.co/256x256/2c2f33/ffffff?text={{ discord.name[0] }}" class="img-fluid rounded-circle border border-5 border-danger mb-3">
{% endif %}
{% if discord.invite %}
<a href="{{ discord.invite }}" class="btn btn-success btn-lg my-3" target="_blank">Join Server</a>
{% endif %}
</div>
<div class="col-md-8">
<h4>Description</h4>
<p id="description-text">{{ server.description or "No description available." }}</p>
<hr>
<h4>Tags</h4>
<p>{{ server.tags or "No tags set." }}</p>
<hr>
<h4>Server Stats</h4>
<p>
<span class="badge bg-primary me-2">Members: {{ human.format_number(discord.approximate_member_count) }}</span>
<span class="badge bg-secondary me-2">Roles: {{ discord.roles | length }}</span>
<span class="badge bg-warning text-dark me-2">Boosts: {{ discord.premium_subscription_count }} (Tier {{ discord.premium_tier }})</span>
</p>
</div>
</div>
</div>
</div>
</div>
<div id="particles-js"></div>
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
<script src="{{url_for('static', filename='js/app.js')}}"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
<script>
// Render markdown for the description
const md = window.markdownit();
const descriptionElement = document.getElementById("description-text");
if (descriptionElement) {
descriptionElement.innerHTML = md.render(descriptionElement.innerHTML);
}
</script>
</body>
</html>