feat(api): Enhance study creation and user management with improved error handling and logging

- Updated the study creation endpoint to return JSON responses for errors and success.
- Added user existence verification before creating a study.
- Enhanced error logging for better debugging.
- Refactored user creation in webhooks to use transactions for atomicity.
- Improved response structure for webhook processing.
- Updated the seed script to streamline role and permission insertion.
- Added new permissions for editing studies and participants.
This commit is contained in:
2024-12-03 15:42:43 -05:00
parent 09b24a0e74
commit 3a955a0568
7 changed files with 291 additions and 183 deletions

View File

@@ -1,135 +1,105 @@
import { db } from "~/db";
import {
permissionsTable,
rolesTable,
rolePermissionsTable,
} from "~/db/schema";
import { config } from "dotenv";
import { db } from "./index";
import { PERMISSIONS } from "~/lib/permissions";
import { ROLES, ROLE_PERMISSIONS } from "~/lib/roles";
import { permissionsTable, rolesTable, rolePermissionsTable } from "./schema";
// Load environment variables from .env.local
config({ path: ".env.local" });
async function seed() {
try {
console.log("Starting seed...");
console.log("🌱 Seeding database...");
// Create permissions
const createdPermissions = await db
.insert(permissionsTable)
.values([
{
name: "View Participant Names",
code: "view_participant_names",
description: "Can view participant names",
},
{
name: "Create Participant",
code: "create_participant",
description: "Can create new participants",
},
{
name: "Delete Participant",
code: "delete_participant",
description: "Can delete participants",
},
{
name: "Create Study",
code: "create_study",
description: "Can create new studies",
},
{
name: "Delete Study",
code: "delete_study",
description: "Can delete studies",
},
{
name: "Manage Roles",
code: "manage_roles",
description: "Can manage user roles",
},
])
.returning();
console.log("Created permissions:", createdPermissions);
// Create roles
const createdRoles = await db
.insert(rolesTable)
.values([
{
name: "Admin",
description: "Full system access",
},
{
name: "Researcher",
description: "Can manage studies and participants",
},
{
name: "Observer",
description: "Can view participant names only",
},
])
.returning();
console.log("Created roles:", createdRoles);
// Find roles by name
const adminRole = createdRoles.find((r) => r.name === "Admin");
const researcherRole = createdRoles.find((r) => r.name === "Researcher");
const observerRole = createdRoles.find((r) => r.name === "Observer");
// Assign permissions to roles
if (adminRole) {
// Admin gets all permissions
await db.insert(rolePermissionsTable).values(
createdPermissions.map((p) => ({
roleId: adminRole.id,
permissionId: p.id,
}))
);
console.log("Assigned all permissions to Admin role");
}
if (researcherRole) {
// Researcher gets specific permissions
const researcherPermissions = createdPermissions.filter((p) =>
[
"view_participant_names",
"create_participant",
"create_study",
].includes(p.code)
);
await db.insert(rolePermissionsTable).values(
researcherPermissions.map((p) => ({
roleId: researcherRole.id,
permissionId: p.id,
}))
);
console.log("Assigned permissions to Researcher role");
}
if (observerRole) {
// Observer gets view-only permissions
const observerPermissions = createdPermissions.filter((p) =>
["view_participant_names"].includes(p.code)
);
await db.insert(rolePermissionsTable).values(
observerPermissions.map((p) => ({
roleId: observerRole.id,
permissionId: p.id,
}))
);
console.log("Assigned permissions to Observer role");
}
console.log("Seeding completed successfully");
} catch (error) {
console.error("Error seeding database:", error);
throw error;
// Insert roles
console.log("Inserting roles...");
for (const [roleKey, roleName] of Object.entries(ROLES)) {
await db.insert(rolesTable)
.values({
name: roleName,
description: getRoleDescription(roleKey),
})
.onConflictDoNothing();
}
// Insert permissions
console.log("Inserting permissions...");
for (const [permKey, permCode] of Object.entries(PERMISSIONS)) {
await db.insert(permissionsTable)
.values({
name: formatPermissionName(permKey),
code: permCode,
description: getPermissionDescription(permKey),
})
.onConflictDoNothing();
}
// Get role and permission IDs
const roles = await db.select().from(rolesTable);
const permissions = await db.select().from(permissionsTable);
// Insert role permissions
console.log("Inserting role permissions...");
for (const [roleKey, permissionCodes] of Object.entries(ROLE_PERMISSIONS)) {
const role = roles.find(r => r.name === ROLES[roleKey as keyof typeof ROLES]);
if (!role) continue;
for (const permissionCode of permissionCodes) {
const permission = permissions.find(p => p.code === PERMISSIONS[permissionCode]);
if (!permission) continue;
await db.insert(rolePermissionsTable)
.values({
roleId: role.id,
permissionId: permission.id,
})
.onConflictDoNothing();
}
}
console.log("✅ Seeding complete!");
}
seed()
.catch(console.error)
.finally(() => process.exit());
function getRoleDescription(roleKey: string): string {
const descriptions: Record<string, string> = {
ADMIN: "Full system administrator with all permissions",
PRINCIPAL_INVESTIGATOR: "Lead researcher responsible for study design and oversight",
RESEARCHER: "Study team member with data collection and analysis capabilities",
WIZARD: "Operator controlling robot behavior during experiments",
OBSERVER: "Team member observing and annotating experiments",
ASSISTANT: "Support staff with limited view access",
};
return descriptions[roleKey] || "";
}
function getPermissionDescription(permKey: string): string {
const descriptions: Record<string, string> = {
CREATE_STUDY: "Create new research studies",
EDIT_STUDY: "Modify existing study parameters",
DELETE_STUDY: "Remove studies from the system",
VIEW_STUDY: "View study details and progress",
VIEW_PARTICIPANT_NAMES: "Access participant identifying information",
CREATE_PARTICIPANT: "Add new participants to studies",
EDIT_PARTICIPANT: "Update participant information",
DELETE_PARTICIPANT: "Remove participants from studies",
CONTROL_ROBOT: "Operate robot during experiments",
VIEW_ROBOT_STATUS: "Monitor robot state and sensors",
RECORD_EXPERIMENT: "Start/stop experiment recording",
VIEW_EXPERIMENT: "View experiment progress and details",
VIEW_EXPERIMENT_DATA: "Access collected experiment data",
EXPORT_EXPERIMENT_DATA: "Download experiment data",
ANNOTATE_EXPERIMENT: "Add notes and annotations to experiments",
MANAGE_ROLES: "Assign and modify user roles",
MANAGE_USERS: "Add and remove system users",
MANAGE_SYSTEM_SETTINGS: "Configure system-wide settings",
};
return descriptions[permKey] || "";
}
function formatPermissionName(permKey: string): string {
return permKey.toLowerCase()
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
seed().catch(console.error);