Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Stage 1: Auth, Questions, and Project Foundation - #1

Merged
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions
Jul 13, 2026
Merged

Stage 1: Auth, Questions, and Project Foundation#1
MorTab1000 merged 26 commits into
mainfrom
feature/stage-1-auth-questions

Conversation

@MorTab1000

@MorTab1000MorTab1000 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers Stage 1 of IVOverflow on feature/stage-1-auth-questions: a working login flow, JWT-protected question APIs, and a React frontend to browse, ask, and view questions.

This PR also establishes the monorepo foundation (Docker Postgres, Prisma, CI, Husky, and backend API tests) that later stages build on.

Infrastructure & DevOps

  • Monorepo layout (client/ + server/) with PostgreSQL via Docker Compose
  • Prisma schema, migrations, and seeded users (SHA-512 passwords)
  • GitHub Actions CI: lint, test, and build for both packages
  • Husky + lint-staged pre-commit formatting/linting

Backend

  • JWT auth middleware and Stage 1 REST endpoints: /login, /userInfo, /createQuestion, /getQuestions, /getQuestionAnswer
  • Shared password hashing utility and Express app extraction for testability
  • 18 Vitest + Supertest API tests (mocked Prisma — no DB required in CI)

Frontend

  • Redux Toolkit auth slice with JWT persistence
  • RTK Query API layer with automatic JWT injection and 401 handling
  • Login page, protected routes, questions list, Ask Question modal, and question detail page
  • Reusable UI primitives (Button, TextField, TextArea, Modal) and typed API models

Test plan

  • docker compose up -d and cd server && npx prisma migrate dev && npx prisma db seed
  • cd server && npm test — all API tests pass
  • cd server && npm run dev and cd client && npm run dev
  • Log in with seeded user (e.g. alice@ivtech.dev / password123)
  • Create a question with tags and confirm it appears on the list and detail pages
  • Confirm unauthenticated access redirects to /login
  • Confirm CI passes on this PR

Notes

  • Answer creation and voting are intentionally out of scope (Stage 2 & 3).
  • GET /getQuestionAnswer returns an empty answers array until answers are implemented.

Summary by CodeRabbit

  • New Features

    • Added login, persistent authentication, protected navigation, logout, and user profile access.
    • Added question listing, detail views, question creation, tags, validation, and loading/error states.
    • Added accessible reusable form controls, buttons, dialogs, and responsive page styling.
    • Added backend health, authentication, and question endpoints.
  • Tests

    • Added automated coverage for health, authentication, authorization, and question workflows.
  • Documentation

    • Expanded architecture and project progress documentation.
  • Chores

    • Improved linting and CI workflows, including automated server test execution.

MorTab1000and others added 26 commits July 12, 2026 22:20
Implements Stage 1's remaining question-browsing UI: QuestionList/
QuestionListItem/TagBadge render the fetched questions, and
AskQuestionForm + AskQuestionModal (built on a new Modal primitive)
let a user submit a new question via useCreateQuestionMutation, with
the list auto-refreshing through RTK Query tag invalidation. The
header's "Ask question" trigger is shared with the routed page via
ProtectedRoute's Outlet context, since AppHeader and the page are
siblings rather than parent/child.
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change delivers Stage 1 authentication and question functionality across the server and client, including JWT-based access, Prisma-backed endpoints, Redux/RTK Query integration, protected routing, login and question interfaces, server tests, CI test execution, and updated architecture/task documentation.

Changes

Stage 1 application

Layer / File(s)Summary
Server application and authentication
server/src/app.ts, server/src/routes/auth.ts, server/src/middleware/*, server/src/utils/*, server/src/lib/*
Adds Express app wiring, JWT authentication, password helpers, Prisma access, protected user information, and login handling.
Question API routes
server/src/routes/questions.ts
Adds authenticated question creation, listing, and question-answer retrieval endpoints.
Server testing and execution tooling
server/tests/*, server/package.json, server/vitest.config.ts, server/tsconfig.eslint.json, .github/workflows/ci.yml
Adds Vitest/Supertest coverage, Prisma mocks, expanded linting, test scripts, and CI test execution.
Client contracts, store, and API access
client/src/types/*, client/src/app/*, client/src/api/*
Adds shared API models, Redux store configuration, auth headers, logout handling, and RTK Query endpoints.
Login and protected application shell
client/src/App.tsx, client/src/main.tsx, client/src/pages/login-page.*, client/src/components/auth/*, client/src/components/layout/*, client/src/features/auth/*
Adds login submission, persisted credentials, protected routing, authenticated layout, user display, and logout behavior.
Question pages and reusable UI
client/src/pages/questions-page.*, client/src/pages/question-detail-page.*, client/src/components/questions/*, client/src/components/ui/*
Adds question listing, details, creation modal/form, validation states, reusable controls, and component styling.
Workflow and architecture alignment
architecture.md, todo.md, AGENT.md, .gitignore, client/package.json
Updates architecture, task status, workflow instructions, repository ignores, and client lint tooling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant User
participant LoginPage
participant RTKQuery
participant Server
participant ReduxStore
User->>LoginPage: Submit email and password
LoginPage->>RTKQuery: Execute login mutation
RTKQuery->>Server: POST /api/login
Server-->>RTKQuery: Return token and user
RTKQuery-->>ReduxStore: Dispatch credentials
ReduxStore-->>LoginPage: Authenticated state
LoginPage-->>User: Navigate to protected questions page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.12% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately summarizes the main Stage 1 scope: authentication, questions, and project foundation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stage-1-auth-questions

Comment @coderabbitai help to get the list of available commands.

@MorTab1000
MorTab1000 marked this pull request as ready for review July 13, 2026 11:45
CopilotAI review requested due to automatic review settings July 13, 2026 11:45

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MorTab1000
MorTab1000 merged commit 0bd6d55 into mainJul 13, 2026
4 of 5 checks passed
@MorTab1000
MorTab1000 deleted the feature/stage-1-auth-questions branch July 13, 2026 11:54

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/app.ts (1)

1-19: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Add a central async error handler for the Prisma routes

  • server/src/app.ts: register a final 4-arg error-handling middleware after the routers, or wrap async routes so rejected Prisma calls reach it.
  • server/src/routes/auth.ts and server/src/routes/questions.ts: the await prisma... calls are unguarded; a rejection here is not forwarded by Express 4 and can leave the request unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 1 - 19, Update server/src/app.ts around the
Express app setup to ensure rejected async route promises reach a final
four-argument error middleware, registering it after authRouter and
questionsRouter; update the await prisma calls in server/src/routes/auth.ts
lines 24-37 and server/src/routes/questions.ts lines 27-37 to forward rejections
through that handler, preserving successful response behavior.
🧹 Nitpick comments (6)
server/src/utils/password.ts (1)

1-9: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unsalted SHA-512 is not suitable for production password storage long-term.

No per-user salt means identical passwords produce identical hashes, and SHA-512 is fast enough to brute-force offline. Based on learnings, this is the explicitly approved Stage 1 scope (hardcoded users + SHA-512), so no change is needed now — just flagging for a future stage to migrate to bcrypt/argon2 with per-user salts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/password.ts` around lines 1 - 9, No code changes are
required in hashPassword or verifyPassword for this approved Stage 1
implementation; retain the current hardcoded-user SHA-512 behavior and record
migration to salted bcrypt or argon2 for a future stage.

Source: Learnings

server/src/utils/jwt.ts (1)

4-4: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Validate JWT_EXPIRES_IN at startup instead of blind type-casting.

Casting the env value with as SignOptions["expiresIn"] bypasses compile-time safety; an invalid value (e.g. a typo) will only surface as a runtime throw inside signToken on the first login request, not at process startup.

Per web search guidance on this exact pattern: "Always validate environment variables that affect security (like JWT expiration)" and "Fail early (at startup) rather than during request handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 4, Replace the blind cast in JWT_EXPIRES_IN
with startup validation that parses and verifies the environment value is an
accepted SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
client/src/features/auth/authSlice.ts (1)

17-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restored token isn't checked for expiration on load.

Given the "one-hour expiration" JWT policy, a token persisted from a stale session will be treated as valid (selectIsAuthenticated → true) immediately on app load, letting protected UI render until the first API call 401s and triggers logout. Decoding the JWT payload's exp claim here and rejecting expired tokens would close this window.

♻️ Suggested expiration check
 function loadStoredAuth(): AuthState {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return { token: null, user: null };
const parsed = JSON.parse(raw) as StoredAuth;
if (!parsed.token || !parsed.user) return { token: null, user: null };
+ const payload = JSON.parse(atob(parsed.token.split(".")[1] ?? "")) as { exp?: number };+ if (payload.exp && payload.exp * 1000 <= Date.now()) {+ return { token: null, user: null };+ }+
return { token: parsed.token, user: parsed.user };
} catch {
// Corrupted/blocked storage (e.g. private browsing) — fall back to signed-out.
return { token: null, user: null };
}
}

As per coding guidelines: "Use JWT authentication with a one-hour expiration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/features/auth/authSlice.ts` around lines 17 - 30, Update
loadStoredAuth to validate the restored token’s JWT exp claim before returning
authenticated state. Decode the token payload, reject malformed or expired
tokens by returning the signed-out AuthState, and preserve the existing
valid-token restoration path.

Source: Coding guidelines

client/src/api/baseApi.ts (1)

11-22: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a request timeout.

fetchBaseQuery has no default timeout and will hang until the browser's own timeout (~5 min) if the backend stalls, leaving isLoading true with no feedback. A small timeout option gives users a bounded failure state.

♻️ Optional timeout
 const rawBaseQuery = fetchBaseQuery({
baseUrl: "/api",
+ timeout: 15000,
prepareHeaders: (headers, { getState }) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/api/baseApi.ts` around lines 11 - 22, Configure a small request
timeout in the fetchBaseQuery options used to create rawBaseQuery, while
preserving the existing baseUrl and prepareHeaders authentication behavior.
Ensure stalled backend requests fail within the bounded timeout so callers leave
the loading state.
client/src/app/store.ts (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setupListeners(store.dispatch) for future-proofing.

RTK Query's official pattern recommends calling setupListeners(store.dispatch) in the store setup so refetchOnFocus/refetchOnReconnect work if enabled later on any endpoint. Not required today since no endpoint currently opts into those behaviors, but it's cheap to add now and avoids a silent no-op trap later.

♻️ Optional addition
 import { configureStore } from "`@reduxjs/toolkit`";
+import { setupListeners } from "`@reduxjs/toolkit/query`";
import { baseApi } from "../api/baseApi";
import authReducer from "../features/auth/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware),
});
++setupListeners(store.dispatch);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/app/store.ts` around lines 5 - 11, Update the store setup around
the exported store and import RTK Query’s setupListeners utility, then call
setupListeners with store.dispatch after configureStore completes so future
refetchOnFocus and refetchOnReconnect options work as expected.
client/src/components/questions/QuestionListItem.tsx (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated tag-list rendering into a shared component. Both sites implement the identical conditional tag-list block (guard on tags.length > 0, map to <li key={tag}><TagBadge tag={tag} /></li>).

  • client/src/components/questions/QuestionListItem.tsx#L29-L37: replace this block with a shared <TagList tags={question.tags} /> component.
  • client/src/pages/question-detail-page.tsx#L49-L57: replace this block with the same shared <TagList tags={question.tags} /> component.
♻️ Proposed shared component
// client/src/components/questions/TagList.tsximportTagBadgefrom"./TagBadge";importstylesfrom"./TagList.module.css";// or reuse an existing shared classexportinterfaceTagListProps{tags: string[];}exportdefaultfunctionTagList({ tags }: TagListProps){if(tags.length===0)returnnull;return(<ulclassName={styles.tags}>{tags.map((tag)=>(<likey={tag}><TagBadgetag={tag}/></li>))}</ul>);}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/questions/QuestionListItem.tsx` around lines 29 - 37,
Extract the duplicated tag rendering into a shared TagList component that
preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@architecture.md`:
- Line 154: Update the folder-tree fenced code block in architecture.md to
specify a text language identifier, using ```text or ```plaintext, while
preserving its existing contents.
- Line 493: Revisit the JWT storage decision in the “JWT storage” architecture
entry before production: prefer an HttpOnly, Secure, SameSite cookie-based flow
instead of localStorage, or explicitly document the accepted XSS/token-theft
risk and compensating controls. Do not leave the decision marked resolved
without recording the chosen approach and its security rationale.
- Around line 158-160: Update the later Project Structure section to remove the
stale client/src/store/ entries and reflect the actual client/src/app/store.ts
and client/src/app/hooks.ts layout, matching the app folder map shown near the
configureStore and typed hook descriptions.
- Around line 143-144: Update the Ask Question documentation to show that the
modal is rendered from both `/` and `/questions/:id`, matching the routing
behavior owned by ProtectedRoute. Update the related component-breakdown entry
as well so state and mutation ownership remain attributed to the page flow
rather than a standalone route. Keep the Question Detail entry consistent with
the same route coverage.
- Around line 379-384: Update the server dependency-installation checklist entry
in todo.md to use npm ci instead of npm install, matching the commands
documented in architecture.md and the CI workflow. Preserve the remaining
checklist steps and wording.
In `@client/package.json`:
- Line 27: Update the eslint-plugin-react-hooks dependency in
client/package.json from the 5.x range to a compatible 6.x release, keeping the
existing dependency declaration format and aligning it with the React 19.2
toolchain.
In `@client/src/components/auth/LoginForm.tsx`:
- Around line 31-42: Update validate() to explicitly validate the trimmed email
against the expected email format when noValidate is enabled, setting
errors.email for invalid non-empty values while preserving the required-field
check. Ensure invalid formats prevent submission and continue using the existing
field-error display path.
In `@client/src/components/questions/QuestionList.module.css`:
- Around line 7-18: Update the color declarations in the .status and
.statusError classes to use darker colors that achieve at least a 4.5:1 contrast
ratio against the white background, while preserving the existing spacing,
alignment, and typography.
In `@client/src/components/ui/Modal.tsx`:
- Around line 11-59: Update Modal to manage keyboard focus while open: move
focus to an appropriate focusable element inside the dialog when it opens, trap
Tab and Shift+Tab within the dialog’s focusable elements, and restore focus to
the previously focused element when it closes. Keep the existing Escape handling
and modal rendering behavior, and scope the changes to the Modal component so
all consumers inherit the fix.
In `@client/src/components/ui/TextArea.tsx`:
- Around line 25-32: Update the textarea markup to generate a stable
error-message id from textareaId, reference it through aria-describedby only
when error is present, and assign the same id to the rendered error paragraph.
Add role="alert" to the error message while preserving the existing conditional
rendering and styling.
In `@client/src/components/ui/TextField.tsx`:
- Around line 18-19: Update the input and error rendering in TextField to
associate validation messages with the field: assign the error paragraph a
stable id derived from inputId and set the input’s aria-describedby to that id
when error is present. Preserve aria-invalid behavior and avoid referencing an
error element when no error exists.
In `@server/package.json`:
- Line 29: Update the `@types/express` dependency in server/package.json from the
5.x range to ^4.17.21, keeping it aligned with the existing express@^4.21.2
dependency.
In `@server/src/app.ts`:
- Around line 8-9: Update the middleware setup in app.ts to restrict CORS to the
application’s configured or trusted origin instead of using unrestricted cors(),
and add Helmet middleware alongside the existing express.json() setup so
standard security headers are applied. Reuse the project’s existing
configuration symbols for the allowed origin if available.
In `@server/src/utils/jwt.ts`:
- Line 3: Update the JWT_SECRET initialization in jwt.ts to remove the hardcoded
fallback and fail immediately when JWT_SECRET is unset, ensuring token signing
and verification cannot proceed with a known secret.
In `@todo.md`:
- Line 29: Update the “Scaffold React app with Redux Toolkit + RTK Query”
checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.
---
Outside diff comments:
In `@server/src/app.ts`:
- Around line 1-19: Update server/src/app.ts around the Express app setup to
ensure rejected async route promises reach a final four-argument error
middleware, registering it after authRouter and questionsRouter; update the
await prisma calls in server/src/routes/auth.ts lines 24-37 and
server/src/routes/questions.ts lines 27-37 to forward rejections through that
handler, preserving successful response behavior.
---
Nitpick comments:
In `@client/src/api/baseApi.ts`:
- Around line 11-22: Configure a small request timeout in the fetchBaseQuery
options used to create rawBaseQuery, while preserving the existing baseUrl and
prepareHeaders authentication behavior. Ensure stalled backend requests fail
within the bounded timeout so callers leave the loading state.
In `@client/src/app/store.ts`:
- Around line 5-11: Update the store setup around the exported store and import
RTK Query’s setupListeners utility, then call setupListeners with store.dispatch
after configureStore completes so future refetchOnFocus and refetchOnReconnect
options work as expected.
In `@client/src/components/questions/QuestionListItem.tsx`:
- Around line 29-37: Extract the duplicated tag rendering into a shared TagList
component that preserves the empty-list guard and TagBadge mapping. In
client/src/components/questions/TagList.tsx, add the component and its styles or
reuse an existing shared class; replace the blocks in
client/src/components/questions/QuestionListItem.tsx lines 29-37 and
client/src/pages/question-detail-page.tsx lines 49-57 with TagList
tags={question.tags}, updating imports in both files.
In `@client/src/features/auth/authSlice.ts`:
- Around line 17-30: Update loadStoredAuth to validate the restored token’s JWT
exp claim before returning authenticated state. Decode the token payload, reject
malformed or expired tokens by returning the signed-out AuthState, and preserve
the existing valid-token restoration path.
In `@server/src/utils/jwt.ts`:
- Line 4: Replace the blind cast in JWT_EXPIRES_IN with startup validation that
parses and verifies the environment value is an accepted
SignOptions["expiresIn"] value, while retaining the "1h" default. Fail
immediately with a clear configuration error when validation fails, so signToken
only receives a validated expiration setting.
In `@server/src/utils/password.ts`:
- Around line 1-9: No code changes are required in hashPassword or
verifyPassword for this approved Stage 1 implementation; retain the current
hardcoded-user SHA-512 behavior and record migration to salted bcrypt or argon2
for a future stage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f36ab6e-ed4d-4130-a739-e75f8ea5f8c1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2a353 and 2012bd7.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENT.md
  • architecture.md
  • client/package.json
  • client/src/App.tsx
  • client/src/api/authApi.ts
  • client/src/api/baseApi.ts
  • client/src/api/questionsApi.ts
  • client/src/app/hooks.ts
  • client/src/app/store.ts
  • client/src/components/.gitkeep
  • client/src/components/auth/LoginForm.module.css
  • client/src/components/auth/LoginForm.tsx
  • client/src/components/layout/AppHeader.module.css
  • client/src/components/layout/AppHeader.tsx
  • client/src/components/layout/ProtectedRoute.tsx
  • client/src/components/questions/AskQuestionForm.module.css
  • client/src/components/questions/AskQuestionForm.tsx
  • client/src/components/questions/AskQuestionModal.tsx
  • client/src/components/questions/QuestionList.module.css
  • client/src/components/questions/QuestionList.tsx
  • client/src/components/questions/QuestionListItem.module.css
  • client/src/components/questions/QuestionListItem.tsx
  • client/src/components/questions/TagBadge.module.css
  • client/src/components/questions/TagBadge.tsx
  • client/src/components/ui/Button.module.css
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Modal.module.css
  • client/src/components/ui/Modal.tsx
  • client/src/components/ui/TextArea.module.css
  • client/src/components/ui/TextArea.tsx
  • client/src/components/ui/TextField.module.css
  • client/src/components/ui/TextField.tsx
  • client/src/features/auth/authSlice.ts
  • client/src/main.tsx
  • client/src/pages/.gitkeep
  • client/src/pages/login-page.module.css
  • client/src/pages/login-page.tsx
  • client/src/pages/question-detail-page.module.css
  • client/src/pages/question-detail-page.tsx
  • client/src/pages/questions-page.module.css
  • client/src/pages/questions-page.tsx
  • client/src/store/.gitkeep
  • client/src/types/answer.ts
  • client/src/types/api.ts
  • client/src/types/auth.ts
  • client/src/types/question.ts
  • client/src/types/user.ts
  • client/src/utils/format-date.ts
  • client/src/utils/get-error-message.ts
  • server/eslint.config.mjs
  • server/package.json
  • server/prisma/seed.ts
  • server/src/app.ts
  • server/src/index.ts
  • server/src/lib/prisma.ts
  • server/src/middleware/.gitkeep
  • server/src/middleware/auth.ts
  • server/src/routes/.gitkeep
  • server/src/routes/auth.ts
  • server/src/routes/questions.ts
  • server/src/types/express.d.ts
  • server/src/utils/jwt.ts
  • server/src/utils/password.ts
  • server/tests/auth.test.ts
  • server/tests/health.test.ts
  • server/tests/questions.test.ts
  • server/tests/setup.ts
  • server/tsconfig.eslint.json
  • server/vitest.config.ts
  • todo.md

Comment threadarchitecture.md
Comment on lines +143 to +144
| Ask Question | _modal on `/`_ | 1 | Overlay form: title, body, tags — not a standalone route |
| Question Detail | `/questions/:id` | 1–3 | Question + answers + vote UI |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the Ask Question documentation with its implementation.

The table limits the modal to /, while the component breakdown says questions-page.tsx owns its state and mutation. client/src/components/layout/ProtectedRoute.tsx, Lines 13-66, owns both and renders the modal from / and /questions/:id; update both entries to prevent future routing/ownership regressions.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 143 - 144, Update the Ask Question
documentation to show that the modal is rendered from both `/` and
`/questions/:id`, matching the routing behavior owned by ProtectedRoute. Update
the related component-breakdown entry as well so state and mutation ownership
remain attributed to the page flow rather than a standalone route. Keep the
Question Detail entry consistent with the same route coverage.

Comment threadarchitecture.md

CSS Modules are co-located with their component (`Component.tsx` + `Component.module.css`), omitted below for brevity.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the folder-tree fence.

markdownlint reports MD040 on this fence. Use ```text (or ```plaintext) so documentation linting does not warn.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 154-154: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 154, Update the folder-tree fenced code block in
architecture.md to specify a text language identifier, using ```text or
```plaintext, while preserving its existing contents.

Source: Linters/SAST tools

Comment threadarchitecture.md
Comment on lines +158 to +160
├── app/
│ ├── store.ts # configureStore; preloads auth state from localStorage
│ └── hooks.ts # typed useAppDispatch / useAppSelector

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale client/src/store/ structure.

This new folder map correctly uses client/src/app/, but the later Project Structure section still lists client/src/store/ at Lines 438-440. Update that section to match the actual client/src/app/store.ts and client/src/app/hooks.ts layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 158 - 160, Update the later Project Structure
section to remove the stale client/src/store/ entries and reflect the actual
client/src/app/store.ts and client/src/app/hooks.ts layout, matching the app
folder map shown near the configureStore and typed hook descriptions.

Comment threadarchitecture.md
Comment on lines +379 to +384
| Job | Steps |
| -------- | --------------------------------------------------------------------- |
| `server` | `npm ci` → `prisma generate` → `lint` → `test` → `build` in `server/` |
| `client` | `npm ci` → `npm run lint` → `npm run build` in `client/` |

CI validates code integrity at each stage without requiring a running database (Prisma `generate` only; no `migrate` in CI). Stage 1 API tests mock Prisma so they run offline in CI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n 'npm (ci|install)|prisma generate|npm run (lint|test|build)' \
.github/workflows/ci.yml architecture.md todo.md

Repository: MorTab1000/IVOverflow

Length of output: 880


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- architecture.md around lines 372-410 ---'
sed -n '372,410p' architecture.md | cat -n
echoecho'--- todo.md around line 48 ---'
sed -n '40,55p' todo.md | cat -n
echoecho'--- .github/workflows/ci.yml ---'
cat -n .github/workflows/ci.yml

Repository: MorTab1000/IVOverflow

Length of output: 4575


Align todo.md with the CI command
todo.md:48 still says npm install, but .github/workflows/ci.yml and architecture.md use npm ci. Update the checklist so the docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` around lines 379 - 384, Update the server
dependency-installation checklist entry in todo.md to use npm ci instead of npm
install, matching the commands documented in architecture.md and the CI
workflow. Preserve the remaining checklist steps and wording.

Comment threadarchitecture.md
- [x] **CI:** GitHub Actions — lint + build on push/PR
- [x] **Vote constraint:** `@@unique([userId, answerId])` — one vote per user per answer
- [x] **Answer ordering:** Server returns answers sorted by vote score (desc)
- [x] **JWT storage:** `localStorage` — backend returns the token in the JSON body (no `Set-Cookie`), so the client must persist it itself; `localStorage` keeps the session alive across refreshes with no refresh-token endpoint to otherwise restore it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Revisit the localStorage JWT decision before production.

Any XSS can read a token stored in localStorage; the one-hour expiry only limits the theft window. Prefer an HttpOnly/Secure/SameSite cookie flow, or explicitly document the accepted risk and compensating controls before treating this decision as resolved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@architecture.md` at line 493, Revisit the JWT storage decision in the “JWT
storage” architecture entry before production: prefer an HttpOnly, Secure,
SameSite cookie-based flow instead of localStorage, or explicitly document the
accepted XSS/token-theft risk and compensating controls. Do not leave the
decision marked resolved without recording the chosen approach and its security
rationale.

Comment on lines +18 to +19
<input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />
{error && <p className={styles.error}>{error}</p>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Associate field errors with their inputs.

aria-invalid is set, but the error paragraph has no id and the input has no aria-describedby. Screen readers may not announce which message belongs to the invalid field.

Suggested fix
 const generatedId = useId();
const inputId = id ?? generatedId;
+ const errorId = `${inputId}-error`;
return (
<div className={[styles.field, className].filter(Boolean).join(" ")}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
- <input id={inputId} className={styles.input} aria-invalid={Boolean(error)} {...rest} />- {error && <p className={styles.error}>{error}</p>}+ <input+ id={inputId}+ className={styles.input}+ {...rest}+ aria-invalid={Boolean(error)}+ aria-describedby={error ? errorId : undefined}+ />+ {error && (+ <p id={errorId} className={styles.error} role="alert">+ {error}+ </p>+ )}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<inputid={inputId}className={styles.input}aria-invalid={Boolean(error)}{...rest}/>
{error&&<pclassName={styles.error}>{error}</p>}
constgeneratedId=useId();
constinputId=id??generatedId;
consterrorId=`${inputId}-error`;
return(
<divclassName={[styles.field,className].filter(Boolean).join(" ")}>
<labelhtmlFor={inputId}className={styles.label}>
{label}
</label>
<input
id={inputId}
className={styles.input}
{...rest}
aria-invalid={Boolean(error)}
aria-describedby={error ? errorId : undefined}
/>
{error&&(
<pid={errorId}className={styles.error}role="alert">
{error}
</p>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/ui/TextField.tsx` around lines 18 - 19, Update the
input and error rendering in TextField to associate validation messages with the
field: assign the error paragraph a stable id derived from inputId and set the
input’s aria-describedby to that id when error is present. Preserve aria-invalid
behavior and avoid referencing an error element when no error exists.

Comment threadserver/package.json
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @types/express 5.0.0 work correctly with express 4.21.2?

💡 Result:

No, @types/express 5.0.0 is not designed to work with Express 4.x and is known to cause TypeScript compilation errors when used in that combination [1][2][3]. The type definitions in @types/express 5.0.0 were specifically updated to support the breaking changes introduced in Express 5.0.0 [4]. Using these types with Express 4.x typically results in "No overload matches" errors and other type mismatches [2][3]. If you are using Express 4.x (such as 4.21.2), you should use the latest version of the @types/express 4.x line (e.g., @types/express@4.17.21 or higher) [2][3][4]. Common issues and solutions include: 1. Automatic Version Mismatch: Some dependency managers may incorrectly resolve @types/express to 5.0.0 even when Express 4.x is installed, especially if other packages (like express-session) have loose dependencies on @types/express [2][3]. 2. Fixing the Version: You can resolve this by pinning the version of @types/express to the 4.x branch in your package.json [2][3]. - For npm, use the overrides field: "overrides": { "@types/express": "^4.17.21" } - For yarn, use the resolutions field: "resolutions": { "/@types/express": "^4.17.21" }

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n## server/package.json\n'
cat -n server/package.json
printf'\n## package manifests mentioning express\n'
rg -n '"express"|`@types/express`|resolutions|overrides' -g 'package.json' -g 'package-lock.json' -g 'yarn.lock' -g 'pnpm-lock.yaml' -g 'bun.lockb'.printf'\n## files named package.json at shallow depth\n'
fd -HI --type f package.json .

Repository: MorTab1000/IVOverflow

Length of output: 17765


Align @types/express with Express 4express@^4.21.2 should use the 4.x type definitions; @types/express@^5.0.0 will surface TypeScript overload/type errors in route and middleware handlers. Pin this to ^4.17.21 instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/package.json` at line 29, Update the `@types/express` dependency in
server/package.json from the 5.x range to ^4.17.21, keeping it aligned with the
existing express@^4.21.2 dependency.

Comment threadserver/src/app.ts
Comment on lines +8 to +9
app.use(cors());
app.use(express.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden default security posture: restrict CORS origin and add Helmet.

cors() with no options reflects/allows all origins by default, and there's no Helmet middleware setting standard security headers (flagged by static analysis, CWE-693).

🛡️ Proposed hardening
+import helmet from "helmet";
import cors from "cors";
import express from "express";
...
const app = express();
-app.use(cors());+app.use(helmet());+app.use(+ cors({+ origin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173",+ }),+);
app.use(express.json());
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(cors());
app.use(express.json());
importhelmetfrom"helmet";
importcorsfrom"cors";
importexpressfrom"express";
constapp=express();
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN??"http://localhost:5173",
}),
);
app.use(express.json());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/app.ts` around lines 8 - 9, Update the middleware setup in app.ts
to restrict CORS to the application’s configured or trusted origin instead of
using unrestricted cors(), and add Helmet middleware alongside the existing
express.json() setup so standard security headers are applied. Reuse the
project’s existing configuration symbols for the allowed origin if available.

Source: Linters/SAST tools

@@ -0,0 +1,17 @@
import jwt, { type SignOptions } from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Hardcoded fallback JWT secret is a critical security risk.

If JWT_SECRET is ever unset in a deployed environment, the app silently signs/verifies tokens using the literal string "change-me-in-production", which is visible in the committed source. Anyone can forge valid, arbitrarily-long-lived tokens for any user, resulting in a full authentication bypass.

🔒 Fail fast instead of falling back to a known secret
-const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-production";+const JWT_SECRET = process.env.JWT_SECRET;+if (!JWT_SECRET) {+ throw new Error("JWT_SECRET environment variable must be set");+}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constJWT_SECRET=process.env.JWT_SECRET??"change-me-in-production";
constJWT_SECRET=process.env.JWT_SECRET;
if(!JWT_SECRET){
thrownewError("JWT_SECRET environment variable must be set");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/utils/jwt.ts` at line 3, Update the JWT_SECRET initialization in
jwt.ts to remove the hardcoded fallback and fail immediately when JWT_SECRET is
unset, ensuring token signing and verification cannot proceed with a known
secret.

Comment threadtodo.md
- [x] Initialize monorepo structure (`client/` + `server/`)
- [x] Add root `docker-compose.yml` (PostgreSQL only)
- [x] Scaffold Express server with TypeScript
- [ ] Scaffold React app with Redux Toolkit + RTK Query

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the React scaffold task complete.

The Stage 1 frontend is implemented, and AGENT.md, Lines 67-69, marks it complete. Leaving this item unchecked makes todo.md inaccurate; mark it [x] or rewrite it to describe the remaining work.

As per coding guidelines, todo.md is the source of truth for the staged development checklist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@todo.md` at line 29, Update the “Scaffold React app with Redux Toolkit + RTK
Query” checklist item in todo.md to `[x]`, reflecting that the Stage 1 frontend
implementation is complete and keeping the staged development checklist
accurate.

Source: Coding guidelines

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MorTab1000