Add hosted legal pages
This commit is contained in:
parent
98e8e3f808
commit
4de3e2406a
5 changed files with 284 additions and 0 deletions
|
|
@ -923,6 +923,13 @@ def _landing_page_html(
|
|||
)
|
||||
|
||||
|
||||
def _static_html_page(static_dir: Path, filename: str) -> str:
|
||||
styles_path = static_dir / "styles.css"
|
||||
styles_version = int(styles_path.stat().st_mtime_ns) if styles_path.exists() else 0
|
||||
template = (static_dir / filename).read_text(encoding="utf-8")
|
||||
return template.replace("{{STYLES_VERSION}}", str(styles_version))
|
||||
|
||||
|
||||
def create_app():
|
||||
deps = _require_gui_dependencies()
|
||||
FastAPI = deps["FastAPI"]
|
||||
|
|
@ -1238,6 +1245,16 @@ def create_app():
|
|||
)
|
||||
)
|
||||
return RedirectResponse(url="/app/")
|
||||
|
||||
@app.api_route("/privacy", methods=["GET", "HEAD"])
|
||||
@app.api_route("/privacy/", methods=["GET", "HEAD"])
|
||||
def privacy_policy():
|
||||
return HTMLResponse(_static_html_page(static_dir, "privacy.html"))
|
||||
|
||||
@app.api_route("/terms", methods=["GET", "HEAD"])
|
||||
@app.api_route("/terms/", methods=["GET", "HEAD"])
|
||||
def terms_of_service():
|
||||
return HTMLResponse(_static_html_page(static_dir, "terms.html"))
|
||||
else: # pragma: no cover - only relevant before frontend assets exist
|
||||
@app.get("/")
|
||||
def root_missing():
|
||||
|
|
|
|||
|
|
@ -229,6 +229,9 @@
|
|||
<p class="copy-tag status-text">
|
||||
Self-hosters can still go straight to <a href="/app/">the app</a>. Hosted mode just gets a front door instead of making everyone trip over raw controls immediately.
|
||||
</p>
|
||||
<p class="copy-tag status-text">
|
||||
<a href="/privacy">Privacy Policy</a> · <a href="/terms">Terms of Service</a>
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
|
|
|
|||
113
src/repixelizer/gui_static/privacy.html
Normal file
113
src/repixelizer/gui_static/privacy.html
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Privacy Policy - Repixelizer</title>
|
||||
<link rel="stylesheet" href="/app/styles.css?v={{STYLES_VERSION}}">
|
||||
<style>
|
||||
.legal-page {
|
||||
max-width: 920px;
|
||||
}
|
||||
|
||||
.legal-page .panel-stack {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.legal-page h2 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.legal-page ul {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.legal-page li,
|
||||
.legal-page p {
|
||||
color: var(--ui-text-muted);
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.legal-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell app-shell legal-page">
|
||||
<section class="app-shell-header ui-surface ui-frame">
|
||||
<div class="brand-lockup">
|
||||
<div class="brand-emblem" aria-hidden="true">
|
||||
<img class="brand-emblem-art" src="/app/logos/repixelizer-logo-monogram.svg" alt="">
|
||||
</div>
|
||||
<div class="copy-stack">
|
||||
<p class="eyebrow">Repixelizer</p>
|
||||
<h1>Privacy Policy</h1>
|
||||
<p class="copy-tag">Effective date: April 29, 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="legal-nav" aria-label="Legal links">
|
||||
<a class="button button-secondary" href="/">Home</a>
|
||||
<a class="button button-secondary" href="/terms">Terms</a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card panel-stack ui-surface ui-frame">
|
||||
<p>
|
||||
Repixelizer is operated by GameCult. This policy describes the hosted demo at
|
||||
<a href="https://repixelizer.gamecult.org">repixelizer.gamecult.org</a>.
|
||||
</p>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Information We Process</h2>
|
||||
<ul>
|
||||
<li>Images you upload for repixelizing, plus generated job outputs and progress data.</li>
|
||||
<li>Basic account/authentication data returned by enabled login providers, such as provider user id, display name, username, and provider scopes.</li>
|
||||
<li>Provider entitlement facts needed to decide access, such as Discord guild role matches.</li>
|
||||
<li>Operational logs and request metadata needed to run, secure, debug, and abuse-protect the service.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>How We Use It</h2>
|
||||
<ul>
|
||||
<li>To run Repixelizer jobs and show you job status/results.</li>
|
||||
<li>To authenticate access to the hosted demo through Heimdall, GameCult's shared auth service.</li>
|
||||
<li>To enforce access rules, rate limits, queue limits, and security checks.</li>
|
||||
<li>To diagnose failures and keep the service from falling over in a dramatic little heap.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Sharing</h2>
|
||||
<p>
|
||||
We do not sell personal data. We share data only with service providers needed to operate the hosted demo,
|
||||
with login providers you choose to authenticate through, or when required by law.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Retention</h2>
|
||||
<p>
|
||||
Uploaded images, generated outputs, auth sessions, and logs are kept only as long as needed for operation,
|
||||
debugging, security, or legal reasons. Hosted demo job data may be deleted at any time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Your Choices</h2>
|
||||
<p>
|
||||
You can sign out from the hosted demo at any time. For privacy questions or deletion requests, contact
|
||||
<a href="mailto:meta@gamecult.org">meta@gamecult.org</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
119
src/repixelizer/gui_static/terms.html
Normal file
119
src/repixelizer/gui_static/terms.html
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Terms of Service - Repixelizer</title>
|
||||
<link rel="stylesheet" href="/app/styles.css?v={{STYLES_VERSION}}">
|
||||
<style>
|
||||
.legal-page {
|
||||
max-width: 920px;
|
||||
}
|
||||
|
||||
.legal-page .panel-stack {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.legal-page h2 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.legal-page ul {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.legal-page li,
|
||||
.legal-page p {
|
||||
color: var(--ui-text-muted);
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.legal-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell app-shell legal-page">
|
||||
<section class="app-shell-header ui-surface ui-frame">
|
||||
<div class="brand-lockup">
|
||||
<div class="brand-emblem" aria-hidden="true">
|
||||
<img class="brand-emblem-art" src="/app/logos/repixelizer-logo-monogram.svg" alt="">
|
||||
</div>
|
||||
<div class="copy-stack">
|
||||
<p class="eyebrow">Repixelizer</p>
|
||||
<h1>Terms of Service</h1>
|
||||
<p class="copy-tag">Effective date: April 29, 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="legal-nav" aria-label="Legal links">
|
||||
<a class="button button-secondary" href="/">Home</a>
|
||||
<a class="button button-secondary" href="/privacy">Privacy</a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card panel-stack ui-surface ui-frame">
|
||||
<p>
|
||||
These terms apply to the hosted Repixelizer demo at
|
||||
<a href="https://repixelizer.gamecult.org">repixelizer.gamecult.org</a>.
|
||||
By using the hosted demo, you agree to these terms.
|
||||
</p>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Use Of The Service</h2>
|
||||
<ul>
|
||||
<li>Use Repixelizer only with images you have the right to process.</li>
|
||||
<li>Do not upload unlawful, abusive, infringing, or privacy-violating material.</li>
|
||||
<li>Do not attack, scrape, overload, bypass access controls, or interfere with the service.</li>
|
||||
<li>The hosted demo is experimental and may change, rate-limit, or go offline without notice.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Your Content</h2>
|
||||
<p>
|
||||
You keep whatever rights you already have in images you upload. You grant GameCult the limited permission
|
||||
needed to process, store, transmit, and display your uploads and outputs so the hosted demo can function.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Access</h2>
|
||||
<p>
|
||||
Hosted demo access may require authentication through supported providers such as Discord or Patreon.
|
||||
Access can be limited, suspended, or revoked if needed to protect the service or enforce these terms.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>No Warranty</h2>
|
||||
<p>
|
||||
Repixelizer is provided as-is. Outputs may be wrong, ugly, cursed, or merely inconvenient. Use your own
|
||||
judgment before relying on results in a real project.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Liability</h2>
|
||||
<p>
|
||||
To the fullest extent allowed by law, GameCult is not liable for indirect, incidental, consequential, or
|
||||
special damages arising from use of the hosted demo.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="copy-stack">
|
||||
<h2>Contact</h2>
|
||||
<p>
|
||||
For questions about these terms, contact <a href="mailto:meta@gamecult.org">meta@gamecult.org</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -414,9 +414,41 @@ def test_gui_hosted_root_serves_landing_page(monkeypatch, tmp_path: Path) -> Non
|
|||
assert "Repixelizer hosted demo" in html
|
||||
assert "Force fake pixel art back onto a real grid, then inspect each step with a full solver view." in html
|
||||
assert 'href="/app/"' in html
|
||||
assert 'href="/privacy"' in html
|
||||
assert 'href="/terms"' in html
|
||||
assert "Login with Discord" not in html
|
||||
|
||||
|
||||
def test_gui_serves_public_legal_pages(monkeypatch, tmp_path: Path) -> None:
|
||||
from repixelizer.gui import create_app
|
||||
|
||||
monkeypatch.setenv("REPIXELIZER_HOSTED_DEMO", "1")
|
||||
monkeypatch.setenv("REPIXELIZER_SPOOL_DIR", str(tmp_path / "spool"))
|
||||
|
||||
app = create_app()
|
||||
privacy_status, privacy_headers, privacy_body = asyncio.run(_get_response(app, "/privacy"))
|
||||
terms_status, terms_headers, terms_body = asyncio.run(_get_response(app, "/terms"))
|
||||
privacy_head_status, _privacy_head_headers, _privacy_head_body = asyncio.run(
|
||||
_get_response(app, "/privacy", method="HEAD")
|
||||
)
|
||||
terms_head_status, _terms_head_headers, _terms_head_body = asyncio.run(
|
||||
_get_response(app, "/terms", method="HEAD")
|
||||
)
|
||||
privacy_html = privacy_body.decode("utf-8")
|
||||
terms_html = terms_body.decode("utf-8")
|
||||
|
||||
assert privacy_status == 200
|
||||
assert "text/html" in privacy_headers["content-type"]
|
||||
assert "Privacy Policy" in privacy_html
|
||||
assert "meta@gamecult.org" in privacy_html
|
||||
assert terms_status == 200
|
||||
assert "text/html" in terms_headers["content-type"]
|
||||
assert "Terms of Service" in terms_html
|
||||
assert "meta@gamecult.org" in terms_html
|
||||
assert privacy_head_status == 200
|
||||
assert terms_head_status == 200
|
||||
|
||||
|
||||
def test_gui_hosted_root_renders_heimdall_login_buttons(monkeypatch, tmp_path: Path) -> None:
|
||||
from repixelizer.gui import create_app
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue