Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗= TalentStreams :toc: left :toc-title: Содержание :toclevels: 2
Платформа персонализированных подборок проверенных кандидатов для работодателей Центральной Азии.
Работодатели получают email-рассылку с анонимными карточками кандидатов. Ссылка в письме персонализирована — каждый работодатель видит только тех кандидатов, кто не исключил его компанию или отрасль из своих предпочтений.
== Технический стек
== Переменные окружения
Создайте файл .env.local в корне проекта:
GOOGLE_SHEET_ID=... GOOGLE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"...","private_key":"...","client_email":"..."}
DATABASE_URL=...
BLOB_READ_WRITE_TOKEN=...
SENDPULSE_CLIENT_ID=... SENDPULSE_CLIENT_SECRET=... SENDPULSE_MASTER_BOOK_NAME=Default SENDPULSE_FROM_EMAIL=[email protected] SENDPULSE_FROM_NAME=TalentStreams
EDITOR_SECRET защищает /editor и /api/publish — без него страница возвращает 404, а API-роут отвечает 401.
APP_URL используется для формирования proxy-ссылок на резюме (APP_URL/api/resume?url=...) — эти ссылки сохраняются в базе, поэтому значение должно совпадать для всех, кто их открывает (даже при локальной разработке против общей базы, см. ниже).
== Запуск локально
Приложение доступно на http://localhost:3000.
CAUTION: Локальный npm run dev обычно указывает на ту же базу (Neon) и ту же таблицу Google Sheets, что и продакшн — отдельного стенда нет. Ссылки на резюме, сохранённые в базе, абсолютные (APP_URL/api/resume?...) и всегда ведут на APP_URL из .env.local, даже если открыты со страницы, отрендеренной локально.
== Структура проекта
app/ page.tsx # Главная: лендинг, формы регистрации list/[listId]/page.tsx # Страница подборки (требует ?e= или ?secret=) profile/[id]/page.tsx # Полный профиль кандидата editor/ layout.tsx # Боковое меню редактора page.tsx # Выпуски (Releases) employers/page.tsx # Управление работодателями candidates/page.tsx # Управление кандидатами requests/page.tsx # Запросы на контакт streams/page.tsx # CRUD стримов settings/page.tsx # Задел на будущее (пока пусто) api/ publish/[listId]/route.ts # API-роут для curl-запуска рассылки upload/route.ts # POST: загрузка резюме → Vercel Blob resume/route.ts # GET: прокси приватных blob-файлов actions.ts # Все Server Actions
lib/ sheets.ts # Google Sheets: чтение, запись, типы (кандидаты, подборки) sendpulse.ts # SendPulse: OAuth, книги, кампании blob.ts # Хелперы для ссылок на Vercel Blob (в т.ч. имя скачиваемого файла) db/ index.ts # Drizzle-клиент (neon-http) schema.ts # Схемы: contactRequests, streams, employers, candidateResumes contact-requests.ts # CRUD запросов на контакт streams.ts # CRUD стримов employers.ts # CRUD работодателей resumes.ts # История версий резюме кандидата migrations/ # SQL-миграции (drizzle-kit)
components/ employer-registration-modal.tsx # Форма работодателя (публичная) employer-edit-modal.tsx # Редактирование работодателя в редакторе employer-create-modal.tsx # Быстрое добавление работодателя из редактора employer-section.tsx # UI подтверждения/отклонения/редактирования заявок candidate-registration-modal.tsx # Форма кандидата (публичная) candidate-edit-modal.tsx # Редактирование кандидата в редакторе candidate-create-modal.tsx # Быстрое добавление кандидата из редактора candidate-section.tsx # Таблица кандидатов: фильтры, модерация, резюме contact-requests-section.tsx # UI управления запросами на контакт streams-table.tsx # Inline-редактор стримов contact-button.tsx # Кнопка «Хочу связаться» general-inquiry-button.tsx # Кнопка «Связаться с нами» (общий запрос) publish-button.tsx # Кнопка запуска рассылки editor-nav.tsx # Боковая навигация редактора
== Где что хранится
Данные разделены между Google Sheets и Neon — не по историческим причинам, а по тому, кто их пишет:
[cols="2,1,3", options="header"] |=== | Данные | Хранилище | Почему
| Кандидаты (профили, регистрации)
| Google Sheets
| Часть полей всё ещё правится только напрямую в таблице (bio, industry, func и т. д.) — полный редактор в UI пока не построен
| Стримы
| Neon
| Полный CRUD через /editor/streams, таблицу больше не открывают
| Работодатели | Neon | Полный жизненный цикл (регистрация → подтверждение → редактирование → отключение → удаление) через UI
| Запросы на контакт | Neon | Приложение единственный писатель — редактор никогда не правит эти строки напрямую
| История версий резюме кандидата
| Neon
| Отдельная таблица (candidateResumes) — Sheets хранит только URL последней версии
| Файлы резюме
| Vercel Blob (приватное)
| Доступ только через прокси /api/resume
|===
Подробности и статус миграции оставшихся таблиц — docs/backlog.adoc, раздел «Приложение: переход на реляционную СУБД».
== Доступ к подборке /list/[listId]
|=== | Параметр | Результат
| ?e=[employer_token]
| Персонализированная подборка: кандидаты фильтруются по предпочтениям работодателя (исключённые компании/отрасли, гео)
| ?secret=[EDITOR_SECRET]
| Все кандидаты без фильтрации (просмотр редактора)
| (без параметров) | 404 |===
== Добавление новой функциональности
=== Новое поле в профиле кандидата (Google Sheets)
. Добавьте ключ в массив KNOWN_KEYS (lib/sheets.ts).
. Добавьте поле в тип Profile или Candidate (lib/sheets.ts).
. Добавьте алиасы в HEADER_ALIASES (английское и русское название заголовка) — используются функцией mapHeader() везде при чтении/записи.
. Заполните поле в buildProfile() / rowToRecord().
=== Новая таблица или поле в Neon
. Добавьте/измените схему в lib/db/schema.ts (Drizzle).
. npm run db:generate — сгенерировать миграцию (drizzle-kit), затем npm run db:migrate — применить.
. CRUD-функции — в соответствующем файле lib/db/*.ts (import "server-only", плоские async-функции, без классов).
=== Новый Server Action
Добавьте async-функцию с директивой "use server" в app/actions.ts.
Для мутаций, которые должны обновить UI редактора, вызывайте revalidatePath(...).
Для ожидаемых пользовательских ошибок (валидация, дубликаты) — возвращайте { ok: false, error } вместо throw: Next.js в production-сборке заменяет сообщение любого throw new Error(...) из Server Action на общий текст, чтобы не утекали детали непредвиденных ошибок — это ломает вывод ожидаемых сообщений пользователю (пример — registerEmployer()).
=== Новый раздел редактора
. Создайте app/editor/<раздел>/page.tsx с проверкой EDITOR_SECRET.
. Добавьте пункт в массив разделов в components/editor-nav.tsx.
=== Новая SendPulse-переменная контакта
Добавьте ключ в объект variables внутри syncEmployerToSendPulse (app/actions.ts).
В шаблоне письма используйте {{имя_переменной}}.
== Документация
docs/backlog.adoc — главный источник правды по архитектуре: список задач с приоритетами, статус миграции каждой таблицы, детали реализации каждой завершённой задачиdocs/spec.adoc — техническое задание (бизнес-логика, правила распределения по стримам, тексты писем)docs/manager-playbook.adoc — инструкция для нетехнического менеджераdocs/workspace.dsl — C4-модель (открывается в https://structurizr.com/dsl[Structurizr DSL])docs/editor-stream-flow.puml — PlantUML sequence-диаграмма жизненного цикла стрима== Запуск рассылки через API