feat: Add legal PDF download link in footer navigation
This commit is contained in:
+112
@@ -10,6 +10,8 @@ import shutil
|
||||
import tarfile
|
||||
import tempfile
|
||||
import threading
|
||||
import unicodedata
|
||||
import zipfile
|
||||
import smtplib
|
||||
from datetime import timedelta, datetime, date
|
||||
from functools import wraps
|
||||
@@ -93,6 +95,31 @@ def set_security_headers(response):
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
INVOICE_UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads", "invoices")
|
||||
TUTORIAL_UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads", "tutorials")
|
||||
LEGAL_DOWNLOAD_DIR = os.path.join(BASE_DIR, "static", "downloads", "legal")
|
||||
LEGAL_DOWNLOAD_FILES = (
|
||||
(
|
||||
"nutzungsbedingungen.pdf",
|
||||
"Nutzungsbedingungen",
|
||||
[
|
||||
"Invario UG Modul Suite",
|
||||
"PDF-Export der Nutzungsbedingungen",
|
||||
"",
|
||||
"Die aktuelle HTML-Version finden Sie unter /nutzungsbedingungen.",
|
||||
"Dieser Download wird vom Server als PDF bereitgestellt.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"datenschutz.pdf",
|
||||
"Datenschutzerklaerung",
|
||||
[
|
||||
"Invario UG Modul Suite",
|
||||
"PDF-Export der Datenschutzerklaerung",
|
||||
"",
|
||||
"Die aktuelle HTML-Version finden Sie unter /datenschutz.",
|
||||
"Dieser Download wird vom Server als PDF bereitgestellt.",
|
||||
],
|
||||
),
|
||||
)
|
||||
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
|
||||
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "Invario_Website")
|
||||
INSTANCE_REPO_URL = os.environ.get("INSTANCE_REPO_URL", "https://github.com/AIIrondev/legendary-octo-garbanzo")
|
||||
@@ -169,6 +196,79 @@ def _is_allowed_tutorial_video_filename(filename: str) -> bool:
|
||||
return lowered.endswith(".mp4") or lowered.endswith(".webm") or lowered.endswith(".ogv") or lowered.endswith(".ogg") or lowered.endswith(".m4v")
|
||||
|
||||
|
||||
def _pdf_safe_text(value: str) -> str:
|
||||
text = unicodedata.normalize("NFKD", value or "")
|
||||
text = text.encode("ascii", "ignore").decode("ascii")
|
||||
return text.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
|
||||
|
||||
|
||||
def _build_simple_pdf_bytes(title: str, lines: list[str]) -> bytes:
|
||||
buffer = BytesIO()
|
||||
|
||||
def write(text: str) -> None:
|
||||
buffer.write(text.encode("latin-1"))
|
||||
|
||||
content_lines = ["BT", "/F1 20 Tf", "72 760 Td", f"({_pdf_safe_text(title)}) Tj", "/F1 11 Tf"]
|
||||
y_position = 730
|
||||
for line in lines:
|
||||
if not line:
|
||||
y_position -= 14
|
||||
continue
|
||||
content_lines.append(f"1 0 0 1 72 {y_position} Tm")
|
||||
content_lines.append(f"({_pdf_safe_text(line)}) Tj")
|
||||
y_position -= 16
|
||||
content_lines.append("ET")
|
||||
content = "\n".join(content_lines).encode("latin-1")
|
||||
|
||||
write("%PDF-1.4\n%\xe2\xe3\xcf\xd3\n")
|
||||
offsets = [0]
|
||||
|
||||
def write_object(object_number: int, body: str) -> None:
|
||||
offsets.append(buffer.tell())
|
||||
write(f"{object_number} 0 obj\n{body}\nendobj\n")
|
||||
|
||||
write_object(1, "<< /Type /Catalog /Pages 2 0 R >>")
|
||||
write_object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>")
|
||||
write_object(
|
||||
3,
|
||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>",
|
||||
)
|
||||
write_object(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||||
offsets.append(buffer.tell())
|
||||
write(f"5 0 obj\n<< /Length {len(content)} >>\nstream\n")
|
||||
buffer.write(content)
|
||||
write("\nendstream\nendobj\n")
|
||||
|
||||
xref_start = buffer.tell()
|
||||
write("xref\n0 6\n")
|
||||
write("0000000000 65535 f \n")
|
||||
for offset in offsets[1:]:
|
||||
write(f"{offset:010d} 00000 n \n")
|
||||
write("trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n")
|
||||
write(f"{xref_start}\n%%EOF\n")
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
def _ensure_legal_pdf_files() -> None:
|
||||
os.makedirs(LEGAL_DOWNLOAD_DIR, exist_ok=True)
|
||||
for filename, title, lines in LEGAL_DOWNLOAD_FILES:
|
||||
target_path = os.path.join(LEGAL_DOWNLOAD_DIR, filename)
|
||||
if os.path.exists(target_path):
|
||||
continue
|
||||
with open(target_path, "wb") as pdf_file:
|
||||
pdf_file.write(_build_simple_pdf_bytes(title, lines))
|
||||
|
||||
|
||||
def _build_legal_pdf_bundle() -> BytesIO:
|
||||
_ensure_legal_pdf_files()
|
||||
bundle = BytesIO()
|
||||
with zipfile.ZipFile(bundle, mode="w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for filename, _, _ in LEGAL_DOWNLOAD_FILES:
|
||||
archive.write(os.path.join(LEGAL_DOWNLOAD_DIR, filename), arcname=filename)
|
||||
bundle.seek(0)
|
||||
return bundle
|
||||
|
||||
|
||||
def _save_invoice_pdf(file_obj, invoice_number: str) -> str | None:
|
||||
if not file_obj or not file_obj.filename:
|
||||
return None
|
||||
@@ -2689,6 +2789,7 @@ def my_instance_management():
|
||||
@login_required
|
||||
def my_tutorials():
|
||||
"""Display tutorial videos for the logged-in user."""
|
||||
origin = request.url_root.rstrip('/')
|
||||
tutorials = [
|
||||
{
|
||||
"_id": "1",
|
||||
@@ -3063,6 +3164,17 @@ def impressum():
|
||||
def nutzungsbedingungen():
|
||||
return render_template("nutzungsbedingungen.html")
|
||||
|
||||
|
||||
@app.route('/downloads/rechtliches')
|
||||
def download_legal_pdfs_bundle():
|
||||
bundle = _build_legal_pdf_bundle()
|
||||
return send_file(
|
||||
bundle,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name="invario-rechtliche-dokumente.zip",
|
||||
)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('username', None)
|
||||
|
||||
@@ -611,7 +611,7 @@
|
||||
<ul class="footer-links">
|
||||
<li><a href="{{ url_for('datenschutz') }}">Datenschutz</a></li>
|
||||
<li><a href="{{ url_for('impressum') }}">Impressum</a></li>
|
||||
<li><a href="{{ url_for('nutzungsbedingungen') }}">Nutzungsbedingungen</a></li>
|
||||
<li><a href="{{ url_for('download_legal_pdfs_bundle') }}">PDF-Download</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user