No description
  • Python 66.8%
  • JavaScript 12.2%
  • HTML 10.9%
  • CSS 9.2%
  • Dockerfile 0.7%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
kaster b60e9e4061
All checks were successful
/ deploy (push) Successful in 39s
fix tags in admin
2026-09-05 03:08:37 +03:00
.forgejo/workflows CI: диагностика ловит подмену символов в DEPLOY_KEY 2026-09-05 01:59:07 +03:00
app fix tags in admin 2026-09-05 03:08:37 +03:00
deploy global refactoring 2026-08-17 23:32:20 +03:00
.dockerignore global refactoring 2026-08-17 23:32:20 +03:00
.gitignore add logs 2026-07-19 17:19:17 +03:00
ARCHITECTURE.md tags 2026-09-04 01:43:16 +03:00
README.md global refactoring 2026-08-17 23:32:20 +03:00

frames of entropy — галерея фотографа

Flask + PostgreSQL + PhotoSwipe. Фотографии, папки, тексты страниц и палитра сайта редактируются в админке; в файловой системе лежат только сами изображения.

Локальный запуск

cd app
python3 -m venv venv && source venv/bin/activate   # Windows: venv\Scripts\activate
pip install -r requirements-dev.txt

# нужна работающая PostgreSQL (или задайте свои POSTGRES_*)
export POSTGRES_PASSWORD=...
export ADMIN_PASSWORD=...
export SECRET_KEY=$(openssl rand -hex 32)

flask --app app db-bootstrap    # накатывает миграции
python app.py                   # http://127.0.0.1:5000, админка — /admin

Без ADMIN_PASSWORD админка закрыта полностью — приложение предупреждает об этом в лог при старте.

Тесты

cd app && venv/bin/python -m pytest

Тесты поднимают отдельный экземпляр приложения на SQLite в памяти и временных каталогах — ни базы, ни Docker не требуется.

Как устроен сайт

Публичная часть. Главная — горизонтальный drill-down по папкам: карточка папки с четырьмя обложками, клик — уезд на уровень вглубь. Адрес отражает путь (/cars/sport), поэтому ссылка на конкретную папку и кнопка «назад» в браузере работают. Ниже папок — лента фото самой папки и всех вложенных, догружается порциями при прокрутке.

Что видно кому. У папки и у страницы есть два независимых флага:

Флаг Эффект
«Скрыт» папка и всё внутри неё исчезают из дерева на главной
«Доступ по временной ссылке» генерирует /album/<token> (для страниц — /page-link/<token>), который работает вне зависимости от флага «скрыт»

Админка (/admin, HTTP basic auth):

  • Фото (папки) — основной экран: дерево папок, drag-n-drop загрузка с описаниями, перетаскивание для ручного порядка, сортировка по имени/дате, флаги видимости.
  • Фото (таблица) — плоский список для точечных правок.
  • Настройки сайта — тексты шапки/подвала, палитра, идентификатор Google Analytics.
  • Страницы — подстраницы в Markdown, попадают в меню автоматически.
  • Логи — хвост текущего лога и скачивание архивов после ротации.

Изображения

Оригинал сохраняется под uuid-именем в static/photos/ — исходное имя остаётся только как подпись (alt). Миниатюра (до 600 px по большей стороне, JPEG) создаётся при загрузке и лениво восстанавливается при запросе /thumbs/<файл>, если её нет: том с миниатюрами не персистентный, и после пересоздания контейнера они отстраиваются заново по мере обращений. Прогреть их заранее — flask --app app rebuild-thumbs.

CLI

Команда Назначение
flask --app app db-bootstrap накатывает миграции; умеет «усыновить» старую БД без Alembic-истории. Идемпотентна, гоняется при каждом старте контейнера
flask --app app import-photos заносит в БД файлы, уже лежащие в static/photos/ (подпапка = папка-категория)
flask --app app rebuild-thumbs прогревает миниатюры для всех фото

Деплой

Push в master запускает .forgejo/workflows/deploy.yml: код синкается в /opt/photo-gallery-src/src, секреты пишутся в /opt/photo-gallery-src/deploy/secrets, далее docker compose up -d --build. Код и постоянные данные лежат в разных директориях, чтобы синк никогда не задел секреты и тома.

Стек — два контейнера (app на gunicorn + postgres:17-alpine), наружу торчит только 127.0.0.1:8001, перед ним nginx. Пароли передаются docker-секретами (файлами), не через environment. Подробности — в комментариях deploy/docker-compose.yml.

Внутреннее устройство кода описано в ARCHITECTURE.md.