Commit Graph

50 Commits

Author SHA1 Message Date
admins 1d5b4f9251 Add API key generator and Zotero script builder 2026-07-06 11:46:31 +05:00
admins 6316c09993 Add PDF HTML template with typography, themes and TOC 2026-07-06 11:40:24 +05:00
admins 9a74339087 Block v4-mapped IPv6 literals in SSRF validator 2026-07-06 11:37:05 +05:00
admins 2c619be043 Add SSRF URL validator for clean-pdf 2026-07-06 11:28:11 +05:00
admins f8f246ca9b Add store catalog config for dashboard upsell checkout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:35:30 +05:00
admins 7c8e72cd94 Add email notification on new lesson comment
Notify admins/curators (or configured notificationEmails) when a student
leaves a comment under a lesson. New setting notifyOnComment (default on) +
toggle in admin settings. Fires only for student comments, not admin/curator
replies; comment text is HTML-escaped in the email.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 10:02:07 +05:00
admins 6317dade9e Add Dataview and Bases generators (Toolbox Phase 2)
Two more inside-LMS generators under /tools, completing the 6-tool set:
- Dataview: DQL builder (TABLE/LIST/TASK/CALENDAR, FROM/WHERE/SORT/LIMIT),
  output wrapped in a dataview code block
- Bases: .base YAML builder (views, filters and:, order, sort, groupBy, limit);
  filter expressions emitted as single-quoted YAML scalars for safety

Syntax verified against official Obsidian docs (research workflow). Audit fix:
Bases sort entries use 'column:' (not 'property:') per documented working .base
files; 'property:' is kept only for groupBy. 14 new tests (47 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:04:09 +05:00
admins b6d555ab3b Add Obsidian Toolbox — 4 syntax generators under /tools
Inside-LMS toolbox for registered students: callout-CSS, YAML-frontmatter,
theme CSS-variables, Style Settings generators. Auth inherited from (student)
route group; no public surface, no email gate.

- Pure generators in src/lib/tools/* with Vitest (33 tests)
- Shared YAML-safe scalar quoter (_shared/yaml.ts) used by frontmatter +
  style-settings: unquoted user input was silently breaking YAML (tags '#x'
  -> null, color default '#7C3AED' -> null, 'a: b' -> parse error)
- hex validation in callout (no NaN), date input constrained, clipboard +
  analytics calls guarded
- Prisma ToolUsage model + migration; logToolUsage Server Action (auth-first,
  10-min dedup window per user/tool)
- Tool index /tools + ToolCard (explicit lucide map, no import *) + header link

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:45:52 +05:00
admins 06b8d9f64b Add Obsidian Wrapped feature (inside-LMS, client-side vault parsing)
- WrappedRun Prisma model + migration (aggregates only, no note names/text)
- /wrapped authed route: FSA + webkitdirectory + drag-drop reader, Web Worker parser
- archetype + score heuristics, ДС-2 result card
- POST /api/wrapped/run (userId from session), public /share/wrapped/[token]
- next/og OG image (Satori+resvg-wasm, alpine-safe) + Fira Mono ttf
- middleware: /share/wrapped public

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:19:59 +05:00
admins 7bf3935c81 Add freemium variant C: archetype persistence, dashboard banner, lesson gate
- User.archetype/utmSource/utmMedium/utmCampaign fields + migration
- register flow reads ?archetype/?utm_* and saves them on the User row
- dashboard ArchetypeBanner: per-archetype 'твой путь' block
- lesson gate after wow-moment lesson (WOW_MOMENT_LESSON_ID=obs-start-l2-006) → tripwire + full-course offer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:26:34 +05:00
admins 9fbb7aea6c Accept CDN URLs for question attachments
Attachments uploaded via question-upload get a CDN URL
(files.second-brain.ru via S3_CDN_URL), but the validator only accepted
the direct S3 endpoint prefix — so every attachment was silently dropped
(message text saved, file lost). Add isAllowedPublicUrl (CDN + direct S3)
in lib/s3 and use it in both question routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 15:11:36 +05:00
admins 2436f77de5 Add 'approve without feedback' button for homework review
Curators/admins can mark a submission APPROVED (and complete the lesson)
without writing feedback. The student sees 'ДЗ принято ✓' and can no
longer resubmit. No email is sent on silent approval.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:10:29 +05:00
admins 27027b134f Use CDN URL for public file links when S3_CDN_URL is set
getPublicUrl returns the CDN URL (files.second-brain.ru via Bunny) when
S3_CDN_URL is configured, falling back to the direct S3 endpoint. This
was a manual patch on the build server that was never committed —
restore it so new uploads also get CDN-backed (RU-accessible) URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:46:31 +05:00
admins 5d4630519c Force download for lesson materials (Content-Disposition: attachment)
Lesson files were served inline (no Content-Disposition) → browsers
opened PDFs in a new tab instead of downloading; flaky across
browsers/providers. Set Content-Disposition: attachment with the
human-readable filename (RFC 5987 for Cyrillic) on upload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:06:51 +05:00
admins a7abf454d9 Add internal grant endpoint + temp-password / force-change flow
- /api/internal/grant: secret-auth endpoint for payment-router to
  provision access on a paid order — find-or-create user (emailVerified,
  temp password, mustChangePassword), enroll by course slug list,
  AccessLog, delivery email. Idempotent by order_id.
- User.mustChangePassword flag (+ migration); (student) layout redirects
  flagged users to /change-password (forced first-login change).
- email.ts: sendCourseGrantEmail (temp password for new buyers).
- middleware: /api/internal is public (own secret auth).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:25:35 +05:00
admins 5a67ac8086 Close captcha-bypass and email-abuse vectors
- Block direct /api/auth/sign-up at the middleware (404): it bypassed
  the Turnstile/honeypot wrapper. /api/register is unaffected — it
  invokes auth.handler programmatically, not through HTTP.
- Tighten rate limits on send-verification-email and forget-password
  (3/min/IP): both send emails to arbitrary addresses and shared the
  loose global 100/min limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:18:30 +05:00
admins 2790a7d24b Wire up email verification flow for new signups
requireEmailVerification was enabled but no sendVerificationEmail was
configured — new users saw "check your email", never received anything,
and couldn't log in (403 shown as "wrong email or password").

- email.ts: add verification email template; welcome email no longer
  claims the account is "confirmed"
- auth.ts: configure emailVerification (send on signup, resend on
  unverified login attempt, auto sign-in after verification, 24h TTL)
- register route: callbackURL=/dashboard so the verify link lands in
  the cabinet
- login form: distinguish 403 (unverified — tell user a fresh link was
  sent) and 429 (rate limit) from wrong credentials

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:01:46 +05:00
admins cf942d223e Mark lesson complete on homework submit; add copy button + styling to code blocks 2026-06-03 14:58:30 +05:00
admins adc1d6fc06 Loosen sign-in rate limit to 10/min per IP for migration onboarding 2026-06-03 14:03:11 +05:00
admins a65125a239 Linkify URLs and preserve line breaks in question messages 2026-05-27 14:16:25 +05:00
admins a9e6272d2d Fix API routes: closed-question guard, file validation, files sanitization, follow-up email
- Add CLOSED status guard in messages POST (returns 409)
- Add extension allowlist check in upload route + text/x-markdown MIME type
- Sanitize files JSON array before DB write
- Add sendQuestionFollowUpEmail helper and use it for student follow-up replies
- Scope email field to staff only in questions list query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:28:08 +05:00
admins 9cb56b9b04 Add question and homework-update email helpers 2026-05-19 13:20:06 +05:00
admins 444b9c0faf Apply tech debt fixes: middleware rename, React.cache, file size limits, remove dead deps
- Rename proxy.ts → middleware.ts, export proxy() → middleware() so Next.js edge
  protection actually activates (F001)
- Add PUBLIC_ROUTES entries for /forgot-password and /reset-password
- Wrap getSettings() in React.cache() to eliminate duplicate DB call in root layout (F003)
- Remove 4 console.log calls from saveLesson Server Action, keep console.error (F005)
- Add 50 MB file size guard to all 6 upload routes before arrayBuffer() read (F004)
- Remove unused deps: @tailwindcss/typography, shadcn, tw-animate-css (F008)
- Update CLAUDE.md: Prisma version 6.x → 7.x
- Add TECH_DEBT_AUDIT.md with 14 findings across 9 dimensions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 19:35:05 +05:00
admins a5e7b20699 Add forgot-password and reset-password flow
Users can now request a password reset link via email. Better Auth
sendResetPassword callback sends a branded email via Resend. Login
page shows success notice after password is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 11:04:06 +05:00
admins 7888a7598b Add coverImage poster to player, fix TipTap v3 editor reset, quiz admin preview
- Add coverImage field to Lesson model (prisma)
- Pass coverImage as poster prop to KinescopePlayer
- Show quiz in read-only preview mode for admin on lesson page
- Fix TipTap v3 editor reset on save: pass [lesson.id] as deps to useEditor
  to prevent setOptions() from reinitializing content on every re-render
- Replace saveLesson Server Action call with fetch PATCH /api/admin/lessons/[id]
  to avoid Next.js 16 automatic RSC refresh after Server Actions
- Simplify revalidatePath: only revalidate module page, not lesson editor page
2026-05-01 13:26:30 +00:00
admins c25369b766 Add threaded comment replies for admin and curator
- Add parentId field to LessonComment (self-referential FK, SetNull on delete)
- Show replies indented under parent comment with left border visual
- Add reply button (visible to admin/curator only) with inline textarea
- Only root comments shown in main list; replies nested below their parent
- Update comment count to include replies
- Server-side validation: only admin/curator can reply, parent must belong to same lesson

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 13:17:30 +05:00
admins c64f393a7b Implement platform settings (Stage 9)
- Wire settings to actual platform behavior: maintenance mode, registration toggle,
  notification emails, curator feedback emails, email verification flag
- Add logo (logoUrl, showLogo) and social network links (YouTube, VK, Telegram) settings
- Show logo + school name dynamically in student layout header
- Add footer to student layout with org requisites and social links
- Register page: read settings server-side, validate terms checkbox with legal links
- Login page: show notice when redirected from closed registration
- Settings form: add Logo and Social Networks sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 15:31:10 +05:00
admins d2150153df Add quiz feature: student UI, admin editor, lesson page integration
- QuizSection component: shows questions as text inputs, read-only after submission
- QuizEditor component: admin CRUD for quiz questions with type selector
- saveQuiz/deleteQuiz server actions for admin
- submitQuizAttempt server action: idempotent, auto-marks lesson complete
- Student lesson page: renders QuizSection, updates complete button logic
- Admin lesson page: renders QuizEditor below homework section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 11:43:16 +05:00
admins bfa037885f Fix saveLesson: sanitize content JSON to prevent RSC proxy error
React treats TipTap's editor.getJSON() output as a non-plain object when
passed through Server Action serialization, causing isDecimal() inside
Prisma's query builder to receive a temporary client reference proxy and
throw ERROR 1213974697. JSON.parse(JSON.stringify()) strips the prototype
chain and any non-enumerable properties, ensuring Prisma receives a
clean plain object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 17:58:13 +05:00
admins 8757537344 Debug: add try/catch and JSON sanitize in saveLesson
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 17:54:10 +05:00
admins af1fb6f61e Fix RSC toStringTag error: import PrismaClient from generated TS client
Use @/generated/prisma/client instead of @prisma/client to avoid
Turbopack creating a broken external proxy for the missing
.prisma/client/default module at runtime. Add @prisma/adapter-pg
to serverExternalPackages, remove unused resolveAlias.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 15:08:09 +05:00
admins 29f6533e63 Switch to prisma-client-js generator to fix Turbopack RSC crash
The new prisma-client generator outputs TypeScript files to src/generated/prisma/
which include import.meta.url at module level. Turbopack sees this and marks the
entire module as a client reference, causing 'Cannot access toStringTag on the
server' on every page that uses Prisma.

Switching to prisma-client-js puts the generated client in node_modules/@prisma/client
where serverExternalPackages can properly exclude it from the server bundle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 14:32:31 +05:00
admins 5dfa79d357 Fix all Server Actions imported from dynamic route paths
All admin and student Client Components were importing Server Actions
from paths with dynamic segments ([courseId], [moduleId], [lessonId], [slug]).
This caused "Cannot access toStringTag on the server" RSC crash.

Consolidated all Server Actions into static files under src/lib/actions/:
- course-actions.ts  (modules + enrollment)
- module-actions.ts  (lessons + reorder + move)
- user-actions.ts    (bulk grant / revoke)
- student-actions.ts (progress + homework + comments)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 14:05:26 +05:00
admins 9eb21e3ab4 Move Server Actions to static paths to fix RSC temporary client reference error
Server Actions imported from dynamic route paths ([courseId]/[moduleId]/[lessonId])
caused "Cannot access toStringTag on the server" crash after saving a lesson.
Moved saveLesson, saveHomework, deleteHomework to src/lib/actions/*.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 13:58:27 +05:00
admins 58a61d6f04 Fix settings: catch DB errors at build time, return defaults
getSettings() and getSetting() now fall back to defaults when the
database is unavailable (e.g., during Docker image build).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 12:23:26 +05:00
admins e77588deb8 Add platform settings (Stage 9)
- Settings key-value table in Prisma with migration
- getSettings() / getSetting() helpers in lib/settings.ts
- Admin UI at /admin/settings with 6 sections: General, Notifications,
  Student profile, Legal docs, Curator permissions, Code injection
- saveSettings() server action with admin-only guard
- Maintenance mode: non-admin users redirected to /maintenance page
- schoolName propagated to page metadata and all email templates
- headCode / bodyCode injected into root layout <head> and <body>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:18:37 +05:00
admins 66b311f17e Polish email template: white outer bg, beige card, Arial font
- Outer background: #FFFFFF
- Card background: #F5F5F0 (beige)
- All text/buttons: Arial/Helvetica (renders consistently on mobile)
- Shadow via table wrapper technique (works in Gmail Android)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:05:47 +05:00
admins 32b0fa9d6f Rewrite email template with inline styles for mobile compatibility
- All styles moved to inline attributes (no <style> block)
- Table-based layout for Gmail/Outlook/mobile client compatibility
- Aubade card effect via border-right/border-bottom:4px
- Monospace font stack with web-safe fallbacks
- btn() and quote() helper functions rewritten as <table> elements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 17:35:30 +05:00
admins c647b29712 Add Markdown import from Obsidian (Stage 8)
- md-to-tiptap.ts: remark-based converter (headings, lists, blockquotes,
  code blocks, bold/italic/strike, links, images, hr)
- Obsidian ![[wikilink]] stripped, [[link|alias]] → plain text
- POST /api/admin/import-md: parses frontmatter (gray-matter) + converts content
- LessonEditor: "Импорт .md" button populates editor without auto-save
- ROADMAP: marked Stages 2, 3, 5, 6, 7, 8 as complete, fixed numbering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:44:42 +05:00
admins b40d518b74 Fix Resend lazy init to avoid build-time API key error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 14:48:14 +05:00
admins 6975a9f97e Add email notifications via Resend
- src/lib/email.ts: HTML templates for 4 email types (Second Brain design)
- Welcome email on user registration (Better Auth databaseHooks)
- Course access email when admin grants enrollment
- Homework submitted email to all admins/curators (first submission only)
- Feedback received email to student with feedback text and lesson link
- Update TECHNICAL.md: Resend domain, from-address, email vars, Stage 3 summary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 14:46:46 +05:00
admins 9d82b73e58 feat: initial commit 2026-04-07 11:30:38 +05:00
admins da46f84ab8 Add S3 client for Hetzner Object Storage 2026-04-07 11:27:18 +05:00
admins 4e6ab6fd2b Configure Better Auth to use bcrypt for password hashing 2026-04-07 11:07:47 +05:00
admins c3aefa8a0f Fix auth client baseURL — use window.location.origin auto-detect 2026-04-07 11:01:49 +05:00
admins 1153bcc13a Add Prisma 7 PostgreSQL adapter
Prisma 7 requires explicit database adapter for runtime connections.
- Install @prisma/adapter-pg + pg
- Update prisma.ts: use PrismaPg adapter with DATABASE_URL
- Update seed.ts: same adapter pattern
- Dockerfile: copy pg deps to runner stage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:48:08 +05:00
admins 0c88dcc535 Fix Prisma 7 client instantiation
In Prisma 7, connection URL is managed via prisma.config.ts.
PrismaClient constructor no longer accepts log options directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:46:43 +05:00
admins d603b32ef5 Fix Prisma 7 import: use /client suffix
Prisma 7 no longer generates index.ts — entry point is client.ts.
Import changed from @/generated/prisma to @/generated/prisma/client.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:45:18 +05:00
admins 024cf7adac Fix build errors for Next.js 16 + Prisma 7
- Fix Prisma import: use @/generated/prisma alias (resolves Turbopack issue)
- Replace middleware.ts with proxy.ts (Next.js 16 convention)
- middleware function renamed to proxy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:40:59 +05:00
admins 80ca4b2d9d Initialize Stage 0: Next.js 16 scaffold with auth and role-based routing
- Next.js 16.2.2 + React 19 + TypeScript + Tailwind v4
- Better Auth with email/password and role system (student/curator/admin)
- Prisma 7 schema: User, Session, Account, Verification + full LMS model
- Role-based dashboards: student /dashboard, curator /curator/dashboard, admin /admin/dashboard
- Auth pages: login, register, verify-email
- Better Auth API route handler
- Middleware for route protection
- Docker Compose with PostgreSQL 16
- Seed script with test users (admin/curator/student)
- CLAUDE.md and ROADMAP.md project documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:32:37 +05:00