Files
ds-galo/README.md
iu 2b2b342492 feat: implement shared-album multi-user photo album
Backend (Go/Gin/pgx): env config, pgx pool with retries, Garage S3 client
(path-style, region garage), embedded golang-migrate migrations, JWT auth with
refresh rotation, albums CRUD, members/roles/invites with no-escalation matrix,
photo upload + async imaging/goexif WebP variants, cursor pagination, presigned
URLs, likes/comments, SSE hub (album + personal), notifications, share links.

Frontend (SvelteKit 2 + Svelte 5 + Tailwind v4): API wrapper with 401 refresh,
auth store, all screens, Uploader/PhotoGrid/Lightbox/Comments, SSE client with
reconnect + REST catch-up, three themes + system via data-theme.

Deploy without Docker: Makefile builds the frontend into backend/static and
embeds it (go:embed) into a single statically linked binary; systemd unit and
nginx SSE example included.

Use gen2brain/webp (-tags nodynamic) instead of cgo kolesa-team/go-webp to keep
CGO_ENABLED=0 static builds; design.md/proposal.md updated accordingly.
2026-10-10 12:50:33 +03:00

5.9 KiB

Galo — многопользовательский фотоальбом

Self-hosted веб-приложение: общие фотоальбомы с ролями, загрузкой фото в S3, live-обновлениями через SSE и доступом по ссылке без аккаунта.

Без Docker: один статичный Go-бинарник (CGO_ENABLED=0) со вшитой статикой SvelteKit через go:embed, запуск через systemd.

Стек

  • Backend: Go, Gin, pgx (PostgreSQL на pg.localnet), AWS SDK v2 (Garage на s3.localnet, path-style), нативный SSE, golang-migrate, slog.
  • Frontend: SvelteKit 2 + Svelte 5, adapter-static, TypeScript, TailwindCSS.
  • Обработка изображений: disintegration/imaging + gen2brain/webp + rwcarlsen/goexif (всё чистый Go).

Требования

  • Go 1.22+ (сборка проверена на 1.26)
  • Node.js 20+ (только для сборки фронтенда)
  • Доступ к pg.localnet:5432 (PostgreSQL) и s3.localnet:3900 (Garage)
  • make

Конфигурация

Скопируйте .env.example в .env и заполните значения:

cp .env.example .env

Ключевые переменные: PG_*, S3_* (обязательны UsePathStyle=true и регион garage), JWT_SECRET, JWT_ACCESS_TTL, JWT_REFRESH_TTL, HTTP_ADDR, SSE_HEARTBEAT_INTERVAL, SSE_CLIENT_BUFFER.

Сборка

make build      # frontend -> backend/static -> go:embed -> bin/shared-album

Отдельные шаги:

make frontend   # npm ci && npm run build, копирует статику в backend/static
make backend    # CGO_ENABLED=0 go build -tags nodynamic -o bin/shared-album

-tags nodynamic обязателен: он заставляет gen2brain/webp использовать транспилированный в Go libwebp вместо динамической загрузки через purego, благодаря чему бинарник остаётся полностью статическим.

Проверка:

file bin/shared-album     # => statically linked

Миграции

Миграции встроены в бинарник и применяются автоматически при старте сервера. Отдельно:

make migrate    # go run ./cmd/migrate — применяет up-миграции к pg.localnet

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

make run        # соберёт backend и запустит ./bin/shared-album
# или напрямую:
./bin/shared-album

Сервер слушает HTTP_ADDR (по умолчанию :8080). Health-check:

curl -s localhost:8080/api/health      # {"status":"ok","db":"ok","s3":"ok"}

Разработка фронтенда с HMR:

cd frontend && npm run dev             # Vite проксирует /api на :8080

Тесты

make test
# интеграционные тесты репозиториев требуют тестовую БД:
TEST_DATABASE_URL='postgres://user:pass@pg.localnet:5432/galo?sslmode=disable' \
  cd backend && go test ./internal/repo/ -v

Деплой через systemd

  1. Соберите бинарник: make build.

  2. Создайте пользователя и каталог:

    sudo useradd --system --home /opt/shared-album album
    sudo mkdir -p /opt/shared-album
    sudo cp bin/shared-album /opt/shared-album/
    sudo cp .env /opt/shared-album/.env
    sudo chown -R album:album /opt/shared-album
    
  3. Установите unit:

    sudo cp deploy/shared-album.service /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now shared-album
    sudo journalctl -u shared-album -f
    

nginx (опционально)

Пример в deploy/nginx-sse.conf. Для SSE критично proxy_buffering off и X-Accel-Buffering: no (последнее сервер выставляет сам).

Структура

backend/
  cmd/server/            # точка входа
  cmd/migrate/           # применение миграций
  internal/config        # чтение env
  internal/db            # pgx + golang-migrate
  internal/storage       # S3 (Garage)
  internal/auth          # JWT, bcrypt, refresh
  internal/albums        # альбомы
  internal/members       # участники, роли, инвайты
  internal/photos        # загрузка, варианты, presigned URL
  internal/social        # лайки, комментарии
  internal/notifications # персональные уведомления
  internal/sharelinks    # ссылки для просмотра
  internal/sse           # SSE-хаб
  internal/app           # маршруты и статика
  migrations/            # SQL-миграции
  static/                # сюда копируется build SvelteKit
frontend/                # SvelteKit 2 + Svelte 5
deploy/                  # systemd unit, пример nginx

API

Полный список эндпоинтов: project.md, раздел 11. Кратко:

  • POST /api/auth/register|login|refresh|logout, GET /api/auth/me
  • GET|POST /api/albums, GET|PATCH|DELETE /api/albums/:id
  • GET /api/albums/:id/members, POST /api/albums/:id/invites, POST /api/invites/:token/accept, PATCH|DELETE /api/albums/:id/members/:user_id
  • GET|POST /api/albums/:id/photos, GET|DELETE /api/photos/:id, GET /api/photos/:id/download
  • POST|DELETE /api/photos/:id/likes, GET|POST /api/photos/:id/comments, DELETE /api/comments/:id
  • GET /api/sse/albums/:id, GET /api/sse/me
  • POST|GET /api/albums/:id/share, DELETE /api/albums/:id/share/:link_id, GET /api/share/:token[/photos[/:photo_id[/download]]]
  • GET /api/notifications, POST /api/notifications/:id/read