from __future__ import annotations import os import sqlite3 from datetime import datetime from functools import wraps from pathlib import Path from urllib import parse, request as urllib_request from flask import Flask, redirect, render_template, request, session, url_for from werkzeug.security import check_password_hash BASE_DIR = Path(__file__).resolve().parent DATA_DIR = BASE_DIR / "data" DB_PATH = DATA_DIR / "infra.db" DEFAULT_SETTINGS = { "company_name": "InfraIT", "phone_display": "+7 987 297-06-66", "phone_link": "+79872970666", "email": "maks@infrait.ru", "site_url": "https://infrait.ru/", "yandex_verification": "PASTE_YOUR_YANDEX_VERIFICATION_TOKEN", "yandex_metrika_id": "", "telegram_bot_token": "", "telegram_chat_id": "", "geo_primary": "Казань и Татарстан — выезд в день запроса", "geo_secondary": "Россия — удалённая поддержка", } app = Flask(__name__) app.secret_key = os.getenv("SECRET_KEY", "change-this-secret-key") app.config.update( SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", ) # Fixed admin password hash (plain password is never stored in code). ADMIN_PASSWORD_HASH = ( "scrypt:32768:8:1$Ac0t7TD7bUhYLg04$25779398c765417771b888aa15d23dd72ee40bea4e48d0cd" "7da9e8e386628a099b1f1e75019059be76c73264deb888959c236f6b776d12f4847e6762d5c76f0f" ) def get_db() -> sqlite3.Connection: DATA_DIR.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(DB_PATH, timeout=30) conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA busy_timeout=30000;") conn.execute("PRAGMA synchronous=NORMAL;") conn.row_factory = sqlite3.Row return conn def init_db() -> None: with get_db() as conn: conn.execute( """ CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ) """ ) conn.execute( """ CREATE TABLE IF NOT EXISTS leads ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL, name TEXT NOT NULL, company TEXT NOT NULL, phone TEXT NOT NULL, email TEXT, city TEXT, computers TEXT NOT NULL, message TEXT ) """ ) for key, value in DEFAULT_SETTINGS.items(): conn.execute( "INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", (key, value), ) conn.commit() def get_settings() -> dict[str, str]: settings = DEFAULT_SETTINGS.copy() with get_db() as conn: rows = conn.execute("SELECT key, value FROM settings").fetchall() for row in rows: settings[row["key"]] = row["value"] return settings def update_settings(new_values: dict[str, str]) -> None: with get_db() as conn: for key, value in new_values.items(): conn.execute( """ INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value """, (key, value), ) conn.commit() def send_telegram_lead(form_data: dict[str, str], settings: dict[str, str]) -> None: token = settings.get("telegram_bot_token", "").strip() chat_id = settings.get("telegram_chat_id", "").strip() if not token or not chat_id: return message = ( "Новая заявка InfraIT\n" f"Имя: {form_data['name']}\n" f"Компания: {form_data['company']}\n" f"Телефон: {form_data['phone']}\n" f"Email: {form_data['email'] or '-'}\n" f"Город: {form_data['city'] or '-'}\n" f"ПК: {form_data['computers']}\n" f"Комментарий: {form_data['message'] or '-'}" ) payload = parse.urlencode({"chat_id": chat_id, "text": message}).encode("utf-8") req = urllib_request.Request( f"https://api.telegram.org/bot{token}/sendMessage", data=payload, method="POST", ) try: urllib_request.urlopen(req, timeout=8).read() except Exception: # Ошибка телеграм-уведомления не должна ломать отправку формы. return def save_lead(form_data: dict[str, str], settings: dict[str, str]) -> None: with get_db() as conn: conn.execute( """ INSERT INTO leads ( created_at, name, company, phone, email, city, computers, message ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( datetime.now().isoformat(timespec="seconds"), form_data["name"], form_data["company"], form_data["phone"], form_data["email"], form_data["city"], form_data["computers"], form_data["message"], ), ) conn.commit() send_telegram_lead(form_data, settings) def admin_required(view): @wraps(view) def wrapped(*args, **kwargs): if not session.get("admin_logged_in"): return redirect(url_for("admin_login")) return view(*args, **kwargs) return wrapped def verify_admin_password(password: str) -> bool: return check_password_hash(ADMIN_PASSWORD_HASH, password) init_db() @app.route("/", methods=["GET", "POST"]) def index(): success = request.args.get("success") == "1" error = None settings = get_settings() if request.method == "POST": form_data = { "name": request.form.get("name", "").strip(), "company": request.form.get("company", "").strip(), "phone": request.form.get("phone", "").strip(), "email": request.form.get("email", "").strip(), "city": request.form.get("city", "").strip(), "computers": request.form.get("computers", "").strip(), "message": request.form.get("message", "").strip(), } required_fields = ["name", "phone", "company", "computers"] if any(not form_data[field] for field in required_fields): error = "Заполните обязательные поля: имя, компания, телефон и количество компьютеров." else: save_lead(form_data, settings) return redirect(url_for("index", success=1) + "#contact") return render_template("index.html", success=success, error=error, settings=settings) @app.route("/admin/login", methods=["GET", "POST"]) def admin_login(): error = None if request.method == "POST": password = request.form.get("password", "") if verify_admin_password(password): session["admin_logged_in"] = True return redirect(url_for("admin_settings")) error = "Неверный пароль." return render_template("admin_login.html", error=error) @app.route("/admin/logout") def admin_logout(): session.pop("admin_logged_in", None) return redirect(url_for("admin_login")) @app.route("/admin/settings", methods=["GET", "POST"]) @admin_required def admin_settings(): success = request.args.get("saved") == "1" settings = get_settings() if request.method == "POST": updates: dict[str, str] = {} for field in DEFAULT_SETTINGS: updates[field] = request.form.get(field, "").strip() update_settings(updates) return redirect(url_for("admin_settings", saved=1)) return render_template("admin_settings.html", settings=settings, success=success) @app.route("/health") def health(): try: with get_db() as conn: conn.execute("SELECT 1") return {"status": "ok"}, 200 except Exception: return {"status": "error"}, 503 if __name__ == "__main__": init_db() app.run(debug=True)