From 2d6091f5e7f4fef50da32ccc5ccf6d7ec7ef0bdd Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 7 Jun 2025 16:57:56 -0700 Subject: [PATCH] feat: add VS Code extension share API endpoint (#86) - Add POST /api/extension/share endpoint for creating task share links - Returns share URL for copying to clipboard in extension - Fix Next.js 15 async params compatibility in share page --- .../(authenticated)/share/[token]/page.tsx | 6 +- src/app/api/extension/share/route.ts | 87 +++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/app/api/extension/share/route.ts diff --git a/src/app/(authenticated)/share/[token]/page.tsx b/src/app/(authenticated)/share/[token]/page.tsx index d25dbc098b..ba5f43e658 100644 --- a/src/app/(authenticated)/share/[token]/page.tsx +++ b/src/app/(authenticated)/share/[token]/page.tsx @@ -19,7 +19,8 @@ export default async function SharedTaskPage({ params }: SharedTaskPageProps) { } try { - const result = await getTaskByShareToken(params.token); + const { token } = await params; + const result = await getTaskByShareToken(token); if (!result) { notFound(); @@ -66,7 +67,8 @@ export default async function SharedTaskPage({ params }: SharedTaskPageProps) { export async function generateMetadata({ params }: SharedTaskPageProps) { try { - const result = await getTaskByShareToken(params.token); + const { token } = await params; + const result = await getTaskByShareToken(token); if (!result) { return { diff --git a/src/app/api/extension/share/route.ts b/src/app/api/extension/share/route.ts new file mode 100644 index 0000000000..af156c5e10 --- /dev/null +++ b/src/app/api/extension/share/route.ts @@ -0,0 +1,87 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; +import { auth } from '@clerk/nextjs/server'; + +import { createTaskShare } from '@/actions/taskSharing'; +import { getTasks } from '@/actions/analytics'; +import { getOrganizationSettings } from '@/actions/organizationSettings'; + +const createShareRequestSchema = z.object({ + taskId: z.string().min(1, 'Task ID is required'), +}); + +export async function POST(request: NextRequest) { + try { + // Use existing Clerk authentication + const { userId, orgId } = await auth(); + + if (!userId || !orgId) { + return NextResponse.json( + { success: false, error: 'Authentication required' }, + { status: 401 }, + ); + } + + const body = await request.json(); + const result = createShareRequestSchema.safeParse(body); + + if (!result.success) { + return NextResponse.json( + { success: false, error: 'Invalid request data' }, + { status: 400 }, + ); + } + + const { taskId } = result.data; + + // Check if task sharing is enabled for the organization + const orgSettings = await getOrganizationSettings(); + + if (!orgSettings.cloudSettings?.enableTaskSharing) { + return NextResponse.json( + { + success: false, + error: 'Task sharing is not enabled for this organization', + }, + { status: 403 }, + ); + } + + // Verify user has access to the task + const tasks = await getTasks({ orgId, userId }); + const task = tasks.find((t) => t.taskId === taskId); + + if (!task) { + return NextResponse.json( + { success: false, error: 'Task not found or access denied' }, + { status: 404 }, + ); + } + + const shareResponse = await createTaskShare({ taskId }); + + if (!shareResponse.success || !shareResponse.data) { + return NextResponse.json( + { + success: false, + error: shareResponse.error || 'Failed to create share link', + }, + { status: 500 }, + ); + } + + return NextResponse.json({ + success: true, + shareUrl: shareResponse.data.shareUrl, + }); + } catch (error) { + console.error('Error in extension share endpoint:', error); + return NextResponse.json( + { + success: false, + error: 'An unexpected error occurred', + }, + { status: 500 }, + ); + } +}