Техническая документация — Платформа визиток МФТИ ВШПИ

Содержание

1. Обзор системы

2. Архитектура

3. Структура проекта

4. Установка и запуск

5. Переменные окружения

6. База данных

7. API и маршруты

8. Модули приложения

9. Безопасность

10. Требования к визитке

11. Миграции БД


1. Обзор системы

Платформа визиток студентов МФТИ ВШПИ — веб-приложение на Python/Flask, позволяющее студентам Высшей школы программной инженерии МФТИ публиковать персональные HTML-визитки.

Ключевые возможности:

Технологический стек:


2. Архитектура

Приложение построено по паттерну Application Factory с разделением на три Blueprint-а.


┌─────────────────────────────────────────────────────────┐
│                    Flask Application                    │
│                                                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐ │
│  │    auth     │  │    cards    │  │      admin      │ │
│  │  Blueprint  │  │  Blueprint  │  │    Blueprint    │ │
│  │             │  │             │  │                 │ │
│  │ /register   │  │ /           │  │ /admin/         │ │
│  │ /login      │  │ /submit     │  │ /admin/users    │ │
│  │ /logout     │  │ /cards/<s>  │  │ /admin/submiss. │ │
│  │ /verify-*   │  │ /vote       │  │                 │ │
│  └─────────────┘  └─────────────┘  └─────────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │              SQLAlchemy ORM                     │    │
│  │   User | Submission | BusinessCard | Vote       │    │
│  └─────────────────────────────────────────────────┘    │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │  sanitizer   │  │  validators  │  │    mailer    │  │
│  │  (bleach)    │  │  (bs4)       │  │  (smtplib)   │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘
                          │
              ┌───────────┴───────────┐
              │       SQLite DB       │
              └───────────────────────┘

3. Структура проекта


mipt_cards/
├── app/
│   ├── __init__.py              # Фабрика create_app(), загрузка .env
│   ├── models.py                # ORM-модели: User, Submission, BusinessCard, Vote
│   ├── sanitizer.py             # HTML-санитайзер на базе bleach
│   ├── validators.py            # Проверка структуры HTML-визитки
│   ├── mailer.py                # Отправка email через SMTP
│   ├── auth/
│   │   ├── __init__.py          # Blueprint регистрации
│   │   └── routes.py            # /register, /login, /logout, /verify-email
│   ├── cards/
│   │   ├── __init__.py          # Blueprint визиток
│   │   └── routes.py            # /, /submit, /cards/<slug>, /vote, /delete
│   ├── admin/
│   │   ├── __init__.py          # Blueprint администратора
│   │   └── routes.py            # /admin/, /admin/users, /admin/submissions/
│   └── templates/
│       ├── base.html            # Базовый шаблон с навигацией
│       ├── index.html           # Главная страница
│       ├── auth/
│       │   ├── login.html
│       │   ├── register.html
│       │   └── verify_email.html
│       ├── cards/
│       │   ├── submit.html      # Форма загрузки визитки
│       │   └── view.html        # Просмотр визитки (fullscreen iframe)
│       └── admin/
│           ├── index.html       # Список заявок на модерацию
│           ├── submission.html  # Просмотр и обработка заявки
│           └── users.html       # Управление пользователями
├── static/
│   ├── css/
│   │   └── main.css             # Стили (VSHPI Neon Glass дизайн)
│   └── js/
│       └── vote.js              # AJAX-логика голосования
├── uploads/
│   ├── pending/                 # Загруженные файлы до модерации
│   └── approved/                # Санитизированные одобренные файлы
├── config.py                    # Конфигурация приложения
├── run.py                       # Точка входа
├── create_admin.py              # Скрипт создания первого администратора
├── migrate_add_deleted_at.py    # Миграция: поле deleted_at
├── migrate_admin_fields.py      # Миграция: поля deleted_by_admin, admin_message_until
├── requirements.txt
├── .env                         # Переменные окружения (не коммитить!)
└── cards.db                     # SQLite база данных

4. Установка и запуск

Требования

Шаги установки


# 1. Перейти в директорию проекта
cd mipt_cards

# 2. Создать виртуальное окружение (рекомендуется)
python -m venv .venv
.venv\Scripts\activate        # Windows
source .venv/bin/activate     # Linux/macOS

# 3. Установить зависимости
pip install -r requirements.txt

# 4. Создать файл .env с настройками (см. раздел 5)

# 5. Применить миграции (если обновляете существующую БД)
python migrate_add_deleted_at.py
python migrate_admin_fields.py

# 6. Создать первого администратора
python create_admin.py

# 7. Запустить сервер
python run.py

Приложение будет доступно по адресу: http://localhost:5000


5. Переменные окружения

Все переменные задаются в файле .env в корне директории mipt_cards/.

ПеременнаяОбязательнаяПо умолчаниюОписание
SECRET_KEYДа (в prod)change-me-in-production-pleaseКлюч подписи сессий и CSRF-токенов
DATABASE_URLНетsqlite:///cards.dbURI базы данных
FLASK_ENVНетdevelopmentРежим запуска (development / production)
MAIL_SERVERДаsmtp.gmail.comSMTP-сервер
MAIL_PORTДа587Порт SMTP
MAIL_USE_TLSНетtrueИспользовать STARTTLS (порт 587)
MAIL_USE_SSLНетfalseИспользовать SSL (порт 465, mail.ru)
MAIL_USERNAMEДаЛогин SMTP-аккаунта
MAIL_PASSWORDДаПароль SMTP-аккаунта
MAIL_FROMНет= MAIL_USERNAMEАдрес отправителя
EMAIL_CODE_TTLНет600Время жизни кода подтверждения (секунды)

Пример .env для mail.ru


SECRET_KEY=your-very-secret-key-here
MAIL_SERVER=smtp.mail.ru
MAIL_PORT=465
MAIL_USE_TLS=False
MAIL_USE_SSL=True
MAIL_USERNAME=your@mail.ru
MAIL_PASSWORD=your_app_password
MAIL_FROM=your@mail.ru

Пример .env для Gmail


SECRET_KEY=your-very-secret-key-here
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
MAIL_USE_TLS=True
MAIL_USE_SSL=False
MAIL_USERNAME=your@gmail.com
MAIL_PASSWORD=your_app_password
MAIL_FROM=your@gmail.com

> Важно: Для Gmail используйте App Password (пароль приложения), а не основной пароль аккаунта.


6. База данных

Схема

Таблица users

ПолеТипОписание
idINTEGER PKИдентификатор
usernameVARCHAR(64) UNIQUEИмя пользователя (латиница, цифры, _, -)
emailVARCHAR(120) UNIQUEEmail в формате *@phystech.edu
password_hashVARCHAR(128)bcrypt-хэш пароля
is_adminBOOLEANФлаг администратора
created_atDATETIMEДата регистрации (UTC)

Таблица submissions

ПолеТипОписание
idINTEGER PKИдентификатор
user_idINTEGER FK→usersАвтор заявки
filenameVARCHAR(256)UUID-имя файла на диске
original_filenameVARCHAR(256)Оригинальное имя файла
statusVARCHAR(16)pending / approved / rejected
rejection_reasonTEXTПричина отклонения (если есть)
submitted_atDATETIMEДата подачи (UTC)
reviewed_atDATETIMEДата рассмотрения (UTC)
reviewed_byINTEGER FK→usersАдминистратор, рассмотревший заявку

Таблица business_cards

ПолеТипОписание
idINTEGER PKИдентификатор
user_idINTEGER FK→users UNIQUEВладелец (один пользователь — одна карточка)
submission_idINTEGER FK→submissionsЗаявка, из которой создана карточка
slugVARCHAR(64) UNIQUEURL-идентификатор (= username)
filenameVARCHAR(256)Путь к санитизированному HTML-файлу
approved_atDATETIMEДата одобрения (UTC)
likes_countINTEGERДенормализованный счётчик лайков
dislikes_countINTEGERДенормализованный счётчик дизлайков
deleted_atDATETIMEДата мягкого удаления (NULL = активна)
deleted_by_adminBOOLEANУдалена администратором
admin_message_untilDATETIMEДо какого времени показывать сообщение об удалении

Таблица votes

ПолеТипОписание
idINTEGER PKИдентификатор
voter_idINTEGER FK→usersГолосующий
card_idINTEGER FK→business_cardsВизитка
vote_typeVARCHAR(8)like / dislike
created_atDATETIMEДата голоса (UTC)

Ограничение: UNIQUE(voter_id, card_id) — один пользователь, один голос на визитку.

ER-диаграмма


users ──────────────── submissions
  │  1                    │ *
  │                       │
  │ 1                     │ 1
  └──── business_cards ───┘
              │ 1
              │
              │ *
            votes
              │ *
              │
              │ 1
            users

7. API и маршруты

Публичные маршруты

МетодURLОписание
GET/Главная страница со списком визиток
GET/cards/<slug>Просмотр визитки
GET/cards/<slug>/rawСырой HTML визитки (для iframe)
GET/loginФорма входа
POST/loginОбработка входа
GET/registerФорма регистрации
POST/registerОбработка регистрации, отправка кода
GET/verify-emailФорма ввода кода подтверждения
POST/verify-emailПроверка кода, создание аккаунта
POST/verify-email/resendПовторная отправка кода

Маршруты для авторизованных пользователей

МетодURLОписание
GET/logoutВыход из системы
GET/submitФорма загрузки/обновления визитки
POST/submitЗагрузка HTML-файла
POST/cards/<slug>/voteЛайк/дизлайк (AJAX, JSON)
POST/cards/<slug>/deleteМягкое удаление визитки

Маршруты администратора

МетодURLОписание
GET/admin/Список заявок на модерацию
GET/admin/submissions/<id>Просмотр заявки с предпросмотром
POST/admin/submissions/<id>/approveОдобрить заявку
POST/admin/submissions/<id>/rejectОтклонить заявку с причиной
GET/admin/usersСписок всех пользователей
POST/admin/users/<id>/toggle-adminВыдать/отозвать права администратора

AJAX API: голосование

POST /cards/<slug>/vote

Request (JSON):


{ "vote_type": "like" }

Response (JSON):


{
  "action": "added",
  "vote_type": "like",
  "likes": 5,
  "dislikes": 1,
  "user_vote": "like"
}

Возможные значения action: added, removed, changed.

Возможные значения vote_type: like, dislike.

user_vote: null означает что голос отменён.

HTTP-коды ответов: 200 OK, 401 Unauthorized, 403 Forbidden, 404 Not Found.


8. Модули приложения

app/__init__.py — фабрика приложения

Функция create_app(config_name):

1. Загружает .env файл до импорта конфига

2. Создаёт Flask-приложение

3. Перечитывает email-настройки из os.environ (обход кэширования)

4. Инициализирует расширения: SQLAlchemy, Flask-Login, CSRFProtect

5. Регистрирует blueprints: auth, cards, admin

6. Создаёт таблицы БД через db.create_all()

7. Устанавливает заголовки безопасности через after_request

app/models.py — модели данных

app/sanitizer.py — HTML-санитайзер

Функция sanitize_html(html_content):

1. Удаляет теги <script>, <iframe>, <object>, <embed>, <noscript>, <base> вместе с содержимым

2. Удаляет атрибуты обработчиков событий (on*)

3. Удаляет javascript: в href/src

4. Применяет bleach с белым списком тегов и атрибутов

5. Вставляет <base target="_blank"> для открытия ссылок в новой вкладке

app/validators.py — валидатор визитки

Функция validate_card_html(html_content)(is_valid: bool, errors: list[str]):

Проверяет:

app/mailer.py — отправка email

Функция send_verification_email(to_email, username, code)bool:

app/auth/routes.py — авторизация

Процесс регистрации с подтверждением email:

1. Пользователь заполняет форму → данные сохраняются в Flask-сессии

2. Пароль хэшируется сразу (не хранится в открытом виде)

3. Генерируется 6-значный цифровой код, отправляется на email

4. Пользователь вводит код на /verify-email

5. Защита от брутфорса: после 5 неверных попыток сессия сбрасывается

6. Код действителен 10 минут (настраивается через EMAIL_CODE_TTL)


9. Безопасность

Аутентификация и авторизация

Изоляция контента визиток

HTTP-заголовки безопасности


X-Content-Type-Options: nosniff
X-Frame-Options: DENY  (кроме /cards/<slug>/raw)
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self'; img-src 'self' data: https:;

Валидация загружаемых файлов


10. Требования к визитке

HTML-файл визитки студента МФТИ ВШПИ должен соответствовать следующим требованиям:

#ТребованиеПроверка
1Валидный HTML5-документПарсинг через BeautifulSoup4
2Тег <title> с ФИО студентаНаличие непустого <title>
3Фотография <img> с атрибутом altНаличие <img alt="...">
4Раздел «О себе» (≥ 50 символов)Заголовок содержит «о себе» / «обо мне»
5Раздел «Учёба» с курсом, группой, направлением МФТИ ВШПИЗаголовок содержит «учёба» / «образование»
6Раздел «Навыки» (≥ 3 технических навыка)Заголовок содержит «навыки» / «skills»
7Контактная информация (email или соцсеть)Regex email или ссылка на VK/Telegram/GitHub/LinkedIn
8Без <script> и обработчиков событий (on*)Regex-поиск
9Без <iframe>, <object>, <embed>Поиск тегов

Ограничения файла: только .html, максимум 512 КБ.


11. Миграции БД

При обновлении существующей базы данных необходимо применить миграции:


# Добавляет поле deleted_at в business_cards
python migrate_add_deleted_at.py

# Добавляет поля deleted_by_admin и admin_message_until в business_cards
python migrate_admin_fields.py

Для новых установок миграции не нужны — db.create_all() создаёт все таблицы автоматически при первом запуске.


Лицензия

MIT License — свободное использование, модификация и распространение.