Модель данных и назначение
Создайте на Go REST API личных заметок с PostgreSQL, миграциями и ручным SQL без ORM. Пользователь имеет id, email, password_hash, created_at. Note содержит id, owner_id, title, body, revision, created_at, updated_at. Grant содержит note_id, grantee_id, permission (read или edit), granted_by и created_at; уникальность пары note/grantee обязательна. Владельцу принадлежит полный контроль, включая передачу разрешения и удаление; получатель разрешения не становится владельцем.
API, проверки и эксплуатация
POST /api/auth/register создаёт аккаунт и возвращает 201; email нормализуется и уникален, пароль имеет минимум 12 символов и хранится только стойким хэшем. POST /api/auth/login возвращает 200 и создаёт случайный session token сроком на 24 часа; в БД хранится только хэш. POST /api/auth/logout отзывает session token и отвечает 204. Защищённые маршруты требуют Authorization: Bearer; отсутствующий, истёкший или отозванный token даёт 401. POST /api/notes создаёт заметку, владелец задаётся из сессии, а не из тела; title содержит 1–160 символов, body не превышает 100000 символов. Защищённые запросы передают сессию в Authorization: Bearer. GET /api/notes возвращает страницы доступных пользователю заметок: собственные и явно расшаренные, без повторов, с limit 1–100 и cursor по updated_at,id. Поиск по title выполняется только внутри того же разрешённого множества.
GET /api/notes/{id} разрешён владельцу и обладателю read/edit; PATCH разрешён только владельцу или edit. PATCH принимает title/body и обязательный expectedRevision. Если revision уже изменена, ответ 409 revision_conflict и данные не перезаписываются. Успех увеличивает revision на 1. POST /api/notes/{id}/grants доступен только владельцу, принимает email получателя и permission, не позволяет предоставить доступ самому владельцу, отвечает 201; отсутствующий адрес даёт 404. PUT заменяет существующее право, DELETE отзывает разрешение; несуществующая пара даёт 404. Удаление заметки разрешено владельцу, каскадно удаляет grants транзакционно. Проверка владения/права должна быть частью того же атомарного действия чтения или записи, где это необходимо; нельзя проверять только по кэшу или ID из клиента. Чужая заметка выглядит как 404, включая попытку читать, обновлять и делиться. Одинаковая политика действует на списках и поиск. Все методы описывают 400/401/404/409 единым JSON-форматом. SQL использует контекстные тайм-аут, логи не содержат title/body/email/token. Индексы покрывают owner, grantee, note и курсор, без N+1. HTTP ограничивает размер тела и корректно закрывается.
Структура программы
Код разделён на небольшие internal-пакеты: internal/httpapi содержит обработчики и DTO, internal/notes — владение, права и ревизии, internal/postgres — заметки, гранты и транзакции. Проверка Bearer-сессии и идентификация пользователя отделены от правил заметок. main.go связывает пакеты, запускает сервер и выполняет shutdown; доступ тестируется без HTTP и БД.