mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-12 07:04:44 -05:00
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:
@@ -2,7 +2,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { auth } from "@clerk/nextjs/server";
|
import { auth } from "@clerk/nextjs/server";
|
||||||
import { db } from "~/db";
|
import { db } from "~/db";
|
||||||
import { studyTable } from "~/db/schema";
|
import { studyTable, usersTable } from "~/db/schema";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const { userId } = await auth();
|
const { userId } = await auth();
|
||||||
@@ -23,19 +23,59 @@ export async function POST(request: Request) {
|
|||||||
const { userId } = await auth();
|
const { userId } = await auth();
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return new NextResponse("Unauthorized", { status: 401 });
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, description } = await request.json();
|
try {
|
||||||
|
// Debug log
|
||||||
|
console.log("Creating study for user:", userId);
|
||||||
|
|
||||||
const study = await db
|
// Verify user exists first
|
||||||
.insert(studyTable)
|
const existingUser = await db
|
||||||
.values({
|
.select()
|
||||||
title,
|
.from(usersTable)
|
||||||
description,
|
.where(eq(usersTable.id, userId));
|
||||||
|
|
||||||
|
console.log("Found user:", existingUser[0]); // Debug log
|
||||||
|
|
||||||
|
const { title, description } = await request.json();
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
console.log("Study data:", { title, description, userId });
|
||||||
|
|
||||||
|
const study = await db
|
||||||
|
.insert(studyTable)
|
||||||
|
.values({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
userId: userId, // Explicitly use the userId from auth
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
console.log("Created study:", study[0]); // Debug log
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(study[0]), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Enhanced error logging
|
||||||
|
console.error("Error details:", {
|
||||||
|
error,
|
||||||
userId,
|
userId,
|
||||||
})
|
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||||||
.returning();
|
errorName: error instanceof Error ? error.name : 'Unknown error type'
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json(study[0]);
|
return new Response(JSON.stringify({
|
||||||
|
error: "Failed to create study",
|
||||||
|
details: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
}), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,59 +59,57 @@ export async function POST(req: Request) {
|
|||||||
// Combine first and last name
|
// Combine first and last name
|
||||||
const fullName = [first_name, last_name].filter(Boolean).join(' ');
|
const fullName = [first_name, last_name].filter(Boolean).join(' ');
|
||||||
|
|
||||||
// Create/update user
|
// Create/update user with a transaction
|
||||||
await db
|
await db.transaction(async (tx) => {
|
||||||
.insert(usersTable)
|
// Create/update user
|
||||||
.values({
|
await tx
|
||||||
id,
|
.insert(usersTable)
|
||||||
name: fullName,
|
.values({
|
||||||
email: primaryEmail,
|
id,
|
||||||
imageUrl: image_url,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: usersTable.id,
|
|
||||||
set: {
|
|
||||||
name: fullName,
|
name: fullName,
|
||||||
email: primaryEmail,
|
email: primaryEmail,
|
||||||
imageUrl: image_url,
|
imageUrl: image_url,
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assign default role (Observer)
|
|
||||||
const observerRole = await db
|
|
||||||
.select()
|
|
||||||
.from(rolesTable)
|
|
||||||
.where(eq(rolesTable.name, 'Observer'))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (observerRole[0]) {
|
|
||||||
await db
|
|
||||||
.insert(userRolesTable)
|
|
||||||
.values({
|
|
||||||
userId: id,
|
|
||||||
roleId: observerRole[0].id,
|
|
||||||
})
|
})
|
||||||
.onConflictDoNothing();
|
.onConflictDoUpdate({
|
||||||
}
|
target: usersTable.id,
|
||||||
|
set: {
|
||||||
|
name: fullName,
|
||||||
|
email: primaryEmail,
|
||||||
|
imageUrl: image_url,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get or create Observer role
|
||||||
|
const observerRole = await tx
|
||||||
|
.select()
|
||||||
|
.from(rolesTable)
|
||||||
|
.where(eq(rolesTable.name, 'Observer'))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (observerRole[0]) {
|
||||||
|
await tx
|
||||||
|
.insert(userRolesTable)
|
||||||
|
.values({
|
||||||
|
userId: id,
|
||||||
|
roleId: observerRole[0].id,
|
||||||
|
})
|
||||||
|
.onConflictDoNothing();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return new Response('User created successfully', { status: 200 });
|
return new Response('User created successfully', { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating user:', error);
|
console.error('Error creating user:', error);
|
||||||
return new Response('Database error', { status: 500 });
|
return new Response(JSON.stringify({ error: 'Database error' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'user.deleted') {
|
return new Response(JSON.stringify({ message: 'Webhook processed successfully' }), {
|
||||||
try {
|
status: 200,
|
||||||
await db
|
headers: { 'Content-Type': 'application/json' }
|
||||||
.delete(usersTable)
|
});
|
||||||
.where(eq(usersTable.id, String(event.data.id)));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting user from database:', error);
|
|
||||||
return new Response('Database error', { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response('Webhook processed successfully', { status: 200 });
|
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,11 @@ export default function Studies() {
|
|||||||
const createStudy = async (e: React.FormEvent) => {
|
const createStudy = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
|
console.log("Sending study data:", {
|
||||||
|
title: newStudyTitle,
|
||||||
|
description: newStudyDescription
|
||||||
|
});
|
||||||
|
|
||||||
const response = await fetch('/api/studies', {
|
const response = await fetch('/api/studies', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -57,12 +62,20 @@ export default function Studies() {
|
|||||||
description: newStudyDescription,
|
description: newStudyDescription,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
console.error("Server response:", errorData);
|
||||||
|
throw new Error(errorData.error || 'Failed to create study');
|
||||||
|
}
|
||||||
|
|
||||||
const newStudy = await response.json();
|
const newStudy = await response.json();
|
||||||
setStudies([...studies, newStudy]);
|
setStudies([...studies, newStudy]);
|
||||||
setNewStudyTitle("");
|
setNewStudyTitle("");
|
||||||
setNewStudyDescription("");
|
setNewStudyDescription("");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating study:', error);
|
console.error('Error creating study:', error);
|
||||||
|
alert(error instanceof Error ? error.message : 'Failed to create study');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { sql } from '@vercel/postgres';
|
import { drizzle } from "drizzle-orm/vercel-postgres";
|
||||||
import { drizzle } from 'drizzle-orm/vercel-postgres';
|
import { sql } from "@vercel/postgres";
|
||||||
|
import { config } from "dotenv";
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config({ path: ".env.local" });
|
||||||
|
|
||||||
|
// Create the database instance
|
||||||
export const db = drizzle(sql);
|
export const db = drizzle(sql);
|
||||||
|
|||||||
222
src/db/seed.ts
222
src/db/seed.ts
@@ -1,135 +1,105 @@
|
|||||||
import { db } from "~/db";
|
|
||||||
import {
|
|
||||||
permissionsTable,
|
|
||||||
rolesTable,
|
|
||||||
rolePermissionsTable,
|
|
||||||
} from "~/db/schema";
|
|
||||||
import { config } from "dotenv";
|
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" });
|
config({ path: ".env.local" });
|
||||||
|
|
||||||
async function seed() {
|
async function seed() {
|
||||||
try {
|
console.log("🌱 Seeding database...");
|
||||||
console.log("Starting seed...");
|
|
||||||
|
|
||||||
// Create permissions
|
// Insert roles
|
||||||
const createdPermissions = await db
|
console.log("Inserting roles...");
|
||||||
.insert(permissionsTable)
|
for (const [roleKey, roleName] of Object.entries(ROLES)) {
|
||||||
.values([
|
await db.insert(rolesTable)
|
||||||
{
|
.values({
|
||||||
name: "View Participant Names",
|
name: roleName,
|
||||||
code: "view_participant_names",
|
description: getRoleDescription(roleKey),
|
||||||
description: "Can view participant names",
|
})
|
||||||
},
|
.onConflictDoNothing();
|
||||||
{
|
|
||||||
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 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()
|
function getRoleDescription(roleKey: string): string {
|
||||||
.catch(console.error)
|
const descriptions: Record<string, string> = {
|
||||||
.finally(() => process.exit());
|
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);
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ export const PERMISSIONS = {
|
|||||||
CREATE_STUDY: "create_study",
|
CREATE_STUDY: "create_study",
|
||||||
DELETE_STUDY: "delete_study",
|
DELETE_STUDY: "delete_study",
|
||||||
MANAGE_ROLES: "manage_roles",
|
MANAGE_ROLES: "manage_roles",
|
||||||
|
EDIT_STUDY: "edit_study",
|
||||||
|
VIEW_STUDY: "view_study",
|
||||||
|
EDIT_PARTICIPANT: "edit_participant",
|
||||||
|
CONTROL_ROBOT: "control_robot",
|
||||||
|
VIEW_ROBOT_STATUS: "view_robot_status",
|
||||||
|
RECORD_EXPERIMENT: "record_experiment",
|
||||||
|
VIEW_EXPERIMENT: "view_experiment",
|
||||||
|
VIEW_EXPERIMENT_DATA: "view_experiment_data",
|
||||||
|
EXPORT_EXPERIMENT_DATA: "export_experiment_data",
|
||||||
|
ANNOTATE_EXPERIMENT: "annotate_experiment",
|
||||||
|
MANAGE_USERS: "manage_users",
|
||||||
|
MANAGE_SYSTEM_SETTINGS: "manage_system_settings",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type PermissionCode = keyof typeof PERMISSIONS;
|
export type PermissionCode = keyof typeof PERMISSIONS;
|
||||||
|
|||||||
70
src/lib/roles.ts
Normal file
70
src/lib/roles.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { PERMISSIONS } from './permissions';
|
||||||
|
|
||||||
|
export const ROLES = {
|
||||||
|
ADMIN: 'admin',
|
||||||
|
PRINCIPAL_INVESTIGATOR: 'principal_investigator',
|
||||||
|
RESEARCHER: 'researcher',
|
||||||
|
WIZARD: 'wizard',
|
||||||
|
OBSERVER: 'observer',
|
||||||
|
ASSISTANT: 'assistant',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type RoleCode = keyof typeof ROLES;
|
||||||
|
|
||||||
|
export const ROLE_PERMISSIONS: Record<RoleCode, Array<keyof typeof PERMISSIONS>> = {
|
||||||
|
ADMIN: Object.keys(PERMISSIONS) as Array<keyof typeof PERMISSIONS>,
|
||||||
|
|
||||||
|
PRINCIPAL_INVESTIGATOR: [
|
||||||
|
'CREATE_STUDY',
|
||||||
|
'EDIT_STUDY',
|
||||||
|
'DELETE_STUDY',
|
||||||
|
'VIEW_STUDY',
|
||||||
|
'VIEW_PARTICIPANT_NAMES',
|
||||||
|
'CREATE_PARTICIPANT',
|
||||||
|
'EDIT_PARTICIPANT',
|
||||||
|
'DELETE_PARTICIPANT',
|
||||||
|
'VIEW_ROBOT_STATUS',
|
||||||
|
'VIEW_EXPERIMENT',
|
||||||
|
'VIEW_EXPERIMENT_DATA',
|
||||||
|
'EXPORT_EXPERIMENT_DATA',
|
||||||
|
'ANNOTATE_EXPERIMENT',
|
||||||
|
'MANAGE_ROLES',
|
||||||
|
'MANAGE_USERS',
|
||||||
|
],
|
||||||
|
|
||||||
|
RESEARCHER: [
|
||||||
|
'VIEW_STUDY',
|
||||||
|
'VIEW_PARTICIPANT_NAMES',
|
||||||
|
'CREATE_PARTICIPANT',
|
||||||
|
'EDIT_PARTICIPANT',
|
||||||
|
'VIEW_ROBOT_STATUS',
|
||||||
|
'VIEW_EXPERIMENT',
|
||||||
|
'VIEW_EXPERIMENT_DATA',
|
||||||
|
'EXPORT_EXPERIMENT_DATA',
|
||||||
|
'ANNOTATE_EXPERIMENT',
|
||||||
|
],
|
||||||
|
|
||||||
|
WIZARD: [
|
||||||
|
'VIEW_STUDY',
|
||||||
|
'VIEW_PARTICIPANT_NAMES',
|
||||||
|
'VIEW_ROBOT_STATUS',
|
||||||
|
'CONTROL_ROBOT',
|
||||||
|
'RECORD_EXPERIMENT',
|
||||||
|
'VIEW_EXPERIMENT',
|
||||||
|
'ANNOTATE_EXPERIMENT',
|
||||||
|
],
|
||||||
|
|
||||||
|
OBSERVER: [
|
||||||
|
'VIEW_STUDY',
|
||||||
|
'VIEW_ROBOT_STATUS',
|
||||||
|
'VIEW_EXPERIMENT',
|
||||||
|
'VIEW_EXPERIMENT_DATA',
|
||||||
|
'ANNOTATE_EXPERIMENT',
|
||||||
|
],
|
||||||
|
|
||||||
|
ASSISTANT: [
|
||||||
|
'VIEW_STUDY',
|
||||||
|
'VIEW_ROBOT_STATUS',
|
||||||
|
'VIEW_EXPERIMENT',
|
||||||
|
],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user