Skip to content

Commit f0d1384

Browse files
author
Marvin Zhang
committed
feat: implement route parameter parsing and service helpers for improved API error handling and validation
1 parent 7691137 commit f0d1384

File tree

9 files changed

+422
-169
lines changed

9 files changed

+422
-169
lines changed

packages/mcp/src/api/devlog-api-client.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ export class DevlogApiClient {
223223
}
224224

225225
async searchDevlogs(query: string, filter?: DevlogFilter): Promise<PaginatedResult<DevlogEntry>> {
226-
const params = new URLSearchParams({ q: query });
226+
const params = new URLSearchParams({ search: query });
227227

228228
if (filter) {
229229
if (filter.status?.length) params.append('status', filter.status.join(','));
@@ -232,9 +232,7 @@ export class DevlogApiClient {
232232
if (filter.archived !== undefined) params.append('archived', String(filter.archived));
233233
}
234234

235-
const response = await this.get(
236-
`${this.getProjectEndpoint()}/devlogs/search?${params.toString()}`,
237-
);
235+
const response = await this.get(`${this.getProjectEndpoint()}/devlogs?${params.toString()}`);
238236
return this.unwrapApiResponse<PaginatedResult<DevlogEntry>>(response);
239237
}
240238

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,80 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
3+
import { RouteParams, ApiErrors } from '@/lib/api-utils';
34

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

78
// GET /api/projects/[id]/devlogs/[devlogId] - Get specific devlog entry
89
export async function GET(
910
request: NextRequest,
10-
{ params }: { params: { id: number; devlogId: number } },
11+
{ params }: { params: { id: string; devlogId: string } },
1112
) {
1213
try {
14+
// Parse and validate parameters
15+
const paramResult = RouteParams.parseProjectAndDevlogId(params);
16+
if (!paramResult.success) {
17+
return paramResult.response;
18+
}
19+
20+
const { projectId, devlogId } = paramResult.data;
21+
1322
const projectService = ProjectService.getInstance();
14-
const project = await projectService.get(params.id);
23+
const project = await projectService.get(projectId);
1524
if (!project) {
16-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
25+
return ApiErrors.projectNotFound();
1726
}
1827

19-
const devlogService = DevlogService.getInstance(params.id);
20-
const entry = await devlogService.get(params.devlogId);
28+
const devlogService = DevlogService.getInstance(projectId);
29+
const entry = await devlogService.get(devlogId);
2130

2231
if (!entry) {
23-
return NextResponse.json({ error: 'Devlog entry not found' }, { status: 404 });
32+
return ApiErrors.devlogNotFound();
2433
}
2534

2635
return NextResponse.json(entry);
2736
} catch (error) {
2837
console.error('Error fetching devlog:', error);
29-
return NextResponse.json({ error: 'Failed to fetch devlog' }, { status: 500 });
38+
return ApiErrors.internalError('Failed to fetch devlog');
3039
}
3140
}
3241

3342
// PUT /api/projects/[id]/devlogs/[devlogId] - Update devlog entry
3443
export async function PUT(
3544
request: NextRequest,
36-
{ params }: { params: { id: number; devlogId: number } },
45+
{ params }: { params: { id: string; devlogId: string } },
3746
) {
3847
try {
48+
// Parse and validate parameters
49+
const paramResult = RouteParams.parseProjectAndDevlogId(params);
50+
if (!paramResult.success) {
51+
return paramResult.response;
52+
}
53+
54+
const { projectId, devlogId } = paramResult.data;
55+
3956
const projectService = ProjectService.getInstance();
40-
const project = await projectService.get(params.id);
57+
const project = await projectService.get(projectId);
4158
if (!project) {
42-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
59+
return ApiErrors.projectNotFound();
4360
}
4461

4562
const data = await request.json();
4663

47-
const devlogService = DevlogService.getInstance(params.id);
64+
const devlogService = DevlogService.getInstance(projectId);
4865

4966
// Verify entry exists and belongs to project
50-
const existingEntry = await devlogService.get(params.devlogId);
67+
const existingEntry = await devlogService.get(devlogId);
5168
if (!existingEntry) {
52-
return NextResponse.json({ error: 'Devlog entry not found' }, { status: 404 });
69+
return ApiErrors.devlogNotFound();
5370
}
5471

5572
// Update entry
5673
const updatedEntry = {
5774
...existingEntry,
5875
...data,
59-
id: params.devlogId,
60-
projectId: params.id, // Ensure project context is maintained
76+
id: devlogId,
77+
projectId: projectId, // Ensure project context is maintained
6178
updatedAt: new Date().toISOString(),
6279
};
6380

@@ -67,37 +84,45 @@ export async function PUT(
6784
} catch (error) {
6885
console.error('Error updating devlog:', error);
6986
const message = error instanceof Error ? error.message : 'Failed to update devlog';
70-
return NextResponse.json({ error: message }, { status: 500 });
87+
return ApiErrors.internalError(message);
7188
}
7289
}
7390

7491
// DELETE /api/projects/[id]/devlogs/[devlogId] - Delete devlog entry
7592
export async function DELETE(
7693
request: NextRequest,
77-
{ params }: { params: { id: number; devlogId: number } },
94+
{ params }: { params: { id: string; devlogId: string } },
7895
) {
7996
try {
97+
// Parse and validate parameters
98+
const paramResult = RouteParams.parseProjectAndDevlogId(params);
99+
if (!paramResult.success) {
100+
return paramResult.response;
101+
}
102+
103+
const { projectId, devlogId } = paramResult.data;
104+
80105
const projectService = ProjectService.getInstance();
81-
const project = await projectService.get(params.id);
106+
const project = await projectService.get(projectId);
82107

83108
if (!project) {
84-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
109+
return ApiErrors.projectNotFound();
85110
}
86111

87-
const devlogService = DevlogService.getInstance(params.id);
112+
const devlogService = DevlogService.getInstance(projectId);
88113

89114
// Verify entry exists and belongs to project
90-
const existingEntry = await devlogService.get(params.devlogId);
115+
const existingEntry = await devlogService.get(devlogId);
91116
if (!existingEntry) {
92-
return NextResponse.json({ error: 'Devlog entry not found' }, { status: 404 });
117+
return ApiErrors.devlogNotFound();
93118
}
94119

95-
await devlogService.delete(params.devlogId);
120+
await devlogService.delete(devlogId);
96121

97122
return NextResponse.json({ success: true });
98123
} catch (error) {
99124
console.error('Error deleting devlog:', error);
100125
const message = error instanceof Error ? error.message : 'Failed to delete devlog';
101-
return NextResponse.json({ error: message }, { status: 500 });
126+
return ApiErrors.internalError(message);
102127
}
103128
}
Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,41 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
2+
import {
3+
RouteParams,
4+
ServiceHelper,
5+
ApiErrors,
6+
ApiResponses,
7+
withErrorHandling,
8+
} from '@/lib/api-utils';
39

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

713
// POST /api/projects/[id]/devlogs/batch/delete - Batch delete devlog entries
8-
export async function POST(request: NextRequest, { params }: { params: { id: number } }) {
9-
try {
10-
const projectService = ProjectService.getInstance();
14+
export const POST = withErrorHandling(
15+
async (request: NextRequest, { params }: { params: { id: string } }) => {
16+
// Parse and validate parameters
17+
const paramResult = RouteParams.parseProjectId(params);
18+
if (!paramResult.success) {
19+
return paramResult.response;
20+
}
1121

12-
const project = await projectService.get(params.id);
22+
const { projectId } = paramResult.data;
1323

14-
if (!project) {
15-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
24+
// Ensure project exists
25+
const projectResult = await ServiceHelper.getProjectOrFail(projectId);
26+
if (!projectResult.success) {
27+
return projectResult.response;
1628
}
1729

30+
// Parse request body
1831
const { ids } = await request.json();
1932

2033
if (!Array.isArray(ids) || ids.length === 0) {
21-
return NextResponse.json(
22-
{ error: 'Invalid request: ids (non-empty array) is required' },
23-
{ status: 400 },
24-
);
34+
return ApiErrors.invalidRequest('ids (non-empty array) is required');
2535
}
2636

27-
const devlogService = DevlogService.getInstance(params.id);
37+
// Get devlog service
38+
const devlogService = await ServiceHelper.getDevlogService(projectId);
2839

2940
const deletedIds = [];
3041
const errors = [];
@@ -33,8 +44,12 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
3344
for (const id of ids) {
3445
try {
3546
const devlogId = parseInt(id);
36-
const existingEntry = await devlogService.get(devlogId);
47+
if (isNaN(devlogId)) {
48+
errors.push({ id, error: 'Invalid devlog ID' });
49+
continue;
50+
}
3751

52+
const existingEntry = await devlogService.get(devlogId);
3853
if (!existingEntry) {
3954
errors.push({ id, error: 'Entry not found' });
4055
continue;
@@ -55,9 +70,5 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
5570
deleted: deletedIds,
5671
errors: errors.length > 0 ? errors : undefined,
5772
});
58-
} catch (error) {
59-
console.error('Error batch deleting devlogs:', error);
60-
const message = error instanceof Error ? error.message : 'Failed to batch delete devlogs';
61-
return NextResponse.json({ error: message }, { status: 500 });
62-
}
63-
}
73+
},
74+
);

packages/web/app/api/projects/[id]/devlogs/batch/note/route.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,41 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
2+
import {
3+
RouteParams,
4+
ServiceHelper,
5+
ApiErrors,
6+
ApiResponses,
7+
withErrorHandling,
8+
} from '@/lib/api-utils';
39

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

713
// POST /api/projects/[id]/devlogs/batch/note - Batch add notes to devlog entries
8-
export async function POST(request: NextRequest, { params }: { params: { id: number } }) {
9-
try {
10-
const projectService = ProjectService.getInstance();
14+
export const POST = withErrorHandling(
15+
async (request: NextRequest, { params }: { params: { id: string } }) => {
16+
// Parse and validate parameters
17+
const paramResult = RouteParams.parseProjectId(params);
18+
if (!paramResult.success) {
19+
return paramResult.response;
20+
}
1121

12-
const project = await projectService.get(params.id);
22+
const { projectId } = paramResult.data;
1323

14-
if (!project) {
15-
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
24+
// Ensure project exists
25+
const projectResult = await ServiceHelper.getProjectOrFail(projectId);
26+
if (!projectResult.success) {
27+
return projectResult.response;
1628
}
1729

30+
// Parse request body
1831
const { ids, note } = await request.json();
1932

2033
if (!Array.isArray(ids) || !note || typeof note !== 'object') {
21-
return NextResponse.json(
22-
{ error: 'Invalid request: ids (array) and note (object) are required' },
23-
{ status: 400 },
24-
);
34+
return ApiErrors.invalidRequest('ids (array) and note (object) are required');
2535
}
2636

27-
const devlogService = DevlogService.getInstance(params.id);
37+
// Get devlog service
38+
const devlogService = await ServiceHelper.getDevlogService(projectId);
2839

2940
const updatedEntries = [];
3041
const errors = [];
@@ -33,8 +44,12 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
3344
for (const id of ids) {
3445
try {
3546
const devlogId = parseInt(id);
36-
const existingEntry = await devlogService.get(devlogId);
47+
if (isNaN(devlogId)) {
48+
errors.push({ id, error: 'Invalid devlog ID' });
49+
continue;
50+
}
3751

52+
const existingEntry = await devlogService.get(devlogId);
3853
if (!existingEntry) {
3954
errors.push({ id, error: 'Entry not found' });
4055
continue;
@@ -69,9 +84,5 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
6984
updated: updatedEntries,
7085
errors: errors.length > 0 ? errors : undefined,
7186
});
72-
} catch (error) {
73-
console.error('Error batch adding notes to devlogs:', error);
74-
const message = error instanceof Error ? error.message : 'Failed to batch add notes to devlogs';
75-
return NextResponse.json({ error: message }, { status: 500 });
76-
}
77-
}
87+
},
88+
);

0 commit comments

Comments
 (0)