Create phase 1 - web service - db is dummy for now, noop

This commit is contained in:
Flare Starfall 2025-09-19 23:00:54 +02:00
parent b2efaf340e
commit 77b43b3030
20 changed files with 249 additions and 9 deletions

3
.gitignore vendored
View File

@ -175,4 +175,5 @@ cython_debug/
#.idea/
# ---> StarfallBot
config.toml
config.toml
web/static/style/*.scss

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"configurations": [
{
"console": "integratedTerminal",
"name": "Python: Current File",
"program": "${workspaceFolder}/app.py",
"request": "launch",
"type": "debugpy"
}
],
"version": "0.2.0"
}

18
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,18 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "always",
"source.organizeImports.ruff": "always",
"source.sort.json": "always"
},
"python.languageServer": "None",
"scss-to-css-compile.browsers": [
"ie > 9",
"iOS > 8",
"Android >= 4.4",
"ff > 38",
"Chrome > 38"
],
"scss-to-css-compile.compileOnSave": false,
"scss-to-css-compile.outDir": "./",
"scss-to-css-compile.output": "compressed"
}

27
app.py
View File

@ -1,7 +1,32 @@
import logging
import os
from threading import Thread
from starfall.config import Config
from starfall.log import Log
from starfall.web import App
if __name__ == "__main__":
Log.setup()
try:
Config.load()
except FileNotFoundError:
pass
dst = Config.create()
logging.getLogger().critical(
msg="\n".join(
[
"*** Config file was not found ***",
"",
"Sample config.toml file has been created in the project's root directory:",
"[b]" + os.path.realpath("./%s" % dst) + "[/b]",
"Edit it and run the program again.",
]
),
extra={"markup": True},
)
exit()
App.__init__()
threads = [Thread(App.run())]

View File

@ -1,12 +1,17 @@
# == StarfallBot configuration file ==
[web]
# What IP the web server is bound to.
# Use "localhost" until the bot is production ready.
host = "localhost"
# What port the web server uses.
port = 5000
# Connection database url.
# sqlite is only production safe to a point - consult the SQLAlchemy
# documentation to see how to set up other databases.
database_url = "sqlite:///starfallbot.db"
# Secret key, used to sign the session cookie.
secret_key = "PRIVATE SECRET KEY CHANGEME"
[discord]
token = "YOUR BOT TOKEN HERE"

View File

@ -1,4 +1,5 @@
discord-py-interactions
flask
flask-sqlalchemy
discord-py-interactions
rich
twitchio

View File

@ -1,14 +1,24 @@
import os
from shutil import copy
from typing import Any
import tomllib
from typing import Annotated, Optional
class Config:
data: Annotated[Optional[dict],
"Configuration imported from config.toml"] = None
data: dict[str, Any] = dict()
"""Configuration imported from config.toml."""
@classmethod
def load(cls):
def create(cls) -> str:
"""Copy config-sample.toml file to config.toml.
Does not alert the user to the modification.
"""
return copy(src="config-sample.toml", dst="config.toml")
@classmethod
def load(cls) -> None:
"""Loads the contents of the config.toml file into the class.
Raises:
@ -16,14 +26,23 @@ class Config:
"""
if not cls.file_exists():
raise FileNotFoundError("config.toml does not exist")
with open("config.toml", "r") as f:
with open("config.toml", "rb") as f:
cls.data = tomllib.load(f)
@classmethod
def file_exists(cls) -> bool:
"""Whether the config.toml file exists or needs to be created.
Returns:
bool: True if path exists and is a file, False otherwise.
"""
return os.path.exists("config.toml") and os.path.isfile("config.toml")
@classmethod
def get(cls, key: str, default: Any):
"""Get config key, if it exists, or return a default.
Returns:
Any: Result, or default if key does not exist.
"""
return cls.data.get(key, default)

3
starfall/db/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

21
starfall/log.py Normal file
View File

@ -0,0 +1,21 @@
import logging
import werkzeug
from rich.logging import RichHandler
class Log:
@classmethod
def setup(cls) -> None:
handler = RichHandler(level=logging.DEBUG, rich_tracebacks=True)
logging.basicConfig(
level=logging.DEBUG,
format="%(message)s",
datefmt="[%Y-%m-%d %H:%M:%S]",
handlers=[handler],
)
# NOTE: pyright's reportPrivateUsage is suppressed here.
# We disable ANSI terminal codes from being added to Werkzeug (Flask) logs;
# RichHandler already handles all the fancy color stuff.
werkzeug.serving._log_add_style = False # pyright: ignore[reportPrivateUsage]

36
starfall/web/__init__.py Normal file
View File

@ -0,0 +1,36 @@
import os
from typing import Any
from flask import Flask
from starfall.config import Config
from starfall.db import db
from starfall.web.home import home_blueprint
class App:
app: Flask = Flask(__name__)
config: dict[str, Any] = dict() # pyright: ignore[reportExplicitAny]
@classmethod
def __init__(cls):
cls.load_config()
cls.app.config.update( # pyright: ignore[reportUnknownMemberType]
SECRET_KEY=cls.config.get("secret_key"),
SQLALCHEMY_DATABASE_URI=cls.config.get("database_url"),
)
cls.app.root_path = os.path.realpath(".")
cls.app.static_folder = os.path.realpath("./web/static")
cls.app.template_folder = "web"
db.init_app(cls.app)
cls.app.register_blueprint(home_blueprint)
@classmethod
def load_config(cls):
cls.config = Config.get("web", {})
@classmethod
def run(cls):
cls.app.run(host=cls.config.get("host"), port=cls.config.get("port"))

8
starfall/web/home.py Normal file
View File

@ -0,0 +1,8 @@
from flask import Blueprint, render_template
home_blueprint: Blueprint = Blueprint("main", __name__)
@home_blueprint.route("/")
def index():
return render_template("home.html")

11
web/home.html Normal file
View File

@ -0,0 +1,11 @@
{% extends "index.html" %}
{% block titleSuffix %}Overview{% endblock %}
{% block body %}
<div class="flex center">
<h1>StarfallBot - Overview</h1>
</div>
{% endblock %}

41
web/index.html Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>StarfallBot - {% block titleSuffix %}Unassigned{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="description" content="StarfallBot administation interface" />
<link rel="stylesheet" href="{{ url_for('static', filename='style/fonts.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='style/main.css') }}" />
<link rel="icon" href="{{ url_for('static', filename='favicon.png') }}" />
{% block head %}
{% endblock %}
</head>
<body>
{% block beforeMain %}
{% endblock %}
<main>
{% block beforeMainContainer %}
{% endblock %}
<div class="container">
{% block body %}
<p>No content has been defined for this page.</p>
{% endblock %}
</div>
{% block afterMainContainer %}
{% endblock %}
</main>
{% block afterMain %}
{% endblock %}
</body>
</html>

BIN
web/static/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,35 @@
/* exo-2-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Exo 2';
font-style: normal;
font-weight: 400;
src: url('../fonts/exo-2-v26-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* exo-2-700 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Exo 2';
font-style: normal;
font-weight: 700;
src: url('../fonts/exo-2-v26-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* overpass-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Overpass';
font-style: normal;
font-weight: 400;
src: url('../fonts/overpass-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* overpass-700 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Overpass';
font-style: normal;
font-weight: 700;
src: url('../fonts/overpass-v19-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}

View File

@ -0,0 +1 @@
body{position:relative;min-height:100%;color:#000;background-image:linear-gradient(to bottom, #205080, #4070a0);background-repeat:no-repeat;background-attachment:fixed;font-family:"Overpass",sans-serif;font-size:18px}main{height:100%;display:flex;justify-content:center}main>.container{width:100%;height:100%;max-width:1400px;padding:8px;background:#001030;border-radius:16px;color:#fff}main .flex{display:flex;flex-flow:row wrap}main .flex.center{justify-content:center}h1{font-family:"Exo 2",sans-serif;font-size:4rem;font-weight:bold;padding:0;margin:0}h2{font-family:"Exo 2",sans-serif;font-size:2rem;font-weight:bold;padding:0;margin:0}h3{font-family:"Exo 2",sans-serif;font-size:1.5rem;font-weight:bold;padding:0;margin:0}h4{font-family:"Exo 2",sans-serif;font-size:1rem;font-weight:bold;padding:0;margin:0}h5{font-family:"Exo 2",sans-serif;font-size:.875rem;font-weight:bold;padding:0;margin:0}h6{font-family:"Exo 2",sans-serif;font-size:.75rem;font-weight:bold;padding:0;margin:0}