diff --git a/app/api/leantime/status-labels/route.ts b/app/api/leantime/status-labels/route.ts index 816fb64a..28545fd8 100644 --- a/app/api/leantime/status-labels/route.ts +++ b/app/api/leantime/status-labels/route.ts @@ -2,92 +2,31 @@ import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { NextResponse } from "next/server"; -interface Task { +interface StatusLabel { id: string; - headline: string; - projectName: string; - status: string; - dueDate: string | null; - details?: string | null; - milestone?: string | null; + name: string; class: string; + statusType: string; + kanbanCol: boolean | string; + sortKey: number | string; } interface Project { id: number; name: string; + labels: StatusLabel[]; } -// Cache for user IDs -const userCache = new Map(); - export async function GET() { - const session = await getServerSession(authOptions); - - if (!session || !session.user?.email) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - try { - console.log('Fetching tasks for user:', session.user.id); - console.log('Using LEANTIME_TOKEN:', process.env.LEANTIME_TOKEN ? 'Present' : 'Missing'); + const session = await getServerSession(authOptions); - // Check cache first - let leantimeUserId = userCache.get(session.user.email); - - // If not in cache, fetch from API - if (!leantimeUserId) { - const userResponse = await fetch('https://agilite.slm-lab.net/api/jsonrpc', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': process.env.LEANTIME_TOKEN || '', - }, - body: JSON.stringify({ - method: 'leantime.rpc.Users.Users.getUserByEmail', - jsonrpc: '2.0', - id: 1, - params: { - email: session.user.email - } - }) - }); - - if (!userResponse.ok) { - const errorData = await userResponse.json(); - console.error('User lookup failed:', errorData); - if (userResponse.status === 429) { - const retryAfter = userResponse.headers.get('retry-after') || '60'; - return NextResponse.json( - { error: "Rate limit exceeded. Please try again later." }, - { - status: 429, - headers: { - 'Retry-After': retryAfter - } - } - ); - } - throw new Error('Failed to fetch user data from Leantime'); - } - - const userData = await userResponse.json(); - console.log('User lookup response:', userData); - - if (!userData.result || !userData.result.id) { - throw new Error('Could not find Leantime user ID'); - } - - leantimeUserId = userData.result.id as number; - // Cache the user ID for 5 minutes - if (session.user.email) { - userCache.set(session.user.email, leantimeUserId); - setTimeout(() => userCache.delete(session.user.email!), 5 * 60 * 1000); - } + if (!session?.user?.email) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // Fetch all tasks assigned to the user - const response = await fetch('https://agilite.slm-lab.net/api/jsonrpc', { + // Get user ID from Leantime + const userResponse = await fetch('https://agilite.slm-lab.net/api/jsonrpc', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -95,28 +34,23 @@ export async function GET() { }, body: JSON.stringify({ jsonrpc: '2.0', - method: 'leantime.rpc.Tickets.Tickets.getAllStatusLabelsByUserId', + method: 'leantime.rpc.users.getAll', id: 1, - params: { - userId: leantimeUserId - } - }) + }), }); - if (!response.ok) { - const errorData = await response.json(); - console.error('Tasks fetch failed:', errorData); - throw new Error(`Failed to fetch tasks: ${errorData.error || 'Unknown error'}`); + if (!userResponse.ok) { + throw new Error('Failed to fetch user from Leantime'); } - const data = await response.json(); - console.log('Tasks response:', JSON.stringify(data, null, 2)); - - if (!data.result) { - return NextResponse.json({ tasks: [] }); + const userData = await userResponse.json(); + const user = userData.result.find((u: any) => u.email === session.user.email); + + if (!user) { + return NextResponse.json({ error: "User not found in Leantime" }, { status: 404 }); } - // Get project details to include project names + // Get projects const projectsResponse = await fetch('https://agilite.slm-lab.net/api/jsonrpc', { method: 'POST', headers: { @@ -131,75 +65,58 @@ export async function GET() { }); if (!projectsResponse.ok) { - throw new Error('Failed to fetch projects'); + throw new Error('Failed to fetch projects from Leantime'); } const projectsData = await projectsResponse.json(); - console.log('Projects response:', JSON.stringify(projectsData, null, 2)); - - const projectsMap = new Map( - projectsData.result.map((project: Project) => [project.id, project.name]) - ); - // Transform the nested structure into a flat array of tasks - const tasks: Task[] = []; - Object.entries(data.result).forEach(([projectId, statusGroups]) => { - const project = projectsData.result.find((p: Project) => p.id === Number(projectId)); - const projectName = project ? project.name : `Project ${projectId}`; - - if (typeof statusGroups === 'object' && statusGroups !== null) { - // Iterate through each status group - Object.entries(statusGroups).forEach(([_, label]) => { - if (typeof label === 'object' && label !== null && 'name' in label) { - const statusLabel = label as { - name: string; - class: string; - statusType: string; - kanbanCol: string; - sortKey: string; - }; - - tasks.push({ - id: `${projectId}-${statusLabel.sortKey}`, - headline: statusLabel.name, - projectName, - status: statusLabel.statusType.toLowerCase(), - dueDate: null, - details: undefined, - milestone: undefined, - class: statusLabel.class - }); - } - }); - } + // Get status labels + const labelsResponse = await fetch('https://agilite.slm-lab.net/api/jsonrpc', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': process.env.LEANTIME_TOKEN || '', + }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'leantime.rpc.Tickets.Tickets.getAllStatusLabelsByUserId', + id: 1, + params: { + userId: user.id + } + }) }); - // Sort tasks by their sortKey - tasks.sort((a, b) => { - const sortKeyA = Number(a.id.split('-')[1]) || 99; - const sortKeyB = Number(b.id.split('-')[1]) || 99; - return sortKeyA - sortKeyB; - }); + if (!labelsResponse.ok) { + throw new Error('Failed to fetch status labels from Leantime'); + } - // Deduplicate tasks based on headline and status - const uniqueTasks = tasks.reduce((acc: Task[], task) => { - const existingTask = acc.find(t => - t.headline === task.headline && - t.status === task.status - ); - - if (!existingTask) { - acc.push(task); - } - - return acc; - }, []); + const labelsData = await labelsResponse.json(); - return NextResponse.json({ tasks: uniqueTasks }); + // Transform the data into the required format + const projects: Project[] = projectsData.result.map((project: any) => { + const projectLabels = labelsData.result[project.id]; + const labels: StatusLabel[] = projectLabels ? Object.entries(projectLabels).map(([key, value]: [string, any]) => ({ + id: key, + name: value.name, + class: value.class, + statusType: value.statusType, + kanbanCol: value.kanbanCol, + sortKey: value.sortKey + })).sort((a: StatusLabel, b: StatusLabel) => Number(a.sortKey) - Number(b.sortKey)) : []; + + return { + id: project.id, + name: project.name, + labels + }; + }).filter((project: Project) => project.labels.length > 0); + + return NextResponse.json({ projects }); } catch (error) { - console.error('Error fetching tasks:', error); + console.error('Error fetching status labels:', error); return NextResponse.json( - { error: error instanceof Error ? error.message : "Failed to fetch tasks" }, + { error: "Failed to fetch status labels" }, { status: 500 } ); } diff --git a/components/flow.tsx b/components/flow.tsx index d3dbfe48..57a22a23 100644 --- a/components/flow.tsx +++ b/components/flow.tsx @@ -101,7 +101,7 @@ export function Flow() {

{project.name}

{project.labels.map((label) => ( -
+
{label.name}