AI Sana Challenge Hub
Веб-MVP на React и Express: бизнес создаёт черновик задачи, AI задаёт уточняющие вопросы и собирает карточку с рейтингом готовности 0–100, задача…
О проекте
Команда: ShymBit
Кейс HackAlem.ai: Образование — AI Sana
Стадия: Доведённый продукт
Задача кейса: Challenge Hub: превратить задачу бизнеса в задание для студенческих команд
Суть
Веб-MVP на React и Express: бизнес создаёт черновик задачи, AI задаёт уточняющие вопросы и собирает карточку с рейтингом готовности 0–100, задача публикуется в каталоге. Студенческие команды с профилями откликаются и получают AI-план решения: архитектура, этапы, распределение задач, риски.
Как устроено
Frontend на React/TypeScript/Vite обращается к Express API, входные данные проверяет Zod. Хранилище: PostgreSQL с миграциями или JSON-файл. Через OpenAI-совместимый API (gpt-4o-mini) генерируются уточняющие вопросы, собирается структурированная карточка, импортируются PDF/DOCX/TXT с цитатами источника и строятся AI-планы для пары «задача — команда» с сохранением истории. Статусы задач и откликов, роли бизнес и студент, владелец проверяется на сервере, отзывы разрешены только при принятом отклике.
Ключевые решения
- генерация уточняющих вопросов
- структурированная карточка задачи
- рейтинг готовности 0–100
- импорт документов с цитатами источника
- AI-план решения для команды
- защита от перезаписи устаревшим ответом AI
Особенности
Сценарий доведён до выбора команды и планирования работы. Для каждой пары «задача — команда» хранится история AI-планов. Запоздавший ответ AI не может перезаписать свежие правки. Поддерживаются и PostgreSQL с миграциями и бэкапами, и JSON с атомарной записью.
Стек
React · TypeScript · Vite · Node.js · Express · PostgreSQL · Zod · node:test · Render
Модели
gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-05975965-shymbit.
README
README.mdAI Sana Challenge Hub
Проект команды ShymBit, репозиторий hack-05975965-shymbit. Русскоязычный веб-MVP для взаимодействия организаций и студенческих команд в контексте МНВО и AI Sana.
Организация описывает проблему, AI помогает превратить её в понятное задание, а студенты находят проект, отправляют отклик и получают предварительный план разработки. Платформа связывает образовательную практику с реальными задачами бизнеса.
Какую проблему решает сайт
Краткого запроса вроде «нужен помощник для первокурсников» недостаточно для начала разработки: неизвестны источники данных, обязательные функции и критерии результата. AI Sana Challenge Hub помогает уточнить эти сведения и собрать их в одном месте.
- Организациям — подготовить задание, посмотреть профили исполнителей и выбрать одну или несколько команд.
- Студентам — найти задачу под свои навыки, представить портфолио и спланировать работу.
- Участникам образовательных проектов — пройти путь от идеи и согласования требований до распределения первых задач.
Почему это функциональный MVP и в чём его особенности
Это не только макет интерфейса: React-приложение обращается к Express API, сервер проверяет входные данные, сохраняет задачи, профили, отклики, отзывы и AI-планы. Основные сценарии проверяются автоматическими тестами, включая взаимодействие frontend-клиента с backend.
Особенность решения — сочетание каталога проектов, AI-уточнения требований, профилей команд и технического планирования в одном сценарии. AI помогает как заказчику сформулировать задачу, так и студентам начать её решение с учётом состава команды. История планов позволяет возвращаться к предыдущим вариантам.
В отличие от простого списка объявлений, здесь предусмотрены подготовка карточки и планирование работы; в отличие от отдельного чата с AI, результат связан с задачей, командой и откликами. Это описание возможностей проекта, а не утверждение об уникальности относительно всех конкурентов: отдельное сравнительное исследование не проводилось.
Структура
backend/ Express API, AI-интеграция, хранилище и тесты
frontend/ React + TypeScript веб-интерфейс
scripts/ Совместный запуск, перезапуск backend и тесты запуска
Frontend передаёт запросы в backend по HTTP в формате JSON. Backend работает с PostgreSQL либо локальным JSON-хранилищем и отдельно обращается к внешнему AI API. Браузер не получает AI-ключ и пароль базы.
Технологии: JavaScript, TypeScript, React, React Router, Vite, Lucide, CSS; Node.js, Express, PostgreSQL, pg, Zod и dotenv. Тесты используют встроенный модуль node:test, сборка — TypeScript и Vite, проверка стиля — ESLint.
Установка и быстрый запуск
Требуются Git, Node.js 22.13 или новее и npm. Для настоящих AI-функций нужны интернет и действующий ключ совместимого провайдера с доступным балансом. Команды ниже выполняются в PowerShell.
1. Скачать проект и установить зависимости
git clone https://github.com/BAITC-Hacks/hack-05975965-shymbit.git
cd hack-05975965-shymbit
npm run setup
Если проект уже скачан, откройте терминал в его корневой папке и выполните только npm run setup. Эта команда устанавливает зависимости обеих частей по lock-файлам; отдельно выполнять npm install в корне не нужно.
2. Настроить окружение
Создайте файлы настроек, не перезаписывая существующие:
if (!(Test-Path backend/.env)) { Copy-Item backend/.env.example backend/.env }
if (!(Test-Path frontend/.env)) { Copy-Item frontend/.env.example frontend/.env }
На macOS/Linux эти файлы можно скопировать вручную: backend/.env.example → backend/.env и frontend/.env.example → frontend/.env. Остальные команды npm одинаковы.
Откройте backend/.env и укажите свой ключ вместо заполнителя:
PORT=3000
DB_FILE=./data/db.json
AI_API_KEY=ваш_ключ
AI_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4o-mini
AI_TIMEOUT_MS=30000
Имя файла — именно .env, а не env и не .env.txt. Если ключ уже внесён, копировать шаблон повторно не нужно. .env.example оставьте в проекте без настоящего ключа. Никогда не помещайте ключ в переменные с префиксом VITE_ и не добавляйте .env в Git.
3. Запустить обе части одной командой
npm run dev
Откройте адрес Local, который напечатает Vite, обычно http://127.0.0.1:5173. Если порт занят, адрес может отличаться. Скрипт подбирает свободный порт backend и автоматически передаёт его frontend: вручную завершать чужие процессы не требуется. Он также перезапускает backend при изменении исходников или backend/.env.
Остановка обеих частей — Ctrl+C в этом терминале. Для следующего запуска достаточно npm run dev. Не запускайте несколько экземпляров backend с одним JSON-файлом: очередь записи действует только внутри одного процесса.
Раздельный запуск и демонстрационный режим
При необходимости используйте два терминала из корня проекта: npm run dev:backend и npm run dev:frontend. В этом режиме VITE_API_URL в frontend/.env должен совпадать с адресом backend; автоматический выбор связанного порта работает только через общий npm run dev.
Для демонстрации интерфейса без backend установите VITE_USE_MOCK_API=true в frontend/.env и запустите только npm run dev:frontend. Это имитация: настоящих AI-запросов нет, данные хранятся в памяти страницы и теряются после перезагрузки. Общий npm run dev всегда использует настоящий backend, независимо от этого флага.
Аккаунты и обновление существующей базы
После запуска нажмите «Войти» → «Регистрация». Выберите роль один раз:
- Бизнес — создаёт и публикует свои задачи, рассматривает отклики и оставляет отзывы.
- Студент — создаёт свои команды, откликается и получает AI-планы. Кабинет «Мои команды» показывает также статусы откликов.
- Без входа доступны опубликованные задачи, профили и отзывы. Черновики, контакты и ответы на AI-вопросы доступны владельцу; AI-планы — владельцу команды.
Пароль: 12–128 символов. Сессия действует до 24 часов. Токен хранится в sessionStorage вкладки (если хранилище недоступно — в памяти), не в URL; после выхода отзывается на сервере. Это не HttpOnly-cookie: защита от XSS остаётся важной. Не сохраняйте токены и пароли в Git.
Если обновляете существующий проект:
npm run setup
# Только если используете PostgreSQL:
npm --prefix backend run db:migrate
npm run dev
Миграция 002_accounts.sql добавляет пользователей, сессии, владельцев и журнал назначений, не удаляя старые задачи. JSON дополняется коллекциями автоматически. Не переключайте рабочую PostgreSQL на JSON, чтобы «вернуть» старые записи.
Старые задачи пропали из «Моих задач»?
У записей, созданных до авторизации, нет владельца. Это не удаление: опубликованные задачи остаются в каталоге, а личные данные закрыты до явного назначения оператором. Автоматически отдавать их первому зарегистрировавшемуся пользователю нельзя.
- Зарегистрируйте аккаунт бизнеса/студента. Его ID доступен на странице «Аккаунт». Проверьте личность владельца вне приложения.
- Сделайте резервную копию и остановите backend. Убедитесь, что
backend/.envуказывает на нужное хранилище. - В терминале из корня проекта:
cd backend
node scripts/assign-owner.js tasks ID_ЗАДАЧИ ID_БИЗНЕСА --server-stopped
node scripts/assign-owner.js teams ID_КОМАНДЫ ID_СТУДЕНТА --server-stopped
node scripts/assign-owner.js applications ID_ОТКЛИКА ID_СТУДЕНТА --server-stopped
cd ..
Подставьте настоящие UUID из своей базы. Команда работает с JSON и PostgreSQL, запрещает неправильную роль и переназначение существующего владельца. Флаг подтверждает остановку сервера, но не останавливает его сам. Планы становятся доступны владельцу соответствующей команды. После назначения запустите сервер снова.
Что реализовано
- регистрация, вход, выход, роли бизнеса и студента, проверка владельца на сервере;
- личные разделы задач, команд и откликов;
- импорт PDF/DOCX/TXT с AI: предложения, цитаты источника и выбор полей перед применением;
- новый дизайн, светлая/тёмная темы, плавные переходы с учётом reduced motion;
- создание и редактирование черновика бизнес-задачи;
- генерация уточняющих вопросов через OpenAI-compatible AI API;
- сохранение ответов и повторная AI-обработка;
- формирование структурированной карточки проекта;
- рейтинг готовности от 0 до 100 с объяснением;
- статусы
draft,needs_clarification,ready,published,archived; - публикация только после подтверждения бизнеса;
- публичный каталог с поиском и фильтрами;
- отправка и обработка откликов студенческих команд;
- статусы отклика
submitted,reviewed,accepted,rejected; - профили команд: участники, роли, навыки, технологии, проекты и ссылки GitHub;
- каталог команд с поиском и фильтрами по навыкам, технологиям и рейтингу;
- отзывы и средний рейтинг команды; отзыв разрешён при наличии принятого отклика на указанную задачу;
- AI-план решения: архитектура, этапы, распределение задач, трудозатраты, риски и первые действия;
- сохранённая история AI-планов для каждой пары «задача — команда»;
- восстановление задач из архива без удаления карточки и откликов;
- ручная публикация из любого неопубликованного состояния, в том числе напрямую из архива;
- русскоязычный адаптивный интерфейс, валидация и обработка ошибок;
- JSON-хранилище с последовательной записью и заменой файла через временный файл;
- PostgreSQL-хранилище с отдельными таблицами, внешними ключами, транзакциями и защитой конкурентных изменений;
- версионированные миграции, разовый импорт из JSON и команды резервного копирования/восстановления;
- защита от перезаписи свежих изменений запоздавшим ответом AI;
- повторная публикация уже опубликованной задачи без изменения её даты;
- сортировка каталога по дате публикации: новые задачи первыми.
Основной сценарий
Черновик → вопросы AI → ответы бизнеса → карточка и рейтинг
→ подтверждение публикации → каталог → отклик команды → выбор команды
AI-уточнение не блокирует публикацию. Можно выбрать «Сохранить и открыть карточку», затем «Опубликовать в каталоге» и подтвердить действие без генерации и без порога 75 баллов. Само создание черновика не делает его публичным.
Архивную задачу откройте через «Мои задачи». Кнопка «Восстановить из архива» возвращает её для редактирования, но не публикует автоматически. Кнопка «Опубликовать в каталоге» сразу возвращает её в каталог после подтверждения. Для изменения опубликованной задачи сначала архивируйте и восстановите её. Отклики и сохранённые планы при этих переходах не удаляются.
Как используется AI
-
Уточнение: сервер отправляет описание задачи в AI API и сохраняет полученные вопросы. Пользователь отвечает на них в интерфейсе.
-
Карточка проекта: AI обрабатывает исходные данные и ответы, формируя проблему, цель, результат, требования, навыки, данные, ограничения, сроки и критерии успеха.
-
План команды: AI получает опубликованную задачу, профиль выбранной команды и дополнительный акцент, например «успеть за пять часов». Возвращает предварительный план разработки с распределением обязанностей.
-
Документы: AI извлекает значения для формы задачи или команды. Сервер проверяет типы и соответствие цитат исходному тексту; пользователь выбирает, что применить. Форма не сохраняется и задача не публикуется автоматически.
Рейтинг готовности рассчитывает обычный код, не AI: учитывается наличие проблемы, цели, результата, данных, ограничений, сроков, критериев успеха и контакта. Баллы отражают заполненность, а не достоверность сведений или гарантию выполнимости. Они носят рекомендательный характер.
Адаптер использует OpenAI-совместимый endpoint /chat/completions с JSON-ответом. По умолчанию настроены https://api.openai.com/v1 и gpt-4o-mini; провайдер и модель меняются через окружение при поддержке этого контракта. Настроены таймауты, обработка сетевых ошибок и некорректных ответов. Для AI-плана проверяются структура и принадлежность указанных участников выбранной команде.
Без действующего ключа доступны обычные операции с задачами, каталогом, профилями и откликами; AI-функции вернут ошибку настройки или провайдера. AI не разрабатывает готовое приложение автоматически, а помогает сформулировать задание и план. Его рекомендации необходимо проверять человеку.
API
| Метод | Путь | Назначение |
|---|---|---|
POST | /api/auth/register, /api/auth/login, /api/auth/logout | Регистрация, вход, выход |
GET | /api/auth/me | Текущий аккаунт |
GET | /api/me/teams, /api/me/applications | Собственные команды и отклики студента |
POST | /api/imports/extract | Документ → предложения полей без сохранения формы |
POST | /api/tasks | Создать черновик |
GET | /api/tasks | Получить опубликованные задачи |
GET | /api/tasks?status=all | Получить только свои задачи бизнеса |
GET | /api/tasks/:id | Получить задачу |
PATCH | /api/tasks/:id | Изменить неопубликованную и неархивную задачу |
POST | /api/tasks/:id/clarify | Сгенерировать вопросы через AI |
POST | /api/tasks/:id/answers | Сохранить ответы бизнеса |
POST | /api/tasks/:id/generate | Сформировать карточку и рейтинг |
POST | /api/tasks/:id/publish | Опубликовать задачу; тело { "confirm": true } |
POST | /api/tasks/:id/archive | Архивировать задачу |
POST | /api/tasks/:id/restore | Восстановить архивную задачу без публикации |
POST | /api/tasks/:id/applications | Отправить отклик |
GET | /api/tasks/:id/applications | Получить отклики |
PATCH | /api/applications/:id | Изменить статус отклика |
POST / GET | /api/teams | Создать профиль / получить команды |
GET / PATCH | /api/teams/:id | Получить / изменить профиль команды |
GET / POST | /api/teams/:id/reviews | Получить / добавить отзывы |
POST | /api/tasks/:taskId/assistant/plan | Создать AI-план; тело { "teamId": "UUID", "focus": "Акцент" } |
GET | /api/tasks/:taskId/assistant/plans?teamId=UUID | История планов задачи для выбранной команды |
GET | /api/assistant-plans/:id | Получить сохранённый план |
GET | /health | Проверить доступность backend |
GET | /ready | Проверить доступность хранилища; при сбое 503 |
Фильтры каталога: status, skill, technology, deadlineBefore. Без параметра status backend возвращает только опубликованные задачи — черновики не попадают в публичный каталог.
deadlineBefore сравнивает строки: для фильтрации по дате передавайте сроки в формате YYYY-MM-DD. В интерфейсе каталога поле «Срок» ищет текстовое совпадение. Фильтры API команд: skill, technology, minimumRating.
Списки возвращаются в формате { "items": [], "total": 0 }, ошибки — { "error": "Описание", "details": {} } (поле details необязательно). Неизвестный объект даёт 404, некорректные входные данные — 400, конфликт изменений — 409. Подтверждение публикации требует именно логическое true.
Приватные запросы требуют Authorization: Bearer <token>. Для PATCH задачи/команды и сохранения ответов нужен If-Match с ETag версии, открытой в форме. При конфликте 409 ввод остаётся в интерфейсе; скопируйте нужные правки, обновите страницу и сравните версии. Отсутствие версии возвращает 428; без входа — 401, чужие операции — 403. Подробнее: API-контракт.
Импорт документа в форму
В создании/редактировании задачи и команды есть блок «Заполнить из документа с AI»:
- Выберите текстовый PDF, DOCX или UTF-8 TXT (до 10 МБ; PDF до 50 страниц, извлечённый текст до 50 000 символов).
- Подтвердите передачу текста внешнему AI-провайдеру. Не прикладывайте секреты и лишние персональные данные.
- Нажмите «Получить предложения», проверьте значения, цитаты, страницы и предупреждения.
- Отметьте нужные поля и примените их: выбранные поля заменят текущие значения только в форме. Неотмеченные не изменятся.
- Проверьте результат и сохраните обычной кнопкой формы.
Подписанные поля структурированного брифа извлекаются прямо из текста. Переносы строк PDF и типографические кавычки не мешают проверке. Если AI сократил цитату, сервер ищет подтверждение полного значения в документе. Неподтверждённое поле пропускается с предупреждением, а остальные предложения остаются доступными: из-за одной ошибки не нужно загружать весь документ заново. Пример для проверки находится в backend/test/fixtures/task-brief.pdf.
Сканы без текста, изображения, XLSX, защищённые или повреждённые документы не поддерживаются. OCR нет. Загруженный файл не записывается в БД/на диск приложения; это не обещание о сроках хранения у внешнего AI-провайдера. Ограничения сервера настраиваются через IMPORT_*.
Контракт данных
API использует camelCase. Пример создания задачи:
{
"title": "AI-помощник для студентов",
"shortDescription": "Нужно ускорить поиск ответов по учебному процессу.",
"organization": "AI Sana Lab",
"contactPerson": "Айдана",
"desiredResult": "Рабочий веб-прототип",
"availableData": "Обезличенные вопросы студентов",
"constraints": "Не передавать персональные данные",
"deadline": "8 недель",
"skills": ["NLP", "UX"],
"technologies": ["React", "Node.js"]
}
Пример отклика:
{
"teamName": "Sana Team",
"members": ["Алия — frontend", "Данияр — backend"],
"solutionDescription": "Создадим прототип с поиском по базе знаний.",
"technologies": ["React", "Node.js"],
"contact": "[email protected]",
"comment": "Готовы показать первый прототип через неделю."
}
В отклик можно добавить teamId существующего профиля. Тогда сервер берёт название, участников и технологии из этого профиля. Для отзыва передаются taskId, authorName, целочисленный score от 1 до 5 и text.
Как проверить решение вручную
- Зарегистрируйтесь как Бизнес. Создайте задачу «AI-помощник для первокурсников»: проблема — поиск информации о расписании и справках, организация — «Учебный университет», контакт — «Координатор», результат — «Текстовый веб-чат», данные — «FAQ и PDF-регламенты», ограничения — «Без персональных данных», срок — «3 недели», навык —
React. - Выберите AI-уточнение, ответьте на вопросы и сформируйте карточку. Для проверки без AI выберите «Сохранить и открыть карточку».
- Подтвердите публикацию и откройте каталог: задача должна появиться. Проверьте поиск по названию и фильтр навыка.
- Выйдите и зарегистрируйтесь как Студент (другая почта). В разделе «Команды» создайте профиль с названием, описанием и участником, укажите роль и навыки. Проекты и GitHub-ссылки можно добавить для проверки портфолио.
- Откройте опубликованную задачу, отправьте отклик от созданной команды с описанием решения и контактом. Войдите обратно бизнесом, откройте свою задачу и в разделе «Отклики» примите его.
- Перейдите к профилю команды из отклика и оставьте отзыв по этой задаче. Проверьте обновление рейтинга. До принятия отклика сервер должен отклонять отзыв.
- Войдите студентом. В карточке задачи выберите команду и создайте AI-план. Проверьте этапы, роли, риски и открытие плана из истории. Для этого шага нужен действующий AI-ключ.
- Войдите бизнесом. Архивируйте задачу: она исчезнет из каталога, но останется в «Моих задачах». Восстановите для редактирования либо опубликуйте напрямую из архива. Убедитесь, что отклик сохранился.
- Остановите приложение через
Ctrl+C, снова выполнитеnpm run dev. При сохранностиbackend/data/db.jsonсозданные записи останутся доступны.
Для проверки используйте вымышленные контакты и обезличенные данные.
Автоматические проверки
Все команды выполняются из корня после npm run setup:
npm --prefix backend test
npm --prefix frontend test
npm run test:startup
npm --prefix frontend run lint
npm --prefix frontend run build
Тесты проверяют основные API-сценарии, публикацию и восстановление, сохранение после перезапуска, конкурентные изменения, интеграцию frontend-клиента с backend, обработку ошибок AI и совместный запуск. В тестах AI заменён контролируемыми ответами: реальный ключ не требуется, платные запросы не выполняются. Это не заменяет ручную проверку интерфейса и выбранного AI-провайдера.
Проверка PostgreSQL
Создайте отдельную тестовую БД, имя которой заканчивается на _test или _tests. Никогда не используйте рабочую базу. Пример для локальной PostgreSQL с вымышленными реквизитами:
$env:TEST_DATABASE_URL = 'postgresql://sana_test:[email protected]:5432/sana_test'
$env:TEST_DB_SSL_MODE = 'disable'
npm --prefix backend run test:postgres
npm --prefix frontend test
Remove-Item Env:TEST_DATABASE_URL
Remove-Item Env:TEST_DB_SSL_MODE
Тесты создают случайную схему и удаляют только её; public не очищается. PostgreSQL-тесты без URL завершаются ошибкой, а не пропускаются молча. При заданном TEST_DATABASE_URL frontend-тест тоже использует PostgreSQL. В .github/workflows/checks.yml предусмотрены обычные проверки и отдельный прогон с PostgreSQL 17. AI-баланс не расходуется.
При реализации локально проверены SQL-миграции и их откат, API, конкурентные изменения, перенос всех коллекций, frontend-сценарий с обоими хранилищами и сохранность после остановки/запуска PostgreSQL 18. Шаблон Render проверен по официальной JSON-схеме. Развёртывание на Render не выполнялось. Команды резервирования подготовлены, но настоящий цикл pg_dump → pg_restore в этой среде не проверен из-за отсутствия клиентских утилит: обязательно выполните шаг 7 до работы с важными данными.
Хранение данных и интеграции
Хранилище выбирается на сервере:
- Без
DATABASE_URLиSTORAGE_DRIVERлокально используетсяbackend/data/db.json;DB_FILEразрешается относительно папкиbackend. - При заданном
DATABASE_URLавтоматически выбирается PostgreSQL. Можно явно задатьSTORAGE_DRIVER=postgres. STORAGE_DRIVER=jsonразрешён только вне production. ВNODE_ENV=productionотсутствие PostgreSQL останавливает запуск. При сбое БД сервер не переключается на пустой JSON.
В PostgreSQL используются таблицы tasks, teams, applications, reviews, assistant_plans, users, sessions, ownership_migrations и журнал schema_migrations. Вложенные карточки и планы хранятся в JSONB, связи защищены внешними ключами. Изменения выполняются в транзакциях, рейтинг вычисляется из отзывов. Запись подтверждается после COMMIT. AI-запросы не удерживают блокировку БД на время ожидания модели.
Локальный JSON переживает выключение, пока сохраняется файл. На Render используйте отдельную PostgreSQL: её данные не зависят от файловой системы приложения и сохраняются при его redeploy. Саму базу нельзя удалять или оставлять на истекающем тарифе. Сохраняйте резервные копии отдельно. Код подключения готов, но облачная база автоматически не создаётся.
Локальный запуск с PostgreSQL
Создайте пустую БД PostgreSQL и пользователя с правами на неё. В backend/.env задайте:
STORAGE_DRIVER=postgres
DATABASE_URL=postgresql://sana:[email protected]:5432/sana
DB_SSL_MODE=disable
DB_POOL_MAX=5
DB_CONNECT_TIMEOUT_MS=5000
DB_STATEMENT_TIMEOUT_MS=10000
Это пример локальных реквизитов, замените их своими. disable допустим только для локального подключения без TLS. Для внешнего адреса Render используйте verify-full; для внутреннего — require (TLS без проверки самоподписанного сертификата). Код управляет TLS через DB_SSL_MODE, SSL-параметры URL не имеют приоритета. Не отключайте проверку для произвольных внешних серверов.
npm run setup
npm --prefix backend run db:migrate
npm run dev
Миграции защищены от одновременного запуска и повторного применения. Существующие миграции не редактируют: для изменений добавляют новый SQL-файл. Production не запускается с отсутствующей/несовместимой схемой. Источник JSON автоматически не импортируется — см. отдельный шаг ниже.
Внешняя интеграция — AI API. Сведения вводят пользователи; синхронизации с университетскими системами и GitHub API нет. Ссылки GitHub в профилях — ссылки на портфолио, а не подключение аккаунтов. Вводимые сведения задачи и команды могут передаваться AI-провайдеру при генерации: не используйте секреты и конфиденциальные материалы.
Ограничения и готовность к публичному запуску
- Регистрация и роли реализованы, но нет подтверждения почты, восстановления пароля, модерации и проверки принадлежности аккаунта организации. Роль бизнес может выбрать любой пользователь.
- Лимиты входа и AI действуют на один процесс: AI по умолчанию 10 запросов за 15 минут на аккаунт, одновременно 2 операции. При масштабировании нужен распределённый лимитер; регистрация множества аккаунтов пока не предотвращена.
- Проверка принятого отклика перед отзывом не подтверждает личность автора: полноценная система доверия ещё не реализована.
- Локальный JSON рассчитан на один процесс. PostgreSQL поддерживает конкурентную запись; автоматическое расписание резервирования необходимо настроить отдельно.
- AI может ошибаться, его оценки времени и рекомендации требуют проверки. Рейтинг готовности не оценивает качество содержания.
- Нет встроенного чата, OCR и автоматического создания готовых решений. Импорт документов извлекает только подтверждённые поля; не хранит исходные файлы. Интерфейс пока только на русском языке.
- Один аккаунт владеет командой; участники профиля пока не получают отдельные совместные права. Нет автоматического слияния конфликтующих правок формы.
Перед открытым запуском настройте HTTPS, резервные копии, мониторинг, лимиты бюджета AI и доверенные reverse proxy; усилите изоляцию парсера документов и защиту регистрации. PostgreSQL и CORS не заменяют эти меры. Текущая версия подходит для контролируемой демонстрации MVP, но не заявляется как готовая защищённая производственная система.
Развёрнутая версия
Публичная ссылка пока не указана: проект не развёрнут автоматически. Ниже — инструкция для самостоятельного размещения. Исходное техническое задание сохранено как справочный документ; актуальные команды находятся здесь.
Как опубликовать сайт с базой данных на Render
Схема: браузер → Static Site (frontend) → Web Service (backend) → PostgreSQL. AI API вызывается только backend.
Шаг 1. Подготовить аккаунт и репозиторий
- Отправьте проверенную версию проекта в свой GitHub.
.env, базы и резервные копии туда не добавляйте. - Создайте аккаунт Render и подключите GitHub-репозиторий.
- Выберите регион и бюджет. PostgreSQL и backend размещайте в одном регионе и аккаунте.
Стоимость: бесплатная Render Postgres истекает через 30 дней и не подходит для постоянного хранения. Бесплатный Web Service засыпает после простоя; его локальные файлы непостоянны. Для долгосрочной базы выбирайте оплачиваемый тариф. Актуальные условия: Render Free. Перед подтверждением создания ресурсов смотрите стоимость в панели.
Шаг 2. Создать PostgreSQL
В Render выберите New → Postgres:
- Name: например
sana-postgres. - Region: выбранный регион, например Frankfurt.
- PostgreSQL: версия 17 (с ней настроен CI; реализация также проверялась локально на 18).
- Compute Plan: подходящий оплачиваемый тариф, если данные должны храниться постоянно.
Дождитесь статуса Available. В разделе Connect найдите Internal Database URL — его нужно добавить в backend как секрет DATABASE_URL. Для подключения с вашего компьютера используется External Database URL, а не внутренний. Не публикуйте эти строки. При внешнем импорте разрешите доступ только со своего IP; после переноса отключите лишний внешний доступ. Подключение Render Postgres.
Шаг 3. Создать backend
Выберите New → Web Service, подключите репозиторий и задайте:
| Поле Render | Значение |
|---|---|
| Branch | main или ваша подготовленная ветка |
| Runtime | Node |
| Root Directory | backend |
| Region | Тот же, что у PostgreSQL |
| Build Command | npm ci |
| Start Command | npm run db:migrate && npm start |
| Health Check Path | /ready |
В Environment добавьте:
| Переменная | Значение |
|---|---|
NODE_VERSION | 22 |
NODE_ENV | production |
STORAGE_DRIVER | postgres |
DATABASE_URL | Internal Database URL вашей базы; секрет |
DB_SSL_MODE | require для внутреннего адреса Render |
DB_POOL_MAX | 5 |
TRUST_PROXY_HOPS | 1 при одном доверенном прокси перед backend; проверьте схему своей инфраструктуры |
AI_API_KEY | Ваш AI-ключ; секрет |
AI_BASE_URL | URL вашего совместимого AI API |
AI_MODEL | Доступная у провайдера модель |
CORS_ORIGINS | HTTPS-адрес frontend без завершающего /; заполнить после шага 4 |
Порт задаёт Render через PORT; приложение слушает 0.0.0.0. npm run dev и ручной выбор свободного порта на хостинге не нужны.
Стартовая команда сначала применяет миграции, затем запускает API. Ошибка БД останавливает запуск без потери данных и без fallback на JSON. Если на вашем платном сервисе используется Pre-deploy Command, перенесите туда npm run db:migrate, а Start Command замените на npm start. Выберите один способ. Команды развёртывания.
После запуска сохраните публичный URL backend. Откройте https://ваш-backend.onrender.com/ready: должен вернуться статус ok. Это проверяет БД, но не наличие рабочих AI-кредитов.
Шаг 4. Создать frontend и связать адреса
Выберите New → Static Site, тот же репозиторий:
| Поле Render | Значение |
|---|---|
| Root Directory | frontend |
| Build Command | npm ci --include=dev && npm run build |
| Publish Directory | dist |
NODE_VERSION | 22 |
VITE_API_URL | Публичный HTTPS-адрес backend без завершающего / |
VITE_USE_MOCK_API | false |
В Redirects/Rewrites добавьте Rewrite: Source /*, Destination /index.html. Это нужно для прямого открытия внутренних страниц.
Когда Render выдаст URL сайта, вернитесь к backend и задайте CORS_ORIGINS=https://ваш-сайт.onrender.com, сохраните настройки и перезапустите deploy backend. Несколько разрешённых адресов разделяются запятой. Отсутствие этой настройки в production блокирует браузерные запросы с другого origin.
Если меняете VITE_API_URL, пересоберите frontend: Vite встраивает адрес в сборку. DATABASE_URL и AI_API_KEY во frontend никогда не добавляются.
Шаг 5. Перенести старые данные, если они нужны
Для нового пустого сайта этот шаг пропустите. Импорт не запускается автоматически и никогда не должен быть частью Start Command.
- Остановите старое приложение через
Ctrl+C, чтобы во время переноса никто не менял JSON. - Скопируйте
backend/data/db.jsonв безопасную папку вне репозитория. Исходный файл не удаляйте. - В локальном
backend/.envвременно укажите External Database URL целевой базы иDB_SSL_MODE=verify-full. Перед импортом ещё раз убедитесь, что выбрана нужная база. Ключи в чат/команды не вставляйте. - Если миграции ещё не применялись, выполните
npm --prefix backend run db:migrate. - Из корня проекта выполните команды, заменив путь на свою копию:
npm --prefix backend run db:import-json -- --file "C:/SanaBackups/db.json" --dry-run
npm --prefix backend run db:import-json -- --file "C:/SanaBackups/db.json" --apply
--dry-run проверяет только исходный файл и ничего не пишет. --apply переносит все коллекции в одной транзакции, сохраняя ID и даты. Повторный импорт одинаковых записей не дублирует их. Конфликт с изменённой записью отменяет весь перенос — новая информация не затирается. Рейтинг вычисляется по перенесённым отзывам.
После переноса назначьте владельцев старым записям без ownerId по разделу «Аккаунты и обновление существующей базы». Перенос включает аккаунты, хеши сессий и журнал назначений, если они уже есть в JSON. Затем проверьте число задач, команды, отклики и историю планов через сайт. Локальный .env затем верните к нужному режиму разработки, чтобы случайно не редактировать рабочую БД. После начала работы с PostgreSQL старый JSON уже не содержит новые записи и не является актуальной резервной копией.
Шаг 6. Проверить сохранность
- Создайте тестовую задачу и профиль команды, опубликуйте задачу, отправьте отклик.
- Перезапустите backend через Render. Записи должны остаться.
- Выполните новый deploy backend и frontend, снова проверьте записи.
- Проверьте архив → восстановление → публикацию, открытие страницы по прямой ссылке и AI-план.
- Убедитесь, что в логах нет ключей и строк подключения.
Не удаляйте PostgreSQL при обновлении приложения. Новые миграции добавляют отдельными файлами; откат кода не откатывает изменения БД.
Шаг 7. Настроить резервные копии
У платных Render Postgres доступны восстановление и экспорт по правилам тарифа. Самостоятельную копию можно делать командой ниже; нужен установленный pg_dump из PostgreSQL той же или более новой основной версии, чем сервер. pg_dump/pg_restore должны быть доступны в PATH. Резервные копии Render.
Укажите в локальном backend/.env внешний DATABASE_URL и DB_SSL_MODE=verify-full, затем:
npm --prefix backend run db:backup -- --file "C:/SanaBackups/sana-2026-09-23.dump"
Папка должна существовать, файл — ещё нет: команда не перезаписывает копии. Пароль передаётся pg_dump через окружение, а не аргументы команды. Храните копии вне репозитория и вне временного диска Render, с ограниченным доступом. Расписание автоматического резервирования не настроено — задайте его отдельно по нужной частоте.
Для пробного восстановления создайте другую пустую PostgreSQL-базу, не используемую приложением. В локальном backend/.env добавьте RESTORE_DATABASE_URL с её внешним URL и RESTORE_DB_SSL_MODE=verify-full. DATABASE_URL при этом оставьте адресом исходной базы. Выполните:
npm --prefix backend run db:restore -- --file "C:/SanaBackups/sana-2026-09-23.dump" --apply
Команда отказывается работать с непустой БД и с тем же адресом/именем базы, что в DATABASE_URL. Восстановление выполняется одной транзакцией без --clean; в целевую БД не должны писать другие процессы. Не считайте проверку адресов защитой от разных DNS-алиасов одной базы: вручную подтвердите, что это отдельная новая БД. Используйте только доверенные собственные архивы.
Временно подключите тестовый экземпляр приложения к восстановленной БД, проверьте сущности и связи. Рабочий DATABASE_URL переключайте только после проверки. Не храните секрет RESTORE_DATABASE_URL в Render без необходимости; после операции удалите его из локального окружения.
Альтернатива: шаблон Blueprint
В корне есть render.yaml для создания трёх ресурсов. Он предусматривает платную PostgreSQL и отключает автоматические deploy после первоначального создания. Ничего не создаётся, пока вы сами не примените Blueprint в Render. Сначала проверьте стоимость и регион. Формат Blueprint.
В Render выберите New → Blueprint и репозиторий. Задайте запрошенные секреты/адреса. Если адреса ещё неизвестны, можно временно указать https://example.invalid для CORS_ORIGINS и VITE_API_URL, затем заменить их реальными адресами двух созданных сервисов и выполнить deploy обеих частей. Импорт выполняйте отдельно по шагу 5. Шаблон закрывает внешний доступ к PostgreSQL; для локального импорта/бэкапа временно разрешите свой IP в Networking и потом закройте доступ.
Если что-то не работает
| Симптом | Что проверить |
|---|---|
| Ошибка схемы при старте | DATABASE_URL, TLS, регион и результат db:migrate |
/ready возвращает 503 | Доступность и тариф PostgreSQL, лимит соединений |
| Backend работает, браузер сообщает ошибку | CORS_ORIGINS должен точно совпадать с адресом сайта; затем redeploy backend |
| Frontend обращается к localhost | Задать VITE_API_URL и пересобрать Static Site |
Прямой URL страницы даёт 404 | Rewrite /* → /index.html |
| На сайте нет старых задач | Импортировать JSON в ту же БД, которую использует backend; проверить фильтры/статусы |
| AI не отвечает | Серверные ключ, модель, URL провайдера и баланс; БД не заменяет AI API |
Эта инструкция и локальные тесты не означают, что сайт уже размещён: создание ресурсов, облачный deploy и проверка восстановления в вашем аккаунте выполняются отдельно. До открытого доступа проверьте ограничения регистрации, изоляцию документов, бюджет AI и резервные копии, перечисленные выше.
Авторы коммитов
История коммитов
- 2026-09-23 17:42Финальная версияMrP0
- 2026-09-23 17:31Подробно описан проект и сценарий проверки для жюриYero
- 2026-09-23 17:27Связаны новые функции и дизайн с базой данныхMrP0
- 2026-09-23 17:14Обновлены каталог и карточка задачи по одобренному дизайнуYero
- 2026-09-23 17:12Обновлена отметка работы над бэкендом без изменения кодаAshil
- 2026-09-23 17:07Объединён бэкенд с базой данныхMrP0
- 2026-09-23 16:53Добавлены база данных и инструкция размещенияMrP0
- 2026-09-23 16:47Добавлены светлая и тёмная темы, новый шрифт и плавные анимацииYero
Все коммиты (23)
- 2026-09-23 16:46Добавлены роли пользователей и импорт документов через ИИAshil
- 2026-09-23 16:06Обновлён дизайн в стиле Развилки и совместимость с бэкендомYero
- 2026-09-23 16:01Исправление баговMrP0
- 2026-09-23 15:44Исправлены публикация задач и каталогAshil
- 2026-09-23 15:31Стабилизирован запуск приложения и перезапуск сервераMrP0
- 2026-09-23 15:11Связаны профили команд, отклики и AI-планыMrP0
- 2026-09-23 15:07Объединены ветки интерфейса и сервераMrP0
- 2026-09-23 15:02Добавлены профили команд, AI-планы и обновлён дизайнYero
- 2026-09-23 14:52Проверена обработка ошибок AIAshil
- 2026-09-23 14:49Добавлены профили команд и AI-помощникAshil
- 2026-09-23 14:23Исправлен запуск сервера и загрузка окруженияMrP0
- 2026-09-23 14:10Связаны frontend и backendMrP0
- 2026-09-23 13:53frontend v1.0MrP0
- 2026-09-23 13:45Обновлена документация проектаAshil
- 2026-09-23 13:27Добавлен backend MVP AI SanaAshil
Структура репозитория (113 файлов)
.gitattributes .github/workflows/checks.yml .gitignore README.md backend/.env.example backend/.gitignore backend/API_CONTRACT.md backend/integration/postgres.test.js backend/migrations/001_initial.sql backend/migrations/002_accounts.sql backend/package-lock.json backend/package.json backend/scripts/assign-owner.js backend/scripts/backup.mjs backend/scripts/import-json.mjs backend/scripts/migrate.mjs backend/scripts/restore.mjs backend/src/app.js backend/src/config.js backend/src/db/importJson.js backend/src/db/migrate.js backend/src/db/pool.js backend/src/db/records.js backend/src/imports.js backend/src/readiness.js backend/src/security.js backend/src/server.js backend/src/services/aiService.js backend/src/services/documentParser.js backend/src/services/documentWorker.js backend/src/services/importEvidence.js backend/src/store.js backend/src/stores/index.js backend/src/stores/postgresStore.js backend/src/validation.js backend/test-support/postgres.js backend/test/api.test.js backend/test/catalog.test.js backend/test/fixtures/task-brief.pdf backend/test/imports-evidence.test.js backend/test/security-imports.test.js backend/test/storage.test.js backend/testSupport.js docs/POSTGRES_RENDER_IMPLEMENTATION_PROMPT.md frontend/.env.example frontend/.gitignore frontend/eslint.config.js frontend/index.html frontend/package-lock.json frontend/package.json frontend/public/images/sana-studio.png frontend/public/images/sana-studio.prompt.txt frontend/src/App.tsx frontend/src/catalog.css frontend/src/components/Access.tsx frontend/src/components/ApplicationForm.tsx frontend/src/components/AssistantPlanner.tsx frontend/src/components/AsyncState.tsx frontend/src/components/DocumentImport.tsx frontend/src/components/Layout.tsx frontend/src/components/ReadinessScore.tsx frontend/src/components/StatusBadge.tsx frontend/src/components/TagInput.tsx frontend/src/components/TaskCard.tsx frontend/src/components/TaskProgress.tsx frontend/src/components/TeamUI.tsx frontend/src/components/ThemeToggle.tsx frontend/src/components/Toast.tsx frontend/src/hub.css frontend/src/integration.css frontend/src/lib/api.ts frontend/src/lib/mockApi.ts frontend/src/lib/mockTeams.ts frontend/src/lib/normalizers.ts frontend/src/lib/session.ts frontend/src/lib/theme.ts frontend/src/lib/usePageMotion.ts frontend/src/main.tsx frontend/src/motion.css frontend/src/pages/ApplicationsPage.tsx frontend/src/pages/AuthPage.tsx frontend/src/pages/CatalogPage.tsx frontend/src/pages/ClarifyPage.tsx frontend/src/pages/CreateTaskPage.tsx frontend/src/pages/HomePage.tsx frontend/src/pages/MyTasksPage.tsx frontend/src/pages/NotFoundPage.tsx frontend/src/pages/StudentDashboard.tsx frontend/src/pages/TaskPage.tsx frontend/src/pages/TeamEditorPage.tsx frontend/src/pages/TeamPage.tsx frontend/src/pages/TeamsPage.tsx frontend/src/sana.css frontend/src/status-fixes.css frontend/src/styles.css frontend/src/theme-motion.css frontend/src/themes.css frontend/src/types.ts frontend/src/vite-env.d.ts frontend/test-support/import-example.txt frontend/test-support/preview.mjs frontend/test/integration.test.mjs frontend/test/theme.test.mjs frontend/tsconfig.app.json frontend/tsconfig.json frontend/tsconfig.node.json frontend/vite.config.ts output/pdf/primer-zadachi-ai-sana.pdf package.json render.yaml scripts/dev-runtime.mjs scripts/dev-runtime.test.mjs scripts/dev.mjs
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.