export const dynamic = 'force-dynamic'; import { NextRequest, NextResponse } from 'next/server'; import { supabase } from '@/lib/supabase'; import { ensureAuthenticated, getAuthContext } from '@/lib/access-control'; async function canAccessTask(userId: string, isAdmin: boolean, accessibleProjectIds: string[], taskId: string) { if (isAdmin) return { ok: true as const }; const { data: task, error } = await supabase .from('tasks') .select('id,project_id,owner_user_id,executor_ids,help_requested_to_user_id') .eq('id', taskId) .maybeSingle(); if (error) return { ok: false as const, status: 500, error: error.message }; if (!task) return { ok: false as const, status: 404, error: '计划不存在' }; if (task.project_id && accessibleProjectIds.includes(task.project_id)) return { ok: true as const }; if (task.owner_user_id === userId) return { ok: true as const }; if (task.help_requested_to_user_id === userId) return { ok: true as const }; if (Array.isArray(task.executor_ids) && task.executor_ids.includes(userId)) return { ok: true as const }; return { ok: false as const, status: 403, error: '无权限' }; } export async function GET(request: NextRequest) { const ctx = await getAuthContext(request); const auth = ensureAuthenticated(ctx); if (!auth.ok) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const searchParams = request.nextUrl.searchParams; const task_id = searchParams.get('task_id'); const help_requested_to_user_id = searchParams.get('help_requested_to_user_id'); let query = supabase .from('task_steps') .select(` *, task:tasks( id, title, project_id, kr_id, owner_user_id, help_requested_to_user_id, help_requested_by_user_id, help_requested_at ) `) .order('created_at', { ascending: false }); if (!ctx!.isAdmin) { if (help_requested_to_user_id && help_requested_to_user_id !== ctx!.user.id) { return NextResponse.json({ error: '无权限' }, { status: 403 }); } if (task_id) { const access = await canAccessTask(ctx!.user.id, ctx!.isAdmin, ctx!.accessibleProjectIds, task_id); if (!access.ok) { return NextResponse.json({ error: access.error }, { status: access.status }); } } if (!task_id && !help_requested_to_user_id) { query = query.or(`executor_id.eq.${ctx!.user.id},help_requested_to_user_id.eq.${ctx!.user.id}`); } } if (task_id) query = query.eq('task_id', task_id); if (help_requested_to_user_id) query = query.eq('help_requested_to_user_id', help_requested_to_user_id); const { data, error } = await query; if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } return NextResponse.json(data); } export async function POST(request: NextRequest) { const ctx = await getAuthContext(request); const auth = ensureAuthenticated(ctx); if (!auth.ok) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const body = await request.json(); const { task_id, content, note, executor_id, start_date, end_date, is_completed, help_requested_to_user_id, help_requested_by_user_id, } = body; const isHelpRequested = Boolean(help_requested_to_user_id); if (!task_id) { return NextResponse.json({ error: '计划ID为必填项' }, { status: 400 }); } const access = await canAccessTask(ctx!.user.id, ctx!.isAdmin, ctx!.accessibleProjectIds, task_id); if (!access.ok) { return NextResponse.json({ error: access.error }, { status: access.status }); } const { data, error } = await supabase .from('task_steps') .insert({ task_id, content, note, executor_id, start_date, end_date, is_completed, help_requested_to_user_id: help_requested_to_user_id || null, help_requested_by_user_id: isHelpRequested ? (ctx!.isAdmin ? (help_requested_by_user_id || null) : ctx!.user.id) : null, help_requested_at: isHelpRequested ? new Date().toISOString() : null, }) .select() .single(); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } return NextResponse.json(data); } export async function PATCH(request: NextRequest) { const ctx = await getAuthContext(request); const auth = ensureAuthenticated(ctx); if (!auth.ok) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const body = await request.json(); const { id, content, note, executor_id, start_date, end_date, is_completed, help_requested_to_user_id, help_requested_by_user_id, } = body; if (!id) { return NextResponse.json({ error: 'Missing step id' }, { status: 400 }); } const { data: stepRow, error: stepErr } = await supabase .from('task_steps') .select('id,task_id') .eq('id', id) .maybeSingle(); if (stepErr) return NextResponse.json({ error: stepErr.message }, { status: 500 }); if (!stepRow) return NextResponse.json({ error: '步骤不存在' }, { status: 404 }); const access = await canAccessTask(ctx!.user.id, ctx!.isAdmin, ctx!.accessibleProjectIds, stepRow.task_id); if (!access.ok) { return NextResponse.json({ error: access.error }, { status: access.status }); } const updateData: Record = {}; if (content !== undefined) updateData.content = content; if (note !== undefined) updateData.note = note; if (executor_id !== undefined) updateData.executor_id = executor_id; if (start_date !== undefined) updateData.start_date = start_date; if (end_date !== undefined) updateData.end_date = end_date; if (is_completed !== undefined) updateData.is_completed = is_completed; if (help_requested_to_user_id !== undefined) { updateData.help_requested_to_user_id = help_requested_to_user_id; if (help_requested_to_user_id) { updateData.help_requested_by_user_id = ctx!.isAdmin ? (help_requested_by_user_id || null) : ctx!.user.id; updateData.help_requested_at = new Date().toISOString(); } else { updateData.help_requested_by_user_id = null; updateData.help_requested_at = null; } } const { data, error } = await supabase .from('task_steps') .update(updateData) .eq('id', id) .select() .single(); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } return NextResponse.json(data); } export async function DELETE(request: NextRequest) { const ctx = await getAuthContext(request); const auth = ensureAuthenticated(ctx); if (!auth.ok) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const searchParams = request.nextUrl.searchParams; const id = searchParams.get('id'); if (!id) { return NextResponse.json({ error: 'Missing id' }, { status: 400 }); } const { data: stepRow, error: stepErr } = await supabase .from('task_steps') .select('id,task_id') .eq('id', id) .maybeSingle(); if (stepErr) return NextResponse.json({ error: stepErr.message }, { status: 500 }); if (!stepRow) return NextResponse.json({ error: '步骤不存在' }, { status: 404 }); const access = await canAccessTask(ctx!.user.id, ctx!.isAdmin, ctx!.accessibleProjectIds, stepRow.task_id); if (!access.ok) { return NextResponse.json({ error: access.error }, { status: access.status }); } const { error } = await supabase .from('task_steps') .delete() .eq('id', id); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } return NextResponse.json({ success: true }); }