diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..09ae7cf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,126 @@ +# AGENTS.md + +Инструкции для агентов, работающих в этом репозитории. Читай вместе с `README.md` +(актуальный источник правды) и `project.md` (исходное ТЗ). + +## Что это + +Self-hosted фотоальбом: Go-бэкенд с вшитой через `go:embed` статикой SvelteKit, +один статический бинарник (`CGO_ENABLED=0`), без Docker. Плюс отдельный Android-клиент. + +По умолчанию (`DB_BACKEND=sqlite`, `STORAGE_BACKEND=fs`) работает полностью локально, +без Postgres и S3. Postgres/Garage (`pg.localnet`, `s3.localnet`) — опциональные слои. + +## Команды + +Все команды — из корня репозитория. Голый `make` только печатает список целей. + +```bash +make build # frontend -> backend/static -> go:embed -> bin/galo +make frontend # npm run build (инкрементально), копирует статику в backend/static +make backend # CGO_ENABLED=0 go build -tags nodynamic -o ../bin/galo +make run # frontend + backend + запуск ./bin/galo +make migrate # применяет только Postgres-миграции (cmd/migrate) +make test # cd backend && go test ./... (только бэкенд!) +make testdata # наполняет запущенный инстанс через scripts/seed.py +make android # cd android && ./gradlew :app:assembleDebug +make install-android-app # сборка + установка debug-APK на устройство +``` + +- `-tags nodynamic` обязателен: без него `gen2brain/webp` тянет динамический libwebp + и бинарник перестаёт быть статическим. +- Порядок сборки важен: `frontend` должен отработать до `backend`, иначе `go:embed + all:static` вшивает только `backend/static/.gitkeep` и бинарник выходит без UI. +- `make test` не запускает фронтенд-тесты; отдельных make-целей для них нет: + `cd frontend && npm run test` (vitest, окружение `node`, файлы `src/**/*.test.ts`) и + `cd frontend && npm run check` (svelte-check/typecheck). +- Один Go-тест: `cd backend && go test ./internal/photos/ -run TestName -v`. +- Интеграционные тесты Postgres-репозиториев пропускаются без `TEST_DATABASE_URL`: + `TEST_DATABASE_URL='postgres://user:pass@pg.localnet:5432/galo?sslmode=disable' \ + cd backend && go test ./internal/repo/ -v` +- Тесты видео (`internal/e2e`) сами пропускаются, если нет `ffmpeg`/`ffprobe`. + Тесты e2e репозиториев поднимают SQLite в `t.TempDir()` и внешних сервисов не требуют. + +## Тесты на эмуляторе + +Instrumented-прогоны (`:app:connectedDebugAndroidTest`) и сам запуск AVD **могут зависнуть** — +не дожидайся их молча. Пока идёт прогон, проверяй прогресс **примерно раз в минуту**: жив ли +процесс, обновляются ли результаты, отвечает ли устройство: + +```bash +pgrep -fa "GradleWrapperMain|Gradle Test Executor" +ls -la android/app/build/outputs/androidTest-results/connected/debug/ +adb devices +``` + +Если прогресса нет дольше пары минут — снимай прогон (`kill`), а не жди дальше, и разбирайся +по дампу потоков (`jstack ` JVM тестов). То же относится к обычным unit-тестам: они тоже +идут в отдельной JVM, и её зависание видно только по тому, что результаты перестали +обновляться. + +## Архитектура + +- Go-модуль `galo` (`backend/go.mod`, Go 1.26). Точки входа: `backend/cmd/server`, + `cmd/migrate`, `cmd/reconcile`, `cmd/backfill-exif`. +- Доменные пакеты в `backend/internal/` (albums, auth, members, photos, social, + notifications, sharelinks, storage, sse, settings, video). Каждый содержит сервис, + HTTP-обработчики и `Repository`-интерфейс. +- `internal/store/store.go` — единственное место, которое знает конкретные реализации. + При добавлении метода в `Repository` нужно реализовать его в **обоих** вариантах: + Postgres (`NewStore/NewPostgres`) и SQLite (`NewSQLiteStore/NewSQLite`). +- `backend/internal/app` — маршруты/статика; `internal/repo/contract` — контрактные тесты, + которые гоняют одинаковый набор проверок для SQLite и Postgres. +- `backend/embed.go` вшивает `static` (фронтенд) и `migrations/**`. Паттерны эмбеда — + `migrations/*.sql migrations/sqlite/*.sql`: новые файлы должны им соответствовать. +- Права проверяются на уровне сервиса, не только в UI. Блобы наружу отдаются только + через бэкенд: `GET /api/objects/` с HMAC-подписью, один маршрут для авторизованных + и для share-ссылок. Ключи объектов непрозрачны: `objects/{object_id}/{role}`. +- SSE (`/api/sse/...`) авторизуется через `?token=`, т.к. `EventSource` не шлёт заголовки. + Для nginx критичен `proxy_buffering off`. +- `android/` — самостоятельный Gradle-модуль, серверный и веб-код не трогает; использует + уже существующий HTTP API. Тесты: `cd android && ./gradlew test`. + +## Миграции (частый источник ошибок) + +Схема поддерживается в **двух диалектах** с независимой нумерацией: + +- Postgres: `backend/migrations/NNNNNN_*.{up,down}.sql` +- SQLite: `backend/migrations/sqlite/NNNNNN_*.{up,down}.sql` + +Любое изменение схемы нужно добавить в оба каталога, номера версий не обязаны совпадать +(сейчас они разошлись). Миграции вшиты в бинарник и применяются автоматически при старте +сервера для выбранного бэкенда. `make migrate` применяет только Postgres. +`cmd/reconcile` существует для сверки хранилища с журналом `objects` (сборка мусора +журналом управляется и хранилище не сканирует). + +## Конфигурация и данные + +- `.env` читается вручную в `cmd/*/main.go` из `.env`, затем `../.env` (без dotenv-библиотеки). +- Секреты подписи JWT и URL объектов не задаются в env: генерируются при первом старте и + лежат в слое метаданных (`app_settings`), поэтому переживают перезапуск и рестарт SQLite. +- Локальные артефакты в корне (`galo.db*`, `/data/`, `bin/`, `backend/static/*`, + `frontend/build/`) в git не попадают — не коммить их. + +## Фронтенд + +- SvelteKit 2 + Svelte 5 (runes), `adapter-static` с `fallback: index.html`, Tailwind v4. +- Тёмная/светлая/нейтральная темы через `data-theme` на `` и семантические + CSS-токены. В компонентах — только семантические классы (`bg-bg-elevated`, `text-muted` + и т.п.), **никаких `dark:` префиксов**. +- Vite dev-сервер проксирует `/api` на `localhost:8080` (см. `vite.config.ts`). + +## OpenSpec + +Спецификации — в `openspec/specs//spec.md`; завершённые изменения — +в `openspec/changes/archive/`. Активных изменений сейчас нет. +Workflow-команды и скиллы лежат в `.opencode/` и требуют CLI `openspec` +(в этом окружении он может быть не установлен — проверь `command -v openspec`). +При изменении поведения сначала обновляй/создавай спеку, а не только код. + +## Конвенции репозитория + +- Ветка `develop`; коммиты — Conventional Commits с русским описанием + (`feat(realtime): ...`, `test(android): ...`, `chore: ...`). Функциональные ветки + вливаются в `develop` через merge-коммиты. +- `project.md` — исходное ТЗ и местами устарел (например, называет S3 бэкендом по + умолчанию и старую раскладку ключей `albums/...`). Ориентируйся на `README.md` и код.