Synaplan
开源 AI 软件平台,面向企业:自托管 AI 代理,符合 GDPR 的聊天机器人小部件,RAG 知识库,WhatsApp 与 Outlook AI,MCP,多模型路由。支持 Docker、Kubernetes 或在 Germ 托管。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Мультитенантная CRM для логопедических центров
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Мультитенантный SaaS для логопедических центров Кыргызстана. Next.js 15 · TypeScript · Supabase (Postgres + Auth + RLS) · pnpm monorepo. Каталог logoped-crm/ — предыдущий одно-тенантный MVP, отдельный репозиторий. Новый код живёт в apps/ и packages/. Локальный стек (supabase start) требует Docker. Если его нет, проект уже подключён к облачной базе: Значения для apps/web/.env.local уже прописаны. В этом режиме работают pnpm dev и все проверки приложения; недоступен только pnpm db:test — pgTAP запускается лишь на локальном стеке.查看完整 README ↗
Мультитенантный SaaS для логопедических центров Кыргызстана. Next.js 15 · TypeScript · Supabase (Postgres + Auth + RLS) · pnpm monorepo.
Каталог
logoped-crm/— предыдущий одно-тенантный MVP, отдельный репозиторий. Новый код живёт вapps/иpackages/.
Локальный стек (supabase start) требует Docker. Если его нет, проект уже
подключён к облачной базе:
| Проект | logocrm, регион ap-south-1 |
| Дашборд | https://supabase.com/dashboard/project/hiwstqrnxrlfuanvfggq |
| Миграции | 0001, 0002, 0003 применены |
| Тестовый вход | [email protected] / password123 |
Значения для apps/web/.env.local уже прописаны. В этом режиме работают
pnpm dev и все проверки приложения; недоступен только pnpm db:test —
pgTAP запускается лишь на локальном стеке.
Новые миграции в облако катятся так:
cd packages/db && supabase link --project-ref hiwstqrnxrlfuanvfggq && supabase db push
Тестовый пользователь с известным паролем живёт в базе, открытой в интернет. Перед первыми реальными данными его нужно удалить, а тариф проекта и политику паролей — пересмотреть.
corepack enable && corepack prepare pnpm@9 --activate)brew install supabase/tap/supabase)pnpm install
cd packages/db && supabase start
supabase start напечатает API URL и anon key. Перенесите их в конфиг веба:
cp apps/web/.env.example apps/web/.env.local
Заполните NEXT_PUBLIC_SUPABASE_URL и NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
(для старых проектов — NEXT_PUBLIC_SUPABASE_ANON_KEY, читаются оба), затем:
pnpm db:reset
pnpm dev
Приложение — http://127.0.0.1:3000, Supabase Studio — http://127.0.0.1:54323, почта локально (magic link) — http://127.0.0.1:54324.
Тестовый пользователь из seed: [email protected] / password123.
| Команда | Что делает |
|---|---|
pnpm dev | Next.js в режиме разработки |
pnpm build | production-сборка |
pnpm lint | ESLint |
pnpm typecheck | tsc --noEmit по всем пакетам |
pnpm test | Vitest в core и contracts |
pnpm db:start | поднять локальный Supabase |
pnpm db:stop | остановить его |
pnpm db:reset | прогнать миграции с нуля и применить seed |
pnpm db:test | pgTAP-тесты |
pnpm db:types | перегенерировать packages/db/src/database.types.ts |
pnpm db:link <ref> | связать репозиторий с облачным проектом |
pnpm db:push | накатить новые миграции в облако |
cd packages/db
supabase migration new add_students
Правьте созданный файл в supabase/migrations/, затем:
pnpm db:reset && pnpm db:test && pnpm db:types
После этого обязательно проверьте линтер Supabase — вкладка Advisors в
дашборде проекта. Он ловит то, чего не видят pgTAP-тесты: таблицы без RLS и
security definer-функции, открытые роли anon. Именно так были найдены
дыры, закрытые миграциями 0002 и 0003.
Миграции неизменяемы после мержа в main: ошибку исправляет следующая
миграция, а не редактирование старой.
Шаблон обязателен для всех таблиц с данными центра — подробности и объяснения в docs/Database.md.
create table public.students (
id uuid primary key default gen_random_uuid(),
center_id uuid not null default public.current_center()
references public.centers (id) on delete cascade,
full_name text not null,
custom_fields jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
created_by uuid default auth.uid(),
deleted_at timestamptz
);
create index students_center_idx on public.students (center_id) where deleted_at is null;
create trigger students_set_updated_at
before update on public.students
for each row execute function extensions.moddatetime(updated_at);
call public.apply_tenant_rls('students'); -- RLS: owner/admin своего центра
call public.apply_audit('students'); -- история изменений
Коротко о правилах: id — uuid; деньги — integer в тыйынах; статусы —
lookup-таблицы, не enum; удаление — только deleted_at; произвольные поля —
custom_fields jsonb.
После миграции добавьте pgTAP-тест «чужой центр не видит строку» и
перегенерируйте типы (pnpm db:types).
packages/contracts/src/events.ts:export const lessonCompletedSchema = z.object({
type: z.literal('lesson.completed'),
payload: z.object({
center_id: z.string().uuid(),
lesson_id: z.string().uuid(),
}),
})
export const appEventSchema = z.discriminatedUnion('type', [
centerCreatedSchema,
membershipCreatedSchema,
lessonCompletedSchema, // ← добавить
])
security definer-функции (RPC); из триггера —
emit_event_internal (0071); без сессии — emit_event_unchecked/_platform.
С 0075 клиентская роль emit_event не исполняет, и вызов из функции без
security definer упадёт permission denied вместе со всей записью:perform public.emit_event(
'lesson.completed',
jsonb_build_object('center_id', new.center_id, 'lesson_id', new.id)
);
events where processed_at is null, валидирует
строку через parseAppEvent() и проставляет processed_at.Имя события — сущность.действие в прошедшем времени. Доставка at-least-once:
обработчики обязаны быть идемпотентными (ADR-003).
Схема меняется только файлами в packages/db/supabase/migrations и только
через CI:
PR → CI (app + db с pgTAP + e2e) → merge в main → CI на main → джоб «Схема» → db push
Джоб «Схема» в ci.yml вызывает deploy-staging.yml с needs: [db, app].
Это не дисциплина, а зависимость между джобами: деплой физически не может
опередить ни pgTAP, ни Vitest-зеркала SQL-логики. Приёмка Playwright в гейт
деплоя не входит — она про интерфейс, а не про схему, — но обязательна для
мержа через required status checks на main.
Checkout в джобе деплоя берёт тот же коммит, что проверял pgTAP. Прежняя
связка через workflow_run брала верхушку main, и между зелёным прогоном и
деплоем мог проскочить непроверенный пуш.
Ручной запуск Deploy staging возможен только с main: с ветки он обошёл бы
pgTAP.
Prod (этап 8) автоматического деплоя не получит — туда руками и осознанно.
В Settings → Environments → staging нужен один секрет. Пока открыт
supabase/supabase#44210
— пулер отвергает пароль роли postgres после сброса, стабильно и во многих
регионах включая ap-south-1, — рабочий вариант только первый:
| Секрет | Где взять | Что даёт |
|---|---|---|
SUPABASE_ACCESS_TOKEN | Supabase → аватар → Account → Access Tokens → Generate | CLI получает временную роль через API, пароль базы не нужен. Токен аккаунтный, поэтому ref проекта зашит в workflow |
SUPABASE_DB_PASSWORD | Project Settings → Database → Reset | Строку собирает workflow. Доступ к одной базе, а не к аккаунту. Вернуться к нему, когда #44210 закроют |
SUPABASE_DB_URL | Connect → Session pooler | Запасной путь, только через пулер |
Приоритет в этом же порядке: если задан токен, пароль и строка не читаются. Держать стоит один секрет, а не все три.