ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Chartix.in is a paid, AI-assisted learning platform for the CMT® (Chartered Market Technician) exam — the global professional designation in technical analysis. It combines a structured curriculum (chapters → subtopics → notes → questions), a full quiz/mock-test engine, an AI study tutor grounded in the platform's own materials via retrieval-augmented generation, interactive market tools (Indicator Lab, FII/DII dashboard, custom Index Builder), a freemium trial + Razorpay billing funnel, a blog, and an admin content-authoring + social-media studio — all in one Next.js application.README 전문 보기 ↗Chartix.in is a paid, AI-assisted learning platform for the CMT® (Chartered Market Technician) exam — the global professional designation in technical analysis. It combines a structured curriculum (chapters → subtopics → notes → questions), a full quiz/mock-test engine, an AI study tutor grounded in the platform's own materials via retrieval-augmented generation, interactive market tools (Indicator Lab, FII/DII dashboard, custom Index Builder), a freemium trial + Razorpay billing funnel, a blog, and an admin content-authoring + social-media studio — all in one Next.js application.
Live: chartix.in · Audience: CMT candidates worldwide (all three levels) · Type: Single-tenant SaaS web app.
CMT® and Chartered Market Technician® are registered trademarks of the CMT Association. Chartix is not affiliated with, endorsed by, or sponsored by the CMT Association.
Chartix is a CMT exam-preparation LMS. A candidate signs up, gets a 7-day free trial, studies richly-formatted notes organised by the CMT curriculum, practises with quizzes and full mock tests, asks an AI tutor ("Chartix Scholar") about any concept — including uploading their own charts for analysis — and upgrades to paid access via Razorpay. Admins author all content (notes, questions, chapter revision summaries, blog posts) through a dashboard, moderate user reports, manage coupons/users, and generate social-media carousels and market-data report images from a built-in "Social Studio".
The CMT curriculum is broad and technical, and quality prep material is scarce. Chartix packages a structured, exam-focused curriculum with:
USER) preparing for CMT Level I, II, or III — a global audience.ADMIN) — the Chartix team authoring content and running the business.Study notes → practise quizzes → ask the AI tutor → track performance → subscribe. On the admin side: author curriculum → generate/QA questions with AI → publish → moderate reports → market the product.
<=> cosine distance; no separate vector store.CmtLevel (LEVEL_1/2/3). Notes are authored in TinyMCE and stored as both contentJson (source of truth) and rendered contentHtml, with an optional watermarkConfig./user/notes; access is gated by trial/premium/entitlement.chapter.service.ts, subtopic.service.ts, note.service.ts back the read APIs; publish/soft-delete flags (isPublished, isDeleted) control visibility.SUBTOPIC, CHAPTER, CUSTOM, FULL_TEST (QuizMode). Each attempt (QuizAttempt) stores per-item snapshots (QuizAttemptItem.questionSnapshotJson) so a later edit to a question never changes a past attempt. Items support two-colour flagging (YELLOW/RED), timing, and review./user/quiz → start → answer → flag → complete → review./api/quizzes/start, /answer, /complete, /review, /flag (all under quiz.service.ts); results feed the analytics service./api/chat) that answers CMT questions in a fixed teaching structure, embeds relevant diagrams from the student's own notes, analyses uploaded charts with vision, and adapts to the student via per-user memory + quiz performance. See §7.TRIAL_SCHOLAR_DAILY); paid/admin are unlimited. Chart uploads are separately capped (15/day).ChapterSummary) with six sections — summary bullets, key concepts, formulas, exam tips, high-yield facts, one-minute revision — often AI-drafted then admin-published. Students can bookmark individual items (SummaryBookmark)./user/summary, /api/chapter-summary/[chapterId] (+ /bookmark)./api/user/analytics, /coach, /chat).analytics.service.ts reads QuizAttempt/QuizAttemptItem; the tutor consumes a compact weak/strong summary to tailor teaching./tools/[indicator]) — an educational library of technical indicators./tools/fii-dii) — foreign vs domestic institutional flows (cash + F&O), served from the app's own FiiDiiLog/FnoLog tables plus static tool assets./tools/index-builder) — build, weight (equal / market-cap), chart, save, and optionally publish a custom stock index (Index model, shareId for public sharing).trialStartedAt/trialExpiresAt, subscriptionStatus=TRIAL). Trial-free chapters flagged via Chapter.isTrialFree.Payment marked PAID → premium granted → PDF invoice emailed. Billing details (incl. GST) captured at checkout.Coupon) — free-access (grants Entitlement for N days on specific/all chapters) and price-discount (PERCENT/FIXED off checkout).trial-drip cron emails trial-day nudges (idempotent via UserActivity.lastDripDay)./blog, /blog/[slug], BlogPost) with subscriber capture (BlogSubscriber, token-based unsubscribe) and a public homepage chatbot.ContactSubmission, BotFeedback, and CandidateFeedback (structured feedback the CMT Association can request from Prep Providers).Under /admin/* (ADMIN-only): chapters, subtopics, notes (rich editor + image upload/autolabel), questions (+ AI generation), chapter summaries, blog, coupons, users, contacts, note/question report moderation, the two chatbots' knowledge & Q&A corrections, and Social Studio (AI carousel generation + FII/DII report images).
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Next.js 15 (App Router) | SSR/RSC pages, route handlers, middleware |
| UI | React 19 + TypeScript | Server + client components |
| Styling | Tailwind CSS + @tailwindcss/typography | Design system, prose |
| Rich text | TinyMCE (@tinymce/tinymce-react) + TipTap (@tiptap/*) | Note/question authoring |
| ORM | Prisma 6 | Schema, migrations, typed queries |
| Database | Neon PostgreSQL (serverless driver @neondatabase/serverless + @prisma/adapter-neon) | Relational data |
| Vector search | pgvector (raw <=> queries via Prisma $queryRawUnsafe) | RAG over notes + public docs |
| Auth | NextAuth v5 (next-auth@5-beta) — Google OAuth + Credentials | Sessions (JWT), roles |
| Password hashing | bcryptjs | Credentials login + reset |
| AI | OpenAI — gpt-4o (chat/vision), gpt-4o-mini (memory), text-embedding-3-small (1536-dim) | Tutor, generators, embeddings |
| Rate limiting | Upstash Redis + @upstash/ratelimit (sliding window) | Abuse/cost control |
| Payments | Razorpay (razorpay SDK + checkout) | Subscriptions |
| Invoices | pdf-lib | GST-ready PDF invoices |
| Resend | Transactional (welcome, invoice, reset, drip, contact) | |
| Media | Cloudinary (signed uploads) | Note images, PDFs |
| Doc ingest |
Not present / not used (don't assume): no Supabase (the
.env.examplestill references it, but the code uses Neon + NextAuth), no Redux, no GraphQL, no separate backend service, no dedicated vector database (pgvector is inside Postgres), no Docker/CI config in-repo, and no automated test suite (see §20).
Chartix is a single Next.js application deployed on Vercel. There is no separate backend — the "backend" is Next.js route handlers (src/app/api/**/route.ts) and server services (src/server/services/*), all talking to Neon Postgres via Prisma. External providers (OpenAI, Razorpay, Cloudinary, Resend, Upstash, GA4) are called server-side.
flowchart TD
subgraph Client[Browser]
PAGES[RSC + client pages]
TOOLS[Static tool pages<br/>index-builder / fii-dii]
CHECKOUT[Razorpay checkout.js]
end
subgraph Edge[Next.js Middleware - every request]
MW[Auth gate · CSP/HSTS · bot & AI-crawler block]
end
subgraph App[Next.js app on Vercel]
API[Route handlers /api/**]
SVC[Server services]
POL[Policies: auth · access · csrf · rate-limit]
end
subgraph Data[Neon PostgreSQL]
PRISMA[(Prisma models)]
VEC[(pgvector: knowledge_chunks)]
end
subgraph Ext[External providers]
OAI[OpenAI]
RZP[Razorpay]
CLD[Cloudinary]
RES[Resend]
UPS[Upstash Redis]
GA[GA4 Data API]
end
subgraph Cron[Vercel Cron]
C1[fii-dii-log]
C2[trial-drip]
C3[eod-update]
end
PAGES --> MW --> API --> POL --> SVC --> PRISMA
API --> VEC
API --> OAI & RZP & CLD & RES & UPS & GA
CHECKOUT --> RZP
RZP -->|webhook| API
Cron --> API
App Router with route groups: (auth) (sign-in/up, reset), (dashboard) (/user/*, /admin/*), and top-level marketing/legal/tool pages. Server Components fetch data directly; client components use React Query and call /api handlers. The market tools (/tools/fii-dii, /tools/index-builder) are largely static HTML/JS assets in public/ rendered inside same-origin iframes.
Three layers inside the app:
src/app/api/**) — HTTP ent/exit, validation, response shaping.src/server/policies/*) — auth (require authenticated/admin), access (trial/premium/entitlement), csrf (origin allowlist), rate-limit (Upstash).src/server/services/*) — business logic + Prisma (chapter, subtopic, note, question, quiz, analytics, dashboard, media).REST-style JSON handlers, grouped: api/admin/* (ADMIN), api/user/* + feature routes (authenticated), api/auth/*, api/payments/*, api/cron/* (CRON_SECRET), and a few public ones (api/public-chat, api/blog/*, api/contact). See §9.
Neon Postgres via Prisma, using the serverless adapter (@prisma/adapter-neon + ws) so it works on Vercel's serverless runtime. RAG uses a knowledge_chunks table with a pgvector embedding column queried by raw SQL.
Multiple stateless LLM "agents", each an API route or lib function with its own system prompt (tutor, public bot, question generator, summary generator, image autolabel, studio, analytics coach). RAG context comes from pgvector. See §7–§8.
Three Vercel Cron endpoints (bearer-authenticated with CRON_SECRET): fii-dii-log (ingest daily flows), eod-update (end-of-day market update, weekdays), trial-drip (trial nudge emails). Plus after() fire-and-forget work inside request handlers (memory updates, engagement counters).
In-process function calls (route → policy → service → Prisma). Cross-cutting state (sessions) rides in the NextAuth JWT cookie, decoded in middleware without a DB call. External calls are plain HTTPS to provider SDKs/REST.
chartix/
├── prisma/
│ ├── schema.prisma # ~30 models/enums: users, curriculum, quizzes, payments, FII/DII, AI
│ └── seed.mjs # DB seeding
├── src/
│ ├── middleware.ts # Auth gate + CSP/HSTS + scraper/AI-crawler blocking (runs on all routes)
│ ├── app/
│ │ ├── (auth)/ # sign-in, sign-up, reset-password
│ │ ├── (dashboard)/
│ │ │ ├── admin/ # ADMIN console (chapters…users, studio) — layout runs requireAdminUser()
│ │ │ └── user/ # student dashboard, notes, quiz, summary, analytics, feedback
│ │ ├── blog/ tools/ about/ pricing/ contact/ terms/ … # public pages
│ │ └── api/ # all route handlers (see §9)
│ │ ├── admin/* # ADMIN CRUD + AI generators + studio + reports
│ │ ├── auth/* # NextAuth, register, password-reset, session, logout
│ │ ├── chat, public-chat # AI tutor + public homepage bot
│ │ ├── quizzes/* # quiz engine
│ │ ├── payments/razorpay/* # order, verify, webhook
│ │ ├── chapter-summary/*, chapters/*, notes, me, contact, feedback, blog/*
│ │ ├── fii-dii/*, index-builder/*, index-tools/* # market tools APIs
│ │ ├── user/* # dashboard, analytics, trial-status, reports
│ │ └── cron/* # fii-dii-log, trial-drip, eod-update
│ ├── server/
│ │ ├── policies/ # auth.ts, access.ts, csrf.ts, rate-limit.ts
│ │ ├── services/ # chapter, subtopic, note, question, quiz, analytics, dashboard, media
│ │ └── validators/ # zod schemas (content, admin-content, quiz)
│ ├── lib/
│ │ ├── ai/ # openai.ts, rag.ts, knowledge-store.ts (pgvector), memory.ts, image-captions.ts, pdf-processor.ts
│ │ ├── auth/ # NextAuth config (auth.ts) + helpers
│ │ ├── payments/ # razorpay.ts (signature verify, grant)
│ │ ├── invoices/ # generate.ts (pdf-lib), send.ts (Resend)
│ │ ├── email/ # Resend client + templates
│ │ ├── studio/ # Social Studio design system, prompts, export, content-plan
│ │ ├── reports/ # FII/DII report data + next/og PNG render
│ │ ├── index-builder/, tools/, chapter-summary/, cloudinary/, security/, seo/, geo/, qod/, db/, utils/
│ └── components/ # UI (admin/, user/, marketing, shared)
├── public/ # static assets + the fii-dii / index-builder tool apps
├── docs/ # internal design notes (architecture, api, database, auth, security-audit…)
├── sql/ + *.sql # setup scripts (pgvector, invoices, coupons, trial, password-reset…)
├── vercel.json # crons + build command (prisma generate && next build)
├── next.config.mjs # images (Cloudinary), serverExternalPackages, redirects
└── tailwind.config.ts / tsconfig.json / package.json
Key inter-module relationships
/admin/* page is protected by the admin layout calling requireAdminUser(); every /api/admin/* handler calls it too (defence in depth).src/lib/ai/rag.ts is the retrieval brain — it chunks note HTML (preserving image markers), embeds, stores, and searches knowledge_chunks; chat/route.ts consumes it.src/server/policies/access.ts centralises the trial/premium/entitlement decision used by content and chat routes.src/lib/payments + src/lib/invoices + src/lib/email form the checkout→access→invoice chain.Browser request
↓
middleware.ts ── block scrapers/AI crawlers (UA regex) → 403 if matched
↓ ── decode NextAuth JWT from cookie (no DB call)
↓ ── /admin/* requires role=ADMIN, /user/* requires auth → else redirect /sign-in
↓ ── attach CSP (report-only) + HSTS + security headers
↓
App Router (RSC/page or /api route handler)
↓
requireAuthenticatedUser() / requireAdminUser() (server/policies/auth.ts → auth() session)
↓
access policy (trial/premium/entitlement) · csrf origin check · rate limit (Upstash)
↓
server service → Prisma → Neon Postgres
↓
JSON response / rendered RSC
/api/chat) — end to endStudent sends message (+ optional level, chat history, uploaded chart image)
↓
requireAuthenticatedUser() → validate image (type/size), enforce trial/chart rate limits
↓
getTrialState() → block if trial ended & not paid; count Scholar question for trial cap
↓ (in parallel)
buildContext(message, level) → embed query → pgvector search notes → text + diagram list
prisma.botQAPair(botType='study') → admin "mandatory corrections"
getUserMemory(userId) → per-student profile
getUserAnalyticsData(userId) → weak/strong topics
↓
buildSystemPrompt(...) → fixed teaching structure OR chart-analysis structure (if image uploaded)
↓
pickVisionImages(): caption-match note diagrams → attach up to 2 as vision input
↓
openai.chat.completions.create({ model: gpt-4o, stream: true, temperature: 0.4 })
↓ (on vision failure → retry once text-only)
stream tokens back to client (ReadableStream)
↓ after(): updateUserMemory() on gpt-4o-mini + increment scholarUsed (non-blocking)
Client: /api/payments/razorpay/order → create Payment(status=CREATED) + Razorpay order
↓
Razorpay Checkout modal → user pays
↓
Path A (instant): /verify → CSRF check → verify HMAC(order|payment, key_secret)
↓ → confirm order belongs to user → if not already PAID:
↓ grantPremiumAccess() · Payment=PAID · bump coupon · issueInvoice() · welcome email
Path B (truth): /webhook → verify webhook signature → same grant, idempotent
Note HTML → <img> tags replaced with space-free markers (URL + caption survive chunking) → strip HTML → word-chunk (size 600, overlap 80) → text-embedding-3-small per chunk → INSERT ... embedding::vector into knowledge_chunks. Search: embed the query → 1 - (embedding <=> $query::vector) > 0.45, ORDER BY embedding <=> $query.
All agents are stateless server functions calling OpenAI over the SDK. There is no autonomous multi-step tool-calling loop or agent framework — orchestration is explicit TypeScript. Each agent owns a domain-specific system prompt and, where relevant, RAG grounding + admin overrides.
src/app/api/chat/route.ts| Aspect | Detail |
|---|---|
| Purpose | Teach CMT concepts and analyse student charts, grounded in the platform's own notes. |
| Input | Message, level, last 8 turns of history, optional uploaded chart image (data URL). |
| Output | Streamed markdown in a fixed structure (What is X / Key Principles / Real-World Example / Exam Tips) or a chart-analysis structure. |
| Model | gpt-4o (vision-capable), temperature 0.4, max_tokens 1200, streamed. Memory updates on gpt-4o-mini. |
| Tools/data | RAG context (buildContext), note-diagram vision attachment (caption-matched, ≤2, detail:'low'), uploaded-chart vision (detail:'high'), admin Q&A "mandatory corrections", per-user memory, quiz-performance summary. |
| Decision-making | Prompt selects concept-explainer vs chart-analysis mode; pickVisionImages() scores note captions against the question; admin corrections override everything. |
| State/memory | Per-student memory profile (getUserMemory/updateUserMemory) persisted and re-injected. |
| Guardrails | Never give trading/investment advice; never invent facts; no LaTeX (plain Unicode formulas); no emojis; verify uploaded image is actually a chart before analysing. |
| Error handling | Vision request failure → retry once text-only; auth/limit errors → typed JSON. |
| Rate limits | Trial: TRIAL_SCHOLAR_DAILY/day; chart uploads: 15/day — both via Upstash. |
| Trigger | Student sends a message in the Scholar UI. |
src/app/api/public-chat/route.ts| Aspect | Detail |
|---|---|
| Purpose | Answer general CMT-program questions on the public homepage (lead-gen). |
| Grounding | RAG over admin-uploaded documents only (PublicBotSource → knowledge_chunks, searchPublicBotChunks). |
| Guardrails | Answer only from context; if unknown, direct to cmtassociation.org / sign-up; ≤300 words; plain-text formulas. Admin BotQAPair(botType='public') are mandatory facts. |
| Access | Public (unauthenticated), rate-limited. |
src/app/api/admin/generate-questions/route.tsAdmin-only. Drafts MCQ questions (with options/explanations) for a chapter/subtopic that admins review before publishing. Model: OpenAI. Output feeds the Question/QuestionOption authoring flow.
src/app/api/admin/chapter-summary/generate/route.tsAdmin-only. Drafts the six-section ChapterSummary (summary, key concepts, formulas, exam tips, high-yield, one-minute) for admin editing/publishing.
src/app/api/admin/notes/[id]/images/autolabel/route.tsAdmin-only. Uses vision to caption note diagrams so the tutor can later pick the right one for a student's question (captions are the matching key in pickVisionImages).
src/app/api/admin/studio/generate/route.tsAdmin-only. Generates platform-specific social carousel copy (Instagram/LinkedIn/X/Reddit; Beginner/Practitioner voice) as JSON (response_format: json_object, gpt-4o). Paired with client-side PNG export (src/lib/studio/export.ts) and FII/DII report images (src/lib/reports).
src/app/api/user/analytics/{coach,chat}/route.tsAuthenticated. Turns the student's quiz analytics into coaching guidance / a conversational view of their performance.
Agents don't call each other; they share the Postgres/pgvector layer. The tutor uniquely maintains state (student memory) that closes a feedback loop across sessions:
sequenceDiagram
participant S as Student
participant C as /api/chat
participant R as RAG (pgvector)
participant O as OpenAI gpt-4o
participant M as Memory (gpt-4o-mini)
S->>C: question (+ optional chart)
C->>R: embed + search notes
R-->>C: context text + diagram captions
C->>O: system prompt + context + vision images (stream)
O-->>S: streamed answer
C->>M: after() update student memory
M-->>C: persisted profile (used next time)
| Concern | Implementation |
|---|---|
| Provider | OpenAI only. |
| Models | gpt-4o (tutor/vision, public bot, generators, studio), gpt-4o-mini (MEMORY_MODEL, background memory), text-embedding-3-small (EMBEDDING_MODEL, 1536 dims). |
| Prompt architecture | Large hand-written system prompts with strict formatting + safety rules; RAG context and admin corrections appended in delimited sections; corrections are highest priority. |
| Structured output | JSON mode for Social Studio; the tutor/public bot emit structured markdown parsed by convention on the client. |
| Tool/function calling | Not used — retrieval, vision selection, and rate limiting are plain TypeScript around the model. |
| RAG | pgvector inside Neon. Chunk size 600 / overlap 80; cosine via 1 - (embedding <=> vector); similarity threshold 0.45; note images preserved through chunking via marker tokens so diagrams stay tied to their text. |
| Embeddings | text-embedding-3-small, stored as a vector(1536) column (knowledge_chunks). |
| Vision | Student chart uploads (detail:'high') and caption-matched note diagrams (detail:'low', ≤2) sent as image_url parts. |
| Streaming | Tutor streams via ReadableStream; other agents are request/response. |
| Context/token control | History capped at 8 turns; message ≤2000 chars; vision images capped in count and size; public bot ≤300 words. |
| Memory | Per-student profile updated post-answer on the cheap model, re-injected next time (never surfaced to the student). |
| Guardrails | Prompt-level: no trading advice, no invented facts, curriculum-only, plain-text formulas; admin "mandatory corrections" override model output. |
| Reliability | Vision→text-only retry; after() for non-blocking side effects; feedback capture () for later tuning. |
Route handlers live under src/app/api/**/route.ts. Responses follow a { success, data|error } convention; auth failures throw AuthError (mapped to 401/403/503).
| Group | Auth | Examples |
|---|---|---|
api/admin/* | requireAdminUser() (ADMIN) | chapters, subtopics, notes, questions, coupons, users, contacts, reports, studio, reports/fii-dii, generate-questions, chapter-summary/generate |
api/user/*, api/chat, api/quizzes/*, api/chapter-summary/* | requireAuthenticatedUser() | dashboard, analytics, trial-status, quiz engine, tutor |
api/payments/razorpay/* | authenticated + CSRF (order/verify); signature (webhook) | order, verify, webhook |
api/auth/* | mixed | [...nextauth], register, password-reset, session, logout |
api/cron/* | CRON_SECRET bearer | fii-dii-log, trial-drip, eod-update |
| Public | none (rate-limited) | public-chat, blog/subscribe, contact, fii-dii/, index-tools/ |
POST /api/chat
Content-Type: application/json
Cookie: <NextAuth session>
{ "message": "Explain RSI divergence", "level": "LEVEL_1", "history": [], "image": null }
Flow: auth → validate/limit → RAG + corrections + memory + performance (parallel) → build prompt → gpt-4o stream → text/plain token stream; memory updated via after().
POST /api/payments/razorpay/verify
{ "razorpay_order_id": "order_x", "razorpay_payment_id": "pay_y", "razorpay_signature": "sig" }
Flow: CSRF origin check → auth → HMAC verify (order|payment, key_secret) → ownership check → idempotent grant → invoice + welcome email → { success, data: { premiumUntil } }. Errors: 403 (origin), 400 (missing/invalid sig), 404 (foreign order), 500 (with "if charged, contact support").
POST /api/quizzes/start
{ "mode": "CHAPTER", "level": "LEVEL_1", "selection": { "chapterId": "..." } }
Creates a QuizAttempt with per-item snapshots; subsequent calls: /answer, /items/[itemId]/flag, /complete, /review.
directUrl for migrations, url (pooled) for runtime.knowledge_chunks.embedding vector(1536)), set up via SQL in sql//supabase-vector-setup.sql.cuid(). Heavy use of composite @@index for list/publish queries; content models use soft delete (isDeleted/deletedAt) + isPublished gating.User (role, premium, trial fields, passwordHash), PasswordResetToken (SHA-256 hash, expiry, single-use), UserActivity (engagement counters), Entitlement (per-chapter grant), Coupon.Chapter → Subtopic → Note (+ MediaAsset), ChapterSummary, SummaryBookmark.Question → QuestionOption, QuizAttempt → QuizAttemptItem (snapshots), QuestionReport, NoteReport.Payment (Razorpay + billing/GST fields) → Invoice.BotFeedback, BotQAPair, PublicBotSource, plus knowledge_chunks (raw SQL, pgvector).FiiDiiLog (cash), FnoLog (F&O OI).BlogPost, BlogSubscriber, ContactSubmission, CandidateFeedback, Index.erDiagram
User ||--o{ QuizAttempt : takes
User ||--o{ Payment : makes
User ||--o{ Entitlement : holds
User ||--|| UserActivity : has
Payment ||--|| Invoice : generates
Chapter ||--o{ Subtopic : contains
Subtopic ||--o{ Note : contains
Subtopic ||--o{ Question : has
Chapter ||--o| ChapterSummary : summarised_by
Question ||--o{ QuestionOption : has
QuizAttempt ||--o{ QuizAttemptItem : records
Question ||--o{ QuizAttemptItem : snapshotted_in
Chapter ||--o{ Entitlement : unlocks
Lifecycle & integrity: cascade deletes on ownership (onDelete: Cascade for user/attempt/payment children); quiz items snapshot the question + selected option so historical attempts are immutable; Entitlement is upserted (@@unique([userId, chapterId])) and extends on re-redeem; Invoice.number is a unique human ID (CHX-2026-0001).
Mechanism: NextAuth v5 (src/lib/auth/auth.ts), JWT session strategy (session.strategy = 'jwt').
Providers:
AUTH_GOOGLE_ID/SECRET) — first sign-in calls upsertOAuthUser() which creates the User, sets providerAccountId, and starts the 7-day trial.authorize() verifies the bcrypt passwordHash; registration at /api/auth/register.Session flow:
jwt callback enriches the token with dbId, role, isPremium, providerAccountId (DB lookup on first sign-in).session callback exposes those on session.user.next-auth/jwt decode, salt = cookie name) — no DB call — to gate /admin/* (role ADMIN) and /user/* (authenticated), redirecting to /sign-in otherwise.Authorization layers (defence in depth):
requireAuthenticatedUser() / requireAdminUser() — in every sensitive API handler and the admin layout; admin also requires SUPER_ADMIN_EMAIL to be configured (else 503).access.ts — content/feature access via trial/premium/entitlement.Password reset: token emailed; only its SHA-256 hash is stored (PasswordResetToken), single-use (consumedAt) and time-limited (expiresAt), so a DB leak can't reset passwords.
CSRF: state-changing payment routes call validateCsrfOrigin() (Origin allowlist via ALLOWED_ORIGINS).
flowchart TD
A[Sign in: Google or credentials] --> B[NextAuth jwt callback: role, isPremium, dbId]
B --> C[Encrypted JWT session cookie]
C --> D[Middleware decodes cookie on each request]
D -->|/admin & role=ADMIN| E[Allow]
D -->|/user & authed| E
D -->|else| F[Redirect /sign-in]
E --> G[API handler: requireAuthenticatedUser / requireAdminUser]
G --> H[access policy: trial / premium / entitlement]
1 User signs in with Google → 2 upsertOAuthUser creates User, sets trialStartedAt/trialExpiresAt, subscriptionStatus=TRIAL → 3 JWT issued → 4 /user dashboard shows trial state (/api/user/trial-status) → 5 trial-free chapters unlocked.
1 Open a note (access-checked) → 2 ask Scholar a question / upload a chart → 3 /api/chat runs RAG + memory + performance → 4 gpt-4o streams the answer with the relevant diagram embedded → 5 memory updated for next time.
sequenceDiagram
participant U as Student
participant Q as /api/quizzes/*
participant DB as Postgres
U->>Q: start (mode, selection)
Q->>DB: create QuizAttempt + item snapshots
loop each question
U->>Q: answer / flag
Q->>DB: upsert QuizAttemptItem
end
U->>Q: complete
Q->>DB: score, mark COMPLETED
U->>Q: review
Q-->>U: per-item correctness + explanations
1 Choose plan on /pricing → 2 /api/payments/razorpay/order creates Payment(CREATED) + Razorpay order → 3 Checkout modal → pay → 4 client /verify (HMAC + ownership + idempotent grant) and Razorpay /webhook (source of truth) → 5 Payment=PAID, premium granted, coupon bumped, 6 PDF invoice emailed + welcome email.
/api/redeem-coupon → validate Coupon (active, redemptions, chapters) → upsert Entitlement (extend expiry) → content unlocked without payment.
Admin creates Chapter → Subtopic → Note (TinyMCE), uploads diagrams to Cloudinary (signed), autolabels images, optionally AI-generates questions/summary, reviews, and publishes. Publishing/editing a note re-embeds it into knowledge_chunks for RAG.
fii-dii-log (13:00 UTC daily) → ingest daily FII/DII into FiiDiiLog.eod-update (13:00 UTC Mon–Fri) → end-of-day market update.trial-drip (03:30 UTC daily) → send the right trial-day nudge email (idempotent via UserActivity.lastDripDay).| Service | Purpose | Auth | Where |
|---|---|---|---|
| OpenAI | Tutor, public bot, generators, embeddings, memory | OPENAI_API_KEY | src/lib/ai/*, chat routes, admin generators |
| Neon Postgres | Primary DB (+ pgvector) | DATABASE_URL/DIRECT_URL | src/lib/db/prisma.ts |
| Razorpay | Payments (checkout, verify, webhook) | RAZORPAY_KEY_ID/SECRET, RAZORPAY_WEBHOOK_SECRET, public key | src/lib/payments, api/payments/razorpay/* |
| Cloudinary | Media uploads (signed) | CLOUDINARY_* | src/lib/cloudinary, api/admin/uploads/* |
| Resend | Transactional email | RESEND_API_KEY | src/lib/email, invoices, reset, drip, contact |
| Upstash Redis | Rate limiting (sliding window) | UPSTASH_REDIS_REST_URL/TOKEN | src/server/policies/rate-limit.ts |
| Google OAuth | Sign-in | AUTH_GOOGLE_ID/SECRET, AUTH_SECRET | src/lib/auth/auth.ts |
| GA4 Data API | Admin analytics | GA4_* | admin analytics |
| Yahoo Finance | Market data for tools (client-side, allowlisted in CSP) | none | /tools/* static apps |
Failure handling: provider calls are wrapped and fail toward a safe default — rate-limiter falls back to allowing when Upstash env is absent (build safety); chat retries text-only on vision failure; invoice/welcome-email failures are logged without blocking the grant; cron endpoints are idempotent.
Implemented:
src/middleware.ts): CSP (currently report-only while violations are tuned), HSTS (2-year, preload), X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy, and hard-blocking of AI crawlers + scraping libraries by User-Agent (returns 403; search/preview bots stay allowed).requireAuthenticatedUser/requireAdminUser in handlers + access policy; admin requires SUPER_ADMIN_EMAIL.isomorphic-dompurify sanitises rendered note/blog HTML; CSP locks script/frame/connect origins as a second layer.validateCsrfOrigin) on payment mutations.src/server/validators/*); image type/size checks before forwarding to OpenAI.poweredByHeader:false; Cloudinary uploads are signed server-side.Known gaps / posture notes (honest):
'unsafe-inline'/'unsafe-eval' (required by Next.js hydration + the inline-heavy static tool pages) — DOMPurify is the primary XSS defence, CSP is defence-in-depth.isPremium-only block is stubbed (commented TODO) in chat/route.ts.AuthError(statusCode) → consistent 401/403/503; handlers return { success:false, error:{message} } with appropriate HTTP status..catch(() => null) so they never block a chat.after() runs memory updates and engagement counters off the response path (Vercel won't kill them mid-flight the way it would a dangling promise).lastDripDay; FII/DII rows upserted by date PK.console.error with route tags ([chat/route], [payments/verify], …); optional Prisma query logging via LOG_PRISMA_QUERIES.serverExternalPackages) so DB access works under Vercel functions; connection pooling via Neon (DATABASE_URL pooled, DIRECT_URL for migrations).<=> with a similarity floor (0.45) and small chunks keeps retrieval cheap.Promise.all.optimizePackageImports for lucide-react/react-query; AVIF/WebP images via next/image + Cloudinary.public/ (cheap, cacheable) rather than rendered per request.Configure a .env.local (never commit real secrets). The variables the code actually reads (authoritative — the committed .env.example still references a legacy Supabase setup and is out of date):
# Database (Neon Postgres)
DATABASE_URL="postgresql://...pooled..."
DIRECT_URL="postgresql://...direct..." # migrations
# NextAuth v5
AUTH_SECRET="..."
AUTH_GOOGLE_ID="..."
AUTH_GOOGLE_SECRET="..."
NEXTAUTH_URL="https://chartix.in"
ALLOWED_ORIGINS="https://chartix.in,https://www.chartix.in"
SUPER_ADMIN_EMAIL="[email protected]" # or ADMIN_EMAIL / ADMIN_EMAILS
# OpenAI
OPENAI_API_KEY="sk-..."
# Payments (Razorpay)
RAZORPAY_KEY_ID="rzp_live_or_test..."
RAZORPAY_KEY_SECRET="..."
NEXT_PUBLIC_RAZORPAY_KEY_ID="..."
RAZORPAY_WEBHOOK_SECRET="..."
# Media (Cloudinary)
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="..."
CLOUDINARY_API_KEY="..."
CLOUDINARY_API_SECRET="..."
# Email (Resend)
RESEND_API_KEY="..."
CONTACT_FROM_EMAIL="..."
CONTACT_TO_EMAIL="..."
# Rate limiting (Upstash Redis)
UPSTASH_REDIS_REST_URL="..."
UPSTASH_REDIS_REST_TOKEN="..."
# Cron auth
CRON_SECRET="..."
# Editors & analytics
NEXT_PUBLIC_TINYMCE_API_KEY="..."
NEXT_PUBLIC_GA_ID="G-XXXXXXXXXX"
GA4_PROPERTY_ID="..."
GA4_CLIENT_EMAIL="..."
GA4_PRIVATE_KEY="..." # or GA4_PRIVATE_KEY_B64
NEXT_PUBLIC_SITE_URL="https://chartix.in"
⚠️ Never commit real values. The repo's
.env.examplepredates the Neon/NextAuth migration — treat the list above (derived fromprocess.env.*usage in code) as the source of truth.
git clone <repo-url>
cd chartix
npm install
.env.local with the variables in §17 (at minimum DATABASE_URL, DIRECT_URL, AUTH_SECRET, AUTH_GOOGLE_ID/SECRET, OPENAI_API_KEY).npm run prisma:generate
npm run prisma:migrate # apply migrations
Enable pgvector and create knowledge_chunks using the SQL in sql/ / supabase-vector-setup.sql.npm run db:seed
npm run dev # http://localhost:3000
role to ADMIN in the DB and ensure SUPER_ADMIN_EMAIL matches, then visit /admin.npm run lint
npm run format
npm run prisma:studio
Cron endpoints (
/api/cron/*) require aCRON_SECRETbearer; trigger them manually in dev with the header set. Payments need Razorpay test keys.
master (the production branch).npx prisma generate && next build (from vercel.json), install npm install.DATABASE_URL at runtime, DIRECT_URL for migrations.vercel.json hitting /api/cron/* (bearer CRON_SECRET).next.config.mjs).flowchart LR
Dev[git push master] --> V[Vercel build: prisma generate + next build]
V --> P[Production deployment]
P <-->|Prisma/Neon adapter| N[(Neon Postgres + pgvector)]
P --> OAI[OpenAI] & RZP[Razorpay] & CLD[Cloudinary] & RES[Resend] & UPS[Upstash]
VC[Vercel Cron] -->|CRON_SECRET| P
RZP -->|webhook| P
Honest state: there is no automated test framework in-repo (no Jest/Vitest/Playwright suite, no CI config committed). Quality is currently guarded by:
npm run lint) + Prettier.src/server/validators/*).check-both-notes.mjs, various *.sql and *_FIX.md verification notes under docs/).See §22 for the recommended test strategy.
Inferred from the code and in-repo docs (marked inferred where not explicitly documented):
BotQAPair injected at highest priority lets non-engineers fix wrong AI answers without redeploys.serverExternalPackages). (inferred.)(Not implemented — separated from the working system above.)
'unsafe-inline' where possible.BotFeedback..env.example to the real Neon/NextAuth variables; add sample Vercel/Neon setup docs and seed fixtures.flowchart TD
U[User / Admin] --> B[Browser: Next.js RSC + client]
B --> MW[Middleware: auth gate + CSP/HSTS + crawler block]
MW --> API[Route handlers /api/**]
API --> POL[Policies: auth · access · csrf · rate-limit]
POL --> SVC[Server services]
SVC --> DB[(Neon Postgres)]
API --> VEC[(pgvector: knowledge_chunks)]
subgraph Agents[AI agents - OpenAI]
SCH[Chartix Scholar gpt-4o + vision]
PUB[Public CMT bot]
GEN[Question / Summary generators]
STU[Social Studio generator]
MEM[Memory gpt-4o-mini]
end
API --> Agents
Agents --> VEC
subgraph Jobs[Vercel Cron]
J1[fii-dii-log]
J2[eod-update]
J3[trial-drip]
end
Jobs --> API
subgraph Providers[External]
RZP[Razorpay] --> API
API --> CLD[Cloudinary]
API --> RES[Resend]
API --> UPS[Upstash Redis]
API --> GA[GA4]
end
SVC --> B
API --> B
B --> U
In one paragraph: A student or admin hits the Next.js app; middleware gates the route (role + trial), applies security headers, and blocks scrapers. Route handlers run auth/access/CSRF/rate-limit policies, then call server services and Prisma against Neon Postgres. The AI tutor and other agents ground themselves in the same database via pgvector RAG and OpenAI, streaming answers under strict education-only guardrails while quietly maintaining per-student memory. Payments flow through Razorpay with an idempotent verify-plus-webhook grant that unlocks premium and emails a PDF invoice, while Vercel Cron jobs keep market data and trial nudges fresh — all shipped as a single Vercel deployment from the master branch.
Generated from source-code analysis of this repository. Where a detail lives outside the repo (Vercel/Neon dashboard config, exact SQL for the pgvector table) the README says so rather than guessing. CMT® and Chartered Market Technician® are registered trademarks of the CMT Association; Chartix is not affiliated with, endorsed by, or sponsored by the CMT Association.
| pdf-parse |
| Public-bot knowledge from PDFs |
| Sanitisation | isomorphic-dompurify | XSS defence on rendered HTML |
| Validation | Zod + react-hook-form + @hookform/resolvers | Input schemas, forms |
| Data fetching | @tanstack/react-query | Client cache/state |
| Studio export | html-to-image + jszip | Client-side PNG/zip carousel export |
| Report images | next/og ImageResponse (satori + resvg) | Server-rendered FII/DII report PNGs |
| Analytics | Vercel Analytics, GA4 (@next/third-parties, Data API) | Product + admin analytics |
| Hosting | Vercel (+ Vercel Cron) | Deploy, edge, scheduled jobs |
| Tooling | ESLint, Prettier | Lint/format |
BotFeedback| Cost control | Upstash rate limits (Scholar/day, chart uploads/day, public bot); cheap model for memory; low-detail note-image vision. |