Workspace блог на Rust: REST и gRPC бэкенд блога, общая клиентская библиотека, CLI и браузерный фронт на WebAssembly. Данные — PostgreSQL, аутентификация — JWT (Bearer).
В учебном ТЗ для POST /api/auth/login иногда приведён JSON с полем username. В этом репозитории везде согласован вход по email (тот же, что при регистрации) и password:
- HTTP
POST /api/auth/login, gRPCLoginRequestвproto/blog.proto; - blog-cli:
login --email … --password …; - blog-wasm и
index.html: форма входа по email.
Регистрация по-прежнему: username, email, password. Если проверяющий ожидает пример с username в теле логина — используйте email или согласуйте отличие явно.
| Крейт | Назначение |
|---|---|
| blog-server | HTTP (Actix), gRPC (Tonic), PostgreSQL (sqlx), JWT, миграции при старте. Общая бизнес-логика в сервисах; HTTP и gRPC делят AuthService и BlogService. |
| blog-client | Библиотека: один API для двух транспортов — Transport::Http (reqwest, REST /api/...) и Transport::Grpc (tonic, код из proto/blog.proto). Хранит JWT после register / login. |
| blog-cli | Консольная утилита поверх blog-client: подкоманды register, login, CRUD постов, list; токен сохраняется в .blog_token в текущей директории. |
| blog-wasm | Фронт в браузере: wasm-bindgen, HTTP через gloo-net, JWT в localStorage (ключ blog_token). Сборка даёт JS-glue в blog-wasm/pkg/. |
| blog-integration-tests | Интеграционный сценарий: Postgres в Docker + сервер + пять параллельных CLI; не обязателен для ручного запуска блога. |
Связь по данным: blog-server — источник истины в БД. blog-client не зависит от сервера как от крейта, только совместим по контрактам API.
В одном Cargo workspace пять членов (четыре основных крейта по ТЗ плюс blog-integration-tests). По Cargo бинарник blog-cli зависит от blog-client; blog-wasm к blog-client не подключается: в браузере HTTP идёт через gloo-net к REST сервера.
┌─────────────┐
│ PostgreSQL │
└──────▲──────┘
│
┌────────┴────────┐
│ blog-server │
│ HTTP + gRPC │
└────────┬────────┘
│
┌───────────────┴───────────────┐
│ │
HTTP / gRPC HTTP (REST)
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ blog-cli │ │ blog-wasm │
│ (бинарник) │ │ (браузер) │
└──────┬──────┘ └─────────────┘
│
│ зависимость Cargo: crate blog-cli → blog-client
▼
┌──────────────┐
│ blog-client │
│ (библиотека) │
└──────────────┘
- Rust (stable), Cargo, цель wasm32-unknown-unknown для фронта (
rustup target add wasm32-unknown-unknown). - PostgreSQL 17 (или совместимая версия).
- Для сборки WASM-обвязки:
wasm-bindgenиз пакета wasm-bindgen-cli (cargo install wasm-bindgen-cli), либо wasm-pack.
-
Скопируйте пример окружения и подставьте секреты:
cp .env.example .env
-
DATABASE_URL— строка подключения к Postgres (см. пример в.env.example). Для Docker ниже пользователь/БД совпадают сdocker-compose.yml. -
JWT_SECRET— достаточно длинная случайная строка для подписи JWT (на практике не короче 32 символов). Пример генерации:openssl rand -hex 32
Вставьте результат в
.envкак значениеJWT_SECRET. -
HTTP_PORT/GRPC_PORT— порты HTTP и gRPC (по умолчанию8080и50051).
Файл .env не коммитьте; в репозитории только .env.example.
Из корня проекта:
docker compose up -dПоднимется Postgres с пользователем blog_user, паролем blog_pass, БД blog_db, порт 5432 по умолчанию (PG_HOST_PORT в docker-compose.yml — другой порт, если занят или для изолированного стека интеграционных тестов). Строка подключения может совпадать с .env.example.
Создайте пользователя и базу и пропишите их в DATABASE_URL.
Миграции применяются при старте сервера автоматически.
cargo build --workspace
cargo test --workspace
cargo fmt --all -- --checkПри необходимости воспроизводимых сборок закоммитьте Cargo.lock (сейчас он может быть в .gitignore по условиям ТЗ — ориентируйтесь на требования курса и CI).
Артефакты Cargo по умолчанию собираются в tmp/target (см. .cargo/config.toml), каталог tmp/ в git не входит.
| Задача | Команды (из каталога blog_notes) |
|---|---|
| Все юнит- и интеграционные тесты крейтов, кроме тяжёлого Docker-сценария | cargo test --workspace --exclude blog-integration-tests |
| Полный прогон, включая blog-integration-tests (нужны Docker и Compose v2) | Сначала cargo build -p blog-cli -p blog-server, затем cargo test --workspace или только cargo test -p blog-integration-tests --test cli_five_clients_integration |
Миграции на живой Postgres (переменная DATABASE_URL, тест помечен #[ignore]) |
cargo test -p blog-server --test migrations_integration -- --ignored --nocapture |
Крейт blog-integration-tests в build.rs проверяет наличие бинарников blog-cli и blog-server в каталоге target (в т.ч. tmp/target); при отсутствии — сообщение с подсказкой собрать их явно.
В .github/workflows/ настроены проверки:
| Файл | Когда | Содержание |
|---|---|---|
ci-main.yml |
Пуш и pull request в main / master, вручную (Actions → Run workflow) |
fmt, clippy -D warnings, cargo build --workspace, сборка blog-cli + blog-server, cargo test --workspace, тест миграций с Postgres service (--ignored), сборка blog-wasm под wasm32-unknown-unknown. Для сценария blog-integration-tests на раннере доступен Docker. |
ci-branch-*.yml |
Пуш в прочие ветки при изменении соответствующих путей | Ускоренные проверки отдельных крейтов без полного прогона. |
Локально повторить главный сценарий: cargo fmt --all -- --check, cargo clippy --workspace --all-targets --no-deps -- -D warnings, затем команды из раздела Тесты.
Если на GitHub корень репозитория не совпадает с каталогом workspace (например workspace в подкаталоге), в workflow нужно задать defaults.run.working-directory на этот каталог.
Нужны файл .env в корне blog_notes (см. .env.example), поднятая БД (docker compose up -d или свой Postgres).
Сервер сам подхватывает .env (через dotenvy).
./scripts/run-server.shЭквивалент: cargo run --bin blog-server из каталога blog_notes.
Ожидаемо:
- HTTP:
http://127.0.0.1:8080(илиHTTP_PORTв.env). - gRPC: порт
50051(илиGRPC_PORT). - Проверка живости:
GET /health.
Остановка: Ctrl+C (корректно гасятся и HTTP, и gRPC).
После ./scripts/build-wasm-web.sh (или wasm-pack build --target web в blog-wasm/):
./scripts/serve-wasm-web.shПо умолчанию порт 8765; аргументом можно задать другой, например ./scripts/serve-wasm-web.sh 8000. В браузере откройте напечатанный URL; API_BASE в index.html должен совпадать с HTTP-портом сервера.
Отдельно не «запускается» — подключается как зависимость. Проверка сборки:
cargo build -p blog-cliРаботает из любой директории; ищет .env в текущей папке и пишет токен в .blog_token.
По умолчанию HTTP http://127.0.0.1:8080, для gRPC добавьте --grpc и при необходимости --server http://127.0.0.1:50051.
Примеры:
# Регистрация и сохранение JWT в .blog_token
cargo run -p blog-cli -- register \
--username alice \
--email alice@example.com \
--password secret123
# Вход
cargo run -p blog-cli -- login --email alice@example.com --password secret123
# Посты (нужен предыдущий login/register в этой же директории)
cargo run -p blog-cli -- create --title "Привет" --content "Текст поста"
cargo run -p blog-cli -- list --limit 20 --offset 0
cargo run -p blog-cli -- get --id 1
cargo run -p blog-cli -- update --id 1 --title "Новый заголовок" --content "Новый текст"
cargo run -p blog-cli -- delete --id 1
# Тот же сценарий по gRPC
cargo run -p blog-cli -- --grpc register --username bob --email bob@example.com --password secret456
cargo run -p blog-cli -- --grpc login --email bob@example.com --password secret456
cargo run -p blog-cli -- --grpc create --title "gRPC пост" --content "Содержание"Кратко: сборка артефактов, затем ./scripts/serve-wasm-web.sh (см. выше).
-
Соберите модуль и JS-glue:
./scripts/build-wasm-web.sh
Нужны
cargo, цельwasm32-unknown-unknownиwasm-bindgenвPATH.Альтернатива:
cd blog-wasm wasm-pack build --target web -
Запустите раздачу статики из корня
blog_notes—./scripts/serve-wasm-web.sh [порт]или вручнуюpython3 -m http.serverиз того же каталога. -
Откройте в браузере напечатанный URL.
API_BASEвindex.html(по умолчаниюhttp://127.0.0.1:8080) должен указывать на ваш HTTP-бэкенд.
Кеш браузера: при странном UI сделайте жёсткое обновление (Ctrl+Shift+R / Cmd+Shift+R). Регистрация и вход по email — см. раздел «Контракт входа» выше.
Сгенерированный каталог blog-wasm/pkg/ обычно не коммитится; после клонирования сборку нужно повторить.
Замените при необходимости хост и порт.
# Регистрация
curl -s -X POST http://127.0.0.1:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"curl_user","email":"curl@example.com","password":"secret123"}'
# Ответ содержит token — подставьте в переменную (для шагов ниже часто достаточно токена из register):
export TOKEN="<jwt из ответа>"
# Отдельный вход пользователя, уже зарегистрированного ранее (в теле — email и password)
curl -s -X POST http://127.0.0.1:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"curl@example.com","password":"secret123"}'
# Создание поста
curl -s -X POST http://127.0.0.1:8080/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Заголовок","content":"Текст"}'
# Список постов (публично)
curl -s "http://127.0.0.1:8080/api/posts?limit=10&offset=0"
# Один пост
curl -s http://127.0.0.1:8080/api/posts/1Полный цикл: register → login по --email (если новая сессия без .blog_token) → create → list → get → update → delete (примеры в разделе CLI выше).
- Запущены Postgres,
./scripts/run-server.sh(илиcargo run --bin blog-server), собранblog-wasm/pkg, статика через./scripts/serve-wasm-web.sh. - При необходимости поправить
API_BASEвindex.htmlпод свой бэкенд. - Регистрация или вход по email — токен в
localStorage(см. «Контракт входа»). - Создать пост; для своих постов доступны изменение и удаление.
- Токен CLI: файл
.blog_tokenв рабочей директории (не коммитится). - Ошибки и запросы сервера логируются через tracing (уровень можно задать переменной окружения, например
RUST_LOG=info). - CORS на HTTP настроен пермиссивно для локальной разработки фронта.
- Проверки в CI: раздел CI (GitHub Actions) и каталог
.github/workflows/.
Как в Cargo.toml workspace: MIT OR Apache-2.0.