diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 139ff88..706ce09 100755 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,4 +1,4 @@ -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { NextResponse, type NextRequest } from "next/server"; import { z } from "zod"; import { @@ -9,7 +9,7 @@ import { } from "~/lib/storage/minio"; import { auth } from "~/server/auth"; import { db } from "~/server/db"; -import { mediaCaptures, trials } from "~/server/db/schema"; +import { experiments, mediaCaptures, studyMembers, trials } from "~/server/db/schema"; const uploadSchema = z.object({ trialId: z.string().optional(), @@ -71,16 +71,37 @@ export async function POST(request: NextRequest) { // Check trial access if trialId is provided if (validatedTrialId) { const trial = await db - .select() + .select({ + id: trials.id, + studyId: experiments.studyId, + }) .from(trials) + .innerJoin(experiments, eq(trials.experimentId, experiments.id)) .where(eq(trials.id, validatedTrialId)) .limit(1); - if (!trial.length) { + if (!trial.length || !trial[0]) { return NextResponse.json({ error: "Trial not found" }, { status: 404 }); } - // TODO: Check if user has access to this trial through study membership + // Check if user has access to this trial through study membership + const membership = await db + .select() + .from(studyMembers) + .where( + and( + eq(studyMembers.studyId, trial[0].studyId), + eq(studyMembers.userId, session.user.id) + ) + ) + .limit(1); + + if (!membership.length) { + return NextResponse.json( + { error: "Insufficient permissions to upload to this trial" }, + { status: 403 } + ); + } } // Generate unique file key diff --git a/src/components/admin/system-stats.tsx b/src/components/admin/system-stats.tsx index 6786021..e713d58 100755 --- a/src/components/admin/system-stats.tsx +++ b/src/components/admin/system-stats.tsx @@ -2,13 +2,12 @@ import { Badge } from "~/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { api } from "~/trpc/react"; export function SystemStats() { - // TODO: Implement admin.getSystemStats API endpoint - // const { data: stats, isLoading } = api.admin.getSystemStats.useQuery({}); - const isLoading = false; + const { data: stats, isLoading } = api.admin.getSystemStats.useQuery({}); - if (isLoading) { + if (isLoading || !stats) { return (
{Array.from({ length: 4 }).map((_, i) => ( @@ -26,19 +25,30 @@ export function SystemStats() { ); } - // Mock data for now since we don't have the actual admin router implemented - const mockStats = { - totalUsers: 42, - totalStudies: 15, - totalExperiments: 38, - totalTrials: 127, - activeTrials: 3, - systemHealth: "healthy", - uptime: "7 days, 14 hours", - storageUsed: "2.3 GB", + const formatBytes = (bytes: number) => { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; }; - const displayStats = mockStats; + const formatUptime = (seconds: number) => { + const d = Math.floor(seconds / (3600 * 24)); + const h = Math.floor((seconds % (3600 * 24)) / 3600); + return `${d} days, ${h} hours`; + }; + + const displayStats = { + totalUsers: stats.users.total, + totalStudies: stats.studies.total, + totalExperiments: stats.experiments.total, + totalTrials: stats.trials.total, + activeTrials: stats.trials.running, + systemHealth: "healthy", + uptime: formatUptime(stats.system.uptime), + storageUsed: formatBytes(stats.storage.totalSize), + }; return (
diff --git a/src/components/experiments/designer/panels/InspectorPanel.tsx b/src/components/experiments/designer/panels/InspectorPanel.tsx index 9b46416..a8ec54b 100755 --- a/src/components/experiments/designer/panels/InspectorPanel.tsx +++ b/src/components/experiments/designer/panels/InspectorPanel.tsx @@ -21,6 +21,7 @@ import { PackageSearch, PanelRightClose, } from "lucide-react"; +import { toast } from "sonner"; /** * InspectorPanel @@ -371,15 +372,13 @@ export function InspectorPanel({ actionDefinitions={actionRegistry.getAllActions()} studyPlugins={studyPlugins} onReconcileAction={(actionId) => { - // Placeholder: future diff modal / signature update - - console.log("Reconcile TODO for action:", actionId); + toast.info("Action Reconcile coming soon!"); }} onRefreshDependencies={() => { - console.log("Refresh dependencies TODO"); + toast.info("Refresh dependencies coming soon!"); }} onInstallPlugin={(pluginId) => { - console.log("Install plugin TODO:", pluginId); + toast.info("Install plugin coming soon!"); }} />