Userauth part9 bookmarks api#42
Conversation
- Create 002_auth_users.sql: users table with email/password, provider columns for future GitHub OAuth, auto-update trigger - Add auth config block (JWT_SECRET, JWT_EXPIRES_IN, bcryptRounds) - Add JWT_SECRET to production required env vars - Install jsonwebtoken and bcryptjs dependencies - Update .env.example with auth variables
- auth.js middleware: requireAuth (hard 401) and optionalAuth (soft attach) for JWT verification from Authorization: Bearer header - authService.js: registerUser, loginUser, getUserById with bcrypt hashing, email normalization, and sanitized user output - authController.js: HTTP handlers with input validation (email format, password length 8–128), proper error code propagation - Export query() from supabaseService.js for use by authService
- Create authRoutes.js: POST /register, POST /login, GET /me - Mount auth routes at /api/v1/auth in route index - Add auth endpoints to API documentation endpoint - Export requireAuth and optionalAuth from middleware index
- Add auth types and API service - Add AuthContext for managing user state and modal visibility - Add useAuth hook - Add LoginModal component with tabs for login and registration
- Create ProtectedRoute component for guarding routes - Create LoginPrompt component for inline auth gating - Guard the /audio route in App.tsx - Guard the notes and canvas panels in TranscriptDetail.tsx - Update Layout.tsx to show user avatar and logout button
- Auto-attach Authorization header in API service - Intercept 401 Unauthorized responses to clear token - Add auth endpoints to frontend config - Add authLimiter to protect backend auth routes
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
40 issues found across 60 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="backend/src/config/index.js">
<violation number="1" location="backend/src/config/index.js:70">
P1: JWT auth can be bypassed in any environment where `JWT_SECRET` is unset, because tokens are signed/verified with a hardcoded predictable fallback secret. Consider requiring `JWT_SECRET` (or generating a per-environment secret outside source control) instead of a static default.</violation>
</file>
<file name="backend/supabase/migrations/002_audiobook_seed.sql">
<violation number="1" location="backend/supabase/migrations/002_audiobook_seed.sql:56">
P1: Episodes 11-18 of the Bitcoin Beginners Guide playlist (Transactions, Outputs, Locks, Keys/Addresses, Private Keys, Public Keys, Digital Signatures, SegWit) all get the same wallet-themed audio file. Each topic clearly requires a distinct audio recording.</violation>
</file>
<file name="src/hooks/useNotes.ts">
<violation number="1" location="src/hooks/useNotes.ts:37">
P1: Switching accounts can show another user's cached notes because this query key is not user-scoped. Including `user?.id` in the notes query key (or clearing query cache on logout) prevents cross-account cache reuse.</violation>
<violation number="2" location="src/hooks/useNotes.ts:109">
P2: `updateNote` currently promises `color`/`position` updates that never reach the API, causing silent no-op writes for callers. Narrow the hook types to fields actually supported by `notesApi.update`, or add color/position support end-to-end.</violation>
</file>
<file name="backend/src/controllers/authController.js">
<violation number="1" location="backend/src/controllers/authController.js:19">
P1: Login can fail with 500 on malformed payloads because `email`/`password` types are not validated before `authService.loginUser`. Validating both fields as non-empty strings here preserves expected 400 behavior for bad input.</violation>
<violation number="2" location="backend/src/controllers/authController.js:19">
P2: Register can return a server error for malformed `password` types because the controller never enforces `password` as a string before calling `authService.registerUser`. Adding explicit type checks here keeps invalid payloads in the 400 validation path.</violation>
</file>
<file name="src/components/notes/TranscriptNotes.tsx">
<violation number="1" location="src/components/notes/TranscriptNotes.tsx:65">
P1: Pending-text handling can cause state updates while rendering, which risks render loops and `Cannot update a component while rendering a different component` warnings. Moving this block into a `useEffect` tied to `pendingSelectedText`/`consumedText` keeps updates in the post-render lifecycle.</violation>
<violation number="2" location="src/components/notes/TranscriptNotes.tsx:326">
P3: Filtered searches can show an empty pane without feedback because the empty-state condition is based on `activeNotesList` instead of the filtered `notes` array. A separate `notes.length === 0` no-results state would make search/filter behavior clear.</violation>
</file>
<file name="src/hooks/useWhiteboard.ts">
<violation number="1" location="src/hooks/useWhiteboard.ts:15">
P1: Switching to a transcript with no saved whiteboard can show and persist the previous transcript’s graph because state is only updated when localStorage has data. Adding an explicit empty-state reset in the no-data path keeps transcript data isolated.</violation>
</file>
<file name="src/components/ProtectedRoute.tsx">
<violation number="1" location="src/components/ProtectedRoute.tsx:33">
P1: Users can be redirected away immediately instead of being allowed to authenticate, because redirect and modal-open effects share the same condition. Consider tracking whether this route opened the modal and redirecting only after that modal is later closed without a user.</violation>
</file>
<file name="backend/src/routes/index.js">
<violation number="1" location="backend/src/routes/index.js:24">
P1: The API root docs endpoint becomes auth-protected because bookmarksRoutes is mounted at `/` and applies `requireAuth` globally. Mounting this router at root changes `/api/v1/` behavior (and other unmatched paths) from public docs/404 flow to 401; consider scoping bookmarks/highlights routes so auth middleware only applies to those endpoints.</violation>
</file>
<file name="src/components/whiteboard/TranscriptWhiteboard.tsx">
<violation number="1" location="src/components/whiteboard/TranscriptWhiteboard.tsx:31">
P1: Concept nodes can be dropped during initial load because node-sync runs before notes data is ready. The effect currently gates only on `isLoaded`, so it can treat the temporary empty notes state as deletions and rewrite the whiteboard state; consider also gating on `isNotesLoading`.</violation>
<violation number="2" location="src/components/whiteboard/TranscriptWhiteboard.tsx:74">
P2: Delete Selected currently sends note deletions for every selected node id, not only concept/note nodes. If non-concept nodes are present, this path can issue invalid delete requests; filtering by node type before calling `deleteNote` would prevent that.</violation>
<violation number="3" location="src/components/whiteboard/TranscriptWhiteboard.tsx:138">
P3: `fitView` timer is never cleaned up, so rapid node-count changes can queue multiple delayed animations and fire after unmount. Returning a cleanup to clear the timeout would make this effect deterministic.</violation>
</file>
<file name="backend/src/services/authService.js">
<violation number="1" location="backend/src/services/authService.js:40">
P1: Concurrent register requests for the same email can return a 500 instead of `EMAIL_EXISTS`/409. The separate existence check plus plain `INSERT` races on the unique email constraint, and the resulting DB error is normalized to `DATABASE_ERROR`; an `ON CONFLICT` registration path would keep behavior deterministic.</violation>
</file>
<file name="src/components/notes/NoteCard.tsx">
<violation number="1" location="src/components/notes/NoteCard.tsx:86">
P2: Note actions are hidden on small screens, so mobile users may be unable to access edit/pin/delete unless they tab-focus invisible controls. The container starts at `opacity-0` for all sizes; use mobile-first visible styles and apply hover-only hiding from `sm` upward.</violation>
</file>
<file name="src/components/LoginModal.tsx">
<violation number="1" location="src/components/LoginModal.tsx:189">
P2: The password visibility toggle is skipped by keyboard navigation because `tabIndex={-1}` is applied. Leaving it focusable (and optionally adding an aria-label) keeps this control usable for non-pointer users.</violation>
</file>
<file name="src/components/notes/NoteEditor.tsx">
<violation number="1" location="src/components/notes/NoteEditor.tsx:41">
P2: Editing a second note without closing the editor can keep the first note’s draft values, so updates may be applied with stale content. The local state is derived from `editingNote` only on initial render and is not reset when `editingNote` changes.</violation>
</file>
<file name="services/notesService.ts">
<violation number="1" location="services/notesService.ts:35">
P2: Note color selections are lost after save/reload because the service hardcodes every mapped note to `slate` and does not persist color in request payloads. Consider preserving/storing `color` in the API contract (or removing color editing UI until backend support exists) so note styling remains consistent.</violation>
<violation number="2" location="services/notesService.ts:50">
P3: This service bypasses centralized endpoint constants by hardcoding the notes path, which increases drift risk when routes change. Using `config.endpoints.notes` would align with existing service conventions and keep endpoint management in one place.</violation>
</file>
<file name="src/contexts/AuthContext.tsx">
<violation number="1" location="src/contexts/AuthContext.tsx:70">
P1: A delayed failed token validation can log out a user right after successful login because this catch always clears auth state. Guard the clear so it only runs when the token being validated is still current.</violation>
<violation number="2" location="src/contexts/AuthContext.tsx:79">
P2: After a 401, the app can still look logged in until refresh because the API client removes the token directly but AuthContext only syncs auth state on mount. Consider wiring a shared logout/unauthorized signal so token removal also clears `user` in context.</violation>
</file>
<file name="services/api.ts">
<violation number="1" location="services/api.ts:121">
P2: After a 401 outside `/audio`, the app can keep a stale authenticated UI state until manual refresh because only that route forces re-sync. Consider using a route-agnostic auth-state sync path instead of a hardcoded `/audio` reload condition.</violation>
</file>
<file name="backend/src/controllers/notesController.js">
<violation number="1" location="backend/src/controllers/notesController.js:10">
P3: UUID validation regex is duplicated in another controller, which makes future format/validation updates easy to miss in one path. Reusing a shared validator/util constant would keep note and highlight/bookmark ID validation behavior consistent.</violation>
<violation number="2" location="backend/src/controllers/notesController.js:21">
P2: Filtering notes by `transcript_id` can return a server error for malformed/repeated query params because the controller forwards `req.query.transcript_id` without checking it is a single non-empty string. Adding explicit query validation here keeps invalid input on the 400 path instead of surfacing a DB error.</violation>
</file>
<file name="backend/src/routes/notesRoutes.js">
<violation number="1" location="backend/src/routes/notesRoutes.js:23">
P2: Notes endpoints currently bypass route-level `express-validator` middleware, so requests hit controllers without the project’s standardized validation/sanitization step. Consider adding `validationRules` + `validate` in this router (especially for `:id` and create/update payloads) to keep behavior consistent with backend conventions.</violation>
</file>
<file name="src/pages/TranscriptDetail.tsx">
<violation number="1" location="src/pages/TranscriptDetail.tsx:45">
P2: Deep links to `?tab=notes` don't open the notes experience because `rightTab` always starts as `"none"`. Initializing sidebar/focus state from `initialTab` (or syncing in an effect) keeps URL tab behavior consistent.</violation>
</file>
<file name="backend/supabase/migrations/002_auth_users.sql">
<violation number="1" location="backend/supabase/migrations/002_auth_users.sql:21">
P3: User writes will maintain two email indexes with no additional lookup benefit because `email` is already `UNIQUE`. Dropping `idx_users_email` avoids unnecessary index storage and write overhead.</violation>
<violation number="2" location="backend/supabase/migrations/002_auth_users.sql:33">
P2: This migration can fail on re-apply because `trg_users_updated_at` is created unconditionally. Adding a `DROP TRIGGER IF EXISTS ... ON users` before `CREATE TRIGGER` keeps migration behavior consistent and repeatable.</violation>
</file>
<file name="services/authService.ts">
<violation number="1" location="services/authService.ts:14">
P2: Auth requests now use hardcoded endpoint strings instead of the shared config endpoint map, so route updates can silently break only this service. Using `config.endpoints.auth.*` here would keep auth routing consistent with the rest of the centralized API configuration.</violation>
</file>
<file name="src/pages/Library.tsx">
<violation number="1" location="src/pages/Library.tsx:437">
P2: Editing a note from the library triggers a full page reload, which resets in-memory UI/query state and makes navigation feel slower than the rest of the app. This comes from using `window.location.href` in `onEdit`; routing through React Router navigation keeps SPA behavior consistent.</violation>
</file>
<file name="backend/src/controllers/bookmarksController.js">
<violation number="1" location="backend/src/controllers/bookmarksController.js:95">
P2: Highlights over 500 chars are not actually capped at 500 because the truncation appends `...` after slicing to 500. Keeping the stored value at 500 max (or slicing to 497 before ellipsis) would make behavior match the API limit.</violation>
</file>
<file name="backend/src/middleware/rateLimiter.js">
<violation number="1" location="backend/src/middleware/rateLimiter.js:92">
P2: The barrel export in `middleware/index.js` re-exports `authLimiter` but is missing `userDataLimiter`, even though both were added in this change. Routes importing via the barrel will not find `userDataLimiter`, and future developers may not discover it exists through the index.</violation>
</file>
<file name="backend/src/routes/authRoutes.js">
<violation number="1" location="backend/src/routes/authRoutes.js:18">
P2: Authentication can start failing with 429 after repeated profile fetches because `/me` is counted in the same strict limiter bucket as `/login` and `/register`. Consider scoping `authLimiter` to the brute-force targets (login/register) and leaving `/me` under the general/user-data limits.</violation>
</file>
<file name="src/components/notes/NotesSearchBar.tsx">
<violation number="1" location="src/components/notes/NotesSearchBar.tsx:26">
P2: The search field may be hard to identify for assistive technology users because it relies on placeholder text instead of an explicit accessible name. Adding an `aria-label` (or visible label) gives the control a reliable name for screen readers.</violation>
</file>
<file name="backend/supabase/migrations/003_user_data.sql">
<violation number="1" location="backend/supabase/migrations/003_user_data.sql:24">
P2: Transcript-scoped note reads filter by both `user_id` and `transcript_id`, but the new index is only on `transcript_id`. A composite index on `(user_id, transcript_id)` would better match the actual query path and reduce per-user filtering work as note volume grows.</violation>
<violation number="2" location="backend/supabase/migrations/003_user_data.sql:43">
P3: This migration comment states `audiobooks.user_progress` already uses UUID `user_id` with a FK to `users(id)`, but current schema still keeps `user_id` as `TEXT` without that FK. Keeping this inaccurate note can mislead future migrations and debugging, so aligning the comment with the real schema (or adding the missing migration) would avoid drift.</violation>
</file>
<file name="src/components/Layout.tsx">
<violation number="1" location="src/components/Layout.tsx:277">
P3: The new Profile menu row appears clickable but has no behavior, which creates a dead-end interaction in the account menu. Wiring it to a route/action or marking it disabled would avoid misleading users.</violation>
<violation number="2" location="src/components/Layout.tsx:289">
P2: Mobile users lose the direct sign-in affordance in the header because the new auth button is hidden below `sm`. Showing the same trigger on mobile (or adding an equivalent item in the mobile menu) would keep authentication access consistent across breakpoints.</violation>
</file>
<file name="backend/src/services/bookmarksService.js">
<violation number="1" location="backend/src/services/bookmarksService.js:36">
P3: This service adds an exported helper that is never used, so it increases maintenance surface without affecting behavior. Consider removing `isBookmarked` (or wiring it into a real code path) to keep this module focused and easier to evolve.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| // Authentication (JWT) | ||
| auth: { | ||
| jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production', |
There was a problem hiding this comment.
P1: JWT auth can be bypassed in any environment where JWT_SECRET is unset, because tokens are signed/verified with a hardcoded predictable fallback secret. Consider requiring JWT_SECRET (or generating a per-environment secret outside source control) instead of a static default.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/config/index.js, line 70:
<comment>JWT auth can be bypassed in any environment where `JWT_SECRET` is unset, because tokens are signed/verified with a hardcoded predictable fallback secret. Consider requiring `JWT_SECRET` (or generating a per-environment secret outside source control) instead of a static default.</comment>
<file context>
@@ -65,6 +65,13 @@ const config = {
+ // Authentication (JWT)
+ auth: {
+ jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production',
+ jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
+ bcryptRounds: 12,
</file context>
| 'How it all began — Satoshi Nakamoto''s whitepaper, the cypherpunk movement, and the creation of the first decentralized digital currency.', | ||
| 1, | ||
| NULL, | ||
| 'https://duqjdsbziertijeycmbk.supabase.co/storage/v1/object/public/audiobooks/Bitcoin%20Beginners%20Guide/Why_Bitcoin_wallets_hold_no_digital_coins%20(1).m4a', |
There was a problem hiding this comment.
P1: Episodes 11-18 of the Bitcoin Beginners Guide playlist (Transactions, Outputs, Locks, Keys/Addresses, Private Keys, Public Keys, Digital Signatures, SegWit) all get the same wallet-themed audio file. Each topic clearly requires a distinct audio recording.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/supabase/migrations/002_audiobook_seed.sql, line 56:
<comment>Episodes 11-18 of the Bitcoin Beginners Guide playlist (Transactions, Outputs, Locks, Keys/Addresses, Private Keys, Public Keys, Digital Signatures, SegWit) all get the same wallet-themed audio file. Each topic clearly requires a distinct audio recording.</comment>
<file context>
@@ -53,7 +53,7 @@ VALUES
'How it all began — Satoshi Nakamoto''s whitepaper, the cypherpunk movement, and the creation of the first decentralized digital currency.',
1,
- NULL,
+ 'https://duqjdsbziertijeycmbk.supabase.co/storage/v1/object/public/audiobooks/Bitcoin%20Beginners%20Guide/Why_Bitcoin_wallets_hold_no_digital_coins%20(1).m4a',
372,
'pending',
</file context>
|
|
||
| // ─── Query ───────────────────────────────────────────────── | ||
| const { data: notes = [], isLoading } = useQuery<Note[]>({ | ||
| queryKey: NOTES_QUERY_KEY, |
There was a problem hiding this comment.
P1: Switching accounts can show another user's cached notes because this query key is not user-scoped. Including user?.id in the notes query key (or clearing query cache on logout) prevents cross-account cache reuse.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/useNotes.ts, line 37:
<comment>Switching accounts can show another user's cached notes because this query key is not user-scoped. Including `user?.id` in the notes query key (or clearing query cache on logout) prevents cross-account cache reuse.</comment>
<file context>
@@ -0,0 +1,147 @@
+
+ // ─── Query ─────────────────────────────────────────────────
+ const { data: notes = [], isLoading } = useQuery<Note[]>({
+ queryKey: NOTES_QUERY_KEY,
+ queryFn: () => notesApi.getAll(),
+ enabled: !!user,
</file context>
| const { email, password, name } = req.body; | ||
|
|
||
| // Input validation | ||
| if (!email || !password) { |
There was a problem hiding this comment.
P1: Login can fail with 500 on malformed payloads because email/password types are not validated before authService.loginUser. Validating both fields as non-empty strings here preserves expected 400 behavior for bad input.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/controllers/authController.js, line 19:
<comment>Login can fail with 500 on malformed payloads because `email`/`password` types are not validated before `authService.loginUser`. Validating both fields as non-empty strings here preserves expected 400 behavior for bad input.</comment>
<file context>
@@ -0,0 +1,84 @@
+ const { email, password, name } = req.body;
+
+ // Input validation
+ if (!email || !password) {
+ throw new APIError('Email and password are required', 400, 'VALIDATION_ERROR');
+ }
</file context>
|
|
||
| // Auto-open editor when pendingSelectedText arrives | ||
| const [consumedText, setConsumedText] = useState<string | undefined>(undefined) | ||
| if (pendingSelectedText && pendingSelectedText !== consumedText) { |
There was a problem hiding this comment.
P1: Pending-text handling can cause state updates while rendering, which risks render loops and Cannot update a component while rendering a different component warnings. Moving this block into a useEffect tied to pendingSelectedText/consumedText keeps updates in the post-render lifecycle.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/notes/TranscriptNotes.tsx, line 65:
<comment>Pending-text handling can cause state updates while rendering, which risks render loops and `Cannot update a component while rendering a different component` warnings. Moving this block into a `useEffect` tied to `pendingSelectedText`/`consumedText` keeps updates in the post-render lifecycle.</comment>
<file context>
@@ -0,0 +1,359 @@
+
+ // Auto-open editor when pendingSelectedText arrives
+ const [consumedText, setConsumedText] = useState<string | undefined>(undefined)
+ if (pendingSelectedText && pendingSelectedText !== consumedText) {
+ setConsumedText(pendingSelectedText)
+ setActiveTab('notes')
</file context>
| ); | ||
|
|
||
| -- Indexes | ||
| CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); |
There was a problem hiding this comment.
P3: User writes will maintain two email indexes with no additional lookup benefit because email is already UNIQUE. Dropping idx_users_email avoids unnecessary index storage and write overhead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/supabase/migrations/002_auth_users.sql, line 21:
<comment>User writes will maintain two email indexes with no additional lookup benefit because `email` is already `UNIQUE`. Dropping `idx_users_email` avoids unnecessary index storage and write overhead.</comment>
<file context>
@@ -0,0 +1,36 @@
+);
+
+-- Indexes
+CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+CREATE INDEX IF NOT EXISTS idx_users_provider ON users(provider, provider_id);
+
</file context>
| import { asyncHandler, APIError } from '../middleware/errorHandler.js'; | ||
| import * as notesService from '../services/notesService.js'; | ||
|
|
||
| const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; |
There was a problem hiding this comment.
P3: UUID validation regex is duplicated in another controller, which makes future format/validation updates easy to miss in one path. Reusing a shared validator/util constant would keep note and highlight/bookmark ID validation behavior consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/controllers/notesController.js, line 10:
<comment>UUID validation regex is duplicated in another controller, which makes future format/validation updates easy to miss in one path. Reusing a shared validator/util constant would keep note and highlight/bookmark ID validation behavior consistent.</comment>
<file context>
@@ -0,0 +1,96 @@
+import { asyncHandler, APIError } from '../middleware/errorHandler.js';
+import * as notesService from '../services/notesService.js';
+
+const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+
+/**
</file context>
| <DropdownMenuItem className="cursor-pointer"> | ||
| Profile | ||
| </DropdownMenuItem> |
There was a problem hiding this comment.
P3: The new Profile menu row appears clickable but has no behavior, which creates a dead-end interaction in the account menu. Wiring it to a route/action or marking it disabled would avoid misleading users.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/Layout.tsx, line 277:
<comment>The new Profile menu row appears clickable but has no behavior, which creates a dead-end interaction in the account menu. Wiring it to a route/action or marking it disabled would avoid misleading users.</comment>
<file context>
@@ -241,11 +251,52 @@ export const Layout = ({ children }: { children: React.ReactNode }) => {
+ <span className="text-xs truncate max-w-full">{user.email}</span>
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem className="cursor-pointer">
+ Profile
+ </DropdownMenuItem>
</file context>
| <DropdownMenuItem className="cursor-pointer"> | |
| Profile | |
| </DropdownMenuItem> | |
| <DropdownMenuItem disabled className="text-muted-foreground"> | |
| Profile | |
| </DropdownMenuItem> |
| )} | ||
|
|
||
| {/* Empty state */} | ||
| {!isLoading && activeNotesList.length === 0 && !isCreating && ( |
There was a problem hiding this comment.
P3: Filtered searches can show an empty pane without feedback because the empty-state condition is based on activeNotesList instead of the filtered notes array. A separate notes.length === 0 no-results state would make search/filter behavior clear.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/notes/TranscriptNotes.tsx, line 326:
<comment>Filtered searches can show an empty pane without feedback because the empty-state condition is based on `activeNotesList` instead of the filtered `notes` array. A separate `notes.length === 0` no-results state would make search/filter behavior clear.</comment>
<file context>
@@ -0,0 +1,359 @@
+ )}
+
+ {/* Empty state */}
+ {!isLoading && activeNotesList.length === 0 && !isCreating && (
+ <div className="text-center py-12 space-y-3">
+ <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-secondary">
</file context>
| */ | ||
| getAll: async (transcriptId?: string): Promise<Note[]> => { | ||
| const qs = transcriptId ? `?transcript_id=${encodeURIComponent(transcriptId)}` : ''; | ||
| const rows = await api.get<NoteRow[]>(`/api/v1/notes${qs}`); |
There was a problem hiding this comment.
P3: This service bypasses centralized endpoint constants by hardcoding the notes path, which increases drift risk when routes change. Using config.endpoints.notes would align with existing service conventions and keep endpoint management in one place.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/notesService.ts, line 50:
<comment>This service bypasses centralized endpoint constants by hardcoding the notes path, which increases drift risk when routes change. Using `config.endpoints.notes` would align with existing service conventions and keep endpoint management in one place.</comment>
<file context>
@@ -0,0 +1,96 @@
+ */
+ getAll: async (transcriptId?: string): Promise<Note[]> => {
+ const qs = transcriptId ? `?transcript_id=${encodeURIComponent(transcriptId)}` : '';
+ const rows = await api.get<NoteRow[]>(`/api/v1/notes${qs}`);
+ return rows.map(rowToNote);
+ },
</file context>
Summary by cubic
Adds JWT authentication and user-scoped Notes, Bookmarks, and Highlights APIs, then wires the frontend with AuthContext, LoginModal, and protected routes. Audiobook progress now ties to real users; includes DB migrations, rate limiting, and UI for notes and a concept whiteboard.
New Features
/api/v1/auth: register, login, me (JWT withjsonwebtoken, hashed passwords withbcryptjs)requireAuthandoptionalAuthmiddleware;authLimiteranduserDataLimiter/api/v1/notes/api/v1/bookmarks,/api/v1/highlightsJWT_SECRET,JWT_EXPIRES_IN;queryexport for shared DB accessusers,notes,bookmarks,highlights(+ indexes/triggers)AuthProvider,useAuth,LoginModal,ProtectedRoute; API auto-attaches Authorization header and handles 401suseNotes, editor/cards/search, transcript Notes panel, Library Notes tab@xyflow/reactMigration
JWT_SECRETandJWT_EXPIRES_INinbackend/.env(generate a strong secret)Written for commit ebf650e. Summary will update on new commits.