Commit Graph

213 Commits

Author SHA1 Message Date
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 04e8377dab Show hint on lessons with pending homework or quiz
Lessons with homework/quiz have no manual complete button — they
auto-complete on submission. Before submission nothing was shown,
leaving students unsure how to progress. Add a muted hint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:22:44 +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 85cf8be705 Explicitly resend verification email on unverified login
Better Auth does not auto-resend on 403 (verified against Resend logs),
so request a fresh link via authClient.sendVerificationEmail before
telling the user a new email was sent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:05:32 +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 619ae393bd Allow /api/register through auth middleware
The Turnstile/honeypot wrapper /api/register was missing from
PUBLIC_ROUTES, so unauthenticated POSTs were redirected to /login (307).
The register form followed the redirect, got HTML, and failed to parse
it — surfacing as "connection error". This silently broke signups for
new users. Add /api/register alongside /api/auth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:26:19 +05:00
admins 8240db5d22 Make registration errors informative
- Backend: explicitly return 409 EMAIL_TAKEN when the email already
  exists, instead of Better Auth's silent 200 (common after migration).
- Frontend: split error handling — network failure, non-JSON body
  (502/proxy), and meaningful server errors get distinct messages; the
  "email already registered" case shows login / reset-password links
  instead of a generic "connection error".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:20:56 +05:00
admins 05f46ec830 Add "login as user" to user profile card
Extract ImpersonateButton into a shared component and reuse it on the
admin user profile page (header, hidden for admins) — same behaviour as
the users list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:55:57 +05:00
admins 3b8a1ce7ee Link question author name to their admin profile
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 10:53:01 +05:00
admins a290b9495c Enroll modal: days input instead of date; hide bundle if base course owned
- Quick-enroll modal now takes access duration in days (0/empty = unlimited)
  and shows the computed expiry date, instead of a manual date picker.
- Dashboard upsell no longer shows the "Всё включено" bundle of a course
  the student already owns in its base edition (obsidian/zotero).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:13:15 +05:00
admins 8c9f1e0a55 Add mobile spacer so admin content clears the hamburger button
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 10:01:58 +05:00
admins ffc5905309 Make admin/curator sidebar collapsible on mobile
The admin shell sidebar was always fixed at 208px with no mobile
handling, so on phones it covered half the screen with no way to hide
it. Add a hamburger toggle + overlay (mirroring the student course
sidebar): off-canvas on <lg, sticky as before on desktop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 09:58:15 +05:00
admins 3ed5540f40 Reduce blur on locked course cards for readability
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:06:32 +05:00
admins 7dc3412903 Add locked course cards (upsell) to student dashboard
Show the sellable course lineup on the dashboard: courses the student
doesn't own render as blurred cards with a lock and a CTA linking to the
product landing page (new tab). Owned courses are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:01:39 +05:00
admins 7c85a7835b Widen dashboard course cards and enlarge title/description text 2026-06-04 10:32:30 +05:00
admins ecf48de7e8 Copy code block verbatim (fences live in content for Obsidian snippets) 2026-06-03 15:34:15 +05:00
admins ecbca89cef Copy full markdown fence for language-tagged code blocks (dataview/query) 2026-06-03 15:23:27 +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 6624a0c535 Restore list markers (disc/decimal) for lesson content lists 2026-06-03 13:34:11 +05:00
admins a65125a239 Linkify URLs and preserve line breaks in question messages 2026-05-27 14:16:25 +05:00
admins 14b1eacf0d Switch homework pending filter from feedbacks-none to status-based (PENDING/REVIEWING) 2026-05-27 11:59:25 +05:00
admins d9e3531c49 Apply lesson cover as poster for embedded kinescopeVideo nodes 2026-05-26 16:40:47 +05:00
admins 5e5f616888 debug: move banner outside kinescopeId block to catch null ID 2026-05-26 16:22:18 +05:00
admins 4222834539 temp: visible debug banner for coverImage 2026-05-26 16:11:33 +05:00
admins 2525ca2b32 temp: debug coverImage value in lesson page 2026-05-26 15:49:47 +05:00
admins cfd3681979 Fix poster overlay hidden behind Kinescope iframe
Kinescope renders an iframe that ignores z-index stacking.
Switch to mutually-exclusive render: show poster image OR
the player, never both. Click on poster dismisses it and
reveals the player.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:40:23 +05:00
admins c2c2190cb6 Show custom lesson cover as video poster overlay
Kinescope React player ignores the poster prop. Replace with a
click-to-dismiss overlay rendered above the player: shows the
lesson coverImage, a play button, and on click hides itself
and calls player.play() via ref.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:35:39 +05:00
admins 6542f9b6b6 Add NEXT_PUBLIC_TURNSTILE_SITE_KEY build arg to Dockerfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 12:39:46 +05:00
admins 231ed083be Add anti-bot registration protection
- Custom /api/register route: honeypot, disposable email blocking, Cloudflare Turnstile verification
- Register form: honeypot hidden field, Turnstile widget (loads when NEXT_PUBLIC_TURNSTILE_SITE_KEY set)
- Install disposable-email-domains package
- Add NEXT_PUBLIC_TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY env vars to .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 12:35:06 +05:00
admins c1c33a0619 Make links in lesson content visually distinct (underline + bold)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:35:07 +05:00
admins 7e6ad8067c Add in-content video embed and image replace/delete controls
- KinescopeVideo TipTap node: insert video anywhere in lesson content via toolbar button; editor shows ID preview, student view renders KinescopePlayer
- ImageWithControls NodeView: hover over any image in editor to reveal Replace and Delete buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:27:52 +05:00
admins 287347168d Add cross-module lesson navigation and admin edit button
- Admin lesson editor now navigates across module boundaries (full course flat list)
- Student lesson view shows "Edit" button for admins linking to lesson editor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:05:46 +05:00
admins f3428420e2 Add migration for Session.impersonatedBy column 2026-05-25 16:01:57 +05:00
admins 38267fa432 Add staging environment plan and deploy script
Staging runs at staging.school.second-brain.ru on Hetzner port 3011.
Deploy script: ~/Documents/Claude/scripts/deploy-staging.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 15:13:26 +05:00
admins 30405e768b Show last login date on admin user detail page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 09:37:10 +05:00
admins 3b57b14d9b Add file attachments to questions (new question form + admin reply)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:26:17 +05:00
admins 367764b71e Expand /questions/new form: wider container, larger inputs, hint texts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:10:33 +05:00
admins acf7ee49aa Replace single-line reply input with resizable textarea in QuestionSplitView 2026-05-19 18:01:27 +05:00
admins 751c012f3d Add /admin/comments page with delete and pagination
- Add admin auth guard (redirect to /dashboard if not admin)
- Add delete-action.ts with deleteComment(commentId) soft-delete action
- Fix pagination label to show "Страница X из Y · Всего: N"
- Existing actions.ts, comments-table.tsx, and admin-nav entry preserved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:31:38 +05:00
admins 7084806aac Add search, filters, pagination to homework list
- Replace client HomeworkFilters component with server-side <form method="GET">
- Switch search param from `search` to `q`, add lesson title to search scope
- Change status filter from DB status field to feedback-count logic (pending=no feedbacks, reviewed=has feedbacks)
- Update pagination label to "Страница X из Y · Всего: N"
- Preserve all existing submission links and layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:29:47 +05:00
admins b2fa98051f Add quick enroll button to admin users table
Adds a "+ Доступ" button to each user row in the admin users table.
Clicking it opens a centered modal with a course dropdown, optional
expiry date, and a Server Action that upserts CourseEnrollment, logs
to AccessLog, and sends sendCourseAccessEmail on new enrollments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:27:57 +05:00
admins 4f5b5c535a Add search, filters, pagination to admin users table
- Add emailVerified filter (true/false/any) to UsersSearch component
- Wire emailVerified param through page searchParams, where clause, and pageUrl helper
- Preserve emailVerified in pagination links alongside existing search/role/balance params
- Update pagination label to "Страница X из Y · Всего: N"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:25:53 +05:00
admins e5ba94cb33 Fix security, transaction, and badge issues from final review
- Validate file URLs against S3 prefix in messages route (Fix 1)
- Guard attachment hrefs with https:// check in QuestionThread and QuestionSplitView (Fix 2)
- Wrap message create + updatedAt bump in prisma.$transaction (Fix 3)
- Add questionsBadge count query to curator layout for admin branch (Fix 4)
- Fire-and-forget email sends with void Promise.all (Fix 5)
- Wrap req.json() calls in try/catch returning 400 on parse failure (Fix 6)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:56:31 +05:00
admins 12e1785ff2 Send homework-updated email to staff on submission edit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:52:04 +05:00
admins bd1e77c2a3 Add questions nav links and admin unread badge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:49:18 +05:00
admins d2362a3f1e Fix QuestionSplitView error handling, loading state, file key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:47:35 +05:00
admins 3a2f64d47d Fix QuestionSplitView panel widths and message bubble styling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:45:57 +05:00