Files
hristudio/src/app/api/invitations/route.ts
Sean O'Connor 95b106d9e9 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.
2024-12-04 12:32:54 -05:00

122 lines
3.1 KiB
TypeScript

import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { db } from "~/db";
import { invitationsTable, studyTable, rolesTable } from "~/db/schema";
import { eq } from "drizzle-orm";
import { randomBytes } from "crypto";
import { sendInvitationEmail } from "~/lib/email";
import { PERMISSIONS, checkPermissions } from "~/lib/permissions-server";
import { ApiError, createApiResponse } from "~/lib/api-utils";
// Helper to generate a secure random token
function generateToken(): string {
return randomBytes(32).toString('hex');
}
export async function GET(request: Request) {
try {
const url = new URL(request.url);
const studyId = url.searchParams.get("studyId");
if (!studyId) {
return ApiError.BadRequest("Study ID is required");
}
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
const invitations = await db
.select({
id: invitationsTable.id,
email: invitationsTable.email,
accepted: invitationsTable.accepted,
expiresAt: invitationsTable.expiresAt,
createdAt: invitationsTable.createdAt,
roleName: rolesTable.name,
})
.from(invitationsTable)
.innerJoin(rolesTable, eq(invitationsTable.roleId, rolesTable.id))
.where(eq(invitationsTable.studyId, parseInt(studyId)));
return createApiResponse(invitations);
} catch (error) {
return ApiError.ServerError(error);
}
}
export async function POST(request: Request) {
try {
const { email, studyId, roleId } = await request.json();
const permissionCheck = await checkPermissions({
studyId,
permission: PERMISSIONS.MANAGE_ROLES
});
if (permissionCheck.error) {
return permissionCheck.error;
}
const { userId } = permissionCheck;
// Get study details
const study = await db
.select()
.from(studyTable)
.where(eq(studyTable.id, studyId))
.limit(1);
if (!study[0]) {
return ApiError.NotFound("Study");
}
// Verify the role exists
const role = await db
.select()
.from(rolesTable)
.where(eq(rolesTable.id, roleId))
.limit(1);
if (!role[0]) {
return ApiError.BadRequest("Invalid role");
}
// Generate invitation token
const token = generateToken();
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7); // 7 days expiration
// Create invitation
const [invitation] = await db
.insert(invitationsTable)
.values({
email,
studyId,
roleId,
token,
invitedById: userId,
expiresAt,
})
.returning();
// Send invitation email
await sendInvitationEmail({
to: email,
inviterName: "A researcher", // TODO: Get inviter name
studyTitle: study[0].title,
role: role[0].name,
token,
});
return createApiResponse(invitation);
} catch (error) {
return ApiError.ServerError(error);
}
}