Skip to content

Commit e3732fd

Browse files
authored
Merge pull request #39 from codervisor/copilot/fix-ba15e10c-9577-41cf-820c-2ebb78495a45
Implement document upload and management system for AI agents
2 parents 6ab2f1a + 79f05b4 commit e3732fd

File tree

19 files changed

+1410
-5
lines changed

19 files changed

+1410
-5
lines changed

.devlog/devlog.sqlite

Whitespace-only changes.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { NextRequest } from 'next/server';
2+
import { DocumentService, DevlogService } from '@codervisor/devlog-core/server';
3+
import { ApiErrors, createSuccessResponse, RouteParams, ServiceHelper } from '@/lib/api/api-utils';
4+
import { RealtimeEventType } from '@/lib/realtime';
5+
6+
// Mark this route as dynamic to prevent static generation
7+
export const dynamic = 'force-dynamic';
8+
9+
// GET /api/projects/[name]/devlogs/[devlogId]/documents/[documentId] - Get specific document
10+
export async function GET(
11+
request: NextRequest,
12+
{ params }: { params: { name: string; devlogId: string; documentId: string } },
13+
) {
14+
try {
15+
// Parse and validate parameters
16+
const projectResult = RouteParams.parseProjectName(params);
17+
if (!projectResult.success) {
18+
return projectResult.response;
19+
}
20+
21+
const { projectName } = projectResult.data;
22+
const { devlogId, documentId } = params;
23+
24+
if (!devlogId || !documentId) {
25+
return ApiErrors.invalidRequest('Missing devlogId or documentId');
26+
}
27+
28+
// Parse devlogId as number
29+
const parsedDevlogId = parseInt(devlogId);
30+
if (isNaN(parsedDevlogId)) {
31+
return ApiErrors.invalidRequest('Invalid devlogId');
32+
}
33+
34+
// Get project using helper
35+
const projectHelperResult = await ServiceHelper.getProjectByNameOrFail(projectName);
36+
if (!projectHelperResult.success) {
37+
return projectHelperResult.response;
38+
}
39+
40+
const project = projectHelperResult.data.project;
41+
42+
// Verify devlog exists
43+
const devlogService = DevlogService.getInstance(project.id);
44+
const devlog = await devlogService.get(parsedDevlogId, false);
45+
if (!devlog) {
46+
return ApiErrors.devlogNotFound();
47+
}
48+
49+
// Get document
50+
const documentService = DocumentService.getInstance(project.id);
51+
const document = await documentService.getDocument(documentId);
52+
53+
if (!document) {
54+
return ApiErrors.notFound('Document not found');
55+
}
56+
57+
// Verify document belongs to the specified devlog
58+
if (document.devlogId !== parsedDevlogId) {
59+
return ApiErrors.notFound('Document not found');
60+
}
61+
62+
return createSuccessResponse(document);
63+
} catch (error) {
64+
console.error('Error fetching document:', error);
65+
return ApiErrors.internalError('Failed to fetch document');
66+
}
67+
}
68+
69+
// DELETE /api/projects/[name]/devlogs/[devlogId]/documents/[documentId] - Delete document
70+
export async function DELETE(
71+
request: NextRequest,
72+
{ params }: { params: { name: string; devlogId: string; documentId: string } },
73+
) {
74+
try {
75+
// Parse and validate parameters
76+
const projectResult = RouteParams.parseProjectName(params);
77+
if (!projectResult.success) {
78+
return projectResult.response;
79+
}
80+
81+
const { projectName } = projectResult.data;
82+
const { devlogId, documentId } = params;
83+
84+
if (!devlogId || !documentId) {
85+
return ApiErrors.invalidRequest('Missing devlogId or documentId');
86+
}
87+
88+
// Parse devlogId as number
89+
const parsedDevlogId = parseInt(devlogId);
90+
if (isNaN(parsedDevlogId)) {
91+
return ApiErrors.invalidRequest('Invalid devlogId');
92+
}
93+
94+
// Get project using helper
95+
const projectHelperResult = await ServiceHelper.getProjectByNameOrFail(projectName);
96+
if (!projectHelperResult.success) {
97+
return projectHelperResult.response;
98+
}
99+
100+
const project = projectHelperResult.data.project;
101+
102+
// Verify devlog exists
103+
const devlogService = DevlogService.getInstance(project.id);
104+
const devlog = await devlogService.get(parsedDevlogId, false);
105+
if (!devlog) {
106+
return ApiErrors.devlogNotFound();
107+
}
108+
109+
// Verify document exists and belongs to the devlog
110+
const documentService = DocumentService.getInstance(project.id);
111+
const document = await documentService.getDocument(documentId);
112+
113+
if (!document || document.devlogId !== parsedDevlogId) {
114+
return ApiErrors.notFound('Document not found');
115+
}
116+
117+
// Delete document
118+
const deleted = await documentService.deleteDocument(documentId);
119+
120+
if (!deleted) {
121+
return ApiErrors.internalError('Failed to delete document');
122+
}
123+
124+
return createSuccessResponse(
125+
{ message: 'Document deleted successfully' },
126+
{
127+
sseEventType: RealtimeEventType.DEVLOG_UPDATED,
128+
}
129+
);
130+
} catch (error) {
131+
console.error('Error deleting document:', error);
132+
return ApiErrors.internalError('Failed to delete document');
133+
}
134+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { NextRequest } from 'next/server';
2+
import { DocumentService, DevlogService } from '@codervisor/devlog-core/server';
3+
import { ApiErrors, createSuccessResponse, RouteParams, ServiceHelper, createSimpleCollectionResponse } from '@/lib/api/api-utils';
4+
import { RealtimeEventType } from '@/lib/realtime';
5+
6+
// Mark this route as dynamic to prevent static generation
7+
export const dynamic = 'force-dynamic';
8+
9+
// GET /api/projects/[name]/devlogs/[devlogId]/documents - List documents for a devlog
10+
export async function GET(
11+
request: NextRequest,
12+
{ params }: { params: { name: string; devlogId: string } },
13+
) {
14+
try {
15+
// Parse and validate parameters
16+
const paramResult = RouteParams.parseProjectNameAndDevlogId(params);
17+
if (!paramResult.success) {
18+
return paramResult.response;
19+
}
20+
21+
const { projectName, devlogId } = paramResult.data;
22+
23+
// Get project using helper
24+
const projectResult = await ServiceHelper.getProjectByNameOrFail(projectName);
25+
if (!projectResult.success) {
26+
return projectResult.response;
27+
}
28+
29+
const project = projectResult.data.project;
30+
31+
// Verify devlog exists
32+
const devlogService = DevlogService.getInstance(project.id);
33+
const devlog = await devlogService.get(devlogId, false);
34+
if (!devlog) {
35+
return ApiErrors.devlogNotFound();
36+
}
37+
38+
// Get documents using document service
39+
const documentService = DocumentService.getInstance(project.id);
40+
const documents = await documentService.listDocuments(devlogId);
41+
42+
return createSimpleCollectionResponse(documents);
43+
} catch (error) {
44+
console.error('Error fetching devlog documents:', error);
45+
return ApiErrors.internalError('Failed to fetch documents');
46+
}
47+
}
48+
49+
// POST /api/projects/[name]/devlogs/[devlogId]/documents - Upload a document
50+
export async function POST(
51+
request: NextRequest,
52+
{ params }: { params: { name: string; devlogId: string } },
53+
) {
54+
try {
55+
// Parse and validate parameters
56+
const paramResult = RouteParams.parseProjectNameAndDevlogId(params);
57+
if (!paramResult.success) {
58+
return paramResult.response;
59+
}
60+
61+
const { projectName, devlogId } = paramResult.data;
62+
63+
// Get project using helper
64+
const projectResult = await ServiceHelper.getProjectByNameOrFail(projectName);
65+
if (!projectResult.success) {
66+
return projectResult.response;
67+
}
68+
69+
const project = projectResult.data.project;
70+
71+
// Verify devlog exists
72+
const devlogService = DevlogService.getInstance(project.id);
73+
const devlog = await devlogService.get(devlogId, false);
74+
if (!devlog) {
75+
return ApiErrors.devlogNotFound();
76+
}
77+
78+
// Parse multipart form data
79+
const formData = await request.formData();
80+
const file = formData.get('file') as File;
81+
const metadata = formData.get('metadata') as string;
82+
83+
if (!file) {
84+
return ApiErrors.invalidRequest('File is required');
85+
}
86+
87+
// Validate file size (10MB limit)
88+
const maxSize = 10 * 1024 * 1024; // 10MB
89+
if (file.size > maxSize) {
90+
return ApiErrors.invalidRequest('File size exceeds 10MB limit');
91+
}
92+
93+
// Read file content
94+
const arrayBuffer = await file.arrayBuffer();
95+
const buffer = Buffer.from(arrayBuffer);
96+
97+
// Parse metadata if provided
98+
let parsedMetadata: Record<string, any> | undefined;
99+
if (metadata) {
100+
try {
101+
parsedMetadata = JSON.parse(metadata);
102+
} catch {
103+
return ApiErrors.invalidRequest('Invalid metadata JSON');
104+
}
105+
}
106+
107+
// Upload document
108+
const documentService = DocumentService.getInstance(project.id);
109+
const document = await documentService.uploadDocument(
110+
devlogId,
111+
{
112+
originalName: file.name,
113+
mimeType: file.type,
114+
size: file.size,
115+
content: buffer,
116+
},
117+
{
118+
metadata: parsedMetadata,
119+
// TODO: Add uploadedBy from authentication context
120+
}
121+
);
122+
123+
return createSuccessResponse(document, {
124+
status: 201,
125+
sseEventType: RealtimeEventType.DEVLOG_UPDATED,
126+
});
127+
} catch (error) {
128+
console.error('Error uploading document:', error);
129+
return ApiErrors.internalError('Failed to upload document');
130+
}
131+
}

0 commit comments

Comments
 (0)