Sana Hub
SPA на vanilla JS с Node.js-бэкендом: регистрация бизнеса и студентов, AI-уточнение задачи до карточки, детерминированный score готовности 0–100…
О проекте
Команда: Yola Team
Кейс HackAlem.ai: Образование — AI Sana
Стадия: Рабочий MVP
Задача кейса: Challenge Hub: превратить задачу бизнеса в задание для студенческих команд
Суть
SPA на vanilla JS с Node.js-бэкендом: регистрация бизнеса и студентов, AI-уточнение задачи до карточки, детерминированный score готовности 0–100, общий каталог и ручное принятие/отклонение предложений студентов.
Как устроено
Браузер отправляет описание и состояние карточки на POST /api/ai/analyze, сервер вызывает OpenAI Responses API с Structured Outputs JSON Schema (модель gpt-5.6-luna), валидирует ответ и при ошибке отдаёт fallback того же контракта. UI показывает минимум 3 вопроса и редактируемую карточку, после ручного подтверждения задача публикуется; score считается детерминированным модулем по 7 категориям. Аккаунты, профили и сессии хранятся в SQLite (scrypt, opaque-токены с SHA-256), а задачи и отклики пока лежат в localStorage.
Ключевые решения
- Structured Outputs JSON Schema
- детерминированный скоринг готовности
- scrypt-хеширование
- opaque session tokens
Особенности
Минималистичный стек без фреймворков (native node:http, vanilla JS) при аккуратной безопасности сессий; README честно фиксирует границу MVP (задачи в localStorage). Весь код залит одним коммитом.
Стек
JavaScript · Node.js · node:http · SQLite · better-sqlite3 · HTML/CSS · node:test
Модели
gpt-5.6-luna (OpenAI)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-10d6f8ea-yola-team.
README
README.mdSana Hub — AI Sana Challenge Hub
Рабочий MVP для трека HackAlem / AI Sana «от бизнес-задачи к решению».
Sana Hub превращает короткое описание потребности бизнеса в редактируемую карточку задачи: AI находит пробелы и задаёт уточняющие вопросы, приложение прозрачно рассчитывает готовность, а бизнес вручную подтверждает и публикует задачу. Студенты находят её в общем каталоге, отправляют предложения, после чего бизнес самостоятельно принимает или отклоняет каждое из них.
Что работает
- серверная регистрация и вход для ролей Business и Student;
- серверные профили и opaque-сессии в SQLite;
- HttpOnly cookie без JWT или session token в
localStorage; - реальный server-side OpenAI flow с настраиваемой моделью;
- динамические AI-вопросы и валидируемый fallback;
- редактируемая карточка и обязательное подтверждение человеком;
- детерминированный score 0–100 с breakdown и рекомендациями;
- общий каталог, поиск, фильтры и сортировка;
- student proposals и ручные решения бизнеса без auto-assignment;
- сохранение demo-задач, откликов и незавершённой карточки после refresh.
Важная граница MVP: аккаунты, сессии и профили уже хранятся на сервере. Задачи, каталог и отклики пока остаются demo-данными в
localStorageи не являются серверно-авторизованными. Их миграция в БД — отдельный следующий этап.
Архитектура и стек
Browser SPA (HTML/CSS/vanilla JS, hash routes)
│
├── /api/auth/* + HttpOnly cookie
│ ▼
│ Node.js native http
│ ▼
│ SQLite / better-sqlite3
│ users + profiles + sessions
│
└── POST /api/ai/analyze
▼
OpenAI Responses API
validated local fallback
Demo tasks / catalog / proposals → validated localStorage store
Readiness score → deterministic application module
- Frontend: HTML5, CSS, vanilla JavaScript, ES modules.
- Backend: native
node:http, без Express. - Database: SQLite через
better-sqlite3. - Passwords: встроенный
crypto.scryptс уникальной солью. - Sessions: случайный 256-bit opaque token; в БД хранится только SHA-256 hash.
- AI: OpenAI Responses API и Structured Outputs JSON Schema.
- Tests: встроенный
node:testи временные SQLite-БД.
Быстрый запуск для жюри
Требуется Node.js 18+; рекомендуется Node.js 20 LTS.
git clone https://github.com/BAITC-Hacks/hack-10d6f8ea-yola-team.git
cd hack-10d6f8ea-yola-team
npm install
npm start
Откройте http://localhost:4173. Другой порт задаётся через PORT.
Без OPENAI_API_KEY весь сквозной сценарий работает через локальный AI fallback. Для проверки реального OpenAI скопируйте конфигурацию:
PowerShell:
Copy-Item .env.example .env
Bash/zsh:
cp .env.example .env
Затем укажите собственный ключ в локальном .env:
OPENAI_API_KEY=your-key
OPENAI_MODEL=gpt-5.6-luna
OPENAI_REASONING_EFFORT=medium
OPENAI_TIMEOUT_MS=30000
SESSION_TTL_MS=604800000
Файл .env исключён из Git, не входит в production bundle и не выдаётся static server. Настоящих API-ключей и demo-паролей в репозитории нет.
SQLite и production deployment
Путь БД задаётся через DATABASE_PATH.
- Без переменной БД создаётся вне public static root, в соседней с проектом папке
.sana-hub-data/sana-hub.sqlite. - Для production
DATABASE_PATHобязан указывать на постоянный writable volume сервера. - SQLite-файл,
-walи-shmнельзя хранить в Git или в публичной static-директории. - При
NODE_ENV=productionsession cookie автоматически получает атрибутSecure.
Пример production-конфигурации:
NODE_ENV=production
DATABASE_PATH=/var/lib/sana-hub/sana-hub.sqlite
OPENAI_API_KEY=your-server-secret
OPENAI_MODEL=gpt-5.6-luna
OPENAI_REASONING_EFFORT=medium
SESSION_TTL_MS=604800000
GitHub Pages для проекта недостаточно: он не запускает Node.js backend и не может безопасно хранить API-ключ или SQLite. Для публичной ссылки нужен Node.js-хостинг с persistent volume и server-side environment variables.
Регистрация, вход и профиль
- Пользователь регистрируется через
#/register, выбирая Business или Student. - Backend нормализует email, проверяет пароль и сохраняет только scrypt hash и salt.
- Backend создаёт opaque session token, сохраняет только его SHA-256 hash и устанавливает HttpOnly cookie.
- SPA загружает сессию через
GET /api/auth/me; роль интерфейса приходит только с сервера. - Onboarding и редактирование профиля используют
PUT /api/auth/profile. - Выход отзывает серверную сессию и очищает cookie.
Cookie: HttpOnly, SameSite=Lax, Path=/; Secure включается в production. Пароль, salt, password hash и session token никогда не возвращаются в JSON. Истёкшие сессии удаляются и игнорируются.
Auth API
| Метод | Endpoint | Назначение |
|---|---|---|
POST | /api/auth/register | Создать аккаунт и сессию |
POST | /api/auth/login | Войти и создать новую сессию |
POST | /api/auth/logout | Отозвать текущую сессию |
GET | /api/auth/me | Получить безопасного user с profile или null |
PUT | /api/auth/profile | Сохранить профиль согласно серверной роли |
Минимальная длина пароля — 8 символов. Повторный email возвращает 409, неверные credentials — одинаковую безопасную ошибку 401.
SQLite schema
users:id, нормализованный uniqueemail,password_hash,password_salt,role, timestamps;profiles:user_id,name, поля business/student onboarding, timestamp;sessions:id,user_id,token_hash,expires_at,created_at.
AI flow и безопасность фактов
- Browser отправляет описание и текущее состояние карточки на
POST /api/ai/analyze. - Backend передаёт описание, карточку, предыдущие вопросы, ответы и обязательную схему в OpenAI.
- Structured Output валидируется на backend.
- При наличии пробелов UI показывает минимум три релевантных вопроса и редактируемый результат.
- До ручного подтверждения карточка остаётся черновиком.
AI использует только факты пользователя. Он не должен придумывать бюджет, сроки, технологии, данные, контакты, пользователей, ограничения или критерии успеха. При timeout, network/API error, пустом или malformed ответе backend возвращает fallback того же контракта, и UI продолжает работать.
Формула готовности
| Категория | Максимум |
|---|---|
| Контекст и потребность | 20 |
| Данные и материалы | 20 |
| Ожидаемый результат | 15 |
| Критерии успеха | 15 |
| Ограничения | 10 |
| Пользователи | 10 |
| Связь с бизнесом | 10 |
| Итого | 100 |
Уровни: 0–39 — Черновик; 40–69 — Рабочая; 70–89 — Готовая; 90–100 — Приоритетная. Пустые, неподтверждённые поля и «Требуется уточнение» не получают баллы.
Каталог и предложения
- Все опубликованные demo-задачи видимы, включая low-score cards.
- По умолчанию задачи сортируются по score по убыванию.
- Есть поиск и фильтры по теме и readiness level.
- Предложение может отправить только вошедший Student с заполненным профилем.
- В demo-store создаётся производная команда
user-{id}текущего student account. - AI никогда не выбирает и не назначает команду.
- Business вручную принимает или отклоняет предложения.
Demo data
В src/data.js находятся синтетические данные:
- 5 слабых task drafts;
- 5 подтверждённых опубликованных task cards;
- 5 профилей demo-команд;
- 5 proposals с разными статусами.
Серверные user accounts и пароли не seed-ятся. Для демонстрации зарегистрируйте новые Business и Student аккаунты через UI. resetSanaDemo() сбрасывает только demo-store задач/команд/откликов и не изменяет серверные аккаунты или сессии.
Demo script — до 5 минут
- Открыть приложение без сессии: в header видны «Регистрация» и «Вход».
- Зарегистрировать Business, заполнить профиль и создать короткую задачу.
- Показать AI-вопросы, карточку, рост score и публикацию.
- Выйти через профиль.
- Зарегистрировать Student, заполнить профиль и открыть каталог.
- Отправить proposal от производной команды текущего пользователя.
- Выйти, войти обратно как Business и вручную принять или отклонить proposal.
- Обновить страницу и показать сохранение server profile/session и demo-flow.
Маршруты
#/home— landing;#/register,#/login— регистрация и вход;#/onboarding/business,#/onboarding/student— серверный профиль;#/profile— профиль и logout;#/business/dashboard— кабинет Business;#/business/intake,#/business/card— AI-конструктор карточки;#/business/proposals— ручное решение Business;#/catalog,#/task/:id— общий каталог и задача.
Unauthenticated пользователь перенаправляется с profile/onboarding/business routes на регистрацию или вход. Student не получает business UI, а Business не получает student proposal form.
Проверки и production bundle
npm install
npm run check
npm run secret-scan
npm run build
Текущий набор содержит 35 тестов: auth/database/session, AI, scoring, store, catalog, proposals и HTTP server. Auth-тесты используют отдельный временный DATABASE_PATH и не создают demo accounts в рабочей БД.
npm run build создаёт папку dist/ вместе с package-lock.json. Standalone bundle запускается так:
cd dist
npm install --omit=dev
node server.mjs
Для production перед запуском обязательно подключите persistent writable volume и задайте абсолютный DATABASE_PATH.
Структура проекта
.
├── index.html
├── server.mjs # static server + AI/auth API routes
├── src/
│ ├── app.js # SPA routes, auth UI and MVP flow
│ ├── store.js # demo task/proposal local persistence
│ ├── config/env.mjs # server-only .env loader
│ ├── db/database.mjs # SQLite path, schema and initialization
│ └── services/
│ ├── auth-crypto.mjs # scrypt, opaque tokens and SHA-256
│ ├── auth-service.mjs # users, profiles and sessions
│ ├── auth-http.mjs # auth HTTP handlers and cookie policy
│ ├── ai-server.mjs # server-only OpenAI integration
│ ├── ai.js # browser-safe fallback/validation
│ ├── scoring.js
│ ├── marketplace.js
│ └── card-change.js
├── tests/ # isolated auth DB/API and MVP tests
├── scripts/ # build and secret scan
├── package.json
├── package-lock.json
└── .env.example
Out of scope
Нет OAuth, email verification, восстановления пароля, внешних auth providers, сложной RBAC, чата, уведомлений, календаря, файлового хранилища, vector database, собственной ML-модели или production project tracker. Перенос task/proposal данных из demo localStorage в серверную БД также остаётся следующим этапом.
Авторы коммитов
История коммитов
- 2026-09-23 16:28Add task change activity modulenchsvj
Структура репозитория (46 файлов)
.env.example .gitignore README.md card-change.css index.html marketplace.css onboarding.css package-lock.json package.json question-states.css scripts/build.mjs scripts/secret-scan.mjs server.mjs src/api.js src/app.js src/config/env.mjs src/data.js src/db/database.mjs src/db/migrations/001_auth.sql src/db/migrations/002_product_data.sql src/models.js src/services/ai-server.mjs src/services/ai.js src/services/auth-crypto.mjs src/services/auth-http.mjs src/services/auth-service.mjs src/services/card-change.js src/services/marketplace.js src/services/product-http.mjs src/services/product-service.mjs src/services/scoring.js src/store.js styles.css success.css tests/ai-server.test.mjs tests/ai.test.mjs tests/auth-ui.test.mjs tests/auth.test.mjs tests/card-change.test.mjs tests/demo-data.test.mjs tests/helpers/auth-fixture.mjs tests/marketplace.test.mjs tests/product-server.test.mjs tests/scoring.test.mjs tests/server.test.mjs tests/store.test.mjs
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.