Add roles system

This commit is contained in:
2024-10-03 17:50:07 -04:00
parent 7ef9180026
commit 9d9aa52285
8 changed files with 114 additions and 9 deletions

View File

@@ -6,6 +6,8 @@ import { eq } from "drizzle-orm";
import { saveFile } from "~/lib/fileStorage"; import { saveFile } from "~/lib/fileStorage";
import fs from 'fs/promises'; import fs from 'fs/promises';
import { studies, participants } from "~/server/db/schema"; import { studies, participants } from "~/server/db/schema";
import { anonymizeParticipants } from "~/lib/permissions"; // Import the anonymize function
// Function to generate a random string // Function to generate a random string
const generateRandomString = (length: number) => { const generateRandomString = (length: number) => {
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
@@ -37,7 +39,13 @@ export async function GET(request: Request) {
.innerJoin(studies, eq(informedConsentForms.studyId, studies.id)) .innerJoin(studies, eq(informedConsentForms.studyId, studies.id))
.innerJoin(participants, eq(informedConsentForms.participantId, participants.id)); .innerJoin(participants, eq(informedConsentForms.participantId, participants.id));
return NextResponse.json(forms); // Anonymize participant names
const anonymizedForms = forms.map(form => ({
...form,
participantName: `Participant ${form.participantId}` // Anonymizing logic
}));
return NextResponse.json(anonymizedForms);
} }
export async function POST(request: Request) { export async function POST(request: Request) {

View File

@@ -2,8 +2,11 @@ import { db } from "~/server/db";
import { participants, trialParticipants, trials } from "~/server/db/schema"; import { participants, trialParticipants, trials } from "~/server/db/schema";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { eq, sql } from "drizzle-orm"; import { eq, sql } from "drizzle-orm";
import { auth } from "@clerk/nextjs/server"; // Import auth to get userId
import { anonymizeParticipants } from "~/lib/permissions"; // Import the anonymize function
export async function GET(request: Request) { export async function GET(request: Request) {
const { userId } = auth(); // Get the userId from auth
try { try {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const studyId = searchParams.get('studyId'); const studyId = searchParams.get('studyId');
@@ -16,6 +19,7 @@ export async function GET(request: Request) {
.select({ .select({
id: participants.id, id: participants.id,
name: participants.name, name: participants.name,
studyId: participants.studyId,
createdAt: participants.createdAt, createdAt: participants.createdAt,
latestTrialTimestamp: sql<Date | null>`MAX(${trials.createdAt})`.as('latestTrialTimestamp') latestTrialTimestamp: sql<Date | null>`MAX(${trials.createdAt})`.as('latestTrialTimestamp')
}) })
@@ -26,7 +30,10 @@ export async function GET(request: Request) {
.groupBy(participants.id) .groupBy(participants.id)
.orderBy(sql`COALESCE(MAX(${trials.createdAt}), ${participants.createdAt}) DESC`); .orderBy(sql`COALESCE(MAX(${trials.createdAt}), ${participants.createdAt}) DESC`);
return NextResponse.json(participantsWithLatestTrial); // Anonymize participant names
const anonymizedParticipants = anonymizeParticipants(participantsWithLatestTrial, userId);
return NextResponse.json(anonymizedParticipants);
} catch (error) { } catch (error) {
console.error('Error in GET /api/participants:', error); console.error('Error in GET /api/participants:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });

View File

@@ -1,7 +1,7 @@
import { db } from "~/server/db"; import { db } from "~/server/db";
import { users } from "~/server/db/schema"; import { users } from "~/server/db/schema";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { eq } from "drizzle-orm";
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const { email } = await request.json(); const { email } = await request.json();
@@ -11,6 +11,12 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Email is required" }, { status: 400 }); return NextResponse.json({ error: "Email is required" }, { status: 400 });
} }
// Check if the user already exists
const existingUser = await db.select().from(users).where(eq(users.email, email)).limit(1);
if (existingUser) {
return NextResponse.json({ error: "User already exists" }, { status: 409 });
}
// Insert the new user into the database // Insert the new user into the database
const newUser = await db.insert(users).values({ email }).returning(); const newUser = await db.insert(users).values({ email }).returning();
return NextResponse.json(newUser[0]); return NextResponse.json(newUser[0]);

View File

@@ -75,19 +75,21 @@ export function Trials() {
}; };
return ( return (
<Card> <Card className="card-level-1">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-2xl font-bold">Trials</CardTitle> <CardTitle className="text-2xl font-bold">Trials</CardTitle>
<CreateTrialDialog onCreateTrial={createTrial} /> <CreateTrialDialog onCreateTrial={createTrial} />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{trials.length > 0 ? ( {trials.length > 0 ? (
<div className="grid grid-cols-1 gap-4"> <div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{trials.map(trial => ( {trials.map(trial => (
<Card key={trial.id} className="bg-gray-100 p-3 flex items-center justify-between"> <Card key={trial.id} className="card-level-2 p-3 flex items-center justify-between">
<div> <div>
<h3 className="font-semibold">{trial.title}</h3> <h3 className="font-semibold">{trial.title}</h3>
<p className="text-sm text-gray-500">Participants: {trial.participantIds.join(', ')}</p> <p className="text-sm text-gray-500">
Participants: {trial.participantIds ? trial.participantIds.join(', ') : 'None'}
</p>
</div> </div>
<Button <Button
variant="ghost" variant="ghost"

20
src/lib/permissions.ts Normal file
View File

@@ -0,0 +1,20 @@
import { auth } from "@clerk/nextjs/server";
import { Participant } from "../types/Participant";
export const isUserAuthorized = (userId: string | null): boolean => {
// Implement your logic to determine if the user is authorized to see participant names
// For example, you might check if the user is an admin or has a specific role
// return userId !== null; // Placeholder logic, replace with your actual authorization logic
return false;
};
export const anonymizeParticipants = (participants: Participant[], userId: string | null): Participant[] => {
if (isUserAuthorized(userId)) {
return participants; // Return original participants if authorized
}
return participants.map(participant => ({
...participant,
name: `Participant ${participant.id}`, // Anonymize the name
}));
};

View File

@@ -17,7 +17,8 @@ if (env.NODE_ENV !== "production") globalForDb.conn = conn;
export const db = drizzle(conn, { schema }); export const db = drizzle(conn, { schema });
import { initializeContentTypes } from "./init"; import { initializeContentTypes, initializeRoles } from "./init";
// Initialize content types // Initialize content types
initializeContentTypes().catch(console.error); initializeContentTypes().catch(console.error);
initializeRoles().catch(console.error);

View File

@@ -1,5 +1,6 @@
import { db } from "./index"; import { db } from "./index";
import { contentTypes } from "./schema"; import { contentTypes } from "./schema";
import { roles } from "./schema";
export async function initializeContentTypes() { export async function initializeContentTypes() {
const existingTypes = await db.select().from(contentTypes); const existingTypes = await db.select().from(contentTypes);
@@ -11,5 +12,21 @@ export async function initializeContentTypes() {
// Add other content types as needed // Add other content types as needed
]); ]);
console.log("Content types initialized"); console.log("Content types initialized");
} else {
console.log("Content types already initialized");
}
}
export async function initializeRoles() {
const existingRoles = await db.select().from(roles);
if (existingRoles.length === 0) {
await db.insert(roles).values([
{ name: "Basic User" }, // Role ID 0
{ name: "Admin" }, // Role ID 1
]);
console.log("Roles initialized");
} else {
console.log("Roles already initialized");
} }
} }

View File

@@ -6,7 +6,8 @@ import {
serial, serial,
varchar, varchar,
timestamp, timestamp,
integer integer,
boolean
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm"; import { sql } from "drizzle-orm";
@@ -85,6 +86,7 @@ export const users = pgTable(
{ {
id: serial("id").primaryKey(), id: serial("id").primaryKey(),
email: varchar("email", { length: 256 }).notNull().unique(), email: varchar("email", { length: 256 }).notNull().unique(),
roleId: integer("role_id").references(() => roles.id).default(0), // Link to roles
createdAt: timestamp("created_at", { withTimezone: true }) createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`) .default(sql`CURRENT_TIMESTAMP`)
.notNull(), .notNull(),
@@ -109,4 +111,46 @@ export const trialParticipants = pgTable(
trialId: integer("trial_id").references(() => trials.id).notNull(), trialId: integer("trial_id").references(() => trials.id).notNull(),
participantId: integer("participant_id").references(() => participants.id).notNull(), participantId: integer("participant_id").references(() => participants.id).notNull(),
} }
);
export const roles = pgTable(
"role",
{
id: serial("id").primaryKey(),
name: varchar("name", { length: 50 }).notNull().unique(),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
}
);
export const permissions = pgTable(
"permission",
{
id: serial("id").primaryKey(),
name: varchar("name", { length: 50 }).notNull().unique(),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
}
);
export const rolePermissions = pgTable(
"role_permissions",
{
id: serial("id").primaryKey(),
roleId: integer("role_id").references(() => roles.id).notNull(),
permissionId: integer("permission_id").references(() => permissions.id).notNull(),
}
);
export const permissionTypes = pgTable(
"permission_type",
{
id: serial("id").primaryKey(),
name: varchar("name", { length: 50 }).notNull().unique(),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
}
); );