-
Notifications
You must be signed in to change notification settings - Fork 179
feat: ai tutorial for any open source codebase #305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: ai tutorial for any open source codebase #305
Conversation
|
@FirePheonix is attempting to deploy a commit to the AJEET PRATAP SINGH's projects Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThis PR introduces an end-to-end tutorial generation system. It adds a Prisma model for persisting tutorials, creates backend services for GitHub repository crawling and LLM-powered analysis, implements a multi-step tutorial generation pipeline, exposes tRPC operations for tutorial management, and provides frontend components for generating, browsing, and viewing tutorials with rich markdown rendering and syntax highlighting. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Web as Frontend<br/>(TutorialGenerator)
participant tRPC as tRPC API<br/>(tutorial router)
participant GHCrawler as GitHub<br/>Crawler Service
participant GitHub as GitHub API
participant LLM as LLM Service
participant OpenRouter as OpenRouter<br/>API
participant DB as Prisma<br/>(Database)
User->>Web: Enter repo URL & language
Web->>tRPC: checkExisting(repoUrl)
tRPC->>DB: Query tutorials by repoUrl
alt Existing tutorials found
tRPC-->>Web: Return existing tutorials
User->>Web: Confirm regenerate or view
else No existing tutorials
Web->>tRPC: listRepoFiles(repoUrl)
tRPC->>GHCrawler: listRepoFiles()
GHCrawler->>GitHub: Fetch repo file tree
GitHub-->>GHCrawler: File metadata
GHCrawler-->>tRPC: File list with sizes
tRPC-->>Web: Return file list
User->>Web: Select files (max 3MB)
Web->>tRPC: generate({ repoUrl, selectedFiles, language })
tRPC->>GHCrawler: crawlSelectedFiles()
GHCrawler->>GitHub: Fetch file contents
GitHub-->>GHCrawler: Raw file content
tRPC->>LLM: Step 2: Identify abstractions
LLM->>OpenRouter: Send code + prompt
OpenRouter-->>LLM: LLM response (JSON)
tRPC->>LLM: Step 3: Analyze relationships
LLM->>OpenRouter: Send abstractions + code
OpenRouter-->>LLM: Relationship graph
tRPC->>LLM: Step 4: Order chapters
LLM->>OpenRouter: Determine sequence
OpenRouter-->>LLM: Chapter order
tRPC->>LLM: Step 5: Write chapters
LLM->>OpenRouter: Generate per-chapter markdown
OpenRouter-->>LLM: Chapter content
tRPC->>DB: Insert tutorial
DB-->>tRPC: Tutorial persisted
tRPC-->>Web: Return generated TutorialResult
Web->>Web: Store generated tutorial
Web->>Web: Switch to TutorialViewer
end
User->>Web: View tutorial
Web->>Web: Render chapters, code blocks, Mermaid diagram
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
1 similar comment
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 20
🧹 Nitpick comments (20)
apps/web/package.json (1)
23-23: Move type definitions to devDependencies.
@types/react-syntax-highlighteris a TypeScript type definition package and should be placed indevDependenciesrather thandependencies. Type definitions are only needed at build time, not runtime.🔎 Suggested change
Move line 23 from
dependenciestodevDependencies:"devDependencies": { "@tailwindcss/line-clamp": "^0.4.4", "@types/jsdom": "^27.0.0", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/react-syntax-highlighter": "^15.5.13", "@types/sanitize-html": "^2.16.0",apps/web/src/app/globals.css (1)
85-87: Hardcoded hex value in pre background.Per coding guidelines, hardcoded hex values should be avoided. Consider using a CSS variable or Tailwind's theme colors. However, I understand this may be intentional to match the github-dark highlight.js theme.
🔎 Optional: Use CSS variable
.tutorial-content pre { - @apply bg-[#0d1117] !important; + background-color: var(--background) !important; }Or define a custom color in tailwind.config.ts if this specific shade is needed.
apps/web/src/components/tutorial/TutorialViewer.tsx (4)
126-137: Direct innerHTML assignment bypasses React's XSS protection.While
highlight.jsdoes escape HTML entities by default, directly settinginnerHTMLis still a risk if the library behavior changes or if there are edge cases. Consider using thehljs.highlightElementapproach or ensuring the output is sanitized.🔎 Alternative: Use highlightElement or sanitize
useEffect(() => { if (codeRef.current && language && children) { try { if (hljs.getLanguage(language)) { - const highlighted = hljs.highlight(children, { language }).value; - codeRef.current.innerHTML = highlighted; + codeRef.current.textContent = children; + hljs.highlightElement(codeRef.current); } } catch (e) { console.error("Highlight error:", e); } } }, [children, language]);
53-53: Consider stricter typing for chapters field.
Chapter[] | unknownis quite permissive. If the API can return chapters as a JSON string or array, consider a more explicit union type likeChapter[] | stringto better document the expected formats and improve type safety.- chapters: Chapter[] | unknown; + chapters: Chapter[] | string;
539-545: Consider next/image for image optimization.Per guidelines,
next/imageshould be used for image optimization. However, since tutorial images come from dynamic external sources (markdown content), you may need to configureremotePatternsinnext.config.jsor use a custom loader. If images are primarily from GitHub, consider adding github.com to allowed patterns.
505-505: Avoid usinganytype.Per coding guidelines, avoid
anytype. Define proper types for the ReactMarkdown code component props.🔎 Suggested typing
+interface CodeComponentProps { + node?: unknown; + inline?: boolean; + className?: string; + children?: React.ReactNode; +} + - code({ node, inline, className, children, ...props }: any) { + code({ inline, className, children }: CodeComponentProps) {apps/web/src/components/tutorial/TutorialHistory.tsx (4)
6-12: Define a shared TypeScript type for Tutorial instead of local interface.The Tutorial interface is defined locally but appears to mirror the data structure returned by the API. Consider extracting this to a shared types file to maintain consistency across components and avoid duplication.
💡 Suggested approach
Create a shared types file (e.g.,
apps/web/src/types/tutorial.ts) and import it here:// apps/web/src/types/tutorial.ts export interface Tutorial { id: string; projectName: string; repoUrl: string; language: string; createdAt: string | Date; }Then import in this file:
-interface Tutorial { - id: string; - projectName: string; - repoUrl: string; - language: string; - createdAt: string | Date; -} +import type { Tutorial } from "@/types/tutorial";
31-36: Replace browserconfirm()with an accessible dialog component.The browser's native
confirm()dialog doesn't follow the design system and has accessibility limitations. Consider using a proper modal/dialog component that supports keyboard navigation and aria labels.As per coding guidelines: "Include proper aria labels for accessibility" and "Ensure keyboard navigation works."
38-57: Consider using a date formatting library for consistency.While the date formatting logic is correct, using a library like
date-fnsordayjswould provide better consistency across the application and reduce custom date manipulation code.
82-113: Enhance accessibility for interactive tutorial cards.The tutorial cards use a clickable
divwithout proper accessibility attributes. For better keyboard navigation and screen reader support, consider the following improvements:
- Add
role="button"andtabIndex={0}to the tutorial card div- Add
onKeyDownhandler for Enter/Space key support- Add
aria-labelto the delete button describing the action with contextAs per coding guidelines: "Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)."
🔎 Suggested improvements
<div key={tutorial.id} onClick={() => onSelectTutorial(tutorial.id)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onSelectTutorial(tutorial.id); + } + }} className="group p-4 bg-dash-base hover:bg-dash-hover border border-dash-border rounded-lg cursor-pointer transition-all hover:border-brand-purple/30" > {/* ... */} <button onClick={(e) => handleDelete(e, tutorial.id)} + aria-label={`Delete tutorial ${tutorial.projectName}`} className="p-1.5 rounded-md opacity-0 group-hover:opacity-100 hover:bg-red-500/10 transition-all" title="Delete tutorial" >apps/api/src/index.ts (1)
65-70: The implementation correctly applies route-specific size limits with appropriate ordering and ordering precedence.The 5MB limit for tRPC endpoints provides adequate buffer for the stated 3.0MB file content requirement. However, consider that the tRPC input validation schema (via Zod) validates only request parameters (maxFiles, maxAbstractions, selectedFiles), not actual file content payload sizes. While the Express middleware provides a hard limit, adding explicit file size validation within the tutorial router schema would provide earlier rejection of oversized payloads with more granular error feedback.
apps/web/src/components/tutorial/FileBrowser.tsx (2)
17-19: Avoid usinganytype for the tutorial parameter.The
onTutorialGeneratedcallback usesanyfor the tutorial type. Define a proper TypeScript interface for the tutorial object to improve type safety. Based on coding guidelines, avoidanytype.Suggested improvement
+interface Tutorial { + id: string; + projectName: string; + indexContent: string; + chapters: Array<{ filename: string; content: string }>; + mermaidDiagram: string; + fromHistory?: boolean; +} + interface TutorialGeneratorProps { - onTutorialGenerated: (tutorial: any) => void; + onTutorialGenerated: (tutorial: Tutorial) => void; }
314-346: Dead code inhandleToggle- unreachable variableallFiles.Lines 326-329 compute
allFilesbut it's never used. The actual logic usesfilePathscomputed fromfolderNode. This appears to be leftover code that should be removed.} else if (children) { - // For folders, toggle all children - const allFiles = children.flatMap(c => getAllFilePaths({ - ...{ name: "", path, type: "dir", children }, - children, - } as TreeNode)); - // Get actual file paths from the folder const folderNode: TreeNode = { name: "", path, type: "dir", children }; const filePaths = getAllFilePaths(folderNode);apps/web/src/components/tutorial/TutorialGenerator.tsx (3)
17-19: Replaceanytypes with proper TypeScript interfaces.Multiple uses of
anytype violate coding guidelines. Define proper interfaces for tutorial data and error handling.Suggested improvement
+interface ExistingTutorial { + id: string; + projectName: string; + createdAt: string; + language: string; + isOwnTutorial: boolean; +} + +interface GeneratedTutorial { + id: string; + projectName?: string; + indexContent?: string; + chapters?: Array<{ filename: string; content: string }>; + mermaidDiagram?: string; + fromHistory?: boolean; +} + interface TutorialGeneratorProps { - onTutorialGenerated: (tutorial: any) => void; + onTutorialGenerated: (tutorial: GeneratedTutorial) => void; } - const [existingTutorials, setExistingTutorials] = useState<any[]>([]); + const [existingTutorials, setExistingTutorials] = useState<ExistingTutorial[]>([]); // Generate mutation const generateMutation = trpc.tutorial.generate.useMutation({ - onSuccess: (data: any) => { + onSuccess: (data: GeneratedTutorial) => { setViewMode("form"); onTutorialGenerated(data); }, - onError: (err: any) => { + onError: (err: Error) => { setViewMode("form"); setError(err.message); }, });Also applies to: 31-31, 50-50, 54-54
84-85: Type the caught errors properly instead of usingany.Error handling uses
anytype. Useunknownand narrow the type appropriately.- } catch (err: any) { - setError(err.message || "Failed to fetch repository files"); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : "Failed to fetch repository files");Also applies to: 127-128
238-253: Add aria-label to the language select element for accessibility.The select element lacks an accessible label association. While there's a visible label, adding
idandhtmlForattributes creates a proper association.<div> - <label className="block text-sm font-medium text-text-secondary mb-2"> + <label + htmlFor="tutorial-language" + className="block text-sm font-medium text-text-secondary mb-2" + > Tutorial Language </label> <select + id="tutorial-language" value={language} onChange={(e) => setLanguage(e.target.value)}apps/api/src/services/tutorial.service.ts (2)
70-84: Avoidanyreturn type and add safer JSON parsing.The
parseJsonFromResponsefunction returnsany, which propagates type unsafety. Consider usingunknownand validating the structure, or at minimum documenting the expected shape. Additionally, the regex matching could fail silently.Suggested improvement
-function parseJsonFromResponse(response: string): any { +function parseJsonFromResponse(response: string): unknown { // Try to find JSON block in markdown code fence const jsonMatch = response.match(/```json\n?([\s\S]*?)```/); if (jsonMatch && jsonMatch[1]) { - return JSON.parse(jsonMatch[1].trim()); + try { + return JSON.parse(jsonMatch[1].trim()); + } catch (e) { + // fall through to try raw JSON + } } // Try to find raw JSON array or object const rawJsonMatch = response.match(/(\[[\s\S]*\]|\{[\s\S]*\})/); if (rawJsonMatch && rawJsonMatch[1]) { return JSON.parse(rawJsonMatch[1]); } throw new Error("No JSON block found in LLM response"); }
693-700: Internal step functions are exposed unnecessarily.The
tutorialServiceobject exports internal pipeline functions (identifyAbstractions,analyzeRelationships, etc.) that appear to be implementation details. Consider whether these need to be part of the public API or if onlygenerateTutorialshould be exposed.apps/api/src/services/github-crawler.service.ts (2)
375-411: Consider parallel fetching with concurrency limit forcrawlSelectedFiles.The function fetches files sequentially, which can be slow for many files. Consider using
Promise.allwith a concurrency limiter for better performance while avoiding rate limits.Suggested improvement
+const CONCURRENCY_LIMIT = 5; + +async function processInBatches<T, R>( + items: T[], + processor: (item: T) => Promise<R>, + batchSize: number +): Promise<R[]> { + const results: R[] = []; + for (let i = 0; i < items.length; i += batchSize) { + const batch = items.slice(i, i + batchSize); + const batchResults = await Promise.all(batch.map(processor)); + results.push(...batchResults); + } + return results; +} + export async function crawlSelectedFiles( repoUrl: string, selectedPaths: string[], options: { maxFileSize?: number } = {} ): Promise<CrawlResult> { const { maxFileSize = 100 * 1024 } = options; const { owner, repo, branch } = parseGitHubUrl(repoUrl); console.log(`Crawling ${selectedPaths.length} selected files from ${owner}/${repo}`); - const files: Array<{ path: string; content: string }> = []; - let skippedFiles = 0; - - for (const filePath of selectedPaths) { - // Fetch file content - const content = await fetchFileContent(owner, repo, filePath, branch); - if (content) { - files.push({ path: filePath, content }); - console.log(`Added ${filePath}`); - } else { - skippedFiles++; - } - } + const results = await processInBatches( + selectedPaths, + async (filePath) => { + const content = await fetchFileContent(owner, repo, filePath, branch); + return { filePath, content }; + }, + CONCURRENCY_LIMIT + ); + + const files = results + .filter((r): r is { filePath: string; content: string } => r.content !== null) + .map(r => ({ path: r.filePath, content: r.content })); + + const skippedFiles = results.filter(r => r.content === null).length;
1-6: Unused import:graphqlfrom@octokit/graphql.The
graphqlimport is declared but never used in this file. Remove unused imports per coding guidelines.-import { graphql } from "@octokit/graphql"; import dotenv from "dotenv";
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
apps/api/prisma/migrations/20251215163900_add_tutorial_model/migration.sql(1 hunks)apps/api/prisma/schema.prisma(1 hunks)apps/api/src/index.ts(1 hunks)apps/api/src/routers/_app.ts(2 hunks)apps/api/src/routers/tutorial.ts(1 hunks)apps/api/src/services/github-crawler.service.ts(1 hunks)apps/api/src/services/llm.service.ts(1 hunks)apps/api/src/services/tutorial.service.ts(1 hunks)apps/web/package.json(2 hunks)apps/web/src/app/(main)/dashboard/tutorial/page.tsx(1 hunks)apps/web/src/app/globals.css(1 hunks)apps/web/src/components/dashboard/Sidebar.tsx(2 hunks)apps/web/src/components/tutorial/FileBrowser.tsx(1 hunks)apps/web/src/components/tutorial/TutorialGenerator.tsx(1 hunks)apps/web/src/components/tutorial/TutorialHistory.tsx(1 hunks)apps/web/src/components/tutorial/TutorialViewer.tsx(1 hunks)apps/web/src/components/tutorial/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (23)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Files:
apps/api/src/routers/_app.tsapps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/api/src/services/llm.service.tsapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.tsapps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoidany
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located insrc/store/)
Use absolute imports from@/prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders
Files:
apps/api/src/routers/_app.tsapps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/api/src/services/llm.service.tsapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.tsapps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (.cursorrules)
Organize imports: react → third-party → local components → utils → types
Files:
apps/api/src/routers/_app.tsapps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/api/src/services/llm.service.tsapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.tsapps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations
Files:
apps/api/src/routers/_app.tsapps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/api/src/services/llm.service.tsapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.tsapps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}
📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Files:
apps/api/src/routers/_app.tsapps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/api/src/services/llm.service.tsapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.tsapps/web/src/components/tutorial/TutorialViewer.tsx
apps/api/src/**/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)
Files:
apps/api/src/routers/_app.tsapps/api/src/services/llm.service.tsapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/*.ts
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Avoid
anytype; useunknownfor truly dynamic data and narrow with type guards
Files:
apps/api/src/routers/_app.tsapps/api/src/services/llm.service.tsapps/api/src/routers/tutorial.tsapps/api/src/index.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/web/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
apps/web/src/**/*.{ts,tsx}: Always follow the design system defined inapps/web/src/lib/design-tokens.tsandapps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g.,#5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Usefont-sansfor standard UI text (Geist Sans) andfont-monofor code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobilep-4(1rem) and desktopp-[60px]
Use appropriate border radius: small elementsrounded-lg, mediumrounded-xl, largerounded-2xl, buttonsrounded-[16px]
Use animation durations: fastduration-100(0.1s), normalduration-300(0.3s), slowduration-600(0.6s)
Files:
apps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursorrules)
Optimize images using next/image
apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located insrc/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions
Files:
apps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)
apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from@/prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')
Files:
apps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
apps/api/src/**/{services,models,database}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Encrypt sensitive data (passwords, tokens, API keys) before storing in database
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{database,services}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Handle database connection failures gracefully with retry logic
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{database,models,services}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Always use parameterized queries or ORM methods to prevent SQL injection
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,database}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Wrap database transactions in try-catch blocks with proper rollback on failure
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,repositories}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Avoid N+1 queries; use eager loading or batch queries when fetching related data
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,models}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Validate data against schema before database operations
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{database,clients,services}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Use connection pooling for database and external service clients
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,clients,handlers}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Implement timeouts for external API calls to prevent hanging requests
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
apps/api/src/**/{middleware,services,routes,controllers}/*.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Log all critical operations (auth attempts, payment processing, data mutations)
Files:
apps/api/src/services/llm.service.tsapps/api/src/services/tutorial.service.tsapps/api/src/services/github-crawler.service.ts
**/*[A-Z]*.{tsx,ts}
📄 CodeRabbit inference engine (.cursorrules)
Use PascalCase for component file names (e.g.,
UserProfile.tsx)
Files:
apps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/components/**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)
apps/web/src/components/**/*.{tsx,ts,jsx,js}: Never use hardcoded hex values directly in components; always reference colors from the design token system using Tailwind classes
Use semantic color names from the design token system that describe purpose, not appearance (e.g., bg-brand-purple, bg-surface-primary, text-text-primary)
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale for section padding: p-4 (1rem) on mobile, p-[60px] on desktop
Use rounded-lg (0.5rem) for small elements, rounded-xl (1rem) for medium elements, rounded-2xl (1.5rem) for large elements, and rounded-[16px] for buttons
Use duration-100 (0.1s) for fast transitions, duration-300 (0.3s) for normal transitions, and duration-600 (0.6s) for slow transitions
Use available custom animations: animate-accordion-down, animate-accordion-up, animate-scrollRight, animate-scrollLeft, animate-customspin, animate-spin-slow, animate-spin-slow-reverse, animate-marquee, animate-marquee-vertical, animate-shine
Prefer functional components with TypeScript
Extract reusable logic into custom hooks
Prefer controlled components over uncontrolled
Include proper aria labels for accessibility
Ensure keyboard navigation works in interactive components
Maintain proper heading hierarchy in page components
Provide alt text for images
Use 'class:' instead of the ternary operator in class tags whenever possible
Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Always follow the design system defined inapps/web/src/lib/design-tokens.tsandapps/web/tailwind.config.ts
Files:
apps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/components/**/*.{tsx,ts}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)
apps/web/src/components/**/*.{tsx,ts}: Use proper TypeScript types and avoid usinganytype
Use descriptive prop names and define prop types using TypeScript interfaces or types
Name components using PascalCase (e.g., UserProfile.tsx)
Files:
apps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/tutorial/index.tsapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
apps/api/src/{index,main,app,server}.{js,ts}
📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)
Validate required environment variables on startup; fail fast if missing
Files:
apps/api/src/index.ts
🧠 Learnings (13)
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Implement rate limiting on all public endpoints to prevent abuse
Applied to files:
apps/api/src/routers/_app.tsapps/api/src/index.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,types}/*.ts : Define strict TypeScript interfaces for request/response bodies
Applied to files:
apps/api/src/routers/_app.tsapps/api/src/index.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Use middleware for auth checks; never duplicate auth logic in route handlers
Applied to files:
apps/api/src/routers/_app.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Use zod or similar for runtime validation of incoming data
Applied to files:
apps/api/src/routers/_app.tsapps/api/src/index.ts
📚 Learning: 2025-12-17T12:39:29.991Z
Learnt from: huamanraj
Repo: apsinghdev/opensox PR: 262
File: apps/web/src/components/checkout/CheckoutWrapper.tsx:5-5
Timestamp: 2025-12-17T12:39:29.991Z
Learning: In Next.js apps, avoid using redirect() from next/navigation inside Client Components during render. It should not be used in event handlers like onClick or onSubmit; for those cases, use useRouter().push() or router.replace() instead. This guidance applies to TSX files (React components) across the codebase; implement in client components to handle conditional redirects during render, and use router-based navigation in handlers.
Applied to files:
apps/web/src/app/(main)/dashboard/tutorial/page.tsxapps/web/src/components/tutorial/TutorialHistory.tsxapps/web/src/components/dashboard/Sidebar.tsxapps/web/src/components/tutorial/TutorialGenerator.tsxapps/web/src/components/tutorial/FileBrowser.tsxapps/web/src/components/tutorial/TutorialViewer.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Ensure keyboard navigation works in interactive components
Applied to files:
apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Applied to files:
apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Prefer functional components with TypeScript
Applied to files:
apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Include proper aria labels for accessibility
Applied to files:
apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Organize imports in order: React → third-party → local components → utils → types
Applied to files:
apps/web/src/components/tutorial/FileBrowser.tsxapps/web/package.json
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Remove unused imports
Applied to files:
apps/web/package.json
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{logging,middleware,utils}/*.{js,ts} : Use structured logging (JSON format) for easier parsing and analysis
Applied to files:
apps/api/src/index.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Maintain proper heading hierarchy
Applied to files:
apps/web/src/components/tutorial/TutorialViewer.tsx
🧬 Code graph analysis (7)
apps/api/src/routers/_app.ts (1)
apps/api/src/routers/tutorial.ts (1)
tutorialRouter(20-183)
apps/api/src/services/llm.service.ts (1)
apps/api/test-ddos.js (1)
response(28-28)
apps/web/src/components/tutorial/TutorialHistory.tsx (1)
apps/web/src/lib/trpc.ts (1)
trpc(4-4)
apps/web/src/components/tutorial/TutorialGenerator.tsx (4)
apps/web/src/lib/trpc.ts (1)
trpc(4-4)apps/web/src/components/tutorial/FileBrowser.tsx (1)
FileBrowser(264-503)apps/web/src/components/ui/input.tsx (1)
Input(22-22)apps/web/src/providers/trpc-provider.tsx (1)
TRPCProvider(11-53)
apps/api/src/services/tutorial.service.ts (2)
apps/api/src/services/llm.service.ts (1)
callLLM(71-87)apps/api/src/services/github-crawler.service.ts (3)
parseGitHubUrl(78-101)crawlSelectedFiles(375-411)crawlGitHubFiles(268-328)
apps/api/src/services/github-crawler.service.ts (1)
apps/api/test-ddos.js (1)
response(28-28)
apps/web/src/components/tutorial/TutorialViewer.tsx (1)
apps/web/src/lib/trpc.ts (1)
trpc(4-4)
🪛 ast-grep (0.40.0)
apps/api/src/services/github-crawler.service.ts
[warning] 47-47: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexPattern}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
apps/web/src/components/tutorial/TutorialViewer.tsx
[warning] 100-100: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
apps/web/src/components/tutorial/TutorialViewer.tsx
[error] 101-101: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (19)
apps/web/src/components/dashboard/Sidebar.tsx (1)
53-58: LGTM!The new tutorial route entry follows the established pattern for free routes with proper icon, label, and badge. The "AI" badge appropriately signals the feature's nature.
apps/web/package.json (1)
32-48: LGTM!The markdown/syntax highlighting/diagram rendering dependencies are appropriate for the tutorial content features. The version selections align with current stable releases.
apps/web/src/app/globals.css (2)
99-152: Hardcoded hex values for Mermaid theming are acceptable here.While the codebase generally avoids hardcoded hex values, Mermaid generates inline SVGs that require direct CSS overrides with
!important. The color palette is cohesive with the dark theme. Consider documenting these color choices in a comment for maintainability.
154-193: LGTM!Good use of Tailwind utility classes for syntax highlighting token colors, maintaining consistency with the design system approach.
apps/api/src/services/llm.service.ts (2)
43-54: LGTM on error handling.Good error handling with informative error messages. The error logging captures API response details for debugging without exposing sensitive information like the API key.
71-91: LGTM on the service interface.Clean exported API with sensible defaults. The caching logic is straightforward.
apps/web/src/components/tutorial/TutorialViewer.tsx (1)
195-227: LGTM on Mermaid initialization.Good configuration of Mermaid with dark theme variables and sensible defaults for flowcharts and sequence diagrams. The
startOnLoad: falseis correct since rendering is handled manually.apps/web/src/components/tutorial/index.ts (1)
1-3: LGTM! Clean barrel exports.The index file correctly re-exports the tutorial components, enabling clean imports throughout the application.
apps/api/src/routers/tutorial.ts (1)
9-18: LGTM! Well-defined input validation.The input schema uses zod validation effectively with appropriate constraints on maxAbstractions (3-15) and maxFiles (5-100) to prevent abuse.
Based on learnings: "Use zod or similar for runtime validation of incoming data."
apps/web/src/components/tutorial/FileBrowser.tsx (2)
37-84: LGTM onbuildFileTreeimplementation.The tree-building logic correctly handles path splitting, hierarchical insertion, and sorting (folders first, then alphabetical). The recursive sorting approach is clean.
86-113: LGTM on utility functions.The
formatSizeandgetFileColorhelper functions are well-implemented with clear logic and appropriate fallbacks.apps/web/src/components/tutorial/TutorialGenerator.tsx (3)
21-22: Use UPPER_SNAKE_CASE for constants per coding guidelines.The constant is correctly named but could benefit from explicit typing.
60-89: LGTM on the file fetching workflow.The
handleFetchFilesfunction properly validates the GitHub URL, checks for existing tutorials before proceeding, and handles errors with appropriate state management.
154-216: LGTM on the existing tutorial modal implementation.The modal properly displays existing tutorials with ownership indication and provides clear actions for viewing existing or generating new tutorials.
apps/api/src/services/tutorial.service.ts (3)
630-691: LGTM on the maingenerateTutorialorchestration.The function properly coordinates the multi-step pipeline with clear logging, handles both user-selected and automatic file selection, and validates that files were found before proceeding.
550-625: LGTM oncombineTutorialimplementation.The function correctly generates the Mermaid diagram, builds the index content, and assembles the chapter files with proper filename generation and content normalization.
89-185: Consider input sanitization for LLM prompts.User-provided repository content is directly interpolated into LLM prompts. While the LLM itself handles this, consider that malicious code content could potentially influence LLM behavior (prompt injection). This is a defense-in-depth consideration.
apps/api/src/services/github-crawler.service.ts (2)
78-101: LGTM onparseGitHubUrlimplementation.The URL parsing correctly extracts owner, repo, and optional branch from various GitHub URL formats, with proper error handling for invalid URLs.
330-369: LGTM onlistRepoFilesimplementation.The function correctly lists files without fetching content, applies default patterns for filtering, and returns structured data suitable for the file browser UI.
| -- CreateTable | ||
| CREATE TABLE "Tutorial" ( | ||
| "id" TEXT NOT NULL, | ||
| "userId" TEXT NOT NULL, | ||
| "repoUrl" TEXT NOT NULL, | ||
| "projectName" TEXT NOT NULL, | ||
| "language" TEXT NOT NULL DEFAULT 'english', | ||
| "indexContent" TEXT NOT NULL, | ||
| "mermaidDiagram" TEXT NOT NULL, | ||
| "chapters" JSONB NOT NULL, | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
|
|
||
| CONSTRAINT "Tutorial_pkey" PRIMARY KEY ("id") | ||
| ); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "Tutorial_userId_idx" ON "Tutorial"("userId"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "Tutorial_repoUrl_idx" ON "Tutorial"("repoUrl"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Consider adding a foreign key constraint for userId.
The Tutorial table references userId but doesn't enforce referential integrity with a foreign key constraint. This could lead to orphaned tutorial records if users are deleted. Consider adding a foreign key constraint with an appropriate ON DELETE action (CASCADE or SET NULL).
🔎 Suggested fix
After line 15, add:
-- Add foreign key constraint
ALTER TABLE "Tutorial" ADD CONSTRAINT "Tutorial_userId_fkey"
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE;Also ensure the Prisma schema reflects this relationship with:
model Tutorial {
// ... existing fields
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
// ... existing fields
tutorials Tutorial[]
}🤖 Prompt for AI Agents
In apps/api/prisma/migrations/20251215163900_add_tutorial_model/migration.sql
around lines 1 to 21, the "Tutorial" table defines userId but lacks a foreign
key constraint; add a constraint after the table definition to enforce
referential integrity, e.g. ALTER TABLE "Tutorial" ADD CONSTRAINT
"Tutorial_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE
CASCADE (or ON DELETE SET NULL if preferred), and update the Prisma schema so
Tutorial has user User @relation(fields: [userId], references: [id], onDelete:
Cascade) and User includes tutorials Tutorial[] to keep the DB and Prisma models
in sync.
| model Tutorial { | ||
| id String @id @default(cuid()) | ||
| userId String | ||
| repoUrl String | ||
| projectName String | ||
| language String @default("english") | ||
| indexContent String @db.Text | ||
| mermaidDiagram String @db.Text | ||
| chapters Json // Array of {filename: string, content: string} | ||
| createdAt DateTime @default(now()) | ||
| updatedAt DateTime @updatedAt | ||
| @@index([userId]) | ||
| @@index([repoUrl]) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add Prisma relation between Tutorial and User models.
The Tutorial model references userId but doesn't define a Prisma relation to the User model. This prevents you from using Prisma's relation features and doesn't enforce referential integrity at the schema level.
🔎 Recommended changes
Update the Tutorial model:
model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([repoUrl])
}And add to the User model (around line 44):
model User {
id String @id @default(cuid())
email String @unique
firstName String
authMethod String
createdAt DateTime @default(now())
lastLogin DateTime @updatedAt
completedSteps Json?
accounts Account[]
payments Payment[]
subscriptions Subscription[]
+ tutorials Tutorial[]
}After making these changes, run:
npx prisma migrate dev --name add_tutorial_user_relation📝 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.
| model Tutorial { | |
| id String @id @default(cuid()) | |
| userId String | |
| repoUrl String | |
| projectName String | |
| language String @default("english") | |
| indexContent String @db.Text | |
| mermaidDiagram String @db.Text | |
| chapters Json // Array of {filename: string, content: string} | |
| createdAt DateTime @default(now()) | |
| updatedAt DateTime @updatedAt | |
| @@index([userId]) | |
| @@index([repoUrl]) | |
| } | |
| model Tutorial { | |
| id String @id @default(cuid()) | |
| userId String | |
| repoUrl String | |
| projectName String | |
| language String @default("english") | |
| indexContent String @db.Text | |
| mermaidDiagram String @db.Text | |
| chapters Json | |
| createdAt DateTime @default(now()) | |
| updatedAt DateTime @updatedAt | |
| user User @relation(fields: [userId], references: [id], onDelete: Cascade) | |
| @@index([userId]) | |
| @@index([repoUrl]) | |
| } |
🤖 Prompt for AI Agents
In apps/api/prisma/schema.prisma around lines 106 to 120, the Tutorial model
declares userId but lacks a Prisma relation to the User model; add a relation by
keeping the userId scalar and adding a relation field (e.g., user User
@relation(fields: [userId], references: [id])) and ensure userId remains String,
then update the User model (around line ~44) to include a back-relation field
(e.g., tutorials Tutorial[]) so Prisma enforces referential integrity; after
editing the schema run npx prisma migrate dev --name add_tutorial_user_relation
to apply the migration.
| import { projectRouter } from "./projects.js"; | ||
| import { authRouter } from "./auth.js"; | ||
| import { paymentRouter } from "./payment.js"; | ||
| import { tutorialRouter } from "./tutorial.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine key files in the tutorial router area
find apps/api/src -name "*.ts" | grep -E "(trpc|router)" | head -20Repository: apsinghdev/opensox
Length of output: 299
🏁 Script executed:
#!/bin/bash
# Look at the _app.ts file where the import is
cat apps/api/src/routers/_app.tsRepository: apsinghdev/opensox
Length of output: 862
🏁 Script executed:
#!/bin/bash
# Look for tutorial router file
find apps/api -name "*tutorial*" -type fRepository: apsinghdev/opensox
Length of output: 136
🏁 Script executed:
#!/bin/bash
# Search for protectedProcedure and publicProcedure definitions
rg -n "protectedProcedure|publicProcedure" apps/api/src --type ts -A 2Repository: apsinghdev/opensox
Length of output: 6084
🏁 Script executed:
#!/bin/bash
# Examine the delete mutation and service implementation
sed -n '157,175p' apps/api/src/routers/tutorial.ts
cat apps/api/src/services/tutorial.service.ts | head -100Repository: apsinghdev/opensox
Length of output: 2882
🏁 Script executed:
#!/bin/bash
# Check for rate limiting middleware/configuration
rg -n "rateLimit|middleware" apps/api/src --type ts | head -20Repository: apsinghdev/opensox
Length of output: 624
🏁 Script executed:
#!/bin/bash
# Look for rate limiting or middleware configuration files
find apps/api -name "*rate*" -o -name "*middleware*" | head -20Repository: apsinghdev/opensox
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Check the rate limiting setup in index.ts
sed -n '40,100p' apps/api/src/index.tsRepository: apsinghdev/opensox
Length of output: 1880
🏁 Script executed:
#!/bin/bash
# Check if there's endpoint-specific rate limiting for generate
rg -n "generate.*rate|rate.*generate" apps/api/src --type ts -iRepository: apsinghdev/opensox
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look at the full trpc middleware setup and how rate limiting applies
sed -n '260,280p' apps/api/src/index.tsRepository: apsinghdev/opensox
Length of output: 560
🏁 Script executed:
#!/bin/bash
# Check the generate endpoint implementation and if it has any internal protection
sed -n '112,155p' apps/api/src/routers/tutorial.tsRepository: apsinghdev/opensox
Length of output: 1349
Secure tutorial endpoints with authentication and per-endpoint rate limiting.
All tutorial router endpoints use publicProcedure and fall back to "anonymous" user when unauthenticated. This creates security and data consistency issues:
- generate: Allows anonymous users to trigger resource-intensive LLM-powered tutorial generation without authentication
- delete/getUserTutorials: Use a shared "anonymous" userId, causing multiple unauthenticated users to share the same data scope—allowing unauthorized access to or deletion of others' tutorials
- Rate limiting: Global
/trpcrate limit (100 req/15min) exists but is insufficient for the expensivegenerateendpoint
Refactor to:
- Use
protectedProcedurefor all three endpoints (generate, delete, getUserTutorials) - Remove the "anonymous" fallback and require authentication
- Consider adding endpoint-specific rate limiting for
generatevia middleware
Per the learnings: "Use middleware for auth checks; never duplicate auth logic in route handlers" and "Implement rate limiting on all public endpoints."
🤖 Prompt for AI Agents
In apps/api/src/routers/_app.ts around line 7, the tutorial router currently
exposes endpoints as publicProcedure and relies on an "anonymous" fallback;
update the router so generate, delete and getUserTutorials use
protectedProcedure (remove any anonymous user fallback and require
authentication), move all auth checks into middleware rather than inside
handlers, and add/attach an endpoint-specific rate-limit middleware to the
generate endpoint to throttle expensive LLM requests (e.g., per-user or per-IP
limits) while keeping global rate limits intact.
| checkExisting: publicProcedure | ||
| .input(z.object({ repoUrl: z.string() })) | ||
| .query(async ({ input, ctx }) => { | ||
| // @ts-ignore | ||
| const currentUserId = ctx.user?.id || "anonymous"; | ||
|
|
||
| const tutorials = await prisma.tutorial.findMany({ | ||
| where: { | ||
| repoUrl: input.repoUrl, | ||
| }, | ||
| orderBy: { createdAt: "desc" }, | ||
| select: { | ||
| id: true, | ||
| projectName: true, | ||
| language: true, | ||
| createdAt: true, | ||
| userId: true, | ||
| }, | ||
| }); | ||
|
|
||
| return { | ||
| exists: tutorials.length > 0, | ||
| tutorials: tutorials.map(t => ({ | ||
| ...t, | ||
| isOwnTutorial: t.userId === currentUserId, | ||
| })), | ||
| }; | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove @ts-ignore and properly type the context user.
The @ts-ignore directive on line 42 suppresses TypeScript errors instead of fixing the underlying type issue. This hides potential runtime errors and makes the code harder to maintain.
Additionally, the "anonymous" fallback enables unauthenticated access which may not be the intended behavior for comparing tutorial ownership.
🔎 Recommended fix
Define proper types for the context:
// In your context.ts or types file
interface AuthenticatedContext {
user: {
id: string;
email: string;
// ... other user properties
};
}Then update the procedure:
checkExisting: publicProcedure
.input(z.object({ repoUrl: z.string() }))
.query(async ({ input, ctx }) => {
- // @ts-ignore
- const currentUserId = ctx.user?.id || "anonymous";
+ const currentUserId = ctx.user?.id;Consider whether this endpoint should require authentication or if anonymous access is intentional.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 39 to 66, remove the //
@ts-ignore and properly type ctx.user instead of falling back to the string
"anonymous": update the tRPC context types (or import the existing
Context/Session types) so ctx.user is typed as optional or required user object,
then either make this procedure require authentication (switch to
protectedProcedure or guard at the router level) so currentUserId comes from
ctx.user!.id, or if anonymous access is intended, set currentUserId to
undefined/null and adjust the isOwnTutorial comparison to handle absence of a
user; ensure you update imports and the context/type definitions rather than
suppressing the TypeScript error.
| getUserTutorials: publicProcedure.query(async ({ ctx }) => { | ||
| // @ts-ignore | ||
| const userId = ctx.user?.id || "anonymous"; | ||
|
|
||
| const tutorials = await prisma.tutorial.findMany({ | ||
| where: { userId }, | ||
| orderBy: { createdAt: "desc" }, | ||
| select: { | ||
| id: true, | ||
| projectName: true, | ||
| repoUrl: true, | ||
| language: true, | ||
| createdAt: true, | ||
| }, | ||
| }); | ||
|
|
||
| return tutorials; | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getUserTutorials should require authentication.
This endpoint retrieves user-specific tutorials but falls back to "anonymous" when no user is authenticated. This could lead to:
- Anonymous users seeing each other's tutorials
- Misleading UI showing tutorials for unauthenticated users
- Type safety issues with the @ts-ignore directive
This endpoint should use a protectedProcedure instead of publicProcedure to enforce authentication.
🔎 Recommended fix
- getUserTutorials: publicProcedure.query(async ({ ctx }) => {
- // @ts-ignore
- const userId = ctx.user?.id || "anonymous";
+ getUserTutorials: protectedProcedure.query(async ({ ctx }) => {
+ const userId = ctx.user.id;
const tutorials = await prisma.tutorial.findMany({Ensure protectedProcedure is imported from your trpc.ts file.
📝 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.
| getUserTutorials: publicProcedure.query(async ({ ctx }) => { | |
| // @ts-ignore | |
| const userId = ctx.user?.id || "anonymous"; | |
| const tutorials = await prisma.tutorial.findMany({ | |
| where: { userId }, | |
| orderBy: { createdAt: "desc" }, | |
| select: { | |
| id: true, | |
| projectName: true, | |
| repoUrl: true, | |
| language: true, | |
| createdAt: true, | |
| }, | |
| }); | |
| return tutorials; | |
| }), | |
| getUserTutorials: protectedProcedure.query(async ({ ctx }) => { | |
| const userId = ctx.user.id; | |
| const tutorials = await prisma.tutorial.findMany({ | |
| where: { userId }, | |
| orderBy: { createdAt: "desc" }, | |
| select: { | |
| id: true, | |
| projectName: true, | |
| repoUrl: true, | |
| language: true, | |
| createdAt: true, | |
| }, | |
| }); | |
| return tutorials; | |
| }), |
🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 90 to 107, the getUserTutorials
handler uses publicProcedure and falls back to "anonymous" with a @ts-ignore,
which allows unauthenticated access and risks leaking tutorials; change it to
use protectedProcedure (import it from your trpc setup), remove the @ts-ignore,
read userId from ctx.user.id (no fallback), and rely on the protectedProcedure
guard so you can safely query prisma.tutorial.findMany({ where: { userId }, ...
}) and return the results.
| return ( | ||
| <div> | ||
| <div | ||
| className={`flex items-center gap-2 py-1.5 px-2 rounded-md cursor-pointer hover:bg-dash-hover transition-colors group ${ | ||
| level > 0 ? "ml-4" : "" | ||
| }`} | ||
| style={{ paddingLeft: `${level * 16 + 8}px` }} | ||
| > | ||
| {/* Expand/Collapse for folders */} | ||
| {isFolder ? ( | ||
| <button | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| onToggleExpand(node.path); | ||
| }} | ||
| className="p-0.5 hover:bg-dash-base rounded" | ||
| > | ||
| {isExpanded ? ( | ||
| <ChevronDownIcon className="w-4 h-4 text-text-tertiary" /> | ||
| ) : ( | ||
| <ChevronRightIcon className="w-4 h-4 text-text-tertiary" /> | ||
| )} | ||
| </button> | ||
| ) : ( | ||
| <span className="w-5" /> | ||
| )} | ||
|
|
||
| {/* Checkbox */} | ||
| <button | ||
| onClick={() => onToggle(node.path, node.type, node.children)} | ||
| className={`w-4 h-4 rounded border flex items-center justify-center transition-colors ${ | ||
| selectionState === "full" | ||
| ? "bg-brand-purple border-brand-purple" | ||
| : selectionState === "partial" | ||
| ? "bg-brand-purple/50 border-brand-purple" | ||
| : "border-dash-border hover:border-brand-purple/50" | ||
| }`} | ||
| > | ||
| {selectionState === "full" && ( | ||
| <CheckIcon className="w-3 h-3 text-white" /> | ||
| )} | ||
| {selectionState === "partial" && ( | ||
| <div className="w-2 h-0.5 bg-white rounded" /> | ||
| )} | ||
| </button> | ||
|
|
||
| {/* Icon */} | ||
| {isFolder ? ( | ||
| isExpanded ? ( | ||
| <FolderOpenIcon className="w-4 h-4 text-yellow-400" /> | ||
| ) : ( | ||
| <FolderIcon className="w-4 h-4 text-yellow-400" /> | ||
| ) | ||
| ) : ( | ||
| <DocumentIcon className={`w-4 h-4 ${getFileColor(node.name)}`} /> | ||
| )} | ||
|
|
||
| {/* Name */} | ||
| <span | ||
| className={`flex-1 text-sm truncate ${ | ||
| isSelected || selectionState !== "none" | ||
| ? "text-text-primary" | ||
| : "text-text-secondary" | ||
| }`} | ||
| onClick={() => onToggle(node.path, node.type, node.children)} | ||
| > | ||
| {node.name} | ||
| </span> | ||
|
|
||
| {/* Size for files */} | ||
| {node.size !== undefined && ( | ||
| <span className="text-xs text-text-tertiary"> | ||
| {formatSize(node.size)} | ||
| </span> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* Children */} | ||
| {isFolder && isExpanded && ( | ||
| <div> | ||
| {node.children.map((child) => ( | ||
| <TreeNodeItem | ||
| key={child.path} | ||
| node={child} | ||
| selectedPaths={selectedPaths} | ||
| onToggle={onToggle} | ||
| expandedPaths={expandedPaths} | ||
| onToggleExpand={onToggleExpand} | ||
| level={level + 1} | ||
| /> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add accessibility attributes to interactive elements.
The TreeNodeItem component has interactive elements (expand/collapse button, checkbox button, clickable name) that lack proper accessibility attributes. Per coding guidelines, interactive elements need aria-label, tabindex, and onKeyDown handlers.
Suggested improvements
{isFolder ? (
<button
onClick={(e) => {
e.stopPropagation();
onToggleExpand(node.path);
}}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.stopPropagation();
+ onToggleExpand(node.path);
+ }
+ }}
+ aria-label={isExpanded ? `Collapse ${node.name}` : `Expand ${node.name}`}
+ aria-expanded={isExpanded}
className="p-0.5 hover:bg-dash-base rounded"
> {/* Checkbox */}
<button
onClick={() => onToggle(node.path, node.type, node.children)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onToggle(node.path, node.type, node.children);
+ }
+ }}
+ aria-label={`${selectionState === 'full' ? 'Deselect' : 'Select'} ${node.name}`}
+ aria-checked={selectionState === 'full' ? true : selectionState === 'partial' ? 'mixed' : false}
+ role="checkbox"
className={`w-4 h-4 rounded border flex items-center justify-center transition-colors ${ {/* Name */}
<span
className={`flex-1 text-sm truncate ${
isSelected || selectionState !== "none"
? "text-text-primary"
: "text-text-secondary"
}`}
onClick={() => onToggle(node.path, node.type, node.children)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ onToggle(node.path, node.type, node.children);
+ }
+ }}
+ tabIndex={0}
+ role="button"
+ aria-label={`Toggle selection for ${node.name}`}
>🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/FileBrowser.tsx around lines 151 to 246,
several interactive pieces (folder expand/collapse button, checkbox button, and
the clickable name span) are missing accessibility attributes and keyboard
handlers; add appropriate ARIA and keyboard support: give the expand/collapse
button an aria-label and aria-expanded reflecting isExpanded, ensure the
checkbox button has aria-label and aria-checked reflecting selectionState
("true"/"mixed"/"false"), make the name span keyboard-accessible by adding
tabindex={0}, role="button" (or convert to a real button) and an onKeyDown
handler that triggers the same action on Enter and Space; ensure all interactive
elements remain focusable and provide clear, contextual aria-label strings
(e.g., `Toggle folder <name>`, `Select <name>`) so screen readers get meaningful
information.
| <button | ||
| onClick={onCancel} | ||
| className="p-2 hover:bg-dash-hover rounded-lg transition-colors" | ||
| > | ||
| <XMarkIcon className="w-5 h-5 text-text-tertiary" /> | ||
| </button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add aria-label to close button.
The close button in the header lacks an accessible label for screen readers. Per coding guidelines, include proper aria labels for accessibility.
<button
onClick={onCancel}
className="p-2 hover:bg-dash-hover rounded-lg transition-colors"
+ aria-label="Close file browser"
>
<XMarkIcon className="w-5 h-5 text-text-tertiary" />
</button>🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/FileBrowser.tsx around lines 399 to 404, the
close button lacks an accessible label; add an aria-label attribute to the
button (e.g., aria-label="Close" or more specific like "Close file browser") so
screen readers announce its purpose, ensure the label is descriptive and
localized if your app uses i18n, and keep the existing icon-only UI intact.
| const extractRepoName = (url: string) => { | ||
| const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/); | ||
| return match ? `${match[1]}/${match[2]}` : url; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, examine the actual file and function
cat -n apps/web/src/components/tutorial/TutorialHistory.tsx | sed -n '55,70p'Repository: apsinghdev/opensox
Length of output: 665
🏁 Script executed:
# Search for other GitHub URL parsing patterns
rg -n "github\.com" --type ts --type tsx -C 2Repository: apsinghdev/opensox
Length of output: 89
🏁 Script executed:
# Test the regex against edge cases
node <<'EOF'
const extractRepoName = (url) => {
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
return match ? `${match[1]}/${match[2]}` : url;
};
const testCases = [
"https://github.com/owner/repo",
"https://github.com/owner/repo/",
"https://github.com/owner/repo.git",
"https://github.com/owner/repo/tree/main",
"https://github.com/owner/repo/pull/123",
"github.com/owner/repo",
"https://github.com/owner/repo.git/"
];
testCases.forEach(url => {
console.log(`Input: ${url}`);
console.log(`Output: ${extractRepoName(url)}`);
console.log('---');
});
EOFRepository: apsinghdev/opensox
Length of output: 510
🏁 Script executed:
# Search for other GitHub URL parsing using different patterns
rg "github" apps/web/src --type ts -i -A 3Repository: apsinghdev/opensox
Length of output: 35307
🏁 Script executed:
# Search for extractRepoName usage
rg "extractRepoName" apps/web/srcRepository: apsinghdev/opensox
Length of output: 268
Fix regex to handle .git suffix in GitHub URLs.
The regex pattern /github\.com\/([^\/]+)\/([^\/]+)/ doesn't strip the .git extension, so URLs like https://github.com/owner/repo.git return owner/repo.git instead of owner/repo. Update the regex to:
const extractRepoName = (url: string) => {
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/)?$/);
return match ? `${match[1]}/${match[2]}` : url;
};This handles trailing slashes, .git suffixes, and additional path segments consistently.
🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/TutorialHistory.tsx around lines 59 to 62,
the current regex doesn't strip a trailing ".git" or handle trailing
slashes/extra path segments; update the pattern to match owner and repo but
optionally discard ".git" and trailing slash or extra segments (e.g., use a
non-greedy capture for repo and an optional non-capturing group for ".git" and
optional slash/end) so the function returns "owner/repo" for inputs like
"https://github.com/owner/repo.git" or with a trailing slash.
| return ( | ||
| <div | ||
| ref={containerRef} | ||
| className="my-6 flex justify-center overflow-x-auto bg-dash-base/50 rounded-lg p-4 border border-dash-border" | ||
| dangerouslySetInnerHTML={{ __html: svg }} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XSS risk with unsanitized SVG injection.
Static analysis correctly flags dangerouslySetInnerHTML here. While Mermaid generates SVGs internally, the output should be sanitized before injection to prevent potential XSS if malicious content is embedded in the diagram definition. The codebase already has dompurify as a dependency.
🔎 Proposed fix using DOMPurify
+import DOMPurify from "dompurify";
+
function MermaidDiagram({ chart }: { chart: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const [svg, setSvg] = useState<string>("");
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const renderChart = async () => {
if (!chart.trim()) return;
try {
const id = `mermaid-${Math.random().toString(36).substring(7)}`;
const { svg: renderedSvg } = await mermaid.render(id, chart);
- setSvg(renderedSvg);
+ setSvg(DOMPurify.sanitize(renderedSvg, { USE_PROFILES: { svg: true } }));
setError(null);
} catch (err) {
console.error("Mermaid render error:", err);
setError("Failed to render diagram");
}
};
renderChart();
}, [chart]);
// ... rest of component
return (
<div
ref={containerRef}
className="my-6 flex justify-center overflow-x-auto bg-dash-base/50 rounded-lg p-4 border border-dash-border"
- dangerouslySetInnerHTML={{ __html: svg }}
+ dangerouslySetInnerHTML={{ __html: svg }} // now sanitized
/>
);
}Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 ast-grep (0.40.0)
[warning] 100-100: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
[error] 101-101: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/TutorialViewer.tsx around lines 97 to 103,
the SVG string is injected with dangerouslySetInnerHTML without sanitization;
import DOMPurify from 'dompurify' and sanitize the SVG before injection (e.g.
const safeSvg = DOMPurify.sanitize(svg, { SAFE_FOR_SVG: true }) or equivalent
options), then pass the sanitized string to dangerouslySetInnerHTML; ensure the
import is added, perform sanitization where svg is produced/updated (or memoize
it) and use the sanitized value in the returned div to eliminate the XSS risk.
| interface TutorialViewerProps { | ||
| tutorialId: string; | ||
| generatedTutorial?: TutorialData | null; | ||
| onBack: () => void; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate interface definition.
TutorialViewerProps is already defined at lines 59-63. Remove this duplicate.
🔎 Fix
-interface TutorialViewerProps {
- tutorialId: string;
- generatedTutorial?: TutorialData | null;
- onBack: () => void;
-}
-
export default function TutorialViewer({📝 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.
| interface TutorialViewerProps { | |
| tutorialId: string; | |
| generatedTutorial?: TutorialData | null; | |
| onBack: () => void; | |
| } | |
| export default function TutorialViewer({ |
🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/TutorialViewer.tsx around lines 170 to 174,
there's a duplicate declaration of the TutorialViewerProps interface (it already
exists at lines 59-63); remove the duplicate declaration and ensure any uses in
the file refer to the existing interface, or if the two variants differ,
consolidate them into the single interface at the original location and update
imports/uses accordingly.
@ajeetunc as proposed, here's the PR for
Here's the working demo video:
https://youtu.be/gmg0PAEpNlc
Do the following changes in the .env file for testing:
(if razorpay keys not provided, do disable the razorpay keys for testing)
It's inspired by Zach's https://code2tutorial.com/ , the open sourced repo is in python, I've implemented it in TS for Opensox.
Summary by CodeRabbit
Release Notes
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.