Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .devlog/devlog.sqlite
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { NextRequest } from 'next/server';
import { DocumentService, DevlogService } from '@codervisor/devlog-core/server';
import { ApiErrors, createSuccessResponse, RouteParams, ServiceHelper } from '@/lib/api/api-utils';
import { RealtimeEventType } from '@/lib/realtime';

// Mark this route as dynamic to prevent static generation
export const dynamic = 'force-dynamic';

// GET /api/projects/[name]/devlogs/[devlogId]/documents/[documentId] - Get specific document
export async function GET(
request: NextRequest,
{ params }: { params: { name: string; devlogId: string; documentId: string } },
) {
try {
// Parse and validate parameters
const projectResult = RouteParams.parseProjectName(params);
if (!projectResult.success) {
return projectResult.response;
}

const { projectName } = projectResult.data;
const { devlogId, documentId } = params;

if (!devlogId || !documentId) {
return ApiErrors.invalidRequest('Missing devlogId or documentId');
}

// Parse devlogId as number
const parsedDevlogId = parseInt(devlogId);
if (isNaN(parsedDevlogId)) {
return ApiErrors.invalidRequest('Invalid devlogId');
}

// Get project using helper
const projectHelperResult = await ServiceHelper.getProjectByNameOrFail(projectName);
if (!projectHelperResult.success) {
return projectHelperResult.response;
}

const project = projectHelperResult.data.project;

// Verify devlog exists
const devlogService = DevlogService.getInstance(project.id);
const devlog = await devlogService.get(parsedDevlogId, false);
if (!devlog) {
return ApiErrors.devlogNotFound();
}

// Get document
const documentService = DocumentService.getInstance(project.id);
const document = await documentService.getDocument(documentId);

if (!document) {
return ApiErrors.notFound('Document not found');
}

// Verify document belongs to the specified devlog
if (document.devlogId !== parsedDevlogId) {
return ApiErrors.notFound('Document not found');
}

return createSuccessResponse(document);
} catch (error) {
console.error('Error fetching document:', error);
return ApiErrors.internalError('Failed to fetch document');
}
}

// DELETE /api/projects/[name]/devlogs/[devlogId]/documents/[documentId] - Delete document
export async function DELETE(
request: NextRequest,
{ params }: { params: { name: string; devlogId: string; documentId: string } },
) {
try {
// Parse and validate parameters
const projectResult = RouteParams.parseProjectName(params);
if (!projectResult.success) {
return projectResult.response;
}

const { projectName } = projectResult.data;
const { devlogId, documentId } = params;

if (!devlogId || !documentId) {
return ApiErrors.invalidRequest('Missing devlogId or documentId');
}

// Parse devlogId as number
const parsedDevlogId = parseInt(devlogId);
if (isNaN(parsedDevlogId)) {
return ApiErrors.invalidRequest('Invalid devlogId');
}

// Get project using helper
const projectHelperResult = await ServiceHelper.getProjectByNameOrFail(projectName);
if (!projectHelperResult.success) {
return projectHelperResult.response;
}

const project = projectHelperResult.data.project;

// Verify devlog exists
const devlogService = DevlogService.getInstance(project.id);
const devlog = await devlogService.get(parsedDevlogId, false);
if (!devlog) {
return ApiErrors.devlogNotFound();
}

// Verify document exists and belongs to the devlog
const documentService = DocumentService.getInstance(project.id);
const document = await documentService.getDocument(documentId);

if (!document || document.devlogId !== parsedDevlogId) {
return ApiErrors.notFound('Document not found');
}

// Delete document
const deleted = await documentService.deleteDocument(documentId);

if (!deleted) {
return ApiErrors.internalError('Failed to delete document');
}

return createSuccessResponse(
{ message: 'Document deleted successfully' },
{
sseEventType: RealtimeEventType.DEVLOG_UPDATED,
}
);
} catch (error) {
console.error('Error deleting document:', error);
return ApiErrors.internalError('Failed to delete document');
}
}
131 changes: 131 additions & 0 deletions apps/web/app/api/projects/[name]/devlogs/[devlogId]/documents/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { NextRequest } from 'next/server';
import { DocumentService, DevlogService } from '@codervisor/devlog-core/server';
import { ApiErrors, createSuccessResponse, RouteParams, ServiceHelper, createSimpleCollectionResponse } from '@/lib/api/api-utils';
import { RealtimeEventType } from '@/lib/realtime';

// Mark this route as dynamic to prevent static generation
export const dynamic = 'force-dynamic';

// GET /api/projects/[name]/devlogs/[devlogId]/documents - List documents for a devlog
export async function GET(
request: NextRequest,
{ params }: { params: { name: string; devlogId: string } },
) {
try {
// Parse and validate parameters
const paramResult = RouteParams.parseProjectNameAndDevlogId(params);
if (!paramResult.success) {
return paramResult.response;
}

const { projectName, devlogId } = paramResult.data;

// Get project using helper
const projectResult = await ServiceHelper.getProjectByNameOrFail(projectName);
if (!projectResult.success) {
return projectResult.response;
}

const project = projectResult.data.project;

// Verify devlog exists
const devlogService = DevlogService.getInstance(project.id);
const devlog = await devlogService.get(devlogId, false);
if (!devlog) {
return ApiErrors.devlogNotFound();
}

// Get documents using document service
const documentService = DocumentService.getInstance(project.id);
const documents = await documentService.listDocuments(devlogId);

return createSimpleCollectionResponse(documents);
} catch (error) {
console.error('Error fetching devlog documents:', error);
return ApiErrors.internalError('Failed to fetch documents');
}
}

// POST /api/projects/[name]/devlogs/[devlogId]/documents - Upload a document
export async function POST(
request: NextRequest,
{ params }: { params: { name: string; devlogId: string } },
) {
try {
// Parse and validate parameters
const paramResult = RouteParams.parseProjectNameAndDevlogId(params);
if (!paramResult.success) {
return paramResult.response;
}

const { projectName, devlogId } = paramResult.data;

// Get project using helper
const projectResult = await ServiceHelper.getProjectByNameOrFail(projectName);
if (!projectResult.success) {
return projectResult.response;
}

const project = projectResult.data.project;

// Verify devlog exists
const devlogService = DevlogService.getInstance(project.id);
const devlog = await devlogService.get(devlogId, false);
if (!devlog) {
return ApiErrors.devlogNotFound();
}

// Parse multipart form data
const formData = await request.formData();
const file = formData.get('file') as File;
const metadata = formData.get('metadata') as string;

if (!file) {
return ApiErrors.invalidRequest('File is required');
}

// Validate file size (10MB limit)
const maxSize = 10 * 1024 * 1024; // 10MB
Copy link

Copilot AI Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 10MB file size limit is hardcoded and duplicated across multiple locations. Consider defining this as a constant in a shared configuration file to ensure consistency and make it easier to modify.

Copilot uses AI. Check for mistakes.
if (file.size > maxSize) {
return ApiErrors.invalidRequest('File size exceeds 10MB limit');
}

// Read file content
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

// Parse metadata if provided
let parsedMetadata: Record<string, any> | undefined;
if (metadata) {
try {
parsedMetadata = JSON.parse(metadata);
} catch {
return ApiErrors.invalidRequest('Invalid metadata JSON');
}
}

// Upload document
const documentService = DocumentService.getInstance(project.id);
const document = await documentService.uploadDocument(
devlogId,
{
originalName: file.name,
mimeType: file.type,
size: file.size,
content: buffer,
},
{
metadata: parsedMetadata,
// TODO: Add uploadedBy from authentication context
}
);

return createSuccessResponse(document, {
status: 201,
sseEventType: RealtimeEventType.DEVLOG_UPDATED,
});
} catch (error) {
console.error('Error uploading document:', error);
return ApiErrors.internalError('Failed to upload document');
}
}
Loading
Loading