# server.py
import re
import json
from datetime import datetime
from pathlib import Path
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from typing import Optional
# ==================== КОНФИГУРАЦИЯ ====================
HOST = "0.0.0.0"
PORT = 8000
DEBUG = True
BASE_DIR = Path(__file__).parent
TEMPLATES_DIR = BASE_DIR / "templates"
STATIC_DIR = BASE_DIR / "static"
DATA_DIR = BASE_DIR / "data"
# Создаем необходимые папки
TEMPLATES_DIR.mkdir(exist_ok=True)
STATIC_DIR.mkdir(exist_ok=True)
DATA_DIR.mkdir(exist_ok=True)
# Создаем подпапки для статики
(STATIC_DIR / "css").mkdir(exist_ok=True)
(STATIC_DIR / "js").mkdir(exist_ok=True)
# ==================== ИНИЦИАЛИЗАЦИЯ FASTAPI ====================
app = FastAPI(title="БС Патриот - Регистрация на круглый стол", debug=DEBUG)
# Подключаем статические файлы
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
# ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
def save_registration_to_file(data: dict) -> dict:
"""Сохраняет заявку в JSON-файл"""
filepath = DATA_DIR / "registrations.json"
registrations = []
if filepath.exists():
try:
with open(filepath, 'r', encoding='utf-8') as f:
registrations = json.load(f)
except (json.JSONDecodeError, IOError):
registrations = []
new_record = {
"id": len(registrations) + 1,
"created_at": datetime.now().isoformat(),
"fullname": data.get("fullname"),
"email": data.get("email"),
"phone": data.get("phone"),
"company": data.get("company"),
"businessSize": data.get("businessSize", ""),
"message": data.get("message", ""),
"status": "pending"
}
registrations.append(new_record)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(registrations, f, ensure_ascii=False, indent=2)
return new_record
def get_html_content() -> str:
"""Читает HTML файл или возвращает fallback"""
html_path = TEMPLATES_DIR / "index.html"
if html_path.exists():
with open(html_path, 'r', encoding='utf-8') as f:
return f.read()
else:
return get_fallback_html()
def get_fallback_html() -> str:
"""Fallback HTML если файл шаблона не найден"""
return """
Круглый стол | БС «Патриот»
Закрытый клуб предпринимателей
Круглый стол с бизнес-сообществом «Патриот»
26 апреля 2026, начало в 11:00
Вход по рекомендации
"""
# ==================== МАРШРУТЫ ====================
@app.get("/", response_class=HTMLResponse)
async def get_landing_page():
"""Главная страница - посадочный лендинг мероприятия"""
return HTMLResponse(content=get_html_content(), status_code=200)
@app.post("/api/register")
async def register(
fullname: str = Form(...),
email: str = Form(...),
phone: str = Form(...),
company: str = Form(...),
businessSize: Optional[str] = Form(None),
message: Optional[str] = Form(None),
agree: Optional[str] = Form(None)
):
"""API-эндпоинт для регистрации участников"""
print(f"Received data: fullname={fullname}, email={email}, phone={phone}, company={company}, agree={agree}")
# Валидация
if not agree or agree != "true":
return JSONResponse(
status_code=400,
content={"success": False, "message": "Необходимо согласие с условиями"}
)
if not fullname or len(fullname.strip()) < 2:
return JSONResponse(
status_code=400,
content={"success": False, "message": "Введите корректное ФИО"}
)
email_pattern = r'^[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,}$'
if not re.match(email_pattern, email):
return JSONResponse(
status_code=400,
content={"success": False, "message": "Введите корректный email"}
)
digits_only = re.sub(r'\D', '', phone)
if len(digits_only) < 10:
return JSONResponse(
status_code=400,
content={"success": False, "message": "Введите корректный номер телефона (минимум 10 цифр)"}
)
if not company or len(company.strip()) < 1:
return JSONResponse(
status_code=400,
content={"success": False, "message": "Укажите название компании"}
)
data = {
"fullname": fullname.strip(),
"email": email.strip().lower(),
"phone": phone.strip(),
"company": company.strip(),
"businessSize": businessSize if businessSize else "",
"message": message.strip() if message else "",
}
try:
saved = save_registration_to_file(data)
print(f"Saved registration: {saved}")
return JSONResponse(content={
"success": True,
"message": "Заявка успешно отправлена! Менеджер сообщества свяжется с вами в ближайшее время."
})
except Exception as e:
print(f"Error saving registration: {e}")
return JSONResponse(
status_code=500,
content={"success": False, "message": f"Ошибка сервера: {str(e)}"}
)
@app.get("/admin/registrations", response_class=HTMLResponse)
async def admin_registrations():
"""Админка для просмотра заявок"""
filepath = DATA_DIR / "registrations.json"
registrations = []
if filepath.exists():
try:
with open(filepath, 'r', encoding='utf-8') as f:
registrations = json.load(f)
except:
registrations = []
registrations.sort(key=lambda x: x.get("created_at", ""), reverse=True)
# Используем обычное форматирование строки без .format()
html = f"""
Админка | Заявки на круглый стол
📋 Заявки на круглый стол «Патриот»
📊 Всего заявок: {len(registrations)}
| ID |
Дата |
ФИО |
Email |
Телефон |
Компания |
Оборот |
Сообщение |
Статус |
"""
for reg in registrations:
created = reg.get("created_at", "")[:16].replace("T", " ")
status_class = "status-pending" if reg.get('status') == 'pending' else ""
message_text = reg.get('message', '—')[:100]
if len(reg.get('message', '')) > 100:
message_text += "..."
html += f"""
| {reg.get('id', '—')} |
{created} |
{reg.get('fullname', '—')} |
{reg.get('email', '—')} |
{reg.get('phone', '—')} |
{reg.get('company', '—')} |
{reg.get('businessSize', '—')} |
{message_text} |
{reg.get('status', 'pending')} |
"""
html += """
← Вернуться на главную
"""
return HTMLResponse(content=html, status_code=200)
@app.get("/health")
async def health_check():
"""Health check для хостинга"""
return {"status": "ok", "timestamp": datetime.now().isoformat()}
# ==================== ЗАПУСК ====================
if __name__ == "__main__":
import uvicorn
print("=" * 60)
print("🚀 Сервер для посадочной страницы БС «Патриот»")
print(f"📁 Шаблоны: {TEMPLATES_DIR}")
print(f"📁 Статика: {STATIC_DIR}")
print(f"📁 Данные: {DATA_DIR}")
print("=" * 60)
print(f"✨ Главная страница: http://localhost:{PORT}")
print(f"🔧 Админка: http://localhost:{PORT}/admin/registrations")
print("=" * 60)
print("💡 Для остановки сервера нажмите Ctrl+C")
print("=" * 60)
uvicorn.run(
app,
host=HOST,
port=PORT,
log_level="info"
)