-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Quick Decision Builder with bulk selection #6
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?
Changes from all commits
17596b3
631d436
af91826
5dcc40e
ddfed6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||||||||||||||||||
| import { NextRequest, NextResponse } from "next/server"; | ||||||||||||||||||||||
| import { prisma } from "@/lib/prisma"; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| formatDecision, | ||||||||||||||||||||||
| ExportFormat, | ||||||||||||||||||||||
| DecisionExportData, | ||||||||||||||||||||||
| } from "@/components/DecisionExportDialog"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export async function GET( | ||||||||||||||||||||||
| request: NextRequest, | ||||||||||||||||||||||
| { params }: { params: Promise<{ id: string; decisionId: string }> } | ||||||||||||||||||||||
| ) { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| const { id: projectId, decisionId } = await params; | ||||||||||||||||||||||
| const { searchParams } = new URL(request.url); | ||||||||||||||||||||||
| const format = (searchParams.get("format") as ExportFormat) || "markdown"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Validate format | ||||||||||||||||||||||
| const validFormats = [ | ||||||||||||||||||||||
| "cursor", | ||||||||||||||||||||||
| "linear", | ||||||||||||||||||||||
| "jira", | ||||||||||||||||||||||
| "slack", | ||||||||||||||||||||||
| "notion", | ||||||||||||||||||||||
| "markdown", | ||||||||||||||||||||||
| "json", | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
| if (!validFormats.includes(format)) { | ||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||
| { error: `Invalid format. Valid formats: ${validFormats.join(", ")}` }, | ||||||||||||||||||||||
| { status: 400 } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Fetch the decision | ||||||||||||||||||||||
| const decision = await prisma.decision.findUnique({ | ||||||||||||||||||||||
| where: { id: decisionId }, | ||||||||||||||||||||||
| include: { | ||||||||||||||||||||||
| project: { | ||||||||||||||||||||||
| select: { name: true }, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!decision) { | ||||||||||||||||||||||
| return NextResponse.json({ error: "Decision not found" }, { status: 404 }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Verify the decision belongs to the project | ||||||||||||||||||||||
| if (decision.projectId !== projectId) { | ||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||
| { error: "Decision does not belong to this project" }, | ||||||||||||||||||||||
| { status: 403 } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Build export data | ||||||||||||||||||||||
| const exportData: DecisionExportData = { | ||||||||||||||||||||||
| id: decision.id, | ||||||||||||||||||||||
| title: decision.title, | ||||||||||||||||||||||
| summary: decision.summary, | ||||||||||||||||||||||
| scope: decision.scope, | ||||||||||||||||||||||
| nonGoals: decision.nonGoals, | ||||||||||||||||||||||
| acceptanceCriteria: decision.acceptanceCriteria, | ||||||||||||||||||||||
| risks: decision.risks, | ||||||||||||||||||||||
| confidenceScore: decision.confidenceScore, | ||||||||||||||||||||||
| linkedFeedbackIds: decision.linkedFeedbackIds, | ||||||||||||||||||||||
| projectName: decision.project.name, | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Format the decision | ||||||||||||||||||||||
| const content = formatDecision(exportData, format); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Return appropriate response based on format | ||||||||||||||||||||||
| if (format === "json") { | ||||||||||||||||||||||
| return NextResponse.json(JSON.parse(content), { | ||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // For text formats, return plain text with appropriate headers | ||||||||||||||||||||||
| const contentType = | ||||||||||||||||||||||
| format === "markdown" ? "text/markdown" : "text/plain"; | ||||||||||||||||||||||
| const filename = `decision-${decision.title | ||||||||||||||||||||||
| .toLowerCase() | ||||||||||||||||||||||
| .replace(/\s+/g, "-")}.${format === "markdown" ? "md" : format}`; | ||||||||||||||||||||||
|
Comment on lines
+86
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Insufficient filename sanitization β special characters in title can break
π‘οΈ Proposed fix: sanitize the title more thoroughly- const filename = `decision-${decision.title
- .toLowerCase()
- .replace(/\s+/g, "-")}.${format === "markdown" ? "md" : format}`;
+ const safeTitle = decision.title
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-|-$/g, "")
+ .slice(0, 100);
+ const extension = format === "markdown" ? "md" : "txt";
+ const filename = `decision-${safeTitle}.${extension}`;Note: the current extension logic also produces non-standard extensions like π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return new NextResponse(content, { | ||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||
| "Content-Type": contentType, | ||||||||||||||||||||||
| "Content-Disposition": `attachment; filename="${filename}"`, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| console.error("Error exporting decision:", error); | ||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||
| { error: "Failed to export decision" }, | ||||||||||||||||||||||
| { status: 500 } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+9
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Check if there's auth middleware protecting API routes
fd "middleware" --type f --extension ts --extension js
echo "---AUTH_PATTERNS---"
# Check for auth patterns in existing API routes
rg -n "getServerSession|auth|currentUser|requireAuth" --type ts -g 'app/api/**'
echo "---NEXTAUTH_CONFIG---"
# Check for NextAuth or auth configuration
fd -e "route.ts" -e "route.js" -e "auth.ts" -e "auth.js" | head -20Repository: Fresh-Industries/pm-analyzer Length of output: 566 π Script executed: #!/bin/bash
# Search for middleware at root level
echo "---MIDDLEWARE_FILES---"
find . -maxdepth 2 -name "middleware.*" -type f
echo "---EXPORT_ROUTE_CONTENT---"
# Check the export route for any auth imports
head -15 app/api/projects/\[id\]/decisions/\[decisionId\]/export/route.ts
echo "---AUTH_LIB_FILES---"
# Check auth library files
ls -la lib/auth* 2>/dev/null || echo "No auth files in lib/"
echo "---OTHER_API_ROUTES---"
# Check a few other API routes to see auth patterns
find app/api -name "route.ts" -type f | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && head -20 {}'Repository: Fresh-Industries/pm-analyzer Length of output: 3688 π Script executed: #!/bin/bash
# Check if any routes import auth utilities
echo "---ROUTES_USING_AUTH---"
rg -l "from.*auth|import.*auth" app/api --type ts | grep -v "\[...all\]" | head -10
echo "---CHECK_AUTH_SERVER_USAGE---"
rg "getSession|auth\(\)" app/api --type ts | head -20
echo "---LIB_AUTH_FILES_CONTENT---"
echo "=== lib/auth.ts ==="
cat lib/auth.ts
echo "=== lib/auth-server.ts ==="
cat lib/auth-server.tsRepository: Fresh-Industries/pm-analyzer Length of output: 866 π Script executed: #!/bin/bash
# Check projects route in detail since it imports auth-client
echo "---PROJECTS_ROUTE_CONTENT---"
cat app/api/projects/route.ts
echo "---CHECK_IF_OTHER_ROUTES_USE_AUTH_CHECKS---"
rg "getServerSession|getSession" app/api --type ts -A 3 | head -30Repository: Fresh-Industries/pm-analyzer Length of output: 1470 Add authentication check to the export endpoint. This route lacks user authentication and is publicly accessible to anyone who can guess a valid π€ Prompt for AI Agents |
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -50,7 +50,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ | |||||
| scope: scope || "", | ||||||
| risks: risks || "", | ||||||
| nonGoals: nonGoals || "", | ||||||
| confidence: confidence || "medium", | ||||||
| confidenceScore: confidence || 0.5, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Using Useful? React with πΒ / π. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a caller sends π Proposed fix- confidenceScore: confidence || 0.5,
+ confidenceScore: confidence ?? 0.5,π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||
| status: "draft", | ||||||
| linkedFeedbackIds: linkedFeedbackIds || [], | ||||||
| }, | ||||||
|
|
||||||
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.
This route imports
formatDecisionfromcomponents/DecisionExportDialog.tsx, but that module is marked with"use client"and pulls in client-only React/UI dependencies. Calling a client export from a server route handler is not safe in Nextβs server runtime, so/api/projects/[id]/decisions/[decisionId]/exportcan fail when invoked. The formatter should live in a server-safe shared module (and be imported by both the dialog and the route) instead of importing from a client component file.Useful? React with πΒ / π.