olly-molly 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/cli.js +137 -52
- package/package.json +4 -15
- package/app/api/agent/execute/route.ts +0 -157
- package/app/api/agent/status/route.ts +0 -38
- package/app/api/check-api-key/route.ts +0 -12
- package/app/api/conversations/[id]/route.ts +0 -35
- package/app/api/conversations/route.ts +0 -24
- package/app/api/members/[id]/route.ts +0 -37
- package/app/api/members/route.ts +0 -12
- package/app/api/pm/breakdown/route.ts +0 -142
- package/app/api/pm/tickets/route.ts +0 -147
- package/app/api/projects/[id]/route.ts +0 -56
- package/app/api/projects/active/route.ts +0 -15
- package/app/api/projects/route.ts +0 -53
- package/app/api/tickets/[id]/logs/route.ts +0 -16
- package/app/api/tickets/[id]/route.ts +0 -60
- package/app/api/tickets/[id]/work-logs/route.ts +0 -16
- package/app/api/tickets/route.ts +0 -37
- package/app/design-system/page.tsx +0 -242
- package/app/favicon.ico +0 -0
- package/app/globals.css +0 -318
- package/app/layout.tsx +0 -37
- package/app/page.tsx +0 -331
- package/components/ThemeProvider.tsx +0 -56
- package/components/ThemeToggle.tsx +0 -31
- package/components/activity/ActivityLog.tsx +0 -96
- package/components/activity/index.ts +0 -1
- package/components/kanban/ConversationList.tsx +0 -75
- package/components/kanban/ConversationView.tsx +0 -132
- package/components/kanban/KanbanBoard.tsx +0 -179
- package/components/kanban/KanbanColumn.tsx +0 -80
- package/components/kanban/SortableTicket.tsx +0 -58
- package/components/kanban/TicketCard.tsx +0 -98
- package/components/kanban/TicketModal.tsx +0 -510
- package/components/kanban/TicketSidebar.tsx +0 -448
- package/components/kanban/index.ts +0 -8
- package/components/pm/PMRequestModal.tsx +0 -196
- package/components/pm/index.ts +0 -1
- package/components/project/ProjectSelector.tsx +0 -211
- package/components/project/index.ts +0 -1
- package/components/team/MemberCard.tsx +0 -147
- package/components/team/TeamPanel.tsx +0 -57
- package/components/team/index.ts +0 -2
- package/components/ui/ApiKeyModal.tsx +0 -101
- package/components/ui/Avatar.tsx +0 -95
- package/components/ui/Badge.tsx +0 -59
- package/components/ui/Button.tsx +0 -60
- package/components/ui/Card.tsx +0 -64
- package/components/ui/Input.tsx +0 -41
- package/components/ui/Modal.tsx +0 -76
- package/components/ui/ResizablePane.tsx +0 -97
- package/components/ui/Select.tsx +0 -45
- package/components/ui/Textarea.tsx +0 -41
- package/components/ui/index.ts +0 -8
- package/db/dev.sqlite +0 -0
- package/db/dev.sqlite-shm +0 -0
- package/db/dev.sqlite-wal +0 -0
- package/db/schema-conversations.sql +0 -26
- package/db/schema-projects.sql +0 -29
- package/db/schema.sql +0 -94
- package/lib/agent-jobs.ts +0 -232
- package/lib/db.ts +0 -564
- package/next.config.ts +0 -10
- package/postcss.config.mjs +0 -7
- package/public/app-icon.png +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/next.svg +0 -1
- package/public/profiles/designer.png +0 -0
- package/public/profiles/dev-backend.png +0 -0
- package/public/profiles/dev-frontend.png +0 -0
- package/public/profiles/pm.png +0 -0
- package/public/profiles/qa.png +0 -0
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- package/tsconfig.json +0 -34
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { ticketService, memberService } from '@/lib/db';
|
|
3
|
-
import OpenAI from 'openai';
|
|
4
|
-
|
|
5
|
-
const openai = new OpenAI({
|
|
6
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* PM Agent - AI-powered feature breakdown
|
|
11
|
-
*
|
|
12
|
-
* Uses OpenAI to analyze feature requests and create appropriate tasks
|
|
13
|
-
* with intelligent assignment to team members.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
interface TaskFromAI {
|
|
17
|
-
title: string;
|
|
18
|
-
description: string;
|
|
19
|
-
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
20
|
-
assignee_role: 'FE_DEV' | 'BACKEND_DEV' | 'QA' | 'DEVOPS';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const SYSTEM_PROMPT = `You are a PM (Project Manager) AI agent for a software development team.
|
|
24
|
-
|
|
25
|
-
Your team consists of:
|
|
26
|
-
- FE_DEV (Frontend Developer): Handles UI/UX, React, Next.js, CSS, components, user interfaces
|
|
27
|
-
- BACKEND_DEV (Backend Developer): Handles APIs, databases, server logic, authentication, business logic
|
|
28
|
-
- QA (QA Engineer): Handles testing, quality assurance, bug verification, E2E tests
|
|
29
|
-
- DEVOPS (DevOps Engineer): Handles deployment, CI/CD, infrastructure, monitoring
|
|
30
|
-
|
|
31
|
-
When given a feature request, you must:
|
|
32
|
-
1. Break it down into specific, actionable tasks
|
|
33
|
-
2. Assign each task to the appropriate team member based on their expertise
|
|
34
|
-
3. Set priorities (CRITICAL > HIGH > MEDIUM > LOW)
|
|
35
|
-
|
|
36
|
-
IMPORTANT RULES:
|
|
37
|
-
- Create focused, single-responsibility tasks
|
|
38
|
-
- Backend tasks should come before frontend integration tasks
|
|
39
|
-
- Always include a QA task for testing
|
|
40
|
-
- Be specific in task descriptions
|
|
41
|
-
- Use Korean for titles and descriptions
|
|
42
|
-
|
|
43
|
-
Respond in JSON format:
|
|
44
|
-
{
|
|
45
|
-
"tasks": [
|
|
46
|
-
{
|
|
47
|
-
"title": "Task title in Korean",
|
|
48
|
-
"description": "Detailed task description in Korean",
|
|
49
|
-
"priority": "HIGH",
|
|
50
|
-
"assignee_role": "BACKEND_DEV"
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
"summary": "Brief summary of the breakdown in Korean"
|
|
54
|
-
}`;
|
|
55
|
-
|
|
56
|
-
async function breakdownWithAI(request: string): Promise<{ tasks: TaskFromAI[]; summary: string }> {
|
|
57
|
-
const completion = await openai.chat.completions.create({
|
|
58
|
-
model: 'gpt-4o-mini',
|
|
59
|
-
messages: [
|
|
60
|
-
{ role: 'system', content: SYSTEM_PROMPT },
|
|
61
|
-
{ role: 'user', content: `Feature Request: ${request}` }
|
|
62
|
-
],
|
|
63
|
-
response_format: { type: 'json_object' },
|
|
64
|
-
temperature: 0.7,
|
|
65
|
-
max_tokens: 2000,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const content = completion.choices[0].message.content;
|
|
69
|
-
if (!content) {
|
|
70
|
-
throw new Error('No response from AI');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return JSON.parse(content);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function getAssigneeByRole(role: string): string | null {
|
|
77
|
-
const member = memberService.getByRole(role);
|
|
78
|
-
return member?.id || null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function POST(request: NextRequest) {
|
|
82
|
-
try {
|
|
83
|
-
const body = await request.json();
|
|
84
|
-
|
|
85
|
-
if (!body.request) {
|
|
86
|
-
return NextResponse.json(
|
|
87
|
-
{ error: 'Feature request is required' },
|
|
88
|
-
{ status: 400 }
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!process.env.OPENAI_API_KEY) {
|
|
93
|
-
return NextResponse.json(
|
|
94
|
-
{ error: 'OpenAI API key not configured' },
|
|
95
|
-
{ status: 500 }
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const pmMember = memberService.getByRole('PM');
|
|
100
|
-
|
|
101
|
-
// Use AI to break down the request
|
|
102
|
-
const aiResponse = await breakdownWithAI(body.request);
|
|
103
|
-
|
|
104
|
-
const createdTickets = [];
|
|
105
|
-
|
|
106
|
-
for (const task of aiResponse.tasks) {
|
|
107
|
-
const assigneeId = getAssigneeByRole(task.assignee_role);
|
|
108
|
-
|
|
109
|
-
const ticket = ticketService.create({
|
|
110
|
-
title: task.title,
|
|
111
|
-
description: task.description,
|
|
112
|
-
priority: task.priority,
|
|
113
|
-
assignee_id: assigneeId || undefined,
|
|
114
|
-
project_id: body.project_id,
|
|
115
|
-
created_by: pmMember?.id,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const assignee = assigneeId ? memberService.getById(assigneeId) : null;
|
|
119
|
-
createdTickets.push({
|
|
120
|
-
...ticket,
|
|
121
|
-
assignee,
|
|
122
|
-
assigned_role: task.assignee_role,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return NextResponse.json({
|
|
127
|
-
success: true,
|
|
128
|
-
original_request: body.request,
|
|
129
|
-
created_by: pmMember,
|
|
130
|
-
tickets_created: createdTickets.length,
|
|
131
|
-
tickets: createdTickets,
|
|
132
|
-
ai_summary: aiResponse.summary,
|
|
133
|
-
message: `PM이 AI를 사용해 "${body.request}" 요청을 분석하여 ${createdTickets.length}개의 태스크를 생성했습니다.`,
|
|
134
|
-
}, { status: 201 });
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.error('Error in PM breakdown:', error);
|
|
137
|
-
return NextResponse.json(
|
|
138
|
-
{ error: 'Failed to process feature request', details: String(error) },
|
|
139
|
-
{ status: 500 }
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { ticketService, memberService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* PM Agent API - Create tickets with automatic assignment
|
|
6
|
-
*
|
|
7
|
-
* The PM Agent analyzes the ticket content and automatically assigns
|
|
8
|
-
* it to the most appropriate team member based on keywords and task type.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Keywords mapping for auto-assignment
|
|
12
|
-
const ROLE_KEYWORDS: Record<string, string[]> = {
|
|
13
|
-
FE_DEV: [
|
|
14
|
-
'frontend', 'ui', 'ux', 'react', 'component', 'css', 'style', 'design',
|
|
15
|
-
'button', 'form', 'page', 'layout', 'responsive', 'animation', 'tailwind',
|
|
16
|
-
'프론트엔드', 'UI', '컴포넌트', '디자인', '스타일', '화면', '페이지'
|
|
17
|
-
],
|
|
18
|
-
BACKEND_DEV: [
|
|
19
|
-
'backend', 'api', 'database', 'db', 'server', 'endpoint', 'rest',
|
|
20
|
-
'authentication', 'auth', 'sql', 'query', 'migration', 'model',
|
|
21
|
-
'백엔드', 'API', '데이터베이스', '서버', '인증'
|
|
22
|
-
],
|
|
23
|
-
QA: [
|
|
24
|
-
'test', 'testing', 'qa', 'quality', 'bug', 'fix', 'verify', 'validation',
|
|
25
|
-
'e2e', 'integration', 'unit test', 'playwright', 'automation',
|
|
26
|
-
'테스트', 'QA', '버그', '검증', '품질'
|
|
27
|
-
],
|
|
28
|
-
DEVOPS: [
|
|
29
|
-
'deploy', 'deployment', 'ci', 'cd', 'pipeline', 'docker', 'kubernetes',
|
|
30
|
-
'infrastructure', 'monitoring', 'logging', 'aws', 'cloud', 'server',
|
|
31
|
-
'배포', '인프라', '파이프라인', '모니터링'
|
|
32
|
-
],
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function analyzeAndAssign(title: string, description?: string): string | null {
|
|
36
|
-
const text = `${title} ${description || ''}`.toLowerCase();
|
|
37
|
-
|
|
38
|
-
// Count keyword matches for each role
|
|
39
|
-
const scores: Record<string, number> = {
|
|
40
|
-
FE_DEV: 0,
|
|
41
|
-
BACKEND_DEV: 0,
|
|
42
|
-
QA: 0,
|
|
43
|
-
DEVOPS: 0,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
for (const [role, keywords] of Object.entries(ROLE_KEYWORDS)) {
|
|
47
|
-
for (const keyword of keywords) {
|
|
48
|
-
if (text.includes(keyword.toLowerCase())) {
|
|
49
|
-
scores[role] += 1;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Find the role with highest score
|
|
55
|
-
let maxScore = 0;
|
|
56
|
-
let assignedRole: string | null = null;
|
|
57
|
-
|
|
58
|
-
for (const [role, score] of Object.entries(scores)) {
|
|
59
|
-
if (score > maxScore) {
|
|
60
|
-
maxScore = score;
|
|
61
|
-
assignedRole = role;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// If no clear match, default to FE_DEV for general tasks
|
|
66
|
-
if (maxScore === 0) {
|
|
67
|
-
assignedRole = 'FE_DEV';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Get the member with this role
|
|
71
|
-
const member = memberService.getByRole(assignedRole!);
|
|
72
|
-
return member?.id || null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function POST(request: NextRequest) {
|
|
76
|
-
try {
|
|
77
|
-
const body = await request.json();
|
|
78
|
-
|
|
79
|
-
if (!body.title) {
|
|
80
|
-
return NextResponse.json(
|
|
81
|
-
{ error: 'Title is required' },
|
|
82
|
-
{ status: 400 }
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Get PM member
|
|
87
|
-
const pmMember = memberService.getByRole('PM');
|
|
88
|
-
|
|
89
|
-
// Auto-assign based on content analysis
|
|
90
|
-
let assigneeId = body.assignee_id;
|
|
91
|
-
let autoAssigned = false;
|
|
92
|
-
|
|
93
|
-
if (!assigneeId && body.auto_assign !== false) {
|
|
94
|
-
assigneeId = analyzeAndAssign(body.title, body.description);
|
|
95
|
-
autoAssigned = true;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Create ticket with PM as creator
|
|
99
|
-
const ticket = ticketService.create({
|
|
100
|
-
title: body.title,
|
|
101
|
-
description: body.description,
|
|
102
|
-
priority: body.priority || 'MEDIUM',
|
|
103
|
-
assignee_id: assigneeId,
|
|
104
|
-
created_by: pmMember?.id,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Get assignee info for response
|
|
108
|
-
const assignee = assigneeId ? memberService.getById(assigneeId) : null;
|
|
109
|
-
|
|
110
|
-
return NextResponse.json({
|
|
111
|
-
...ticket,
|
|
112
|
-
assignee,
|
|
113
|
-
auto_assigned: autoAssigned,
|
|
114
|
-
created_by_pm: true,
|
|
115
|
-
}, { status: 201 });
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error('Error in PM create ticket:', error);
|
|
118
|
-
return NextResponse.json(
|
|
119
|
-
{ error: 'Failed to create ticket' },
|
|
120
|
-
{ status: 500 }
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// GET endpoint to get PM agent info and capabilities
|
|
126
|
-
export async function GET() {
|
|
127
|
-
try {
|
|
128
|
-
const pmMember = memberService.getByRole('PM');
|
|
129
|
-
const allMembers = memberService.getAll();
|
|
130
|
-
|
|
131
|
-
return NextResponse.json({
|
|
132
|
-
pm: pmMember,
|
|
133
|
-
team: allMembers.filter(m => m.role !== 'PM'),
|
|
134
|
-
capabilities: {
|
|
135
|
-
auto_assignment: true,
|
|
136
|
-
supported_roles: Object.keys(ROLE_KEYWORDS),
|
|
137
|
-
keywords: ROLE_KEYWORDS,
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
} catch (error) {
|
|
141
|
-
console.error('Error fetching PM info:', error);
|
|
142
|
-
return NextResponse.json(
|
|
143
|
-
{ error: 'Failed to fetch PM info' },
|
|
144
|
-
{ status: 500 }
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { projectService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
export async function GET(
|
|
5
|
-
request: NextRequest,
|
|
6
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
7
|
-
) {
|
|
8
|
-
try {
|
|
9
|
-
const { id } = await params;
|
|
10
|
-
const project = projectService.getById(id);
|
|
11
|
-
if (!project) {
|
|
12
|
-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
|
|
13
|
-
}
|
|
14
|
-
return NextResponse.json(project);
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error('Error fetching project:', error);
|
|
17
|
-
return NextResponse.json({ error: 'Failed to fetch project' }, { status: 500 });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function DELETE(
|
|
22
|
-
request: NextRequest,
|
|
23
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
24
|
-
) {
|
|
25
|
-
try {
|
|
26
|
-
const { id } = await params;
|
|
27
|
-
const deleted = projectService.delete(id);
|
|
28
|
-
if (!deleted) {
|
|
29
|
-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
|
|
30
|
-
}
|
|
31
|
-
return NextResponse.json({ success: true });
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Error deleting project:', error);
|
|
34
|
-
return NextResponse.json({ error: 'Failed to delete project' }, { status: 500 });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function PATCH(
|
|
39
|
-
request: NextRequest,
|
|
40
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
41
|
-
) {
|
|
42
|
-
try {
|
|
43
|
-
const { id } = await params;
|
|
44
|
-
const body = await request.json();
|
|
45
|
-
|
|
46
|
-
if (body.is_active) {
|
|
47
|
-
const project = projectService.setActive(id);
|
|
48
|
-
return NextResponse.json(project);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return NextResponse.json({ error: 'Invalid update' }, { status: 400 });
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('Error updating project:', error);
|
|
54
|
-
return NextResponse.json({ error: 'Failed to update project' }, { status: 500 });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { projectService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
export async function GET() {
|
|
5
|
-
try {
|
|
6
|
-
const project = projectService.getActive();
|
|
7
|
-
if (!project) {
|
|
8
|
-
return NextResponse.json({ error: 'No active project' }, { status: 404 });
|
|
9
|
-
}
|
|
10
|
-
return NextResponse.json(project);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
console.error('Error fetching active project:', error);
|
|
13
|
-
return NextResponse.json({ error: 'Failed to fetch active project' }, { status: 500 });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { projectService } from '@/lib/db';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
export async function GET() {
|
|
7
|
-
try {
|
|
8
|
-
const projects = projectService.getAll();
|
|
9
|
-
return NextResponse.json(projects);
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error('Error fetching projects:', error);
|
|
12
|
-
return NextResponse.json({ error: 'Failed to fetch projects' }, { status: 500 });
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function POST(request: NextRequest) {
|
|
17
|
-
try {
|
|
18
|
-
const body = await request.json();
|
|
19
|
-
|
|
20
|
-
if (!body.path) {
|
|
21
|
-
return NextResponse.json({ error: 'Project path is required' }, { status: 400 });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Validate that the path exists and is a directory
|
|
25
|
-
const projectPath = body.path;
|
|
26
|
-
if (!fs.existsSync(projectPath)) {
|
|
27
|
-
return NextResponse.json({ error: 'Path does not exist' }, { status: 400 });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const stats = fs.statSync(projectPath);
|
|
31
|
-
if (!stats.isDirectory()) {
|
|
32
|
-
return NextResponse.json({ error: 'Path is not a directory' }, { status: 400 });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Check if it's a git repository
|
|
36
|
-
const gitPath = path.join(projectPath, '.git');
|
|
37
|
-
const isGitRepo = fs.existsSync(gitPath);
|
|
38
|
-
|
|
39
|
-
// Extract project name from path if not provided
|
|
40
|
-
const name = body.name || path.basename(projectPath);
|
|
41
|
-
|
|
42
|
-
const project = projectService.create({
|
|
43
|
-
name,
|
|
44
|
-
path: projectPath,
|
|
45
|
-
description: body.description || (isGitRepo ? 'Git repository' : 'Local project'),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return NextResponse.json({ ...project, is_git_repo: isGitRepo }, { status: 201 });
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Error creating project:', error);
|
|
51
|
-
return NextResponse.json({ error: 'Failed to create project' }, { status: 500 });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { activityService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
export async function GET(
|
|
5
|
-
request: NextRequest,
|
|
6
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
7
|
-
) {
|
|
8
|
-
try {
|
|
9
|
-
const { id } = await params;
|
|
10
|
-
const logs = activityService.getByTicketId(id);
|
|
11
|
-
return NextResponse.json(logs);
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error('Error fetching activity logs:', error);
|
|
14
|
-
return NextResponse.json({ error: 'Failed to fetch activity logs' }, { status: 500 });
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { ticketService, activityService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
export async function GET(
|
|
5
|
-
request: NextRequest,
|
|
6
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
7
|
-
) {
|
|
8
|
-
try {
|
|
9
|
-
const { id } = await params;
|
|
10
|
-
const ticket = ticketService.getById(id);
|
|
11
|
-
if (!ticket) {
|
|
12
|
-
return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Include activity logs
|
|
16
|
-
const logs = activityService.getByTicketId(id);
|
|
17
|
-
|
|
18
|
-
return NextResponse.json({ ...ticket, logs });
|
|
19
|
-
} catch (error) {
|
|
20
|
-
console.error('Error fetching ticket:', error);
|
|
21
|
-
return NextResponse.json({ error: 'Failed to fetch ticket' }, { status: 500 });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function PATCH(
|
|
26
|
-
request: NextRequest,
|
|
27
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
28
|
-
) {
|
|
29
|
-
try {
|
|
30
|
-
const { id } = await params;
|
|
31
|
-
const body = await request.json();
|
|
32
|
-
const { updated_by, ...data } = body;
|
|
33
|
-
|
|
34
|
-
const ticket = ticketService.update(id, data, updated_by);
|
|
35
|
-
if (!ticket) {
|
|
36
|
-
return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
|
|
37
|
-
}
|
|
38
|
-
return NextResponse.json(ticket);
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error('Error updating ticket:', error);
|
|
41
|
-
return NextResponse.json({ error: 'Failed to update ticket' }, { status: 500 });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function DELETE(
|
|
46
|
-
request: NextRequest,
|
|
47
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
48
|
-
) {
|
|
49
|
-
try {
|
|
50
|
-
const { id } = await params;
|
|
51
|
-
const deleted = ticketService.delete(id);
|
|
52
|
-
if (!deleted) {
|
|
53
|
-
return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
|
|
54
|
-
}
|
|
55
|
-
return NextResponse.json({ success: true });
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error('Error deleting ticket:', error);
|
|
58
|
-
return NextResponse.json({ error: 'Failed to delete ticket' }, { status: 500 });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { agentWorkLogService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
export async function GET(
|
|
5
|
-
request: NextRequest,
|
|
6
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
7
|
-
) {
|
|
8
|
-
try {
|
|
9
|
-
const { id } = await params;
|
|
10
|
-
const logs = agentWorkLogService.getByTicketId(id);
|
|
11
|
-
return NextResponse.json(logs);
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error('Error fetching work logs:', error);
|
|
14
|
-
return NextResponse.json({ error: 'Failed to fetch work logs' }, { status: 500 });
|
|
15
|
-
}
|
|
16
|
-
}
|
package/app/api/tickets/route.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { ticketService } from '@/lib/db';
|
|
3
|
-
|
|
4
|
-
export async function GET(request: NextRequest) {
|
|
5
|
-
try {
|
|
6
|
-
const { searchParams } = new URL(request.url);
|
|
7
|
-
const status = searchParams.get('status') || undefined;
|
|
8
|
-
const projectId = searchParams.get('projectId') || undefined;
|
|
9
|
-
const tickets = ticketService.getAll(status, projectId);
|
|
10
|
-
return NextResponse.json(tickets);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
console.error('Error fetching tickets:', error);
|
|
13
|
-
return NextResponse.json({ error: 'Failed to fetch tickets' }, { status: 500 });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function POST(request: NextRequest) {
|
|
18
|
-
try {
|
|
19
|
-
const body = await request.json();
|
|
20
|
-
if (!body.title) {
|
|
21
|
-
return NextResponse.json({ error: 'Title is required' }, { status: 400 });
|
|
22
|
-
}
|
|
23
|
-
const ticket = ticketService.create({
|
|
24
|
-
title: body.title,
|
|
25
|
-
description: body.description,
|
|
26
|
-
priority: body.priority,
|
|
27
|
-
assignee_id: body.assignee_id,
|
|
28
|
-
project_id: body.project_id,
|
|
29
|
-
created_by: body.created_by,
|
|
30
|
-
});
|
|
31
|
-
return NextResponse.json(ticket, { status: 201 });
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Error creating ticket:', error);
|
|
34
|
-
return NextResponse.json({ error: 'Failed to create ticket' }, { status: 500 });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|