Lalabase
Tickets, Doku, Zeiterfassung und Kundengespräche an einem Ort, durchsuchbar per KI. Für Softwarefirmen mit Kundenprojekten. Open Source: auf deinem eigenen Server oder in unserer Cloud in Deutschland.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 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