Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Multi-tenant enrollment management platform for Myanmar language schools.
SaaS platform supporting MMK currency, bilingual Myanmar + English interface, subdomain-based multi-tenancy, and role-based access control.
KuuNyi enables Japanese language schools in Myanmar to manage student enrollments online. Each school gets a dedicated subdomain (e.g. nihon-moment.kuunyi.com) with:
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS |
| Fonts | Noto Sans + Noto Sans Myanmar (Google Fonts) |
| Database | Supabase (PostgreSQL + Storage + Auth) |
| Auth | Supabase Auth via @supabase/ssr |
| Charts | Recharts |
| Excel Export | SheetJS (xlsx) |
| Deployment | Vercel (wildcard subdomain routing) |
| Linting | ESLint + Prettier |
Create .env.local from the example:
cp .env.local.example .env.local
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Yes | Supabase project URL (e.g. https://xxx.supabase.co) |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Yes | Supabase anonymous/public key |
SUPABASE_SERVICE_ROLE_KEY | Yes | Supabase service role key (server-side only, bypasses RLS) |
MESSENGER_APP_ID | No | Meta Developer App ID (for Messenger Bot) |
MESSENGER_APP_SECRET | No | Meta Developer App Secret |
MESSENGER_ENCRYPTION_KEY | No | 64-char hex for AES-256-GCM page token encryption |
MESSENGER_VERIFY_TOKEN | No | Verify token for central webhook (e.g. kuunyi-webhook-2026) |
git clone <repo-url>
cd KuuNyi
npm install
cp .env.local.example .env.local
# Fill in your Supabase project URL, anon key, and service role key
npx supabase login
npx supabase link --project-ref <your-project-ref>
npm run db:migrate
npm run dev
Open http://localhost:3005.
?tenant= query param:http://localhost:3005/enroll/april-2026?tenant=nihon-moment
http://localhost:3005/admin/dashboard?tenant=nihon-moment
nihon-moment.kuunyi.com → slug nihon-moment)?tenant=nihon-moment query param for local developmentx-tenant-slug header → resolveTenantId() looks up tenant_id from tenants tablerequireAuth() resolves tenant from user profile (via Supabase session)resolveTenantId() resolves tenant from middleware header/register, /api/saas/*, /superadmin, /onboarding bypass tenant detectionSee SUBDOMAIN_SETUP.md for detailed Vercel wildcard domain configuration.
| Role | Scope | Permissions |
|---|---|---|
superadmin | Platform-wide | View all schools, suspend/activate tenants, access super admin panel. Cannot access individual school admin dashboards. |
owner | Single tenant | Full access within their school — manage staff, bank accounts, school profile, export data, view analytics. Created on registration. |
staff | Single tenant | View dashboard, intakes, students, payments, announcements, analytics. Cannot access Settings, Export, or bank account management. Added via invite. |
Enforcement layers:
requireOwner() in API routes returns 403 for staffbank_accounts, tenants, and other users/superadmin routes protected by layout-level role checksrc/
├── app/
│ ├── page.tsx # SaaS marketing landing page
│ ├── login/ # Admin login page
│ ├── register/ # School registration form (subdomain check)
│ ├── onboarding/ # 4-step post-registration wizard
│ ├── superadmin/ # Super admin panel (superadmin-only)
│ │ ├── layout.tsx # Auth guard — redirects non-superadmin
│ │ ├── page.tsx # Schools list, stats, suspend/activate
│ │ └── schools/[id]/ # School detail page
│ ├── admin/ # Admin dashboard (protected, requires auth)
│ │ ├── layout.tsx # Auth guard + sidebar shell + RoleProvider
│ │ ├── dashboard/ # Stats overview, recent enrollments
│ │ ├── intakes/ # Intake list + create modal
│ │ │ └── [id]/ # Intake detail: classes, enrollment link
│ │ ├── students/ # Student records, filters, Excel export
│ │ ├── analytics/ # Recharts dashboard (trend, distribution, fill)
│ │ ├── payments/ # Payment verification queue
│ │ ├── announcements/ # Announcement composer + history
│ │ └── settings/ # Bank accounts CRUD + school profile
│ │ ├── staff/ # Staff management (owner-only)
│ │ └── billing/ # Billing placeholder (Free Beta)
│ ├── (public)/
│ │ ├── layout.tsx # Public layout — header, footer, no sidebar
│ │ ├── status/ # Enrollment status checker (search by ref)
│ │ └── enroll/
│ │ ├── [slug]/ # Intake landing page — class cards, bilingual
│ │ ├── form/ # Two-step enrollment form
│ │ └── payment/[ref]/ # Payment instructions + proof upload
│ ├── api/
│ │ ├── saas/
│ │ │ ├── check-subdomain/ # GET — subdomain availability check
│ │ │ └── register/ # POST — create tenant + auth user + profile
│ │ ├── superadmin/schools/ # GET list / GET [id] detail / PATCH suspend
│ │ ├── intakes/ # GET/POST intakes, GET/POST classes
│ │ ├── classes/[id]/ # PATCH class
│ │ ├── admin/
│ │ │ ├── stats/ # GET dashboard stats
│ │ │ ├── analytics/ # GET analytics (trend, distribution, fill)
│ │ │ ├── students/ # GET paginated students + GET [id] detail
│ │ │ ├── payments/pending/ # GET pending payment queue
│ │ │ ├── payments/[id]/verify/ # PATCH approve/reject
│ │ │ ├── bank-accounts/ # GET/POST bank accounts
│ │ │ ├── bank-accounts/[id]/ # PATCH/DELETE bank account
│ │ │ ├── announcements/ # GET/POST announcements
│ │ │ └── staff/ # GET list / POST invite (owner-only)
│ │ │ └── accept/ # GET accept invite token
│ │ └── public/
│ │ ├── enroll/ # POST enrollment submission
│ │ ├── enroll/[slug]/ # GET intake + classes for public landing
│ │ ├── bank-accounts/ # GET active bank accounts (tenant-scoped)
│ │ ├── payments/upload/ # POST payment proof upload
│ │ └── status/ # GET enrollment status by ref
│ └── layout.tsx
├── components/
│ ├── ui/ # StatusBadge, StatsCard, ConfirmModal,
│ │ # Toast, LoadingSpinner, EmptyState
│ └── admin/ # Sidebar, LogoutButton, RoleContext
├── lib/
│ ├── supabase/
│ │ ├── client.ts # Browser client
│ │ ├── server.ts # Server client (cookies)
│ │ └── admin.ts # Service-role client (bypasses RLS)
│ ├── api.ts # requireAuth(), requireOwner(), resolveTenantId()
│ ├── tenant.ts # extractSubdomainFromHost() helper
│ ├── utils.ts # formatMMK, formatMyanmarPhone, etc.
│ └── messenger/
│ ├── send.ts # Meta Graph API send helpers
│ ├── processor.ts # Message routing + keyword matching
│ ├── responses.ts # 6 bot response handlers
│ └── crypto.ts # AES-256-GCM encrypt/decrypt for page tokens
└── types/
└── database.ts # TypeScript types for all tables + enums
supabase/
├── config.toml # Supabase CLI config
└── migrations/
├── 000_combined_schema.sql # Combined initial schema
├── 001–013_*.sql # Individual table/policy migrations
└── 014_staff_roles.sql # Staff roles, invites, RLS updates
| Route | Description |
|---|---|
/ | Marketing landing page — hero, feature cards, CTA |
/register | School registration — name EN/MM, subdomain check, admin email/password |
/onboarding | 4-step wizard — profile, bank accounts, first intake, enrollment link |
| Route | Description |
|---|---|
/superadmin | Stats row, schools table, suspend/activate toggle |
/superadmin/schools/[id] | School detail — owner, intakes, revenue, suspend modal |
| Route | Description |
|---|---|
/admin/dashboard | Stats cards, seats overview, recent enrollments, 60s auto-refresh |
/admin/intakes | List intakes, create new intake modal |
/admin/intakes/[id] | Classes table, Add All 5 Classes, Copy Enrollment Link |
/admin/students | Paginated table, filters, Student Detail Modal, Excel export |
/admin/analytics | Enrollment trend, class distribution, seat fill, date range filter |
/admin/payments | Pending payment card grid, review modal, approve/reject |
/admin/announcements | Composer (intake + class selector + message), sent history |
/admin/settings | Bank accounts CRUD, school profile, Messenger Bot, change password |
/admin/settings/staff | Staff management — list, invite via email (owner-only) |
/admin/settings/billing | Free Beta placeholder |
| Route | Description |
|---|---|
/enroll/[slug] | Intake landing — class cards, level badges, fees, seats remaining |
/enroll/form | Two-step bilingual enrollment form |
/enroll/payment/[ref] | Payment instructions + proof upload |
/status | Enrollment status checker — search by ref |
| Method | Path | Description |
|---|---|---|
GET | /api/saas/check-subdomain?slug=xxx | Check subdomain availability |
POST | /api/saas/register | Create tenant + auth user + owner profile |
| Method | Path | Description |
|---|---|---|
GET | /api/superadmin/schools | List all schools with stats |
GET | /api/superadmin/schools/[id] | School detail with intakes, revenue |
PATCH | /api/superadmin/schools/[id] | Suspend or activate a school |
| Method | Path | Description |
|---|---|---|
GET | /api/public/enroll/[slug] | Classes for an intake |
POST | /api/public/enroll | Submit enrollment (atomic, seat-safe) |
POST | /api/public/payments/upload | Upload payment proof (max 5 MB) |
GET | /api/public/bank-accounts | Active bank accounts |
GET | /api/public/status?ref=NM-YYYY-NNNNN | Check enrollment + payment status |
| Method | Path | Description |
|---|---|---|
GET | /api/messenger/webhook | Central webhook verification (hub.challenge) |
POST | /api/messenger/webhook | Receive messages, route by page_id to tenant |
GET | /api/messenger/webhook/[slug] | Per-school webhook verification (legacy fallback) |
POST | /api/messenger/webhook/[slug] | Per-school message handler (legacy fallback) |
GET | /api/messenger/connect/[slug] | Redirect to Meta OAuth for page connection |
GET | /api/messenger/callback | OAuth callback — exchange code, save encrypted token |
GET | /api/messenger/settings | Read messenger config (auth required) |
PATCH | /api/messenger/settings | Update messenger config (auth required) |
| Method | Path | Description |
|---|---|---|
GET | /api/admin/stats | Dashboard counts and seat summary |
GET | /api/admin/analytics | Analytics data (trend, distribution, fill, conversion) |
GET | /api/admin/students | Paginated students with filters |
GET | /api/admin/students/[id] | Full student detail + signed proof URL |
GET | /api/admin/payments/pending | Payment verification queue |
PATCH | /api/admin/payments/[id]/verify | Approve or reject a payment |
GET | /api/admin/bank-accounts | List bank accounts |
POST | /api/admin/bank-accounts | Add bank account |
PATCH | /api/admin/bank-accounts/[id] | Toggle active / update |
DELETE | /api/admin/bank-accounts/[id] | Delete bank account |
GET | /api/admin/announcements | List announcements |
POST | /api/admin/announcements | Save announcement |
GET | /api/admin/staff | List staff members (owner-only) |
POST | /api/admin/staff | Create staff invite (owner-only) |
GET | /api/admin/staff/accept?token=xxx | Accept invite, create account |
GET | /api/intakes | List intakes |
POST | /api/intakes | Create intake |
GET | /api/intakes/[id]/classes |
9 tables with full Row Level Security (multi-tenant, scoped by tenant_id):
| Table | Description |
|---|---|
tenants | School organisations (name, subdomain, plan, messenger config) |
users | Admin staff (superadmin / owner / staff roles) |
intakes | Enrollment cohorts (e.g. April 2026 Intake) |
classes | JLPT levels N5–N1 per intake, with seat tracking |
enrollments | Student records with auto-generated NM-YYYY-NNNNN refs |
payments | Payment proof submissions with verification status |
bank_accounts | School bank accounts shown to students |
announcements | Admin-composed announcements |
staff_invites | Invite tokens for staff onboarding (7-day expiry) |
Key patterns:
submit_enrollment() — PostgreSQL RPC with SELECT FOR UPDATE to prevent oversellingtrg_payments_sync_enrollment — advances enrollment status when payment is insertedpayment-proofs — private, tenant-scoped, signed URLs for admin accessgetUser() (not getSession()) for server-side auth validationget_my_role() — SECURITY DEFINER function for RLS policy role checks*.kuunyi.com (or *.edu-enroll-xi.vercel.app as fallback)See SUBDOMAIN_SETUP.md for detailed wildcard subdomain configuration.
npx supabase link --project-ref <ref>npm run db:migratepayment-proofs storage bucket (private)Run the tenant isolation test to verify cross-tenant data access is blocked:
export SUPABASE_URL=https://xxx.supabase.co
export SUPABASE_SERVICE_ROLE_KEY=xxx
bash security-audit.sh
Each tenant can connect their Facebook Page to enable an auto-reply bot for student inquiries.
https://kuunyi.com/api/messenger/callback| Variable | Description |
|---|---|
MESSENGER_APP_ID | Meta App ID from the Developer Console |
MESSENGER_APP_SECRET | Meta App Secret |
MESSENGER_ENCRYPTION_KEY | 64-char hex string for AES-256-GCM encryption of page tokens |
Generate the encryption key:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
pages_messaging + pages_read_engagement + pages_show_list permissionstenants table| Button | Payload | Response |
|---|---|---|
| Open Intakes | OPEN_INTAKES | Lists open intakes with class levels and availability |
| Fees | FEES | Shows course fees per JLPT level in Myanmar numerals |
| How to Enroll | HOW_TO_ENROLL | Sends tenant-specific enrollment URL |
| Schedule | SCHEDULE | Shows enrollment open/close dates (EN + Myanmar) |
| Payment | PAYMENT | Lists active bank accounts + payment proof upload link |
| Check Status | CHECK_STATUS | Prompts for ref number, returns enrollment + payment status |
The bot also supports keyword matching for free-text messages (e.g. "fee", "enroll", "ငွေ") and reference number detection (e.g. NM-2026-00042).
One central webhook handles all schools:
https://kuunyi.com/api/messenger/webhook
Configure this in the Meta Developer Console under Webhooks → Callback URL, with MESSENGER_VERIFY_TOKEN as the verify token. Messages are routed to the correct school by matching entry[].id (page ID) against tenants.messenger_page_id.
| Command | Description |
|---|---|
npm run dev | Start dev server (port 3005) |
npm run build | Build for production |
npm run lint | Run ESLint |
npm run format | Format code with Prettier |
npm run db:migrate | Push pending migrations to Supabase |
npm run db:status | List migration status (local vs remote) |
npm run db:diff | Diff local schema vs remote (requires Docker) |
npm run db:reset | Reset local database (WARNING: deletes all data) |
The interface supports both Myanmar and English. Prices are displayed in MMK (Myanmar Kyat).
| List classes for intake |
POST | /api/intakes/[id]/classes | Create class |
PATCH | /api/classes/[id] | Update class |