Javis OS
Javis OS is an Agentic AI that assists entrepreneurs in business tasks, helping them grow their work.
AI translation
Расписание без накладок, абонементы и долги, цели по звукам и отчёты родителям в Telegram. 14 дней бесплатно.
Translation pending
BusinessPLpgSQL · TypeScriptGitHub @kuba386OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Мультитенантная CRM для логопедических центров
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Мультитенантный 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 запускается лишь на локальном стеке.Read the full 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 | Запасной путь, только через пулер |
Приоритет в этом же порядке: если задан токен, пароль и строка не читаются. Держать стоит один секрет, а не все три.