import os
import sqlite3
import uuid as uuid_lib
from datetime import datetime
from flask import (
Flask,
g,
render_template,
request,
redirect,
url_for,
session,
abort,
flash,
Response,
)
from io import BytesIO
import re
from xhtml2pdf import pisa # type: ignore
from docx import Document # type: ignore
from htmldocx import HtmlToDocx # type: ignore
def create_app():
app = Flask(__name__)
# Basic config
app.config["SECRET_KEY"] = os.environ.get(
"SECRET_KEY",
# For production, override via env var. This default is for local/dev only.
"dev-secret-change-me",
)
app.config["DATABASE"] = os.path.join(os.path.dirname(__file__), "app.db")
# Admin credentials (can be overridden via env)
app.config["ADMIN_USERNAME"] = os.environ.get("ADMIN_USERNAME", "ruslan")
app.config["ADMIN_PASSWORD"] = os.environ.get("ADMIN_PASSWORD", "utOgbZ09ruslan")
# --------------------
# Database helpers
# --------------------
def get_db():
if "db" not in g:
g.db = sqlite3.connect(app.config["DATABASE"]) # type: ignore[attr-defined]
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
db = g.pop("db", None)
if db is not None:
db.close()
def init_db():
db = get_db()
db.execute(
"""
CREATE TABLE IF NOT EXISTS pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
title TEXT NOT NULL DEFAULT '',
html TEXT NOT NULL,
created_at TEXT NOT NULL
);
"""
)
# Migration: add 'title' column for older DB versions
try:
cols = {row[1] for row in db.execute("PRAGMA table_info(pages)").fetchall()}
if "title" not in cols:
db.execute("ALTER TABLE pages ADD COLUMN title TEXT NOT NULL DEFAULT ''")
except Exception:
pass
db.commit()
@app.before_request
def _before_request():
# Ensure DB exists
init_db()
@app.teardown_appcontext
def _teardown_appcontext(_=None):
close_db()
# --------------------
# Auth helpers
# --------------------
def is_logged_in() -> bool:
return bool(session.get("logged_in"))
def login_required():
if not is_logged_in():
# For non-auth users: show nothing (404), not even an admin hint
abort(404)
@app.context_processor
def inject_auth():
return {"logged_in": is_logged_in()}
# --------------------
# Routes
# --------------------
@app.route("/")
def index():
# Redirect unauthenticated users to login; authenticated users to admin.
if not is_logged_in():
return redirect(url_for("login"))
return redirect(url_for("admin"))
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username", "")
password = request.form.get("password", "")
if (
username == app.config["ADMIN_USERNAME"]
and password == app.config["ADMIN_PASSWORD"]
):
session["logged_in"] = True
flash("Успешный вход в админку.", "success")
return redirect(url_for("admin"))
flash("Неверные имя пользователя или пароль.", "error")
return render_template("login.html")
@app.route("/logout", methods=["POST"])
def logout():
session.clear()
# After logout, nothing is visible. Return 404-like behavior.
abort(404)
@app.route("/admin", methods=["GET", "POST"])
def admin():
login_required()
db = get_db()
if request.method == "POST":
title = request.form.get("title", "").strip()
html = request.form.get("html", "").strip()
if not html:
flash("HTML не может быть пустым.", "error")
else:
uid = uuid_lib.uuid4().hex
db.execute(
"INSERT INTO pages (uuid, title, html, created_at) VALUES (?, ?, ?, ?)",
(uid, title, html, datetime.utcnow().isoformat(timespec="seconds")),
)
db.commit()
flash("Страница опубликована.", "success")
return redirect(url_for("admin"))
pages = db.execute(
"SELECT id, uuid, title, created_at FROM pages ORDER BY id DESC"
).fetchall()
base_url = request.host_url.rstrip("/")
return render_template("admin.html", pages=pages, base_url=base_url)
@app.route("/admin/edit/
" in lower: idx = lower.rfind("") html = html[:idx] + toolbar + html[idx:] else: html = html + toolbar # Ensure watermark exists on published pages (top-left) wm = ( "" "" "" "
" ) # Export buttons (visible to everyone). If admin overlay exists, offset a bit. top_offset = "60px" if is_logged_in() else "16px" pdf_url = url_for("export_pdf", uid=uid) docx_url = url_for("export_docx", uid=uid) exports = ( f'
' ) overlays = wm + exports lower_all = html.lower() if "" in lower_all: i2 = lower_all.rfind("") html = html[:i2] + overlays + html[i2:] else: html = html + overlays return Response(html, mimetype="text/html; charset=utf-8") def _fetch_page(uid: str): db = get_db() row = db.execute("SELECT title, html FROM pages WHERE uuid = ?", (uid,)).fetchone() if row is None: abort(404) return row def _sanitize_html_for_pdf(html: str) -> str: # xhtml2pdf плохо переносит современный CSS; вычищаем стили/скрипты html = re.sub(r"", "", html, flags=re.I | re.S) html = re.sub(r"\sstyle=(\"|\')(.*?)\1", "", html, flags=re.I | re.S) html = re.sub(r"", "", html, flags=re.I | re.S) return html def _wrap_html_for_export(title: str, html: str) -> str: head_title = f"
" if title else "" return ( "
" + head_title + "