- Go 91.6%
- Makefile 8.4%
| api | ||
| cmd/server | ||
| internal | ||
| .env.example | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| Makefile | ||
| README.md | ||
| sqlc.yaml | ||
АСО-Контроль — Backend
Серверная часть сервиса контроля наличия и исправности аварийно-спасательного оборудования (АСО) воздушных судов.
Бэкенд — единственный источник правды: хранит борты и оборудование, принимает считанные RFID-метки с мобильного приложения, сам сравнивает факт с ожидаемым, сам считает статусы по срокам и сам отправляет заявки инженерам.
Решения по проекту
| Вопрос | Решение |
|---|---|
| Офлайн-режим | Нет. Приложение всегда работает с сервером |
| Роли | inspector и admin. Роли «инженер» в системе нет |
| Заявки инженерам | Уходят во внешний API как уведомление. Для демо — mock |
| Клиенты | Мобильное приложение сейчас, веб-админка позже |
| API | Ресурсный, без эндпоинтов «под экран». Контракт — api/openapi.json, он первичен |
| Стек | Go, PostgreSQL, миграции goose, JWT |
Статус
| # | Этап | Состояние |
|---|---|---|
| 1 | Первая миграция (users, aircraft, equipment_types, equipment), /health |
миграции готовы, /health — нет |
| 2 | Генерация кода из OpenAPI, /auth/login, /auth/me, JWT-middleware, проверка admin |
не начат |
| 3 | CRUD бортов и оборудования, /tags/{code} |
не начат |
| 4 | Осмотры: start, scans, close + интеграционные тесты |
не начат |
| 5 | Заявки (/requests) + mock внешнего API |
не начат |
Структура репозитория
cmd/server/main.go # сборка зависимостей, запуск
api/openapi.json # твой спек
internal/
api/gen.go # сгенерировано oapi-codegen (не править)
db/migrations/*.sql # goose
db/queries/*.sql # запросы для sqlc
db/sqlc/ # сгенерировано sqlc
handler/ # реализация интерфейса StrictServerInterface
service/ # бизнес-логика: осмотры, заявки, статусы
auth/ # JWT, middleware, проверка роли
notify/ # клиент внешней системы заявок
sqlc.yaml
oapi-codegen.yaml
Makefile # generate, migrate, run, test команды миграций
Модель данных
-
users —
login,password_hash(bcrypt черезpgcrypto),full_name,role∈ {inspector,admin}. -
aircraft —
tail_number(уникальный бортовой номер),model. -
equipment_types —
name,has_expiry_date(нужен ли срок годности). -
equipment — единица оборудования: тип,
serial_number,tag_code(код RFID-метки, уникален),aircraft_id,location,expiry_date,status∈ {ok,faulty,missing}. Пара (type_id,serial_number) уникальна. При удалении борта оборудование не удаляется, а открепляется (aircraft_id = NULL). -
equipment_v — view, добавляет
effective_status:Условие effective_statusв таблице faultyилиmissingкак в таблице у типа есть срок и expiry_date < сегодняexpiredу типа есть срок и expiry_date < сегодня + 30 днейexpiringиначе okСтатусы
expiredиexpiringне хранятся — считаются на лету, пересчитывать по расписанию не нужно. Порог 30 дней сейчас зашит во view.
Таблиц inspections, inspection_scans и requests ещё нет — они появятся в миграциях этапов 4 и 5.
API
Базовый URL: http://localhost:8080/api. Полное описание — в api/openapi.json.
Общие правила:
- JSON, поля в
camelCase; датыYYYY-MM-DD, время — ISO 8601 UTC. - Авторизация:
Authorization: Bearer <token>, кроме/auth/loginи/health. - Ошибки:
{"error": {"code": "not_found", "message": "..."}}. - Эндпоинты, помеченные (admin), доступны только администратору.
| Метод | Путь | Описание |
|---|---|---|
| GET | /health |
сервер и БД живы |
| POST | /auth/login |
вход, возвращает токен и пользователя |
| GET | /auth/me |
текущий пользователь |
| GET, POST | /aircraft |
список бортов (со счётчиками equipmentCount, problemCount, lastInspectionAt) / добавить (admin) |
| GET, PUT, DELETE | /aircraft/{id} |
карточка борта с оборудованием / изменить (admin) / удалить (admin) |
| GET, POST | /equipment-types |
типы оборудования / добавить (admin) |
| GET, POST | /equipment |
список (фильтры aircraftId, status) / добавить (admin) |
| GET, PUT, DELETE | /equipment/{id} |
карточка / изменить (admin) / удалить (admin) |
| GET | /tags/{code} |
найти оборудование по коду метки |
| GET, POST | /inspections |
список (фильтры aircraftId, status) / начать осмотр борта |
| GET | /inspections/{id} |
состояние осмотра: найдено / не найдено |
| POST | /inspections/{id}/scans |
отправить один считанный код метки |
| POST | /inspections/{id}/close |
закрыть осмотр, получить итог |
| GET, POST | /requests |
список заявок (фильтр aircraftId) / создать заявку инженерам |
Сценарий осмотра
POST /inspections {aircraftId}— осмотр в статусеin_progress.- На каждую считанную метку —
POST /inspections/{id}/scans {code, source}. Сервер возвращает вердиктresult:expected— оборудование этого борта, засчитано;already_scanned— уже считано в этом осмотре;wrong_aircraft— числится на другом борту;unknown— метки нет в системе.
GET /inspections/{id}— что найдено, что ещё нет.POST /inspections/{id}/close— осмотрclosed; итогInspectionSummary:expected,found,notFound,extra,expired.- По проблемному оборудованию (
faulty/missing/expired) —POST /requests {equipmentId, inspectionId, reason, comment}. Сервер отправляет уведомление во внешний API и сохраняет статусsentилиfailed.
source сканирования: nfc (по умолчанию), ble, manual, mock.
Быстрый старт
Нужны: Go, PostgreSQL с расширением pgcrypto, goose.
# 1. БД и расширение (один раз, от суперпользователя)
psql "$DB_URL" -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;'
# 2. Миграции (Makefile читает DB_URL из окружения)
export DB_URL='postgres://user:pass@localhost:5432/ere-control'
make migrate-up
# 3. Сервер (после этапа 1-2)
go run ./cmd/server
Команды Makefile:
| Команда | Действие |
|---|---|
make migrate-up |
применить миграции |
make migrate-down |
откатить последнюю |
make migrate-status |
показать состояние |
make migrate-new name=add_inspections |
создать новую SQL-миграцию |
00002_seed.sqlвызываетcrypt()иgen_salt()— безpgcryptoмиграция упадёт.
Демо-данные (seed)
| Логин | Пароль | Роль |
|---|---|---|
inspector |
inspector123 |
inspector |
admin |
admin1234! |
admin |
Борты: RA-73001 (Boeing 737-800), RA-89002 (Sukhoi Superjet 100). Типы: огнетушитель, спасательный жилет, аптечка (со сроком), фонарь (без срока). Оборудования в seed пока нет.
Это только для разработки. На проде seed не применять.
Конфигурация
Планируемые переменные окружения (будут в .env.example; .env — в .gitignore):
APP_PORT=8080
DATABASE_URL=postgres://user:pass@localhost:5432/ere-control
AUTH_TOKEN_SECRET= # секрет подписи JWT
AUTH_TOKEN_TTL=12h
NOTIFY_PROVIDER=mock # mock | real
NOTIFY_API_URL=
NOTIFY_API_KEY=
LOG_LEVEL=info
План работ
Этап 1. Миграции и /health
- Миграция:
users,aircraft,equipment_types,equipment, viewequipment_v - Seed
- Каркас сервера, подключение к БД (pgx), конфиг из окружения
GET /healthс пингом БД
Этап 2. Генерация из OpenAPI, авторизация
- Кодогенерация сервера и типов из
api/openapi.json POST /auth/login: проверка bcrypt-хеша, выдача JWTGET /auth/me- JWT-middleware и проверка роли
admin
Этап 3. Борта, оборудование, метки
- CRUD бортов (счётчики
equipmentCount,problemCount,lastInspectionAt) - CRUD типов и оборудования, фильтры
GET /tags/{code}
Этап 4. Осмотры (ядро)
- Миграция:
inspections,inspection_scans POST /inspections,GET /inspections,GET /inspections/{id}POST /inspections/{id}/scansс вердиктом по меткеPOST /inspections/{id}/closeс итогом- Интеграционные тесты на весь сценарий осмотра (пишутся вместе с кодом)
Этап 5. Заявки
- Миграция:
requests - Интерфейс уведомлений + mock-провайдер
POST /requests,GET /requests- Реальный провайдер внешнего API (по готовности)
Правила работы с репозиторием
- Ветки:
mainзащищена, работа вfeature/<кратко>иfix/<кратко>. - Одно изменение — один коммит, стиль сообщений единый.
- В
mainтолько через PR с ревью. - Меняется контракт — в том же PR обновляется
api/openapi.jsonи сообщается команде мобилки. - Секреты в репозиторий не попадают.