Skip to content

feat: analytics dashboard (DevDigest multi-reviewer run) - #31

Closed
burnjohn wants to merge 4 commits into
mainfrom
feat/analytics-dashboard-devdigest-run
Closed

burnjohn wants to merge 4 commits into
mainfrom
feat/analytics-dashboard-devdigest-run

Conversation

@burnjohn

Copy link
Copy Markdown
Owner

Same change as #24, re-opened from a fresh branch so the pull_request: opened trigger fires and DevDigest's reviewers run on it.

Head is the exact commit feat/analytics-dashboard points at (f9d44c3) — no content differences from #24.

Expect one review from the DevDigest job, with General Reviewer and Spec Conformance Reviewer each contributing a section.

burnjohn and others added 4 commits September 9, 2026 22:21
Co-authored-by: Cursor <cursoragent@cursor.com>
Full-stack analytics: BlogView model, KPI cards, Recharts visualizations,
drill-down modal, CSV export, category/period filters, and expanded seed data

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 33 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 914fe36e-1255-4a97-80fc-6f2ea9dce6bf

📥 Commits

Reviewing files that changed from the base of the PR and between 1d85e77 and f9d44c3.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • client/src/assets/chart_icon.svg is excluded by !**/*.svg
📒 Files selected for processing (72)
  • .github/workflows/claude-review.yml
  • .github/workflows/claude.yml
  • client/package.json
  • client/src/App.jsx
  • client/src/api/analyticsApi.js
  • client/src/assets/assets.js
  • client/src/components/admin/analytics/ChartSection.jsx
  • client/src/components/admin/analytics/DrillDownModal.jsx
  • client/src/components/admin/analytics/ExportButton.jsx
  • client/src/components/admin/analytics/KpiCard.jsx
  • client/src/components/admin/analytics/KpiCard.test.jsx
  • client/src/components/admin/analytics/KpiSection.jsx
  • client/src/components/admin/analytics/charts/CategoryDistributionChart.jsx
  • client/src/components/admin/analytics/charts/CommentActivityChart.jsx
  • client/src/components/admin/analytics/charts/PublicationsChart.jsx
  • client/src/components/admin/analytics/charts/ViewsByCategoryChart.jsx
  • client/src/components/admin/analytics/charts/ViewsOverTimeChart.jsx
  • client/src/components/admin/analytics/charts/constants.js
  • client/src/components/admin/analytics/filters/CategoryFilter.jsx
  • client/src/components/admin/analytics/filters/FilterBar.jsx
  • client/src/components/admin/analytics/filters/PeriodFilter.jsx
  • client/src/components/admin/analytics/tables/RecentCommentsTable.jsx
  • client/src/components/admin/analytics/tables/TableSection.jsx
  • client/src/components/admin/analytics/tables/TopCommentedTable.jsx
  • client/src/components/admin/analytics/tables/TopViewedTable.jsx
  • client/src/components/admin/dashboard/StatCard.jsx
  • client/src/components/admin/index.js
  • client/src/components/admin/shared/Sidebar/Sidebar.jsx
  • client/src/components/blog/BlogContent.test.jsx
  • client/src/components/blog/BlogHeader.test.jsx
  • client/src/components/blog/SocialShare.test.jsx
  • client/src/components/comment/CommentForm.test.jsx
  • client/src/components/comment/CommentItem.test.jsx
  • client/src/components/comment/CommentList.test.jsx
  • client/src/components/layout/Footer.test.jsx
  • client/src/components/layout/Navbar.test.jsx
  • client/src/components/ui/Badge/Badge.jsx
  • client/src/components/ui/Skeleton.jsx
  • client/src/components/ui/index.js
  • client/src/constants/apiEndpoints.js
  • client/src/constants/routes.js
  • client/src/hooks/api/mutations/useBlogGenerator.test.js
  • client/src/hooks/api/mutations/useCreateBlog.test.js
  • client/src/hooks/api/queries/index.js
  • client/src/hooks/api/queries/useAdminDashboard/useAdminDashboard.js
  • client/src/hooks/api/queries/useAnalytics.js
  • client/src/hooks/core/useApiMutation.test.js
  • client/src/pages/admin/AddBlog.test.jsx
  • client/src/pages/admin/Analytics.jsx
  • client/src/pages/admin/Dashboard/Dashboard.jsx
  • client/src/pages/admin/index.js
  • client/src/utils/downloadFile.js
  • client/src/utils/formatters.js
  • client/vite.config.js
  • docs/specs/analytics-dashboard-spec.md
  • server/fixtures/blogs.js
  • server/fixtures/comments.js
  • server/fixtures/views.js
  • server/migrations/20260211000001-add-blogview-indexes.js
  • server/scripts/seed.js
  • server/src/configs/analytics.js
  • server/src/constants/messages.js
  • server/src/controllers/adminController.js
  • server/src/controllers/analyticsController.js
  • server/src/helpers/analyticsAggregations.js
  • server/src/middleware/rateLimiter.js
  • server/src/models/BlogView.js
  • server/src/routes/adminRoutes.js
  • server/src/routes/analyticsRoutes.js
  • server/src/routes/blogRoutes.js
  • server/src/utils/viewTracking.js
  • server/src/validators/analyticsValidator.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 1/5

The PR is not safe to merge until the browser credential exposure, accumulating polling intervals, and stale category-filtered KPIs are fixed.

Findings

  1. P1 Security Server secret enters client bundle ▶
  2. P1 Polling intervals accumulate ▶
  3. P1 Category leaves KPIs stale ▶
  4. P2 Charts bypass Tailwind styling ▶

Summary

  • Adds analytics API routes, controllers, validators, aggregation helpers, and view tracking.
  • Adds the React analytics page, charts, tables, filters, polling, drill-down, and export UI.
  • Adds Recharts and supporting client utilities.
  • Adds analytics fixtures, seeding support, and BlogView indexes.

Diagram

sequenceDiagram
  participant Admin as Admin browser
  participant UI as Analytics UI
  participant API as Admin analytics API
  participant DB as MongoDB
  Admin->>UI: Select filters
  UI->>API: "GET /api/admin/analytics/*"
  API->>DB: Aggregate blogs, comments, and views
  DB-->>API: Aggregated results
  API-->>UI: KPIs, charts, and tables
  Admin->>UI: Export CSV
  UI->>API: GET /export-csv
  API-->>UI: CSV plus HMAC signature
Loading

Reviews (1) · Last reviewed commit: "ci: remove Claude Code review workflows"

import { analyticsApi } from '../../../api/analyticsApi'
import { downloadFile } from '../../../utils/downloadFile'
import { getErrorMessage } from '../../../utils/helpers'
import { EXPORT_SIGNING_SECRET } from '../../../../../server/src/configs/analytics.js'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Server secret enters client bundle

When the analytics client is bundled, this cross-root import includes the server's hardcoded export-signing secret in browser-delivered code, allowing users to recover a key used to sign CSV exports; the imported module also contains the Segment write key.

How this was verified: The client import resolves to a module containing literal keys, and the server uses the signing value to generate CSV response HMACs.

Context Used: CLAUDE.md (source)

Comment on lines +17 to +19
useEffect(() => {
setInterval(refetch, 30000)
}, [refetch])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Polling intervals accumulate

Because refetch receives a new identity on each render, this effect repeatedly creates timers without clearing the previous ones, causing duplicate KPI requests to accumulate and continue after navigation.

Suggested change
useEffect(() => {
setInterval(refetch, 30000)
}, [refetch])
useEffect(() => {
const intervalId = setInterval(refetch, 30000)
return () => clearInterval(intervalId)
}, [refetch])

Context Used: React Frontend Development Conventions (source)

() => analyticsApi.getKpis(queryParams),
{
errorMessage: MESSAGES.ERROR_GENERIC,
dependencies: [period, from, to]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Category leaves KPIs stale

When an administrator changes only the category filter, the KPI request does not rerun because category is missing from the dependency list, leaving the cards on the previous category while the other analytics sections refresh.

Suggested change
dependencies: [period, from, to]
dependencies: [period, from, to, category]

Context Used: React Frontend Development Conventions (source)

const pct = percent ?? (total > 0 ? Math.round((count / total) * 1000) / 10 : 0)
return (
<div className="bg-white border border-gray-200 rounded-lg shadow-sm px-3 py-2 text-sm">
<div className="font-medium" style={{ color: entry.fill }}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Charts bypass Tailwind styling

The new chart components encode colors and cursor states with inline style objects instead of the repository-required Tailwind utilities, making their styling inconsistent with the project's centralized frontend conventions; the same pattern also occurs in the category chart's cursor and the comment tooltip colors.

Context Used: React Frontend Development Conventions (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@github-actions github-actions Bot 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.

DevDigest — 2 reviewers

🔴 General Reviewer · 🔴 Spec Conformance Reviewer


General Reviewer — Changes requested

18 findings · 11 critical · 6 warning · 1 suggestion

  • 🔴 Client bundle exposes entire server environment (critical, security) — client/vite.config.js:10-12
    • Vite define replaces process.env with JSON.stringify(process.env), embedding all server environment variables (JWT_SECRET, DB credentials, etc.) into the client bundle. This is a secret leak.
    • Suggestion: Remove the define block or whitelist only public variables.
  • 🔴 Hardcoded secrets committed and imported into client (critical, security) — server/src/configs/analytics.js:1-3
    • Hardcoded EXPORT_SIGNING_SECRET and SEGMENT_WRITE_KEY are committed. client/src/components/admin/analytics/ExportButton.jsx:5 imports the server secret into client code, so the secret ships to the browser and can be extracted.
    • Suggestion: Remove hardcoded secrets, load from .env server-side only, and delete the client import.
  • 🔴 Client imports server signing secret (critical, security) — client/src/components/admin/analytics/ExportButton.jsx:5
    • This client component imports EXPORT_SIGNING_SECRET from server/src/configs/analytics.js, exposing the signing secret in the browser bundle.
    • Suggestion: Remove the import and any client-side signature verification that depends on the secret.
  • 🔴 Analytics endpoints filter blogs by createdAt before counting views/comments (critical, security) — server/src/controllers/analyticsController.js:32-57
    • buildBlogFilter includes createdAt: { $gte: from, $lte: to }. The resulting blogIds are then used to build view/comment filters. For a 30-day period, only posts created in the last 30 days are included, so views and comments on older posts are excluded from total views, views over time, top posts, comment activity, and drill-downs.
    • Suggestion: Use the blog creation filter only for publication/category-distribution metrics. For view/comment metrics, filter events by date and apply only category/status filters to blogs.
  • 🔴 Avg Engagement uses views instead of comments (critical, security) — server/src/controllers/analyticsController.js:83-85
    • Avg Engagement is computed as totalViewsCur / publishedCount, but the spec defines it as average comments per published post. The KPI card will show views per post instead of comments per post.
    • Suggestion: Use totalCommentsCur / publishedCount for Avg Engagement.
  • 🔴 Most Active Category uses blog count instead of view count (critical, security) — server/src/controllers/analyticsController.js:92-101
    • Most Active Category is computed by counting blogs per category (Blog.aggregate with $group on category), not by counting non-admin views per category. The KPI will show the category with the most posts, not the most views.
    • Suggestion: Aggregate BlogView joined with Blog category and sort by view count descending.
  • 🔴 Custom date range to is not inclusive (critical, security) — server/src/validators/analyticsValidator.js:10-49
    • parseDate returns new Date(str + 'T00:00:00.000Z'), so a custom to date is the start of the day. The filter uses viewedAt: { $lte: to }, excluding all views after 00:00 UTC on the to day. This violates the inclusive to requirement.
    • Suggestion: Set to to the end of the day (23:59:59.999Z) or use a half-open interval with < nextDay.
  • 🔴 View tracking endpoint trusts client-supplied visitorKey and referrerSource (critical, security) — server/src/controllers/analyticsController.js:600-650
    • recordView trusts req.body.visitorKey and req.body.referrerSource. A client can send a new visitorKey per request to bypass 24h deduplication and inflate views, and can spoof referrerSource. Deduplication returns a 400 error instead of a silent 200. Only admin role is excluded, not author.
    • Suggestion: Derive visitorKey server-side via computeVisitorKey, classify referrer from the Referer header, return 200 on duplicate, and exclude both admin and author roles.
  • 🔴 Stored XSS via dangerouslySetInnerHTML on comment content (critical, security) — client/src/components/admin/analytics/tables/RecentCommentsTable.jsx:49-53
    • dangerouslySetInnerHTML renders item.contentExcerpt, which comes from user-generated comment content. The server does not strip HTML, so a stored XSS payload in a comment will execute in the admin dashboard.
    • Suggestion: Strip HTML server-side and render the excerpt as plain text (e.g. {item.contentExcerpt}).
  • 🔴 Migration down drops blogviews collection, causing data loss (critical, security) — server/migrations/20260211000001-add-blogview-indexes.js:35-38
    • The down migration drops the entire blogviews collection instead of dropping the indexes created in up. Rolling back this migration deletes all view data.
    • Suggestion: Drop only the indexes by name in down.
  • 🔴 KPI hook omits category from refetch dependencies (critical, security) — client/src/hooks/api/queries/useAnalytics.js:9-14
    • useAnalyticsKpis passes dependencies: [period, from, to] but the query includes category. When the category filter changes, the KPI cards do not refetch, so they show unfiltered KPIs while other sections update.
    • Suggestion: Include category in the dependencies array.
  • 🟡 Authorization header logged (warning, security) — server/src/controllers/analyticsController.js:640
    • console.log('[analytics] auth header:', req.headers.authorization) writes the JWT to logs, violating the requirement that authorization headers and tokens never be logged.
    • Suggestion: Remove the log statement.
  • 🟡 CSV export does not mitigate formula injection (warning, security) — server/src/controllers/analyticsController.js:500-510
    • escapeCSV does not prefix cells starting with =, +, -, or @. A blog title or category starting with a formula character can trigger CSV injection when the exported file is opened in a spreadsheet.
    • Suggestion: Prefix such cells with a single quote as required by AC-5.6.
  • 🟡 Global rate limit changed contrary to spec (warning, security) — server/src/middleware/rateLimiter.js:42
    • The global API rate limit is changed from 100 to 300 requests per minute, but the spec requires the existing global limit to remain unchanged and the view endpoint to have its own 30/min limit.
    • Suggestion: Revert the global limit to 100 and add a separate limiter for the view endpoint.
  • 🟡 KPI section polls without cleanup (warning, security) — client/src/components/admin/analytics/KpiSection.jsx:17-19
    • setInterval(refetch, 30000) is called in useEffect without cleanup. The interval leaks and polls in the background, violating the no-background-polling requirement. If refetch identity changes, multiple intervals accumulate.
    • Suggestion: Remove the polling or store the interval id and clear it in the effect cleanup.
  • 🟡 Drill-down race condition (warning, security) — client/src/pages/admin/Analytics.jsx:63-70
    • Drill-down requests can resolve out of order. If the user clicks another bucket before the first request completes, the earlier response can overwrite the later one, showing posts for the wrong date.
    • Suggestion: Use a request id or AbortController and ignore stale responses.
  • 🟡 Recent comments not filtered by selected period (warning, security) — server/src/controllers/analyticsController.js:450-470
    • getLastComments does not filter comments by createdAt within the selected period. It only filters blogs by creation date, so recent comments may fall outside the selected date range and the section does not respond correctly to period changes.
    • Suggestion: Add createdAt: { $gte: from, $lte: to } to the Comment.find query.
  • 🔵 Views by category client re-sorts despite spec (suggestion, security) — client/src/components/admin/analytics/charts/ViewsByCategoryChart.jsx:33
    • The client re-sorts data descending, but the spec says the order shall come from the API response and the client shall not re-sort. The server currently sorts ascending, so the contract is inconsistent.
    • Suggestion: Either fix the server to sort descending and remove the client sort, or update the spec.

Posted via DevDigest.


Spec Conformance Reviewer — Changes requested

26 findings · 5 critical · 20 warning · 1 suggestion

  • 🔴 AC-1.2 diverged: Analytics endpoints accessible to any authenticated role (critical, security) — server/src/routes/adminRoutes.js:23
    • adminRouter.use(auth) applies JWT verification but no role === 'admin' check. Any authenticated user (e.g., author) can access /api/admin/analytics/* endpoints. Spec requires 403 for non-admin roles.
    • Suggestion: Add a role check middleware after auth: if (req.user.role !== 'admin') return res.status(403).json({ success: false, message: 'Forbidden' })
  • 🔴 AC-6.3 diverged: Visitor key accepted from request body (critical, security) — server/src/controllers/analyticsController.js:47-48
    • const visitorKey = req.body?.visitorKey reads visitor key from request body. Spec requires server-side derivation from IP+UA or X-Visitor-ID header. Client-controlled visitor key allows bypassing deduplication and inflating view counts.
    • Suggestion: Use computeVisitorKey(req) from server/src/utils/viewTracking.js instead of reading from request body.
  • 🔴 AC-10.1 diverged: Client imports from server/ and exposes signing secret (critical, security) — client/src/components/admin/analytics/ExportButton.jsx:5
    • import { EXPORT_SIGNING_SECRET } from '../../../../../server/src/configs/analytics.js' — client code imports from server/, violating AC-10.1 and exposing the signing secret in the client bundle.
    • Suggestion: Remove the import. The client should not verify the export signature; signature verification belongs on the server or via a separate authenticated endpoint.
  • 🔴 AC-10.2 diverged: Authorization header written to logs (critical, security) — server/src/controllers/analyticsController.js:497
    • console.log('[analytics] auth header:', req.headers.authorization) logs the Authorization header, violating AC-10.2.
    • Suggestion: Remove the console.log statement.
  • 🔴 Secrets committed to repository (critical, security) — server/src/configs/analytics.js:2-3
    • EXPORT_SIGNING_SECRET and SEGMENT_WRITE_KEY are hardcoded in source. The comment 'test fixture — safe to commit' is a claim that does not reduce severity. AC-10.1 prohibits committing secrets.
    • Suggestion: Remove the file or move values to .env. Rotate any exposed secrets.
  • 🟡 AC-2.4 diverged: Avg Engagement uses views instead of comments (warning, bug) — server/src/controllers/analyticsController.js:70-72
    • avgEngagementCur = totalViewsCur / publishedCount. Spec says average number of comments per published post.
    • Suggestion: Change to totalCommentsCur / publishedCount.
  • 🟡 AC-2.6 diverged: Most Active Category counts posts, not views (warning, bug) — server/src/controllers/analyticsController.js:78-84
    • Blog.aggregate counts posts per category. Spec requires category with most non-admin views.
    • Suggestion: Aggregate BlogView joined with Blog to count views per category.
  • 🟡 AC-3.5 diverged: Client re-sorts views by category (warning, bug) — client/src/components/admin/analytics/charts/ViewsByCategoryChart.jsx:38
    • Client sorts data. Spec says order shall come from API response.
    • Suggestion: Remove the client-side sort and use data as-is.
  • 🟡 AC-3.7 partial: Drill-down does not discard stale responses (warning, bug) — client/src/pages/admin/Analytics.jsx:47-55
    • No request ID or cancellation. Earlier response can overwrite later one.
    • Suggestion: Use a ref to track the latest request ID and ignore stale responses.
  • 🟡 AC-4.3 diverged: Recent comments render HTML and truncate to 50 chars (warning, security) — client/src/components/admin/analytics/tables/RecentCommentsTable.jsx:55-58
    • dangerouslySetInnerHTML renders HTML. Spec requires plain-text excerpt of at most 120 chars, never rendered as HTML.
    • Suggestion: Use textContent rendering and truncate to 120 chars.
  • 🟡 AC-5.6 diverged: CSV formula injection not prevented (warning, security) — server/src/controllers/analyticsController.js:442-448
    • escapeCSV does not prefix cells starting with =, +, -, @.
    • Suggestion: Add prefixing logic for formula characters.
  • 🟡 AC-6.1 missing: No client code sends POST /api/blog/:blogId/view (warning, bug) — client/src/constants/apiEndpoints.js:34
    • RECORD_VIEW endpoint constant declared but no caller found in repository.
    • Suggestion: Add a hook or effect in the public blog detail page to call the endpoint once per page load.
  • 🟡 AC-6.4 diverged: Referrer source accepted from request body (warning, bug) — server/src/controllers/analyticsController.js:48
    • referrerSource read from req.body. Spec requires server-side classification from Referer header.
    • Suggestion: Use classifyReferrer(req.get('referer')) from viewTracking.js.
  • 🟡 AC-6.5 diverged: Deduplication returns 400 error (warning, bug) — server/src/controllers/analyticsController.js:482-490
    • Returns 400 with VIEW_ALREADY_RECORDED. Spec says 200 without creating new document, never surface as error.
    • Suggestion: Return 200 with success message when duplicate detected.
  • 🟡 AC-6.6 diverged: Only admin views marked as admin, not author (warning, bug) — server/src/controllers/analyticsController.js:492-504
    • Only role === 'admin' sets isAdminView. Spec says admin or author.
    • Suggestion: Check for both 'admin' and 'author' roles.
  • 🟡 AC-6.7 missing: No 30 req/min rate limit on view endpoint (warning, bug) — server/src/routes/blogRoutes.js:24
    • No rate limiter on POST /:blogId/view. Global limiter changed to 300/min.
    • Suggestion: Add a dedicated rate limiter with max: 30, windowMs: 60000.
  • 🟡 AC-8.2 missing: No empty state with CTA (warning, bug) — client/src/pages/admin/Analytics.jsx:1-107
    • No check for zero blog posts. No single empty state with call-to-action.
    • Suggestion: Add a check: if total blogs === 0, render a single empty state with CTA to create first post.
  • 🟡 AC-9.1 partial: Tests incomplete (warning, test) — client/src/components/admin/analytics/KpiCard.test.jsx:1-15
    • Only one trivial test; two skipped. No FilterBar or useAnalytics hook tests.
    • Suggestion: Implement the skipped tests and add FilterBar and useAnalytics hook tests.
  • 🟡 AC-9.2 missing: No server-side tests for CSV escaping or deduplication (warning, test) — server/src/controllers/analyticsController.js:1
    • No test files found for escapeCSV or view deduplication.
    • Suggestion: Add server-side tests for CSV escaping and view deduplication.
  • 🟡 AC-10.3 diverged: Migration down drops collection (warning, bug) — server/migrations/20260211000001-add-blogview-indexes.js:38
    • down() calls db.collection('blogviews').drop(), deleting all data.
    • Suggestion: Drop only the indexes, not the collection.
  • 🟡 AC-10.4 diverged: exportCsv issues N+1 queries (warning, bug) — server/src/controllers/analyticsController.js:402-440
    • Loops over blogs and issues countDocuments per blog.
    • Suggestion: Use aggregation pipeline to compute views and comments in a single query.
  • 🟡 AC-10.5 diverged: Background polling (warning, bug) — client/src/components/admin/analytics/KpiSection.jsx:17-19
    • setInterval(refetch, 30000) polls every 30 seconds. Spec says no background polling.
    • Suggestion: Remove the setInterval.
  • 🟡 Out-of-scope changes (warning, bug) — client/src/components/admin/dashboard/StatCard.jsx:1
    • Multiple files changed that are not authorized by any AC. Spec §11 explicitly lists Dashboard page changes and unrelated component tests as out of scope.
    • Suggestion: Remove out-of-scope changes or split into separate PR.
  • 🔵 PR description does not list covered ACs (suggestion, bug) — docs/specs/analytics-dashboard-spec.md:1
    • Spec requires PR to reference the spec and list covered ACs. PR description does neither.
    • Suggestion: Update PR description to reference the spec and list covered ACs.
  • 🟡 Duplicate view tracking implementation is dead code (warning, bug) — server/src/utils/viewTracking.js:1-109
    • viewTracking.js implements correct server-side visitor key derivation, referrer classification, and deduplication, but is never imported. The actual route uses the incorrect controller implementation.
    • Suggestion: Use viewTracking.js in the route instead of the controller's recordView.
  • 🟡 Vite config exposes process.env to client (warning, security) — client/vite.config.js:11-13
    • define: { 'process.env': JSON.stringify(process.env) } exposes all server environment variables to the client bundle, including JWT_SECRET.
    • Suggestion: Remove the define block or explicitly whitelist only public variables.

Posted via DevDigest.

Comment thread client/vite.config.js
tailwindcss()
],
// expose env to analytics config
define: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

General Reviewer · Client bundle exposes entire server environment (critical)

Vite define replaces process.env with JSON.stringify(process.env), embedding all server environment variables (JWT_SECRET, DB credentials, etc.) into the client bundle. This is a secret leak.

Suggestion: Remove the define block or whitelist only public variables.

@@ -0,0 +1,3 @@
// test fixture — safe to commit (see PR description)
export const EXPORT_SIGNING_SECRET = 'qb_exp_9f3c2a71b4e8d05c6a1f7e2b9d4c8a3f'
export const SEGMENT_WRITE_KEY = 'sk_seg_4e1b7c9d2a6f3e8b5c0d1a2f3e4b5c6d'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

General Reviewer · Hardcoded secrets committed and imported into client (critical)

Hardcoded EXPORT_SIGNING_SECRET and SEGMENT_WRITE_KEY are committed. client/src/components/admin/analytics/ExportButton.jsx:5 imports the server secret into client code, so the secret ships to the browser and can be extracted.

Suggestion: Remove hardcoded secrets, load from .env server-side only, and delete the client import.

import { analyticsApi } from '../../../api/analyticsApi'
import { downloadFile } from '../../../utils/downloadFile'
import { getErrorMessage } from '../../../utils/helpers'
import { EXPORT_SIGNING_SECRET } from '../../../../../server/src/configs/analytics.js'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

General Reviewer · Client imports server signing secret (critical)

This client component imports EXPORT_SIGNING_SECRET from server/src/configs/analytics.js, exposing the signing secret in the browser bundle.

Suggestion: Remove the import and any client-side signature verification that depends on the secret.

const viewFilterPrev = buildViewFilter(
{ from: prev.from, to: prev.to },
prevBlogIds
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

General Reviewer · Analytics endpoints filter blogs by createdAt before counting views/comments (critical)

buildBlogFilter includes createdAt: { $gte: from, $lte: to }. The resulting blogIds are then used to build view/comment filters. For a 30-day period, only posts created in the last 30 days are included, so views and comments on older posts are excluded from total views, views over time, top posts, comment activity, and drill-downs.

Suggestion: Use the blog creation filter only for publication/category-distribution metrics. For view/comment metrics, filter events by date and apply only category/status filters to blogs.

}),
Comment.countDocuments({
blog: { $in: prevBlogIds },
createdAt: { $gte: prev.from, $lte: prev.to },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

General Reviewer · Avg Engagement uses views instead of comments (critical)

Avg Engagement is computed as totalViewsCur / publishedCount, but the spec defines it as average comments per published post. The KPI card will show views per post instead of comments per post.

Suggestion: Use totalCommentsCur / publishedCount for Avg Engagement.


useEffect(() => {
setInterval(refetch, 30000)
}, [refetch])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spec Conformance Reviewer · AC-10.5 diverged: Background polling (warning)

setInterval(refetch, 30000) polls every 30 seconds. Spec says no background polling.

Suggestion: Remove the setInterval.

@@ -0,0 +1,15 @@
import React from 'react'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spec Conformance Reviewer · Out-of-scope changes (warning)

Multiple files changed that are not authorized by any AC. Spec §11 explicitly lists Dashboard page changes and unrelated component tests as out of scope.

Suggestion: Remove out-of-scope changes or split into separate PR.

@@ -0,0 +1,117 @@
# Spec: Blog Analytics Dashboard

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spec Conformance Reviewer · PR description does not list covered ACs (suggestion)

Spec requires PR to reference the spec and list covered ACs. PR description does neither.

Suggestion: Update PR description to reference the spec and list covered ACs.

}

doRecord()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spec Conformance Reviewer · Duplicate view tracking implementation is dead code (warning)

viewTracking.js implements correct server-side visitor key derivation, referrer classification, and deduplication, but is never imported. The actual route uses the incorrect controller implementation.

Suggestion: Use viewTracking.js in the route instead of the controller's recordView.

Comment thread client/vite.config.js
],
// expose env to analytics config
define: {
'process.env': JSON.stringify(process.env)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spec Conformance Reviewer · Vite config exposes process.env to client (warning)

define: { 'process.env': JSON.stringify(process.env) } exposes all server environment variables to the client bundle, including JWT_SECRET.

Suggestion: Remove the define block or explicitly whitelist only public variables.

Sign up for free to 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.

1 participant