From 5989f0d97eda29d3135826d3055d86ed9ef5469a Mon Sep 17 00:00:00 2001 From: Flare Starfall Date: Wed, 18 Mar 2026 10:50:27 +0100 Subject: [PATCH] Login system (not fully complete yet) --- messages.pot | 233 +++++++++---- requirements.txt | 4 +- starfall/db/schema/users.py | 7 +- starfall/web/__init__.py | 48 ++- starfall/web/blueprints/admin.py | 13 - .../web/blueprints/{ => public}/imprint.py | 6 +- starfall/web/blueprints/{ => public}/main.py | 2 +- starfall/web/blueprints/secure/main.py | 25 ++ starfall/web/controllers/base.py | 11 +- .../web/controllers/{ => public}/imprint.py | 3 +- starfall/web/controllers/secure/login.py | 38 +++ starfall/web/controllers/secure/register.py | 72 ++++ starfall/web/controllers/user.py | 15 + translations/en/LC_MESSAGES/messages.mo | Bin 8213 -> 9657 bytes translations/en/LC_MESSAGES/messages.po | 319 +++++++++++------- web/templates/base.jinja | 13 +- web/templates/components/content_empty.jinja | 9 + web/templates/home.jinja | 12 - web/templates/macros/form.jinja | 33 ++ web/templates/public/home.jinja | 15 + web/templates/{ => public}/imprint.jinja | 19 +- web/templates/secure/login.jinja | 28 ++ web/templates/secure/register.jinja | 27 ++ 23 files changed, 694 insertions(+), 258 deletions(-) delete mode 100644 starfall/web/blueprints/admin.py rename starfall/web/blueprints/{ => public}/imprint.py (75%) rename starfall/web/blueprints/{ => public}/main.py (96%) create mode 100644 starfall/web/blueprints/secure/main.py rename starfall/web/controllers/{ => public}/imprint.py (74%) create mode 100644 starfall/web/controllers/secure/login.py create mode 100644 starfall/web/controllers/secure/register.py create mode 100644 starfall/web/controllers/user.py create mode 100644 web/templates/components/content_empty.jinja delete mode 100644 web/templates/home.jinja create mode 100644 web/templates/macros/form.jinja create mode 100644 web/templates/public/home.jinja rename web/templates/{ => public}/imprint.jinja (77%) create mode 100644 web/templates/secure/login.jinja create mode 100644 web/templates/secure/register.jinja diff --git a/messages.pot b/messages.pot index 4542f34..0731007 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Starfall 0.1.0-alpha.3\n" "Report-Msgid-Bugs-To: flare@theflare.at\n" -"POT-Creation-Date: 2026-03-10 10:46+0100\n" +"POT-Creation-Date: 2026-03-18 10:46+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,59 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.18.0\n" +#: starfall/web/controllers/secure/login.py:12 +#: starfall/web/controllers/secure/login.py:16 +msgid "page.login.form.username" +msgstr "" + +#: starfall/web/controllers/secure/login.py:19 +msgid "page.login.error.username" +msgstr "" + +#: starfall/web/controllers/secure/login.py:23 +#: starfall/web/controllers/secure/login.py:27 +msgid "page.login.form.password" +msgstr "" + +#: starfall/web/controllers/secure/login.py:30 +msgid "page.login.error.password" +msgstr "" + +#: starfall/web/controllers/secure/register.py:15 +#: starfall/web/controllers/secure/register.py:20 +msgid "page.register.form.username" +msgstr "" + +#: starfall/web/controllers/secure/register.py:23 +msgid "page.register.error.username" +msgstr "" + +#: starfall/web/controllers/secure/register.py:27 +#: starfall/web/controllers/secure/register.py:31 +msgid "page.register.form.password" +msgstr "" + +#: starfall/web/controllers/secure/register.py:34 +msgid "page.register.error.password" +msgstr "" + +#: starfall/web/controllers/secure/register.py:38 +#: starfall/web/controllers/secure/register.py:42 +msgid "page.register.form.email" +msgstr "" + +#: starfall/web/controllers/secure/register.py:45 +msgid "page.register.error.email" +msgstr "" + +#: starfall/web/controllers/secure/register.py:71 +msgid "page.register.status.email_already_exists" +msgstr "" + +#: starfall/web/controllers/secure/register.py:72 +msgid "page.register.status.success" +msgstr "" + #: web/templates/base.jinja:6 msgid "base.meta.title.empty" msgstr "" @@ -41,96 +94,36 @@ msgstr "" msgid "base.label.toggle_nav" msgstr "" -#: web/templates/base.jinja:47 +#: web/templates/base.jinja:46 +msgid "base.label.login" +msgstr "" + +#: web/templates/base.jinja:53 msgid "base.label.theme.btn" msgstr "" -#: web/templates/base.jinja:52 +#: web/templates/base.jinja:58 msgid "base.label.theme.light" msgstr "" -#: web/templates/base.jinja:55 +#: web/templates/base.jinja:61 msgid "base.label.theme.dark" msgstr "" -#: web/templates/base.jinja:58 +#: web/templates/base.jinja:64 msgid "base.label.theme.auto" msgstr "" -#: web/templates/base.jinja:76 -msgid "page.empty.title" -msgstr "" - -#: web/templates/base.jinja:77 -msgid "page.empty.body" -msgstr "" - -#: web/templates/base.jinja:89 +#: web/templates/base.jinja:90 msgid "base.label.copy" msgstr "" -#: web/templates/home.jinja:3 -msgid "page.home.title" +#: web/templates/components/content_empty.jinja:3 +msgid "page.empty.title" msgstr "" -#: web/templates/home.jinja:8 -msgid "page.home.card.title" -msgstr "" - -#: web/templates/home.jinja:9 -msgid "page.home.card.body" -msgstr "" - -#: web/templates/imprint.jinja:3 web/templates/imprint.jinja:8 -msgid "page.imprint.title" -msgstr "" - -#: web/templates/imprint.jinja:10 -msgid "page.imprint.card.title" -msgstr "" - -#: web/templates/imprint.jinja:12 -msgid "page.imprint.card.body" -msgstr "" - -#: web/templates/imprint.jinja:13 -msgid "page.imprint.card.lastmodified" -msgstr "" - -#: web/templates/imprint.jinja:23 -msgid "page.imprint.legal" -msgstr "" - -#: web/templates/imprint.jinja:31 -msgid "page.imprint.sec1.title" -msgstr "" - -#: web/templates/imprint.jinja:32 -msgid "page.imprint.sec2.title" -msgstr "" - -#: web/templates/imprint.jinja:33 -msgid "page.imprint.sec3.title" -msgstr "" - -#: web/templates/imprint.jinja:34 -msgid "page.imprint.sec4.title" -msgstr "" - -#: web/templates/imprint.jinja:35 -msgid "page.imprint.sec1.body" -msgstr "" - -#: web/templates/imprint.jinja:36 -msgid "page.imprint.sec2.body" -msgstr "" - -#: web/templates/imprint.jinja:37 -msgid "page.imprint.sec3.body" -msgstr "" - -#: web/templates/imprint.jinja:38 -msgid "page.imprint.sec4.body" +#: web/templates/components/content_empty.jinja:6 +msgid "page.empty.body" msgstr "" #: web/templates/errors/not_found.jinja:8 @@ -141,3 +134,95 @@ msgstr "" msgid "error.404.body" msgstr "" +#: web/templates/public/home.jinja:3 +msgid "page.home.title" +msgstr "" + +#: web/templates/public/home.jinja:8 +msgid "page.home.card.title" +msgstr "" + +#: web/templates/public/home.jinja:11 +msgid "page.home.card.body" +msgstr "" + +#: web/templates/public/imprint.jinja:3 web/templates/public/imprint.jinja:8 +msgid "page.imprint.title" +msgstr "" + +#: web/templates/public/imprint.jinja:11 +msgid "page.imprint.card.title" +msgstr "" + +#: web/templates/public/imprint.jinja:13 +msgid "page.imprint.card.body" +msgstr "" + +#: web/templates/public/imprint.jinja:14 +msgid "page.imprint.card.lastmodified" +msgstr "" + +#: web/templates/public/imprint.jinja:24 +msgid "page.imprint.legal" +msgstr "" + +#: web/templates/public/imprint.jinja:32 +msgid "page.imprint.sec1.title" +msgstr "" + +#: web/templates/public/imprint.jinja:33 +msgid "page.imprint.sec2.title" +msgstr "" + +#: web/templates/public/imprint.jinja:34 +msgid "page.imprint.sec3.title" +msgstr "" + +#: web/templates/public/imprint.jinja:35 +msgid "page.imprint.sec4.title" +msgstr "" + +#: web/templates/public/imprint.jinja:36 +msgid "page.imprint.sec1.body" +msgstr "" + +#: web/templates/public/imprint.jinja:37 +msgid "page.imprint.sec2.body" +msgstr "" + +#: web/templates/public/imprint.jinja:38 +msgid "page.imprint.sec3.body" +msgstr "" + +#: web/templates/public/imprint.jinja:39 +msgid "page.imprint.sec4.body" +msgstr "" + +#: web/templates/secure/login.jinja:3 web/templates/secure/login.jinja:8 +msgid "page.login.title" +msgstr "" + +#: web/templates/secure/login.jinja:20 +msgid "page.login.form.submit" +msgstr "" + +#: web/templates/secure/login.jinja:24 +msgid "page.login.label.register" +msgstr "" + +#: web/templates/secure/login.jinja:26 +msgid "page.login.label.lostpassword" +msgstr "" + +#: web/templates/secure/register.jinja:5 web/templates/secure/register.jinja:10 +msgid "page.register.title" +msgstr "" + +#: web/templates/secure/register.jinja:20 +msgid "page.register.form.submit" +msgstr "" + +#: web/templates/secure/register.jinja:24 +msgid "page.register.label.login" +msgstr "" + diff --git a/requirements.txt b/requirements.txt index 1fcbbaa..7a9a535 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,10 @@ discord-py-interactions flask flask_assets flask-babel +flask-login flask-sqlalchemy +flask-wtf libsass livereload rich -twitchio \ No newline at end of file +twitchio diff --git a/starfall/db/schema/users.py b/starfall/db/schema/users.py index b74578b..ba65f90 100644 --- a/starfall/db/schema/users.py +++ b/starfall/db/schema/users.py @@ -3,8 +3,11 @@ from sqlalchemy.orm import Mapped, mapped_column from starfall.db import db -class Users(db.Table): - id: Mapped[int] = mapped_column(primary_key=True) +class User(db.Model): + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) email: Mapped[str] = mapped_column(unique=True) username: Mapped[str] = mapped_column() password: Mapped[str] = mapped_column() + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) diff --git a/starfall/web/__init__.py b/starfall/web/__init__.py index 846a3d0..81b7326 100644 --- a/starfall/web/__init__.py +++ b/starfall/web/__init__.py @@ -8,12 +8,14 @@ from typing import final from flask import Blueprint, Flask, request from flask_assets import Bundle, Environment from flask_babel import Babel +from flask_wtf import CSRFProtect from livereload import Server from starfall.config import Config from starfall.db import db, load_schema from starfall.types import SnapshotQueue from starfall.web.blueprints.base import BaseBlueprint +from starfall.web.controllers.user import login_manager @final @@ -26,6 +28,7 @@ class WebUI: self.config: Config | None = None self.queue: SnapshotQueue | None = None self.server: Server | None = None + self.csrf: CSRFProtect | None = None def select_locale(self): # user = getattr(g, "user", None) @@ -58,12 +61,21 @@ class WebUI: TEMPLATES_AUTO_RELOAD=True, ) self.app.jinja_env.auto_reload = True + logging.getLogger("web").debug("flask initialized") + + db.init_app(self.app) + load_schema() + + with self.app.app_context(): + db.create_all() + logging.getLogger("web").debug("db initialized") self.babel = Babel( self.app, locale_selector=self.select_locale, timezone_selector=self.select_timezone, ) + logging.getLogger("web").debug("babel initialized") self.assets = Environment(self.app) self.assets.load_path = ["web/static/scss", "web/static/css"] @@ -79,37 +91,43 @@ class WebUI: depends=["**/*.scss"], ) _ = self.assets.register("scss", scss) + logging.getLogger("web").debug("assets initialized") - db.init_app(self.app) - load_schema() + self.csrf = CSRFProtect(self.app) + logging.getLogger("web").debug("csrf connected") - with self.app.app_context(): - db.create_all() + login_manager.init_app(self.app) + self.import_blueprints("secure", "secure") + self.import_blueprints("public", "public") self.import_blueprints() self.app.register_blueprint( self.blueprint, options={"queue": self.queue}, ) + logging.getLogger("web").debug("blueprints initialized") self.server = Server(self.app.wsgi_app) - self.server.watch( - filepath=os.path.join(str(self.app.template_folder), "**/*.jinja"), - ) - self.server.watch( - filepath=os.path.join(str(self.app.static_folder), "**/*.js"), - ) - self.server.watch( - filepath=os.path.join(str(self.app.static_folder), "**/*.scss"), - ) + self.server.watch(os.path.join(str(self.app.template_folder), "**/*.jinja")) + self.server.watch(os.path.join(str(self.app.static_folder), "**/*.js")) + self.server.watch(os.path.join(str(self.app.static_folder), "**/*.scss")) + logging.getLogger("web").debug("livereload initialized") + self.server.serve( host=self.config.get("web.host"), port=self.config.get("web.port"), ) - def import_blueprints(self): + def import_blueprints(self, path_suffix: str = "", module_suffix: str = ""): path = os.path.realpath(os.path.dirname(__file__) + os.sep + "blueprints") - for _, module_name, _ in iter_modules([path], f"{__name__}.blueprints."): + if len(path_suffix): + path += os.sep + path_suffix + + prefix = f"{__name__}.blueprints." + if len(module_suffix): + prefix += module_suffix + "." + + for _, module_name, _ in iter_modules([path], prefix): if module_name.endswith("base"): continue diff --git a/starfall/web/blueprints/admin.py b/starfall/web/blueprints/admin.py deleted file mode 100644 index a05b8a4..0000000 --- a/starfall/web/blueprints/admin.py +++ /dev/null @@ -1,13 +0,0 @@ -from flask import Blueprint, Flask, render_template -from flask_assets import Environment - -from starfall.web.blueprints.base import BaseBlueprint - - -class AdminBlueprint(BaseBlueprint): - def __init__(self, blueprint: Blueprint, assets: Environment, app: Flask): - super().__init__(blueprint, assets, app) - blueprint.add_url_rule("/login", methods=["GET", "POST"], view_func=self.login) - - def login(self): - return render_template("admin/login.jinja", bp=self) diff --git a/starfall/web/blueprints/imprint.py b/starfall/web/blueprints/public/imprint.py similarity index 75% rename from starfall/web/blueprints/imprint.py rename to starfall/web/blueprints/public/imprint.py index 1cd69aa..941b77a 100644 --- a/starfall/web/blueprints/imprint.py +++ b/starfall/web/blueprints/public/imprint.py @@ -3,19 +3,19 @@ from flask_assets import Environment from flask_babel import get_locale from starfall.web.blueprints.base import BaseBlueprint -from starfall.web.controllers.imprint import ImprintController +from starfall.web.controllers.public.imprint import ImprintController class ImprintBlueprint(BaseBlueprint): def __init__(self, blueprint: Blueprint, assets: Environment, app: Flask) -> None: super().__init__(blueprint, assets, app) - blueprint.add_url_rule("/imprint", view_func=self.imprint) + blueprint.add_url_rule("/imprint/", view_func=self.imprint) def imprint(self): self._log_access() ImprintController.apply(self) return render_template( - "imprint.jinja", + "public/imprint.jinja", bp=self, lang=get_locale(), ) diff --git a/starfall/web/blueprints/main.py b/starfall/web/blueprints/public/main.py similarity index 96% rename from starfall/web/blueprints/main.py rename to starfall/web/blueprints/public/main.py index fef6009..a6c7bdc 100644 --- a/starfall/web/blueprints/main.py +++ b/starfall/web/blueprints/public/main.py @@ -16,7 +16,7 @@ class MainBlueprint(BaseBlueprint): def index(self): self._log_access() return render_template( - "home.jinja", + "public/home.jinja", bp=self, lang=get_locale(), ) diff --git a/starfall/web/blueprints/secure/main.py b/starfall/web/blueprints/secure/main.py new file mode 100644 index 0000000..b673e5b --- /dev/null +++ b/starfall/web/blueprints/secure/main.py @@ -0,0 +1,25 @@ +from flask import Blueprint, Flask, redirect, render_template, url_for +from flask_assets import Environment + +from starfall.web.blueprints.base import BaseBlueprint +from starfall.web.controllers.secure.login import LoginController +from starfall.web.controllers.secure.register import RegisterController + + +class AdminBlueprint(BaseBlueprint): + def __init__(self, blueprint: Blueprint, assets: Environment, app: Flask): + super().__init__(blueprint, assets, app) + blueprint.add_url_rule("/secure/", view_func=self.secure) + blueprint.add_url_rule("/secure/login/", methods=["GET", "POST"], view_func=self.login) + blueprint.add_url_rule("/secure/register/", methods=["GET", "POST"], view_func=self.register) + + def secure(self): + return redirect(url_for("starfall.login")) + + def login(self): + LoginController.apply(self) + return render_template("secure/login.jinja", bp=self) + + def register(self): + RegisterController.apply(self) + return render_template("secure/register.jinja", bp=self) diff --git a/starfall/web/controllers/base.py b/starfall/web/controllers/base.py index 50acd5e..00510c3 100644 --- a/starfall/web/controllers/base.py +++ b/starfall/web/controllers/base.py @@ -1,2 +1,11 @@ +from werkzeug.security import generate_password_hash, check_password_hash + + class BaseController: - pass + @classmethod + def encrypt_password(cls, password: str): + return generate_password_hash(password) + + @classmethod + def validate_password(cls, pwhash: str, password: str): + return check_password_hash(pwhash, password) \ No newline at end of file diff --git a/starfall/web/controllers/imprint.py b/starfall/web/controllers/public/imprint.py similarity index 74% rename from starfall/web/controllers/imprint.py rename to starfall/web/controllers/public/imprint.py index af95920..a82ed77 100644 --- a/starfall/web/controllers/imprint.py +++ b/starfall/web/controllers/public/imprint.py @@ -1,9 +1,10 @@ from datetime import datetime from starfall.web.blueprints.base import BaseBlueprint +from starfall.web.controllers.base import BaseController -class ImprintController: +class ImprintController(BaseController): @classmethod def apply(cls, bp: BaseBlueprint): bp.data["lastmod"] = cls.last_modified() diff --git a/starfall/web/controllers/secure/login.py b/starfall/web/controllers/secure/login.py new file mode 100644 index 0000000..a5fef83 --- /dev/null +++ b/starfall/web/controllers/secure/login.py @@ -0,0 +1,38 @@ +from flask_babel import _, lazy_gettext +from flask_wtf import FlaskForm +from wtforms import PasswordField, StringField +from wtforms.validators import DataRequired + +from starfall.web.blueprints.base import BaseBlueprint +from starfall.web.controllers.base import BaseController + + +class LoginForm(FlaskForm): + username: StringField = StringField( + label=lazy_gettext("page.login.form.username"), # pyright: ignore[reportArgumentType] + render_kw={ + "autocomplete": "username", + "class": "form-control", + "placeholder": lazy_gettext("page.login.form.username"), + }, + validators=[ + DataRequired(message=lazy_gettext("page.login.error.username")), # pyright: ignore[reportArgumentType] + ], + ) + password: PasswordField = PasswordField( + label=lazy_gettext("page.login.form.password"), # pyright: ignore[reportArgumentType] + render_kw={ + "autocomplete": "password", + "class": "form-control", + "placeholder": lazy_gettext("page.login.form.password"), + }, + validators=[ + DataRequired(message=lazy_gettext("page.login.error.password")), # pyright: ignore[reportArgumentType] + ], + ) + + +class LoginController(BaseController): + @classmethod + def apply(cls, bp: BaseBlueprint): + bp.data["form"] = LoginForm() diff --git a/starfall/web/controllers/secure/register.py b/starfall/web/controllers/secure/register.py new file mode 100644 index 0000000..e2d6e94 --- /dev/null +++ b/starfall/web/controllers/secure/register.py @@ -0,0 +1,72 @@ +from flask import request +from flask_babel import LazyString, lazy_gettext +from flask_wtf import FlaskForm +from wtforms import EmailField, PasswordField, StringField +from wtforms.validators import DataRequired + +from starfall.db import db +from starfall.db.schema.users import User +from starfall.web.blueprints.base import BaseBlueprint +from starfall.web.controllers.base import BaseController + + +class RegisterForm(FlaskForm): + username: StringField = StringField( + label=lazy_gettext("page.register.form.username"), # pyright: ignore[reportArgumentType] + render_kw={ + "autocomplete": "username", + "class": "form-control", + "aria-describedby": "username-addon", + "placeholder": lazy_gettext("page.register.form.username"), + }, + validators=[ + DataRequired(message=lazy_gettext("page.register.error.username")), # pyright: ignore[reportArgumentType] + ], + ) + password: PasswordField = PasswordField( + label=lazy_gettext("page.register.form.password"), # pyright: ignore[reportArgumentType] + render_kw={ + "autocomplete": "password", + "class": "form-control", + "placeholder": lazy_gettext("page.register.form.password"), + }, + validators=[ + DataRequired(message=lazy_gettext("page.register.error.password")), # pyright: ignore[reportArgumentType] + ], + ) + email: EmailField = EmailField( + label=lazy_gettext("page.register.form.email"), # pyright: ignore[reportArgumentType] + render_kw={ + "autocomplete": "email", + "class": "form-control", + "placeholder": lazy_gettext("page.register.form.email"), + }, + validators=[ + DataRequired(message=lazy_gettext("page.register.error.email")), # pyright: ignore[reportArgumentType] + ], + ) + + +class RegisterController(BaseController): + @classmethod + def apply(cls, bp: BaseBlueprint): + if "POST" == request.method: + bp.data["status_class"], bp.data["status"] = cls.handle_form() + bp.data["form"] = RegisterForm() + + @classmethod + def handle_form(cls) -> tuple[str | None, LazyString | None]: + form = RegisterForm() + if not form.validate_on_submit(): + return None, None + user = User( + username=str(form.username.data), + password=cls.encrypt_password(str(form.password.data)), + email=str(form.email.data), + ) + try: + db.session.add(user) + db.session.commit() + except Exception: + return "error", lazy_gettext("page.register.status.email_already_exists") + return "success", lazy_gettext("page.register.status.success") diff --git a/starfall/web/controllers/user.py b/starfall/web/controllers/user.py new file mode 100644 index 0000000..f4ddd6d --- /dev/null +++ b/starfall/web/controllers/user.py @@ -0,0 +1,15 @@ +from starfall.web.controllers.base import BaseController +from flask_login import LoginManager + +login_manager = LoginManager() + + +@login_manager.user_loader +def user(user_id): + return UserController.get(user_id) + + +class UserController(BaseController): + @classmethod + def get(cls, user_id): + return None diff --git a/translations/en/LC_MESSAGES/messages.mo b/translations/en/LC_MESSAGES/messages.mo index 069a805b92b6e81fce8ecf8cc494a4367ee9aca2..fc1a3840bc1f486a64a724714c2380cc332c6656 100644 GIT binary patch delta 2116 zcma)*U1(fI6vwA)Hf_^bYpb>Cns%c7wCy%+D%wP>!Hjl0 zQg^`Za4kFpC4SV-yVifv`tPKEqSPfj@wHuW)%t%yNwj*oQa!NGatLm~KLMpc-TJS> zUi^2VtiKH90N-2x7bprZTaj@S+(3Ob#Gev84&}oM>%U?B58zJbzl4(DIuu2guT<(S z+Es$0%*8IH5JP;&vh&HCL4JL%0|}FPsy_be#-oVP+I&ayOMkbWO;%4Rcq+~OYC6g)@(sH z4g7}%B{%>#lKnf{@Fd)c-@93$AHHuNRUDr)tWcUv=wc69j%xo^Zl&(hn+XNhA(UPo%pe>6I5JpZEhvjr1ryVaaPG$PE znL?JZ{mL*N8#CeJV5Zt=(D;|S%R>L~iTl;}d&(*qMG{2B(GIhc#ruXVc(*{502US1P z`LaXmXnsqw4~F&gj^`$ocDet@rdl?eRm@8*RLVD%9oU{pPrA`r9i1hEsXfkJ)V5zb k>sQ%+kC?7s*S1mPu~*j5`mwG#)4J|YY96QNbuJY94?-cZ?*IS* delta 662 zcmYk(Pbg(k9Ki8|%eYU5{2$(U*G9(c*15>y+RXHng@pxb($sX*Q_4fHm!kAaN){+L zQL>QISZJoa5)0C7hGZdIl2U4Rik0v^?pe5X@8|x0=XcNVoOAa&m%gU2N{l7JWpRhq zoyFtb70wo-i1;&>qK(z~4a@NdmSIxIGgv{qrsItEM0<^@{t-V4kro9xL4#^-BNkKO zqH5TQ>VuzpJcX*kG}huOR^mRE;JF^Z)%hnKml;7_6xH{!w>T|CpH3uo!7QqY)^UT= z$e>!l_q<@C3GE~@B<3&|7j?X%<25wNZ=gD@5lQZ@dE-8|zVHsb-udPdKCto=gb zh#z{#R{mon@kJzP^b+yMjJlm)#(1`0WA@hf*s)~XOAU?;+wEQ|?pjTK7S%qQ!eWJM@Glnte&`+u)0%T Oa=;rJ+W8mS`t%AwXjcUQ diff --git a/translations/en/LC_MESSAGES/messages.po b/translations/en/LC_MESSAGES/messages.po index e1bbee8..d48ac5b 100644 --- a/translations/en/LC_MESSAGES/messages.po +++ b/translations/en/LC_MESSAGES/messages.po @@ -7,16 +7,70 @@ msgid "" msgstr "" "Project-Id-Version: Starfall 0.1.0-alpha.3\n" "Report-Msgid-Bugs-To: flare@theflare.at\n" -"POT-Creation-Date: 2026-03-10 10:46+0100\n" -"PO-Revision-Date: 2026-03-10 01:17+0100\n" +"POT-Creation-Date: 2026-03-18 10:46+0100\n" +"PO-Revision-Date: 2026-03-18 10:47+0100\n" "Last-Translator: Flare Starfall \n" -"Language: en\n" "Language-Team: en \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.18.0\n" +"X-Generator: Poedit 3.9\n" + +#: starfall/web/controllers/secure/login.py:12 +#: starfall/web/controllers/secure/login.py:16 +msgid "page.login.form.username" +msgstr "Username / Email" + +#: starfall/web/controllers/secure/login.py:19 +msgid "page.login.error.username" +msgstr "A username or email is required." + +#: starfall/web/controllers/secure/login.py:23 +#: starfall/web/controllers/secure/login.py:27 +msgid "page.login.form.password" +msgstr "Password" + +#: starfall/web/controllers/secure/login.py:30 +msgid "page.login.error.password" +msgstr "A password is required." + +#: starfall/web/controllers/secure/register.py:15 +#: starfall/web/controllers/secure/register.py:20 +msgid "page.register.form.username" +msgstr "Username" + +#: starfall/web/controllers/secure/register.py:23 +msgid "page.register.error.username" +msgstr "A username is required." + +#: starfall/web/controllers/secure/register.py:27 +#: starfall/web/controllers/secure/register.py:31 +msgid "page.register.form.password" +msgstr "Password" + +#: starfall/web/controllers/secure/register.py:34 +msgid "page.register.error.password" +msgstr "A password is required." + +#: starfall/web/controllers/secure/register.py:38 +#: starfall/web/controllers/secure/register.py:42 +msgid "page.register.form.email" +msgstr "Email" + +#: starfall/web/controllers/secure/register.py:45 +msgid "page.register.error.email" +msgstr "A valid email is required." + +#: starfall/web/controllers/secure/register.py:71 +msgid "page.register.status.email_already_exists" +msgstr "A user with this email already exists." + +#: starfall/web/controllers/secure/register.py:72 +msgid "page.register.status.success" +msgstr "Registration successful. You may now log in." #: web/templates/base.jinja:6 msgid "base.meta.title.empty" @@ -42,79 +96,92 @@ msgstr "Starfall - Logo" msgid "base.label.toggle_nav" msgstr "Open menu" -#: web/templates/base.jinja:47 +#: web/templates/base.jinja:46 +msgid "base.label.login" +msgstr "Login" + +#: web/templates/base.jinja:53 msgid "base.label.theme.btn" msgstr "Theme" -#: web/templates/base.jinja:52 +#: web/templates/base.jinja:58 msgid "base.label.theme.light" msgstr "Light" -#: web/templates/base.jinja:55 +#: web/templates/base.jinja:61 msgid "base.label.theme.dark" msgstr "Dark" -#: web/templates/base.jinja:58 +#: web/templates/base.jinja:64 msgid "base.label.theme.auto" msgstr "Auto" -#: web/templates/base.jinja:76 -msgid "page.empty.title" -msgstr "This page has no content" - -#: web/templates/base.jinja:77 -msgid "page.empty.body" -msgstr "" -"

This page has not been assigned any content.

This is unlikely to" -" be intentional - please notify the site " -"administrator about this problem.

" - -#: web/templates/base.jinja:89 +#: web/templates/base.jinja:90 msgid "base.label.copy" msgstr "© Team Starfall" -#: web/templates/home.jinja:3 +#: web/templates/components/content_empty.jinja:3 +msgid "page.empty.title" +msgstr "This page has no content" + +#: web/templates/components/content_empty.jinja:6 +msgid "page.empty.body" +msgstr "" +"

This page has not been assigned any content.

This is unlikely to be " +"intentional - please notify the site " +"administrator about this problem.

" + +#: web/templates/errors/not_found.jinja:8 +msgid "error.404.title" +msgstr "404 Not Found" + +#: web/templates/errors/not_found.jinja:9 +msgid "error.404.body" +msgstr "" +"

Whatever you were looking for cannot be found here.
You may want to start over.

" + +#: web/templates/public/home.jinja:3 msgid "page.home.title" msgstr "Home of Team Starfall" -#: web/templates/home.jinja:8 +#: web/templates/public/home.jinja:8 msgid "page.home.card.title" msgstr "Shoot for the stars." -#: web/templates/home.jinja:9 +#: web/templates/public/home.jinja:11 msgid "page.home.card.body" msgstr "" -"

Home of Team Starfall, a small group of friends dedicated to messing " -"with video games in ways the developers are unlikely to expect.

And" -" hey - occasionally we get fun(ny) results out of it.

On the side, " -"some of us get involved in all kinds of shenanigans, ranging from " -"character creation in various kinds of games and software, over creating " -"music, to web design and programming.

" +"

Home of Team Starfall, a small group of friends dedicated to messing with " +"video games in ways the developers are unlikely to expect.

And hey - " +"occasionally we get fun(ny) results out of it.

On the side, some of us " +"get involved in all kinds of shenanigans, ranging from character creation in " +"various kinds of games and software, over creating music, to web design and " +"programming.

" -#: web/templates/imprint.jinja:3 web/templates/imprint.jinja:8 +#: web/templates/public/imprint.jinja:3 web/templates/public/imprint.jinja:8 msgid "page.imprint.title" msgstr "Imprint & Privacy" -#: web/templates/imprint.jinja:10 +#: web/templates/public/imprint.jinja:11 msgid "page.imprint.card.title" msgstr "In short" -#: web/templates/imprint.jinja:12 +#: web/templates/public/imprint.jinja:13 msgid "page.imprint.card.body" msgstr "" -"The only thing this site processes is your site visit, stored for no " -"longer than 7 days unless absolutely necessary. This data is only ever " -"accessed by one person (this website's creator), and only in cases where " -"reading the server logs is necessary (say, a website outage, or reading " -"the logs while developing a new part of the website).
This " -"disclaimer is not by itself legally binding. The full privacy statement " -"is found below." +"The only thing this site processes is your site visit, stored for no longer " +"than 7 days unless absolutely necessary. This data is only ever accessed by " +"one person (this website's creator), and only in cases where reading the " +"server logs is necessary (say, a website outage, or reading the logs while " +"developing a new part of the website).
This disclaimer is not by itself " +"legally binding. The full privacy statement is found below." -#: web/templates/imprint.jinja:13 +#: web/templates/public/imprint.jinja:14 msgid "page.imprint.card.lastmodified" msgstr "Last modified:" -#: web/templates/imprint.jinja:23 +#: web/templates/public/imprint.jinja:24 msgid "page.imprint.legal" msgstr "" "Based on the .
Edited to provide details of the data controller, and to " "detail exactly what server data is stored." -#: web/templates/imprint.jinja:31 +#: web/templates/public/imprint.jinja:32 msgid "page.imprint.sec1.title" msgstr "Privacy Policy" -#: web/templates/imprint.jinja:32 +#: web/templates/public/imprint.jinja:33 msgid "page.imprint.sec2.title" msgstr "I. Information about us as controllers of your data" -#: web/templates/imprint.jinja:33 +#: web/templates/public/imprint.jinja:34 msgid "page.imprint.sec3.title" msgstr "II. The rights of users and data subjects" -#: web/templates/imprint.jinja:34 +#: web/templates/public/imprint.jinja:35 msgid "page.imprint.sec4.title" msgstr "III. Information about the data processing" -#: web/templates/imprint.jinja:35 +#: web/templates/public/imprint.jinja:36 msgid "page.imprint.sec1.body" msgstr "" "

Personal data (usually referred to just as „data“ below) will only be " -"processed by us to the extent necessary and for the purpose of providing " -"a functional and user-friendly website, including its contents, and the " +"processed by us to the extent necessary and for the purpose of providing a " +"functional and user-friendly website, including its contents, and the " "services offered there.

\n" "

Per Art. 4 No. 1 of Regulation (EU) 2016/679, i.e. the General Data " -"Protection Regulation (hereinafter referred to as the „GDPR“), " -"„processing“ refers to any operation or set of operations such as " -"collection, recording, organization, structuring, storage, adaptation, " -"alteration, retrieval, consultation, use, disclosure by transmission, " -"dissemination, or otherwise making available, alignment, or combination, " -"restriction, erasure, or destruction performed on personal data, whether " -"by automated means or not.

\n" +"Protection Regulation (hereinafter referred to as the „GDPR“), „processing“ " +"refers to any operation or set of operations such as collection, recording, " +"organization, structuring, storage, adaptation, alteration, retrieval, " +"consultation, use, disclosure by transmission, dissemination, or otherwise " +"making available, alignment, or combination, restriction, erasure, or " +"destruction performed on personal data, whether by automated means or not.\n" "

The following privacy policy is intended to inform you in particular " -"about the type, scope, purpose, duration, and legal basis for the " -"processing of such data either under our own control or in conjunction " -"with others. We also inform you below about the third-party components we" -" use to optimize our website and improve the user experience which may " -"result in said third parties also processing data they collect and " -"control.

\n" +"about the type, scope, purpose, duration, and legal basis for the processing " +"of such data either under our own control or in conjunction with others. We " +"also inform you below about the third-party components we use to optimize " +"our website and improve the user experience which may result in said third " +"parties also processing data they collect and control.

\n" "

Our privacy policy is structured as follows:

\n" -"

I. Information about us as controllers of your data
II. The rights " -"of users and data subjects
III. Information about the data " -"processing

" +"

I. Information about us as controllers of your data
II. The rights of " +"users and data subjects
III. Information about the data processing

" -#: web/templates/imprint.jinja:36 +#: web/templates/public/imprint.jinja:37 msgid "page.imprint.sec2.body" msgstr "" -"

The party responsible for this website (the „controller“) for purposes" -" of data protection law is:
Robert Bäs-Fischlmair a.k.a. Flare " +"

The party responsible for this website (the „controller“) for purposes of " +"data protection law is:
Robert Bäs-Fischlmair a.k.a. Flare " "Starfall
Phone: +43 (0) 677 62890651
Email:
flare@theflare.at

" -#: web/templates/imprint.jinja:37 +#: web/templates/public/imprint.jinja:38 msgid "page.imprint.sec3.body" msgstr "" -"

With regard to the data processing to be described in more detail " -"below, users and data subjects have the right

\n" +"

With regard to the data processing to be described in more detail below, " +"users and data subjects have the right

\n" "
    \n" "
  • to confirmation of whether data concerning them is being processed, " -"information about the data being processed, further information about the" -" nature of the data processing, and copies of the data (cf. also Art. 15 " +"information about the data being processed, further information about the " +"nature of the data processing, and copies of the data (cf. also Art. 15 " +"GDPR);
  • \n" +"
  • to correct or complete incorrect or incomplete data (cf. also Art. 16 " "GDPR);
  • \n" -"
  • to correct or complete incorrect or incomplete data (cf. also Art. 16" -" GDPR);
  • \n" "
  • to the immediate deletion of data concerning them (cf. also Art. 17 " -"DSGVO), or, alternatively, if further processing is necessary as " -"stipulated in Art. 17 Para. 3 GDPR, to restrict said processing per Art. " -"18 GDPR;
  • to receive copies of the data concerning them and/or " -"provided by them and to have the same transmitted to other " -"providers/controllers (cf. also Art. 20 GDPR);
  • to file " -"complaints with the supervisory authority if they believe that data " -"concerning them is being processed by the controller in breach of data " -"protection provisions (see also Art. 77 GDPR).
  • \n" +"DSGVO), or, alternatively, if further processing is necessary as stipulated " +"in Art. 17 Para. 3 GDPR, to restrict said processing per Art. 18 GDPR; " +"
  • to receive copies of the data concerning them and/or provided by them " +"and to have the same transmitted to other providers/controllers (cf. also " +"Art. 20 GDPR);
  • to file complaints with the supervisory authority if " +"they believe that data concerning them is being processed by the controller " +"in breach of data protection provisions (see also Art. 77 GDPR).
  • \n" "
\n" -"

In addition, the controller is obliged to inform all recipients to " -"whom it discloses data of any such corrections, deletions, or " -"restrictions placed on processing the same per Art. 16, 17 Para. 1, 18 " -"GDPR. However, this obligation does not apply if such notification is " -"impossible or involves a disproportionate effort. Nevertheless, users " -"have a right to information about these recipients.

\n" -"

Likewise, under Art. 21 GDPR, users and data subjects have the" -" right to object to the controller's future processing of their data" -" pursuant to Art. 6 Para. 1 lit. f) GDPR. In particular, an objection to " -"data processing for the purpose of direct advertising is " -"permissible.

" +"

In addition, the controller is obliged to inform all recipients to whom " +"it discloses data of any such corrections, deletions, or restrictions placed " +"on processing the same per Art. 16, 17 Para. 1, 18 GDPR. However, this " +"obligation does not apply if such notification is impossible or involves a " +"disproportionate effort. Nevertheless, users have a right to information " +"about these recipients.

\n" +"

Likewise, under Art. 21 GDPR, users and data subjects have the " +"right to object to the controller's future processing of their data " +"pursuant to Art. 6 Para. 1 lit. f) GDPR. In particular, an objection to data " +"processing for the purpose of direct advertising is permissible.

" -#: web/templates/imprint.jinja:38 +#: web/templates/public/imprint.jinja:39 msgid "page.imprint.sec4.body" msgstr "" -"

Your data processed when using our website will be deleted or blocked " -"as soon as the purpose for its storage ceases to apply, provided the " -"deletion of the same is not in breach of any statutory storage " -"obligations or unless otherwise stipulated below.

\n" +"

Your data processed when using our website will be deleted or blocked as " +"soon as the purpose for its storage ceases to apply, provided the deletion " +"of the same is not in breach of any statutory storage obligations or unless " +"otherwise stipulated below.

\n" "

Server data

\n" -"

For technical reasons, the following data sent by your internet " -"browser to us or to our server provider will be collected, especially to " -"ensure a secure and stable website: These server log files record the " -"type and version of your browser, operating system, the webpages on our " -"site visited, and the date and time of your visit. The IP address from " -"which you visited our site is additionally recorded if an error " -"occurs.

\n" +"

For technical reasons, the following data sent by your internet browser " +"to us or to our server provider will be collected, especially to ensure a " +"secure and stable website: These server log files record the type and " +"version of your browser, operating system, the webpages on our site visited, " +"and the date and time of your visit. The IP address from which you visited " +"our site is additionally recorded if an error occurs.

\n" "

The data thus collected will be temporarily stored, but not in " "association with any other of your data.

\n" -"

The basis for this storage is Art. 6 Para. 1 lit. f) GDPR. Our " -"legitimate interest lies in the improvement, stability, functionality, " -"and security of our website.

\n" -"

The data will be deleted within no more than seven days, unless " -"continued storage is required for evidentiary purposes. In which case, " -"all or part of the data will be excluded from deletion until the " -"investigation of the relevant incident is finally resolved.

" +"

The basis for this storage is Art. 6 Para. 1 lit. f) GDPR. Our legitimate " +"interest lies in the improvement, stability, functionality, and security of " +"our website.

\n" +"

The data will be deleted within no more than seven days, unless continued " +"storage is required for evidentiary purposes. In which case, all or part of " +"the data will be excluded from deletion until the investigation of the " +"relevant incident is finally resolved.

" -#: web/templates/errors/not_found.jinja:8 -msgid "error.404.title" -msgstr "404 Not Found" +#: web/templates/secure/login.jinja:3 web/templates/secure/login.jinja:8 +msgid "page.login.title" +msgstr "Login" -#: web/templates/errors/not_found.jinja:9 -msgid "error.404.body" -msgstr "" -"

Whatever you were looking for cannot be found here.
You may want to" -" start over.

" +#: web/templates/secure/login.jinja:20 +msgid "page.login.form.submit" +msgstr "Login" +#: web/templates/secure/login.jinja:24 +msgid "page.login.label.register" +msgstr "Register" + +#: web/templates/secure/login.jinja:26 +msgid "page.login.label.lostpassword" +msgstr "Forgot Password?" + +#: web/templates/secure/register.jinja:5 web/templates/secure/register.jinja:10 +msgid "page.register.title" +msgstr "Register" + +#: web/templates/secure/register.jinja:20 +msgid "page.register.form.submit" +msgstr "Register" + +#: web/templates/secure/register.jinja:24 +msgid "page.register.label.login" +msgstr "Login" diff --git a/web/templates/base.jinja b/web/templates/base.jinja index d2a3eb9..f84113d 100644 --- a/web/templates/base.jinja +++ b/web/templates/base.jinja @@ -40,6 +40,12 @@ {%endblock%}