chore(deps): Update dependencies and enhance API error handling

- Added '@vercel/analytics' to package.json for improved analytics tracking.
- Updated 'next' version from 15.0.2 to 15.0.3 to incorporate the latest features and fixes.
- Refactored API routes for invitations and studies to improve error handling and response structure.
- Enhanced permission checks in the invitations and studies API to ensure proper access control.
- Removed the participants dashboard page as part of a restructuring effort.
- Updated the database schema to include environment settings for users and studies.
- Improved the dashboard components to handle loading states and display statistics more effectively.
This commit is contained in:
2024-12-04 12:32:54 -05:00
parent cb4c0f9c87
commit 95b106d9e9
22 changed files with 1160 additions and 771 deletions

View File

@@ -2,10 +2,11 @@ import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { db } from "~/db";
import { invitationsTable, studyTable, rolesTable } from "~/db/schema";
import { eq, and } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { randomBytes } from "crypto";
import { sendInvitationEmail } from "~/lib/email";
import { hasPermission, hasStudyAccess, PERMISSIONS } from "~/lib/permissions";
import { PERMISSIONS, checkPermissions } from "~/lib/permissions-server";
import { ApiError, createApiResponse } from "~/lib/api-utils";
// Helper to generate a secure random token
function generateToken(): string {
@@ -13,24 +14,21 @@ function generateToken(): string {
}
export async function GET(request: Request) {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
const url = new URL(request.url);
const studyId = url.searchParams.get("studyId");
if (!studyId) {
return NextResponse.json({ error: "Study ID is required" }, { status: 400 });
return ApiError.BadRequest("Study ID is required");
}
// First check if user has access to the study
const hasAccess = await hasStudyAccess(userId, parseInt(studyId));
if (!hasAccess) {
return NextResponse.json({ error: "Study not found" }, { status: 404 });
const permissionCheck = await checkPermissions({
studyId: parseInt(studyId),
permission: PERMISSIONS.MANAGE_ROLES
});
if (permissionCheck.error) {
return permissionCheck.error;
}
// Get all invitations for the study, including role names
@@ -47,37 +45,26 @@ export async function GET(request: Request) {
.innerJoin(rolesTable, eq(invitationsTable.roleId, rolesTable.id))
.where(eq(invitationsTable.studyId, parseInt(studyId)));
return NextResponse.json(invitations);
return createApiResponse(invitations);
} catch (error) {
console.error("Error fetching invitations:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
return ApiError.ServerError(error);
}
}
export async function POST(request: Request) {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
const { email, studyId, roleId } = await request.json();
// First check if user has access to the study
const hasAccess = await hasStudyAccess(userId, studyId);
if (!hasAccess) {
return NextResponse.json({ error: "Study not found" }, { status: 404 });
const permissionCheck = await checkPermissions({
studyId,
permission: PERMISSIONS.MANAGE_ROLES
});
if (permissionCheck.error) {
return permissionCheck.error;
}
// Then check if user has permission to invite users
const canInvite = await hasPermission(userId, PERMISSIONS.MANAGE_ROLES, studyId);
if (!canInvite) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const { userId } = permissionCheck;
// Get study details
const study = await db
@@ -87,7 +74,7 @@ export async function POST(request: Request) {
.limit(1);
if (!study[0]) {
return NextResponse.json({ error: "Study not found" }, { status: 404 });
return ApiError.NotFound("Study");
}
// Verify the role exists
@@ -98,7 +85,7 @@ export async function POST(request: Request) {
.limit(1);
if (!role[0]) {
return NextResponse.json({ error: "Invalid role" }, { status: 400 });
return ApiError.BadRequest("Invalid role");
}
// Generate invitation token
@@ -128,12 +115,8 @@ export async function POST(request: Request) {
token,
});
return NextResponse.json(invitation);
return createApiResponse(invitation);
} catch (error) {
console.error("Error creating invitation:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
return ApiError.ServerError(error);
}
}