- Unify API: lib/api.ts uses /api/v1, inbox uses /api/inbox (rewrites) - Remove localhost refs: openapi, inbox page - Add rewrites: /api/inbox|tmc -> inbox-server, /api/v1 -> FastAPI - Add stub routes: knowledge/insights, recommendations, search, log-error - Transfer from PAPA: prompts (inspection, tmc), scripts, supabase, data/tmc-requests - Fix inbox-server: ORDER BY created_at, package.json - Remove redundant app/api/inbox/files route (rewrites handle it) - knowledge/ in gitignore (large PDFs) Co-authored-by: Cursor <cursoragent@cursor.com>
155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
/**
|
||
* API endpoint для работы с задачами Jira
|
||
* Данные импортируются из CSV файлов в папке "новая папка"
|
||
*/
|
||
|
||
import { NextRequest, NextResponse } from 'next/server';
|
||
import { pool } from '@/lib/database/connection';
|
||
import { getRateLimitIdentifier } from '@/lib/rate-limit';
|
||
import { rateLimit } from '@/lib/rate-limit';
|
||
import { handleError } from '@/lib/error-handler';
|
||
|
||
export const dynamic = 'force-dynamic';
|
||
|
||
export async function GET(request: NextRequest) {
|
||
try {
|
||
// Rate limiting
|
||
const rateLimitResult = rateLimit(getRateLimitIdentifier(request));
|
||
if (!rateLimitResult.allowed) {
|
||
return NextResponse.json(
|
||
{ error: 'Слишком много запросов' },
|
||
{ status: 429, headers: { 'Retry-After': String(rateLimitResult.resetTime) } }
|
||
);
|
||
}
|
||
|
||
const { searchParams } = new URL(request.url);
|
||
const type = searchParams.get('type'); // 'epic', 'story', 'subtask', 'all'
|
||
const epicId = searchParams.get('epic_id');
|
||
const storyId = searchParams.get('story_id');
|
||
|
||
const client = await pool.connect();
|
||
try {
|
||
let result;
|
||
|
||
if (type === 'epic' || !type) {
|
||
// Получаем все эпики
|
||
const epicsResult = await client.query(
|
||
`SELECT
|
||
issue_id as "issueId",
|
||
summary,
|
||
description,
|
||
priority,
|
||
components,
|
||
labels,
|
||
created_at as "createdAt",
|
||
updated_at as "updatedAt"
|
||
FROM jira_epics
|
||
ORDER BY
|
||
CASE priority
|
||
WHEN 'High' THEN 1
|
||
WHEN 'Medium' THEN 2
|
||
WHEN 'Low' THEN 3
|
||
ELSE 4
|
||
END,
|
||
created_at DESC`
|
||
);
|
||
|
||
// Для каждого эпика получаем связанные истории
|
||
for (const epic of epicsResult.rows) {
|
||
const storiesResult = await client.query(
|
||
`SELECT
|
||
issue_id as "issueId",
|
||
summary,
|
||
description,
|
||
priority,
|
||
story_points as "storyPoints",
|
||
components,
|
||
labels,
|
||
acceptance_criteria as "acceptanceCriteria",
|
||
created_at as "createdAt"
|
||
FROM jira_stories
|
||
WHERE parent_epic_id = $1
|
||
ORDER BY priority DESC, story_points DESC`,
|
||
[epic.issueId]
|
||
);
|
||
|
||
epic.stories = storiesResult.rows;
|
||
|
||
// Для каждой истории получаем подзадачи
|
||
for (const story of epic.stories) {
|
||
const subtasksResult = await client.query(
|
||
`SELECT
|
||
issue_id as "issueId",
|
||
summary,
|
||
description,
|
||
priority,
|
||
components,
|
||
labels,
|
||
created_at as "createdAt"
|
||
FROM jira_subtasks
|
||
WHERE parent_story_id = $1
|
||
ORDER BY priority DESC`,
|
||
[story.issueId]
|
||
);
|
||
|
||
story.subtasks = subtasksResult.rows;
|
||
}
|
||
}
|
||
|
||
result = epicsResult.rows;
|
||
} else if (type === 'story') {
|
||
const query = epicId
|
||
? `SELECT * FROM jira_stories WHERE parent_epic_id = $1 ORDER BY priority DESC, story_points DESC`
|
||
: `SELECT * FROM jira_stories ORDER BY priority DESC, story_points DESC`;
|
||
const params = epicId ? [epicId] : [];
|
||
result = (await client.query(query, params)).rows;
|
||
} else if (type === 'subtask') {
|
||
const query = storyId
|
||
? `SELECT * FROM jira_subtasks WHERE parent_story_id = $1 ORDER BY priority DESC`
|
||
: `SELECT * FROM jira_subtasks ORDER BY priority DESC`;
|
||
const params = storyId ? [storyId] : [];
|
||
result = (await client.query(query, params)).rows;
|
||
} else {
|
||
// Все задачи
|
||
const [epics, stories, subtasks] = await Promise.all([
|
||
client.query('SELECT * FROM jira_epics ORDER BY priority DESC'),
|
||
client.query('SELECT * FROM jira_stories ORDER BY priority DESC, story_points DESC'),
|
||
client.query('SELECT * FROM jira_subtasks ORDER BY priority DESC'),
|
||
]);
|
||
|
||
result = {
|
||
epics: epics.rows,
|
||
stories: stories.rows,
|
||
subtasks: subtasks.rows,
|
||
};
|
||
}
|
||
|
||
// Получаем зависимости
|
||
const dependenciesResult = await client.query(
|
||
`SELECT
|
||
from_issue_id as "fromIssueId",
|
||
to_issue_id as "toIssueId",
|
||
link_type as "linkType"
|
||
FROM jira_task_dependencies
|
||
ORDER BY created_at DESC`
|
||
);
|
||
|
||
return NextResponse.json({
|
||
data: result,
|
||
dependencies: dependenciesResult.rows,
|
||
meta: {
|
||
total: Array.isArray(result) ? result.length : Object.keys(result).length,
|
||
type: type || 'epic',
|
||
},
|
||
});
|
||
} finally {
|
||
client.release();
|
||
}
|
||
} catch (error) {
|
||
return handleError(error, {
|
||
path: '/api/jira-tasks',
|
||
method: 'GET',
|
||
});
|
||
}
|
||
}
|