TutorLoom
기업·대학·교육기관을 위한 학습 플랫폼, 무료 오픈소스이며 엔터프라이즈 버전 제공
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기AI-powered English tutor for kids — Next.js + FastAPI, Gemini lesson generation, local TTS and spaced repetition.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Tutor and Professor is a full-stack personal tutoring app for anyone who wants to study — a language, a school subject, or programming. It combines short lessons, quizzes, spaced repetition, AI-generated practice, text-to-speech, account controls, and study tools. Content is written for the age band on the profile, from a six-year-old to an adult, which is also what keeps the extra content rules in place when the learner is a minor.
The project was built as a practical engineering exercise: a real product surface, a typed frontend, a Python API, persistent data, AI integration, runtime backend routing, auth, tests, and deployment constraints.
docs/deploy-vercel-supabase.md.GET /api/study/session only reads: opening the
app never starts a session by itself.services/study_queue_service.py and lib/question-queue.ts), and "Refazer
todas" is there for whoever wants the whole topic again./onboarding asks who is studying and which language,
then places the student with a five-question test instead of starting everyone
at level 1. The test needs no content and no AI, so it works in the account's
first minute./objectives holds what the learner is working
towards, and under each one the list of what has to be studied to get there.
Checking an item moves a percentage of reach that is computed from that list
and nothing else — items carry a weight, so "ler um capítulo" and "terminar o
curso" do not count the same. Reaching 100% is logged as a milestone in the
daily feed, and unchecking an item takes the badge back, because the number
always describes the current list.apps/api/content/plan-templates/) give the same flow for free.scripts/english_core_pack_data.py; run
python scripts/build_english_core_pack.py to regenerate the seed files and
python scripts/init_db.py to load them.services/offline_question_service.py) — deterministic, free,
and immediate. POST /api/study/questions/ensure fills an empty topic, and the
questions panel calls it before it would show an empty state.POST /api/study/questions/prefetch tops a topic up after the response has
already been sent: free questions first, and a provider call only when a key
and credit are actually there. Nobody waits on a spinner for a question.SIGNUP_MODE=manual (the default) holds a new account in the administrator's queue; SIGNUP_MODE=open lets a verified e-mail address in on its own.GET /api/account/export downloads everything stored about the account, POST /api/account/delete erases it.services/password_policy.py applies the same rules on the API, so a direct HTTP call cannot skip them.MAX_FAILED_LOGINS wrong passwords the account is locked for LOGIN_LOCK_MINUTES and answers 429 with Retry-After. The lock clears itself, and a successful login resets the counter./admin with counters for the approval queue, approved accounts, recent signups, and AI authorizations./admin/accounts: every signup waits in a queue and only reaches the app once approved. Rejecting an account drops its open sessions immediately, and a rejection can be reversed later./admin/users, plus the internal learning content editor at /admin/learn.apps/api/services/billing_service.py rather than in a table: a price changes through a reviewable deploy, not a row edited in production. An account with no subscription row is on the free plan, so signup creates nothing and the app runs with billing switched off.Entitlement decides everything: how many students a plan allows, and how much AI it includes. The AI allowance is credited into the balance the administrator already controlled, so there is one meter instead of two competing ones, and hand-granted credits are never taken away.past_due keeps working: a card that failed this morning should not take a student's lesson away before the gateway has finished retrying.POST /api/billing/webhook verifies an HMAC signature over the raw body and ignores repeated deliveries by event id — the retry every gateway sends must not extend a period twice.docs/saas-operacao.md.apple-touch-icon so iPhone and iPad get the real icon instead of a page thumbnail.display: standalone), with both the standardised and the legacy apple-mobile-web-app-capable meta so iOS before 17 also goes full screen./_next/static/ assets, and shows public/offline.html when a navigation fails with no network. It never touches the backend or any /api/ route, so the runtime backend URL is always read fresh.python scripts/generate-pwa-icons.py.Installing it
Installation needs HTTPS, which the Vercel deployment already provides.
| Layer | Technology |
|---|---|
| Frontend | Next.js 15, React 19, TypeScript, Tailwind CSS |
| Backend | FastAPI, SQLModel, Pydantic, SQLAlchemy |
| Database | PostgreSQL: Supabase in production, local on port 5433 for development |
| Migrations | Alembic through database_bootstrap.py, which also keeps Supabase's public Data API locked out of the tables |
| AI | Configurable provider layer, Gemini as the default path |
| TTS | Kokoro-compatible service plus browser fallback; audio cached in a private Supabase Storage bucket |
| Deployment | Vercel for web and API (pdx1, next to the database), Docker/VPS path kept in docs/DEPLOY-VPS.md |
| Tests | Playwright E2E (TypeScript), Python unittest-style scripts, Node assertion scripts, TypeScript check |
Browser (PWA)
|
| Next.js app on Vercel
v
FastAPI API on Vercel (serverless, pdx1)
|
+-- PostgreSQL on Supabase (RLS on, Data API roles revoked)
+-- Supabase Storage (private audio cache, signed URLs)
+-- AI generation services (Gemini by default)
+-- Kokoro TTS, reached through a tunnel, with browser fallback
+-- Review and study scheduling services
english-kids-tutor/
apps/
api/ FastAPI backend
content/ Seed content, inside the deployable unit
lessons/ Seed lesson JSON
quizzes/ Seed quiz JSON
stories/ Story content
admin-learn/ Admin learning modules
web/ Next.js frontend
docs/ Architecture, setup, deployment notes
infra/cloudflare/ Cloudflare Tunnel config example
scripts/ Local automation and test scripts
docker-compose.yml Optional containerized services
From the repository root:
.\start-project.cmd
To start backend, frontend, and Cloudflare Tunnel together:
.\start-project.cmd -WithTunnel
For the deployed Vercel frontend plus local backend flow, use:
.\ativar-tudo.cmd
That script starts FastAPI, opens a Cloudflare Tunnel, and publishes the current backend URL so the Vercel frontend can find it. The Windows launchers also ensure the local PostgreSQL container is running before the API starts.
cd apps/api
python -m pip install -r requirements.txt
python database_bootstrap.py
python -m uvicorn main:app --host 0.0.0.0 --port 8001 --reload
Backend health check:
Invoke-WebRequest http://127.0.0.1:8001/health
cd apps/web
pnpm install
pnpm dev
Local URLs:
127.0.0.1:5433 (keeps the default 5432 free for other local projects)This project uses a dedicated local PostgreSQL instance on host port 5433.
That avoids collisions with another PostgreSQL server that may already be
running on the default 5432 port.
docker compose up db -d
python scripts/migrate_sqlite_to_postgres.py --postgres-url "postgresql://kids_tutor:[email protected]:5433/kids_tutor"
The migration script backs up apps/api/kids_tutor.sqlite, migrates from a
working copy, refuses to copy into a non-empty PostgreSQL database by default,
and verifies row counts before reporting success.
The deployed frontend needs a reachable backend URL. There are two supported paths:
.\ativar-tudo.cmd, which publishes the current tunnel URL./connect in the deployed frontend and paste the Cloudflare Tunnel URL.Example:
https://tutorprofessor.vercel.app/connect
The app stores the backend URL per browser and can also read a shared runtime state from the Vercel API. This avoids redeploying the frontend every time a temporary Cloudflare URL changes.
Create apps/api/.env or use the local secret flow documented in local.secrets.example.
APP_HOST=0.0.0.0
APP_PORT=8001
DATABASE_URL=postgresql://kids_tutor:[email protected]:5433/kids_tutor
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://tutorprofessor.vercel.app,https://english-tutor-kid.vercel.app
SESSION_SECRET=change-me
GEMINI_API_KEY=your-key
GEMINI_MODEL=gemini-3.1-flash-lite
TTS_PROVIDER=kokoro
KOKORO_URL=http://127.0.0.1:8880/v1/audio/speech
PARENT_COOKIE_SECURE=true
PARENT_COOKIE_SAMESITE=none
# The single administrator: the account with this e-mail owns /admin and never
# waits in its own approval queue. ADMIN_PASSWORD_HASH is an optional recovery
# password so the administrator can sign in even if the stored one is lost.
[email protected]
ADMIN_PASSWORD_HASH=
# Brute-force brake on login. Defaults shown; the lock clears itself.
MAX_FAILED_LOGINS=5
LOGIN_LOCK_MINUTES=15
ADMIN_EMAIL names the administrator, but the account itself still has to exist
in the database. Create it (or reset its password) with:
python scripts/create-admin-user.py --email [email protected]
The script prints the ADMIN_PASSWORD_HASH line to store in the environment.
Then sign in at /login with that e-mail and open /admin.
For local development:
NEXT_PUBLIC_API_BASE_URL=http://localhost:8001
For the deployed flow, the runtime backend route can resolve the tunnel URL dynamically.
Browser E2E tests, protected Codegen, isolated test data and GitHub Actions are
documented in docs/testing.md. From apps/web, run
pnpm test:e2e:safety, pnpm test:e2e, or pnpm e2e:codegen to record a new flow.
Useful checks:
cd apps/web
pnpm exec tsc --noEmit
python scripts/test_language_ai_questions.py
python scripts/test_programming_ai_flashcards.py
python scripts/test_ai_flashcard_service.py
python scripts/test_admin_account_approval.py
python scripts/test_ai_credits.py
python scripts/test_password_security.py
python scripts/test_tenant_isolation.py
python scripts/test_objectives_progress.py
python scripts/test_study_plan.py
python scripts/test_objective_deletion_foreign_keys.py
python scripts/test_objective_study_analysis.py
python scripts/test_objective_full_history.py
python scripts/test_objective_analysis_jobs.py
python scripts/test_objective_job_deletion.py
python scripts/test_migration_0041_objective_workflow.py
python scripts/test_account_modules.py
python scripts/test_account_self_service.py
python scripts/test_billing_and_usage.py
test_tenant_isolation.py is the one to run after touching any route. Besides
driving two accounts against each other over HTTP, it audits every registered
route: a data route that does not resolve its tenant through a known helper, or
an /api/admin route that never reaches an admin check, turns it red. Adding an
endpoint that answers with whatever id it was handed fails the build instead of
shipping quietly.
node apps/web/scripts/test-api-offline-fallback.mjs
node apps/web/scripts/test-pwa-manifest-and-sw.mjs
node apps/web/scripts/test-runtime-backend-state.mjs
node apps/web/scripts/test-lesson-question-state.mjs
node apps/web/scripts/test-diverse-question-state.mjs
node apps/web/scripts/test-objectives-ui.mjs
node apps/web/scripts/test-objective-plan-ui.mjs
The test suite is a mix of service-level tests, API behavior checks, and lightweight frontend state tests. It focuses on high-risk areas: AI output validation, concurrent/stale generation flows, runtime backend selection, and review state consistency.
database_bootstrap.py against the Supabase session pooler before pushing code that needs a new one..vercel.app domains (the callback cookie lands on the API's domain); e-mail login is unaffected. A custom domain or a one-time code on the redirect fixes it.start_checkout and the credentials.apps/api/main.py is one large module. The route audit in scripts/test_tenant_isolation.py keeps the security properties honest, but splitting it into routers is still owed.If you are reviewing the project, start here:
apps/web/src/lib/api-config.ts and apps/web/src/lib/runtime-backend.ts for runtime backend resolution.apps/api/main.py around auth, lessons, review, and AI generation routes.apps/api/services/ for validation and domain logic.pnpm exec tsc --noEmit and one or two scripts under scripts/ or apps/web/scripts/.TODO-SAAS.md: what is done and what is left to run this as a product.docs/analise-melhorias-funcionalidades.md: prioritized feature improvement analysis (Portuguese).docs/deploy-vercel-supabase.md: hosting the database on Supabase and the API on Vercel, and what changes when the request has a time limit.docs/saas-operacao.md: operating it for other people — configuration, plans, cost per account, logs, backups, data rights.docs/privacidade.md and docs/termos.md: privacy policy and terms drafts, written from what the software actually does and awaiting legal review.docs/architecture.md: broader architecture notes.docs/setup-local.md: local setup details.docs/cloudflare-tunnel.md: named tunnel setup.docs/vercel-deploy.md: Vercel deployment notes.guia.md: Portuguese guide for running Vercel frontend with local backend.MIT
services/audience.py, so
an adult gets adult examples and a child still gets the content rules a child
needs. Nothing in the product assumes one or the other.