Skip to content

Commit 6585875

Browse files
committed
fix: multiple API/UI/schema issues
- Cast JSON fields (context, data, metrics) to any in events batch create to satisfy Prisma JsonValue - Use session relation when counting agent events in machine-activity stats - Import/use PrismaProjectService.getByName in project hierarchy page (use server-side service) - Show "Last Updated" (updatedAt) in project settings and update ProjectListQuerySchema.sortBy to use updatedAt
1 parent 131738c commit 6585875

File tree

5 files changed

+96
-97
lines changed

5 files changed

+96
-97
lines changed

apps/web/app/api/events/batch/route.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Batch Event Creation API Endpoint
3-
*
3+
*
44
* POST /api/events/batch - Batch create agent events
55
*/
66

@@ -14,17 +14,14 @@ export const dynamic = 'force-dynamic';
1414

1515
/**
1616
* POST /api/events/batch - Batch create events
17-
*
17+
*
1818
* Creates multiple agent events in a single transaction.
1919
* Maximum 1000 events per request for performance.
2020
*/
2121
export async function POST(request: NextRequest) {
2222
try {
2323
// Validate request body
24-
const validation = await ApiValidator.validateJsonBody(
25-
request,
26-
BatchEventsCreateSchema
27-
);
24+
const validation = await ApiValidator.validateJsonBody(request, BatchEventsCreateSchema);
2825

2926
if (!validation.success) {
3027
return validation.response;
@@ -33,10 +30,7 @@ export async function POST(request: NextRequest) {
3330
const events = validation.data;
3431

3532
if (events.length === 0) {
36-
return NextResponse.json(
37-
{ error: 'At least one event is required' },
38-
{ status: 400 }
39-
);
33+
return NextResponse.json({ error: 'At least one event is required' }, { status: 400 });
4034
}
4135

4236
// Get Prisma client
@@ -51,9 +45,9 @@ export async function POST(request: NextRequest) {
5145
agentVersion: event.agentVersion,
5246
sessionId: event.sessionId,
5347
projectId: event.projectId,
54-
context: event.context,
55-
data: event.data,
56-
metrics: event.metrics,
48+
context: event.context as any, // Cast to satisfy Prisma JsonValue type
49+
data: event.data as any,
50+
metrics: event.metrics as any,
5751
parentEventId: event.parentEventId,
5852
relatedEventIds: event.relatedEventIds,
5953
tags: event.tags,
@@ -67,7 +61,7 @@ export async function POST(request: NextRequest) {
6761
created: result.count,
6862
requested: events.length,
6963
},
70-
{ status: 201 }
64+
{ status: 201 },
7165
);
7266
} catch (error) {
7367
console.error('[POST /api/events/batch] Error:', error);
@@ -80,7 +74,7 @@ export async function POST(request: NextRequest) {
8074
error: 'Invalid reference: session or project not found',
8175
details: error.message,
8276
},
83-
{ status: 400 }
77+
{ status: 400 },
8478
);
8579
}
8680
}
@@ -89,7 +83,7 @@ export async function POST(request: NextRequest) {
8983
{
9084
error: error instanceof Error ? error.message : 'Failed to create events',
9185
},
92-
{ status: 500 }
86+
{ status: 500 },
9387
);
9488
}
9589
}

apps/web/app/api/stats/machine-activity/route.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Machine Activity Stats API
3-
*
3+
*
44
* GET /api/stats/machine-activity
55
* Returns aggregated activity statistics by machine
66
*/
@@ -17,24 +17,28 @@ export async function GET(req: NextRequest) {
1717
try {
1818
const searchParams = Object.fromEntries(req.nextUrl.searchParams);
1919
const query = QuerySchema.parse(searchParams);
20-
20+
2121
const prisma = new PrismaClient();
22-
22+
2323
try {
2424
// Aggregate activity by machine
2525
const machines = await prisma.machine.findMany({
26-
where: query.projectId ? {
27-
workspaces: {
28-
some: {
29-
projectId: query.projectId,
30-
},
31-
},
32-
} : undefined,
26+
where: query.projectId
27+
? {
28+
workspaces: {
29+
some: {
30+
projectId: query.projectId,
31+
},
32+
},
33+
}
34+
: undefined,
3335
include: {
3436
workspaces: {
35-
where: query.projectId ? {
36-
projectId: query.projectId,
37-
} : undefined,
37+
where: query.projectId
38+
? {
39+
projectId: query.projectId,
40+
}
41+
: undefined,
3842
include: {
3943
chatSessions: {
4044
select: {
@@ -45,37 +49,37 @@ export async function GET(req: NextRequest) {
4549
},
4650
},
4751
});
48-
52+
4953
// Get event counts for each machine
5054
const machineActivity = await Promise.all(
5155
machines.map(async (machine) => {
52-
const workspaceIds = machine.workspaces.map(w => w.id);
53-
56+
const workspaceIds = machine.workspaces.map((w) => w.id);
57+
5458
const eventCount = await prisma.agentEvent.count({
5559
where: {
56-
chatSession: {
60+
session: {
5761
workspaceId: {
5862
in: workspaceIds,
5963
},
6064
},
6165
},
6266
});
63-
67+
6468
const sessionCount = machine.workspaces.reduce(
6569
(sum, w) => sum + w.chatSessions.length,
66-
0
70+
0,
6771
);
68-
72+
6973
return {
7074
hostname: machine.hostname,
7175
machineType: machine.machineType,
7276
sessionCount,
7377
eventCount,
7478
workspaceCount: machine.workspaces.length,
7579
};
76-
})
80+
}),
7781
);
78-
82+
7983
return NextResponse.json({
8084
success: true,
8185
data: machineActivity,
@@ -88,7 +92,7 @@ export async function GET(req: NextRequest) {
8892
}
8993
} catch (error) {
9094
console.error('[API] Machine activity error:', error);
91-
95+
9296
if (error instanceof z.ZodError) {
9397
return NextResponse.json(
9498
{
@@ -102,10 +106,10 @@ export async function GET(req: NextRequest) {
102106
timestamp: new Date().toISOString(),
103107
},
104108
},
105-
{ status: 422 }
109+
{ status: 422 },
106110
);
107111
}
108-
112+
109113
return NextResponse.json(
110114
{
111115
success: false,
@@ -117,7 +121,7 @@ export async function GET(req: NextRequest) {
117121
timestamp: new Date().toISOString(),
118122
},
119123
},
120-
{ status: 500 }
124+
{ status: 500 },
121125
);
122126
}
123127
}

apps/web/app/projects/[name]/hierarchy/page.tsx

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Project Hierarchy Page
3-
*
3+
*
44
* Displays the complete project hierarchy with machines, workspaces, and sessions
55
*/
66

@@ -10,26 +10,23 @@ import { ChevronLeft } from 'lucide-react';
1010
import { HierarchyTree } from '@/components/agent-observability/hierarchy';
1111
import { Card } from '@/components/ui/card';
1212
import { Button } from '@/components/ui/button';
13-
import { ProjectService } from '@codervisor/devlog-core';
14-
import { HierarchyService } from '@codervisor/devlog-core';
13+
import { PrismaProjectService, HierarchyService } from '@codervisor/devlog-core/server';
1514

1615
interface ProjectHierarchyPageProps {
1716
params: { name: string };
1817
}
1918

20-
export default async function ProjectHierarchyPage({
21-
params,
22-
}: ProjectHierarchyPageProps) {
19+
export default async function ProjectHierarchyPage({ params }: ProjectHierarchyPageProps) {
2320
// Initialize services
24-
const projectService = ProjectService.getInstance();
21+
const projectService = PrismaProjectService.getInstance();
2522
const hierarchyService = HierarchyService.getInstance();
26-
23+
2724
await projectService.initialize();
2825
await hierarchyService.initialize();
2926

30-
// Fetch project by full name
31-
const project = await projectService.getProjectByFullName(params.name);
32-
27+
// Fetch project by name
28+
const project = await projectService.getByName(params.name);
29+
3330
if (!project) {
3431
notFound();
3532
}
@@ -55,13 +52,13 @@ export default async function ProjectHierarchyPage({
5552
{hierarchy.project.description && (
5653
<p className="text-muted-foreground mt-2">{hierarchy.project.description}</p>
5754
)}
58-
55+
5956
{/* Project metadata */}
6057
<div className="flex gap-4 mt-4 text-sm text-muted-foreground">
6158
{hierarchy.project.repoUrl && (
62-
<a
63-
href={hierarchy.project.repoUrl}
64-
target="_blank"
59+
<a
60+
href={hierarchy.project.repoUrl}
61+
target="_blank"
6562
rel="noopener noreferrer"
6663
className="hover:text-foreground transition-colors"
6764
>
@@ -80,9 +77,7 @@ export default async function ProjectHierarchyPage({
8077
{/* Hierarchy Tree */}
8178
{hierarchy.machines.length === 0 ? (
8279
<Card className="p-8 text-center">
83-
<p className="text-muted-foreground">
84-
No machines or workspaces detected yet.
85-
</p>
80+
<p className="text-muted-foreground">No machines or workspaces detected yet.</p>
8681
<p className="text-sm text-muted-foreground mt-2">
8782
Install the devlog collector to start tracking activity for this project.
8883
</p>

apps/web/app/projects/[name]/settings/project-settings-page.tsx

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -79,37 +79,40 @@ export function ProjectSettingsPage() {
7979
fetchCurrentProject();
8080
}, [currentProjectName]);
8181

82-
const handleUpdateProject = useCallback(async (e: React.FormEvent) => {
83-
e.preventDefault();
84-
85-
if (!formData.name.trim()) {
86-
toast.error('Project name is required');
87-
return;
88-
}
89-
90-
if (!project) {
91-
toast.error('Project not found');
92-
return;
93-
}
94-
95-
try {
96-
setIsUpdating(true);
97-
98-
const updates: Partial<Project> = {
99-
name: formData.name.trim(),
100-
description: formData.description?.trim() || undefined,
101-
};
102-
103-
await updateProject(project.name, updates);
104-
toast.success('Project updated successfully');
105-
setHasChanges(false);
106-
} catch (error) {
107-
console.error('Error updating project:', error);
108-
toast.error('Failed to update project');
109-
} finally {
110-
setIsUpdating(false);
111-
}
112-
}, [formData, project, updateProject]);
82+
const handleUpdateProject = useCallback(
83+
async (e: React.FormEvent) => {
84+
e.preventDefault();
85+
86+
if (!formData.name.trim()) {
87+
toast.error('Project name is required');
88+
return;
89+
}
90+
91+
if (!project) {
92+
toast.error('Project not found');
93+
return;
94+
}
95+
96+
try {
97+
setIsUpdating(true);
98+
99+
const updates: Partial<Project> = {
100+
name: formData.name.trim(),
101+
description: formData.description?.trim() || undefined,
102+
};
103+
104+
await updateProject(project.name, updates);
105+
toast.success('Project updated successfully');
106+
setHasChanges(false);
107+
} catch (error) {
108+
console.error('Error updating project:', error);
109+
toast.error('Failed to update project');
110+
} finally {
111+
setIsUpdating(false);
112+
}
113+
},
114+
[formData, project, updateProject],
115+
);
113116

114117
const handleDeleteProject = useCallback(async () => {
115118
if (!project) {
@@ -143,7 +146,7 @@ export function ProjectSettingsPage() {
143146
}, [project]);
144147

145148
const handleFormChange = useCallback((field: keyof ProjectFormData, value: string) => {
146-
setFormData(prev => ({ ...prev, [field]: value }));
149+
setFormData((prev) => ({ ...prev, [field]: value }));
147150
setHasChanges(true);
148151
}, []);
149152

@@ -320,8 +323,8 @@ export function ProjectSettingsPage() {
320323
<p className="text-sm">{new Date(project.createdAt).toLocaleDateString()}</p>
321324
</div>
322325
<div>
323-
<Label className="text-sm text-muted-foreground">Last Accessed</Label>
324-
<p className="text-sm">{new Date(project.lastAccessedAt).toLocaleDateString()}</p>
326+
<Label className="text-sm text-muted-foreground">Last Updated</Label>
327+
<p className="text-sm">{new Date(project.updatedAt).toLocaleDateString()}</p>
325328
</div>
326329
</div>
327330
</CardContent>

apps/web/schemas/project.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const ProjectIdParamSchema = z
1717
})
1818
.refine(
1919
(data) => isValidProjectIdentifier(data.id).valid,
20-
'Project name must follow GitHub naming conventions'
20+
'Project name must follow GitHub naming conventions',
2121
);
2222

2323
/**
@@ -27,7 +27,10 @@ export const CreateProjectBodySchema = z.object({
2727
name: z
2828
.string()
2929
.min(1, 'Project name is required')
30-
.refine(validateProjectDisplayName, 'The repository name can only contain ASCII letters, digits, and the characters -, ., and _.'),
30+
.refine(
31+
validateProjectDisplayName,
32+
'The repository name can only contain ASCII letters, digits, and the characters -, ., and _.',
33+
),
3134
description: z.string().optional(),
3235
repositoryUrl: z.string().optional(),
3336
settings: z
@@ -53,6 +56,6 @@ export const ProjectListQuerySchema = z.object({
5356
limit: z.string().regex(/^\d+$/).transform(Number).optional(),
5457
offset: z.string().regex(/^\d+$/).transform(Number).optional(),
5558
search: z.string().optional(),
56-
sortBy: z.enum(['name', 'createdAt', 'lastAccessedAt']).optional(),
59+
sortBy: z.enum(['name', 'createdAt', 'updatedAt']).optional(),
5760
sortOrder: z.enum(['asc', 'desc']).optional(),
5861
});

0 commit comments

Comments
 (0)