mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-11 22:54:45 -05:00
1236 lines
39 KiB
TypeScript
Executable File
1236 lines
39 KiB
TypeScript
Executable File
import { relations, sql } from "drizzle-orm";
|
|
import {
|
|
bigint,
|
|
boolean,
|
|
index,
|
|
inet,
|
|
integer,
|
|
jsonb,
|
|
pgEnum,
|
|
pgTableCreator,
|
|
primaryKey,
|
|
text,
|
|
timestamp,
|
|
unique,
|
|
uuid,
|
|
varchar,
|
|
} from "drizzle-orm/pg-core";
|
|
import { type AdapterAccount } from "next-auth/adapters";
|
|
|
|
/**
|
|
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
|
* database instance for multiple projects.
|
|
*
|
|
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
|
|
*/
|
|
export const createTable = pgTableCreator((name) => `hs_${name}`);
|
|
|
|
// Enums
|
|
export const systemRoleEnum = pgEnum("system_role", [
|
|
"administrator",
|
|
"researcher",
|
|
"wizard",
|
|
"observer",
|
|
]);
|
|
|
|
export const studyStatusEnum = pgEnum("study_status", [
|
|
"draft",
|
|
"active",
|
|
"completed",
|
|
"archived",
|
|
]);
|
|
|
|
export const studyMemberRoleEnum = pgEnum("study_member_role", [
|
|
"owner",
|
|
"researcher",
|
|
"wizard",
|
|
"observer",
|
|
]);
|
|
|
|
export const experimentStatusEnum = pgEnum("experiment_status", [
|
|
"draft",
|
|
"testing",
|
|
"ready",
|
|
"deprecated",
|
|
]);
|
|
|
|
export const trialStatusEnum = pgEnum("trial_status", [
|
|
"scheduled",
|
|
"in_progress",
|
|
"completed",
|
|
"aborted",
|
|
"failed",
|
|
]);
|
|
|
|
export const stepTypeEnum = pgEnum("step_type", [
|
|
"wizard",
|
|
"robot",
|
|
"parallel",
|
|
"conditional",
|
|
]);
|
|
|
|
export const communicationProtocolEnum = pgEnum("communication_protocol", [
|
|
"rest",
|
|
"ros2",
|
|
"custom",
|
|
]);
|
|
|
|
export const trustLevelEnum = pgEnum("trust_level", [
|
|
"official",
|
|
"verified",
|
|
"community",
|
|
]);
|
|
|
|
export const pluginStatusEnum = pgEnum("plugin_status", [
|
|
"active",
|
|
"deprecated",
|
|
"disabled",
|
|
]);
|
|
|
|
export const blockShapeEnum = pgEnum("block_shape", [
|
|
"action",
|
|
"control",
|
|
"value",
|
|
"boolean",
|
|
"hat",
|
|
"cap",
|
|
]);
|
|
|
|
export const blockCategoryEnum = pgEnum("block_category", [
|
|
"wizard",
|
|
"robot",
|
|
"control",
|
|
"sensor",
|
|
"logic",
|
|
"event",
|
|
]);
|
|
|
|
export const mediaTypeEnum = pgEnum("media_type", ["video", "audio", "image"]);
|
|
|
|
export const exportStatusEnum = pgEnum("export_status", [
|
|
"pending",
|
|
"processing",
|
|
"completed",
|
|
"failed",
|
|
]);
|
|
|
|
// Users and Authentication
|
|
export const users = createTable("user", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
email: varchar("email", { length: 255 }).notNull().unique(),
|
|
emailVerified: timestamp("email_verified", {
|
|
mode: "date",
|
|
withTimezone: true,
|
|
}),
|
|
name: varchar("name", { length: 255 }),
|
|
image: text("image"),
|
|
password: varchar("password", { length: 255 }),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
});
|
|
|
|
export const accounts = createTable(
|
|
"account",
|
|
{
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
type: varchar("type", { length: 255 })
|
|
.$type<AdapterAccount["type"]>()
|
|
.notNull(),
|
|
provider: varchar("provider", { length: 255 }).notNull(),
|
|
providerAccountId: varchar("provider_account_id", {
|
|
length: 255,
|
|
}).notNull(),
|
|
refreshToken: text("refresh_token"),
|
|
accessToken: text("access_token"),
|
|
expiresAt: integer("expires_at"),
|
|
tokenType: varchar("token_type", { length: 255 }),
|
|
scope: varchar("scope", { length: 255 }),
|
|
idToken: text("id_token"),
|
|
sessionState: varchar("session_state", { length: 255 }),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
compoundKey: primaryKey({
|
|
columns: [table.provider, table.providerAccountId],
|
|
}),
|
|
userIdIdx: index("account_user_id_idx").on(table.userId),
|
|
}),
|
|
);
|
|
|
|
export const sessions = createTable(
|
|
"session",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
sessionToken: varchar("session_token", { length: 255 }).notNull().unique(),
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
expires: timestamp("expires", {
|
|
mode: "date",
|
|
withTimezone: true,
|
|
}).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
userIdIdx: index("session_user_id_idx").on(table.userId),
|
|
}),
|
|
);
|
|
|
|
export const verificationTokens = createTable(
|
|
"verification_token",
|
|
{
|
|
identifier: varchar("identifier", { length: 255 }).notNull(),
|
|
token: varchar("token", { length: 255 }).notNull().unique(),
|
|
expires: timestamp("expires", {
|
|
mode: "date",
|
|
withTimezone: true,
|
|
}).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
compoundKey: primaryKey({ columns: [table.identifier, table.token] }),
|
|
}),
|
|
);
|
|
|
|
// Roles and Permissions
|
|
export const userSystemRoles = createTable(
|
|
"user_system_role",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
role: systemRoleEnum("role").notNull(),
|
|
grantedAt: timestamp("granted_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
grantedBy: uuid("granted_by").references(() => users.id),
|
|
},
|
|
(table) => ({
|
|
userRoleUnique: unique().on(table.userId, table.role),
|
|
}),
|
|
);
|
|
|
|
export const permissions = createTable("permission", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 100 }).notNull().unique(),
|
|
description: text("description"),
|
|
resource: varchar("resource", { length: 50 }).notNull(),
|
|
action: varchar("action", { length: 50 }).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
export const rolePermissions = createTable(
|
|
"role_permission",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
role: systemRoleEnum("role").notNull(),
|
|
permissionId: uuid("permission_id")
|
|
.notNull()
|
|
.references(() => permissions.id, { onDelete: "cascade" }),
|
|
},
|
|
(table) => ({
|
|
rolePermissionUnique: unique().on(table.role, table.permissionId),
|
|
}),
|
|
);
|
|
|
|
// Study Hierarchy
|
|
export const studies = createTable("study", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
institution: varchar("institution", { length: 255 }),
|
|
irbProtocol: varchar("irb_protocol", { length: 100 }),
|
|
status: studyStatusEnum("status").default("draft").notNull(),
|
|
createdBy: uuid("created_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
metadata: jsonb("metadata").default({}),
|
|
settings: jsonb("settings").default({}),
|
|
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
});
|
|
|
|
export const studyMembers = createTable(
|
|
"study_member",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
role: studyMemberRoleEnum("role").notNull(),
|
|
permissions: jsonb("permissions").default([]),
|
|
joinedAt: timestamp("joined_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
invitedBy: uuid("invited_by").references(() => users.id),
|
|
},
|
|
(table) => ({
|
|
studyUserUnique: unique().on(table.studyId, table.userId),
|
|
}),
|
|
);
|
|
|
|
export const robots = createTable("robot", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
manufacturer: varchar("manufacturer", { length: 255 }),
|
|
model: varchar("model", { length: 255 }),
|
|
description: text("description"),
|
|
capabilities: jsonb("capabilities").default([]),
|
|
communicationProtocol: communicationProtocolEnum("communication_protocol"),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
export const robotPlugins = createTable("robot_plugin", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
version: varchar("version", { length: 50 }).notNull(),
|
|
manufacturer: varchar("manufacturer", { length: 255 }),
|
|
description: text("description"),
|
|
robotId: uuid("robot_id").references(() => robots.id),
|
|
communicationProtocol: communicationProtocolEnum("communication_protocol"),
|
|
status: pluginStatusEnum("status").default("active").notNull(),
|
|
configSchema: jsonb("config_schema"),
|
|
capabilities: jsonb("capabilities").default([]),
|
|
trustLevel: trustLevelEnum("trust_level").default("community").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
export const blockRegistry = createTable(
|
|
"block_registry",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
blockType: varchar("block_type", { length: 100 }).notNull(),
|
|
pluginId: uuid("plugin_id").references(() => robotPlugins.id),
|
|
shape: blockShapeEnum("shape").notNull(),
|
|
category: blockCategoryEnum("category").notNull(),
|
|
displayName: varchar("display_name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
icon: varchar("icon", { length: 100 }),
|
|
color: varchar("color", { length: 50 }),
|
|
config: jsonb("config").notNull(),
|
|
parameterSchema: jsonb("parameter_schema").notNull(),
|
|
executionHandler: varchar("execution_handler", { length: 100 }),
|
|
timeout: integer("timeout"),
|
|
retryPolicy: jsonb("retry_policy"),
|
|
requiresConnection: boolean("requires_connection").default(false),
|
|
previewMode: boolean("preview_mode").default(true),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
blockTypeUnique: unique().on(table.blockType, table.pluginId),
|
|
categoryIdx: index("block_registry_category_idx").on(table.category),
|
|
}),
|
|
);
|
|
|
|
export const experiments = createTable(
|
|
"experiment",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
version: integer("version").default(1).notNull(),
|
|
robotId: uuid("robot_id").references(() => robots.id),
|
|
status: experimentStatusEnum("status").default("draft").notNull(),
|
|
estimatedDuration: integer("estimated_duration"), // in minutes
|
|
createdBy: uuid("created_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
metadata: jsonb("metadata").default({}),
|
|
visualDesign: jsonb("visual_design"),
|
|
executionGraph: jsonb("execution_graph"),
|
|
pluginDependencies: text("plugin_dependencies").array(),
|
|
integrityHash: varchar("integrity_hash", { length: 128 }),
|
|
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
},
|
|
(table) => ({
|
|
studyNameVersionUnique: unique().on(
|
|
table.studyId,
|
|
table.name,
|
|
table.version,
|
|
),
|
|
visualDesignIdx: index("experiment_visual_design_idx").using(
|
|
"gin",
|
|
table.visualDesign,
|
|
),
|
|
}),
|
|
);
|
|
|
|
export const participants = createTable(
|
|
"participant",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
participantCode: varchar("participant_code", { length: 50 }).notNull(),
|
|
email: varchar("email", { length: 255 }),
|
|
name: varchar("name", { length: 255 }),
|
|
demographics: jsonb("demographics").default({}), // encrypted
|
|
consentGiven: boolean("consent_given").default(false).notNull(),
|
|
consentDate: timestamp("consent_date", { withTimezone: true }),
|
|
notes: text("notes"), // encrypted
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
studyParticipantCodeUnique: unique().on(
|
|
table.studyId,
|
|
table.participantCode,
|
|
),
|
|
}),
|
|
);
|
|
|
|
export const participantDocuments = createTable(
|
|
"participant_document",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
type: varchar("type", { length: 100 }), // MIME type or custom category
|
|
storagePath: text("storage_path").notNull(),
|
|
fileSize: integer("file_size"),
|
|
uploadedBy: uuid("uploaded_by").references(() => users.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
participantDocIdx: index("participant_document_participant_idx").on(
|
|
table.participantId,
|
|
),
|
|
}),
|
|
);
|
|
|
|
export const trials = createTable("trial", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
experimentId: uuid("experiment_id")
|
|
.notNull()
|
|
.references(() => experiments.id),
|
|
participantId: uuid("participant_id").references(() => participants.id),
|
|
wizardId: uuid("wizard_id").references(() => users.id),
|
|
sessionNumber: integer("session_number").default(1).notNull(),
|
|
status: trialStatusEnum("status").default("scheduled").notNull(),
|
|
scheduledAt: timestamp("scheduled_at", { withTimezone: true }),
|
|
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
duration: integer("duration"), // actual duration in seconds
|
|
notes: text("notes"),
|
|
parameters: jsonb("parameters").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
metadata: jsonb("metadata").default({}),
|
|
});
|
|
|
|
export const steps = createTable(
|
|
"step",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
experimentId: uuid("experiment_id")
|
|
.notNull()
|
|
.references(() => experiments.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
type: stepTypeEnum("type").notNull(),
|
|
orderIndex: integer("order_index").notNull(),
|
|
durationEstimate: integer("duration_estimate"), // in seconds
|
|
required: boolean("required").default(true).notNull(),
|
|
conditions: jsonb("conditions").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
experimentOrderUnique: unique().on(table.experimentId, table.orderIndex),
|
|
}),
|
|
);
|
|
|
|
export const actions = createTable(
|
|
"action",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
stepId: uuid("step_id")
|
|
.notNull()
|
|
.references(() => steps.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
type: varchar("type", { length: 100 }).notNull(), // e.g., 'speak', 'move', 'wait', 'collect_data' or pluginId.actionId
|
|
orderIndex: integer("order_index").notNull(),
|
|
parameters: jsonb("parameters").default({}),
|
|
validationSchema: jsonb("validation_schema"),
|
|
timeout: integer("timeout"), // in seconds
|
|
retryCount: integer("retry_count").default(0).notNull(),
|
|
// Provenance & execution metadata
|
|
sourceKind: varchar("source_kind", { length: 20 }), // 'core' | 'plugin'
|
|
pluginId: varchar("plugin_id", { length: 255 }),
|
|
pluginVersion: varchar("plugin_version", { length: 50 }),
|
|
robotId: varchar("robot_id", { length: 255 }),
|
|
baseActionId: varchar("base_action_id", { length: 255 }),
|
|
category: varchar("category", { length: 50 }),
|
|
transport: varchar("transport", { length: 20 }), // 'ros2' | 'rest' | 'internal'
|
|
ros2: jsonb("ros2_config"),
|
|
rest: jsonb("rest_config"),
|
|
retryable: boolean("retryable"),
|
|
parameterSchemaRaw: jsonb("parameter_schema_raw"),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
stepOrderUnique: unique().on(table.stepId, table.orderIndex),
|
|
}),
|
|
);
|
|
|
|
// Participants and Data Protection
|
|
export const consentForms = createTable(
|
|
"consent_form",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
version: integer("version").default(1).notNull(),
|
|
title: varchar("title", { length: 255 }).notNull(),
|
|
content: text("content").notNull(),
|
|
active: boolean("active").default(true).notNull(),
|
|
createdBy: uuid("created_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
storagePath: text("storage_path"), // path in MinIO
|
|
},
|
|
(table) => ({
|
|
studyVersionUnique: unique().on(table.studyId, table.version),
|
|
}),
|
|
);
|
|
|
|
export const participantConsents = createTable(
|
|
"participant_consent",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
consentFormId: uuid("consent_form_id")
|
|
.notNull()
|
|
.references(() => consentForms.id),
|
|
signedAt: timestamp("signed_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
signatureData: text("signature_data"), // encrypted
|
|
ipAddress: inet("ip_address"),
|
|
storagePath: text("storage_path"), // path to signed PDF in MinIO
|
|
},
|
|
(table) => ({
|
|
participantFormUnique: unique().on(
|
|
table.participantId,
|
|
table.consentFormId,
|
|
),
|
|
}),
|
|
);
|
|
|
|
// Robot Platform Integration
|
|
export const plugins = createTable(
|
|
"plugin",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
robotId: uuid("robot_id").references(() => robots.id, {
|
|
onDelete: "cascade",
|
|
}),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
version: varchar("version", { length: 50 }).notNull(),
|
|
description: text("description"),
|
|
author: varchar("author", { length: 255 }),
|
|
repositoryUrl: text("repository_url"),
|
|
trustLevel: trustLevelEnum("trust_level"),
|
|
status: pluginStatusEnum("status").default("active").notNull(),
|
|
configurationSchema: jsonb("configuration_schema"),
|
|
actionDefinitions: jsonb("action_definitions").default([]),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
metadata: jsonb("metadata").default({}),
|
|
},
|
|
(table) => ({
|
|
nameVersionUnique: unique().on(table.name, table.version),
|
|
}),
|
|
);
|
|
|
|
export const studyPlugins = createTable(
|
|
"study_plugin",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
pluginId: uuid("plugin_id")
|
|
.notNull()
|
|
.references(() => plugins.id),
|
|
configuration: jsonb("configuration").default({}),
|
|
installedAt: timestamp("installed_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
installedBy: uuid("installed_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
},
|
|
(table) => ({
|
|
studyPluginUnique: unique().on(table.studyId, table.pluginId),
|
|
}),
|
|
);
|
|
|
|
export const pluginRepositories = createTable(
|
|
"plugin_repository",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
url: text("url").notNull(),
|
|
description: text("description"),
|
|
trustLevel: trustLevelEnum("trust_level").default("community").notNull(),
|
|
isEnabled: boolean("is_enabled").default(true).notNull(),
|
|
isOfficial: boolean("is_official").default(false).notNull(),
|
|
lastSyncAt: timestamp("last_sync_at", { withTimezone: true }),
|
|
syncStatus: varchar("sync_status", { length: 50 }).default("pending"),
|
|
syncError: text("sync_error"),
|
|
metadata: jsonb("metadata").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
createdBy: uuid("created_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
},
|
|
(table) => ({
|
|
urlUnique: unique().on(table.url),
|
|
}),
|
|
);
|
|
|
|
// Experiment Execution and Data Capture
|
|
export const trialEvents = createTable(
|
|
"trial_event",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
trialId: uuid("trial_id")
|
|
.notNull()
|
|
.references(() => trials.id, { onDelete: "cascade" }),
|
|
eventType: varchar("event_type", { length: 50 }).notNull(), // 'action_started', 'action_completed', 'error', 'intervention'
|
|
actionId: uuid("action_id").references(() => actions.id),
|
|
timestamp: timestamp("timestamp", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
data: jsonb("data").default({}),
|
|
createdBy: uuid("created_by").references(() => users.id), // NULL for system events
|
|
},
|
|
(table) => ({
|
|
trialTimestampIdx: index("trial_events_trial_timestamp_idx").on(
|
|
table.trialId,
|
|
table.timestamp,
|
|
),
|
|
}),
|
|
);
|
|
|
|
export const wizardInterventions = createTable("wizard_intervention", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
trialId: uuid("trial_id")
|
|
.notNull()
|
|
.references(() => trials.id, { onDelete: "cascade" }),
|
|
wizardId: uuid("wizard_id")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
interventionType: varchar("intervention_type", { length: 100 }).notNull(),
|
|
description: text("description"),
|
|
timestamp: timestamp("timestamp", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
parameters: jsonb("parameters").default({}),
|
|
reason: text("reason"),
|
|
});
|
|
|
|
export const mediaCaptures = createTable("media_capture", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
trialId: uuid("trial_id")
|
|
.notNull()
|
|
.references(() => trials.id, { onDelete: "cascade" }),
|
|
mediaType: mediaTypeEnum("media_type"),
|
|
storagePath: text("storage_path").notNull(), // MinIO path
|
|
fileSize: bigint("file_size", { mode: "number" }),
|
|
duration: integer("duration"), // in seconds for video/audio
|
|
format: varchar("format", { length: 20 }),
|
|
resolution: varchar("resolution", { length: 20 }), // for video
|
|
startTimestamp: timestamp("start_timestamp", { withTimezone: true }),
|
|
endTimestamp: timestamp("end_timestamp", { withTimezone: true }),
|
|
metadata: jsonb("metadata").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
export const sensorData = createTable(
|
|
"sensor_data",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
trialId: uuid("trial_id")
|
|
.notNull()
|
|
.references(() => trials.id, { onDelete: "cascade" }),
|
|
sensorType: varchar("sensor_type", { length: 50 }).notNull(),
|
|
timestamp: timestamp("timestamp", { withTimezone: true }).notNull(),
|
|
data: jsonb("data").notNull(),
|
|
robotState: jsonb("robot_state").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
sensorTrialTimestampIdx: index("sensor_data_trial_timestamp_idx").on(
|
|
table.trialId,
|
|
table.timestamp,
|
|
),
|
|
}),
|
|
);
|
|
|
|
export const annotations = createTable("annotation", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
trialId: uuid("trial_id")
|
|
.notNull()
|
|
.references(() => trials.id, { onDelete: "cascade" }),
|
|
annotatorId: uuid("annotator_id")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
timestampStart: timestamp("timestamp_start", {
|
|
withTimezone: true,
|
|
}).notNull(),
|
|
timestampEnd: timestamp("timestamp_end", { withTimezone: true }),
|
|
category: varchar("category", { length: 100 }),
|
|
label: varchar("label", { length: 100 }),
|
|
description: text("description"),
|
|
tags: jsonb("tags").default([]),
|
|
metadata: jsonb("metadata").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
// Collaboration and Activity Tracking
|
|
export const activityLogs = createTable(
|
|
"activity_log",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id").references(() => studies.id, {
|
|
onDelete: "cascade",
|
|
}),
|
|
userId: uuid("user_id").references(() => users.id),
|
|
action: varchar("action", { length: 100 }).notNull(),
|
|
resourceType: varchar("resource_type", { length: 50 }),
|
|
resourceId: uuid("resource_id"),
|
|
description: text("description"),
|
|
ipAddress: inet("ip_address"),
|
|
userAgent: text("user_agent"),
|
|
metadata: jsonb("metadata").default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
activityStudyCreatedIdx: index("activity_logs_study_created_idx").on(
|
|
table.studyId,
|
|
table.createdAt,
|
|
),
|
|
}),
|
|
);
|
|
|
|
export const comments = createTable("comment", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
parentId: uuid("parent_id"),
|
|
resourceType: varchar("resource_type", { length: 50 }).notNull(), // 'experiment', 'trial', 'annotation'
|
|
resourceId: uuid("resource_id").notNull(),
|
|
authorId: uuid("author_id")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
content: text("content").notNull(),
|
|
metadata: jsonb("metadata"),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
export const attachments = createTable("attachment", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
resourceType: varchar("resource_type", { length: 50 }).notNull(),
|
|
resourceId: uuid("resource_id").notNull(),
|
|
fileName: varchar("file_name", { length: 255 }).notNull(),
|
|
fileSize: bigint("file_size", { mode: "number" }).notNull(),
|
|
filePath: text("file_path").notNull(),
|
|
contentType: varchar("content_type", { length: 100 }),
|
|
description: text("description"),
|
|
uploadedBy: uuid("uploaded_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
// Data Export and Sharing
|
|
export const exportJobs = createTable("export_job", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
requestedBy: uuid("requested_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
exportType: varchar("export_type", { length: 50 }).notNull(), // 'full', 'trials', 'analysis', 'media'
|
|
format: varchar("format", { length: 20 }).notNull(), // 'json', 'csv', 'zip'
|
|
filters: jsonb("filters").default({}),
|
|
status: exportStatusEnum("status").default("pending").notNull(),
|
|
storagePath: text("storage_path"),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
errorMessage: text("error_message"),
|
|
});
|
|
|
|
export const sharedResources = createTable("shared_resource", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
studyId: uuid("study_id")
|
|
.notNull()
|
|
.references(() => studies.id, { onDelete: "cascade" }),
|
|
resourceType: varchar("resource_type", { length: 50 }).notNull(),
|
|
resourceId: uuid("resource_id").notNull(),
|
|
sharedBy: uuid("shared_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
shareToken: varchar("share_token", { length: 255 }).unique(),
|
|
permissions: jsonb("permissions").default(["read"]),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
accessCount: integer("access_count").default(0).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
// System Configuration
|
|
export const systemSettings = createTable("system_setting", {
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
key: varchar("key", { length: 100 }).notNull().unique(),
|
|
value: jsonb("value").notNull(),
|
|
description: text("description"),
|
|
updatedBy: uuid("updated_by").references(() => users.id),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
});
|
|
|
|
export const auditLogs = createTable(
|
|
"audit_log",
|
|
{
|
|
id: uuid("id").notNull().primaryKey().defaultRandom(),
|
|
userId: uuid("user_id").references(() => users.id),
|
|
action: varchar("action", { length: 100 }).notNull(),
|
|
resourceType: varchar("resource_type", { length: 50 }),
|
|
resourceId: uuid("resource_id"),
|
|
changes: jsonb("changes").default({}),
|
|
ipAddress: inet("ip_address"),
|
|
userAgent: text("user_agent"),
|
|
createdAt: timestamp("created_at", { withTimezone: true })
|
|
.default(sql`CURRENT_TIMESTAMP`)
|
|
.notNull(),
|
|
},
|
|
(table) => ({
|
|
auditCreatedIdx: index("audit_logs_created_idx").on(table.createdAt),
|
|
}),
|
|
);
|
|
|
|
// Relations
|
|
export const usersRelations = relations(users, ({ many }) => ({
|
|
accounts: many(accounts),
|
|
sessions: many(sessions),
|
|
systemRoles: many(userSystemRoles, {
|
|
relationName: "user",
|
|
}),
|
|
grantedRoles: many(userSystemRoles, {
|
|
relationName: "grantedByUser",
|
|
}),
|
|
createdStudies: many(studies),
|
|
studyMemberships: many(studyMembers),
|
|
createdExperiments: many(experiments),
|
|
wizardTrials: many(trials),
|
|
activityLogs: many(activityLogs),
|
|
comments: many(comments),
|
|
attachments: many(attachments),
|
|
annotations: many(annotations),
|
|
interventions: many(wizardInterventions),
|
|
}));
|
|
|
|
export const accountsRelations = relations(accounts, ({ one }) => ({
|
|
user: one(users, { fields: [accounts.userId], references: [users.id] }),
|
|
}));
|
|
|
|
export const sessionsRelations = relations(sessions, ({ one }) => ({
|
|
user: one(users, { fields: [sessions.userId], references: [users.id] }),
|
|
}));
|
|
|
|
export const userSystemRolesRelations = relations(
|
|
userSystemRoles,
|
|
({ one }) => ({
|
|
user: one(users, {
|
|
fields: [userSystemRoles.userId],
|
|
references: [users.id],
|
|
relationName: "user",
|
|
}),
|
|
grantedByUser: one(users, {
|
|
fields: [userSystemRoles.grantedBy],
|
|
references: [users.id],
|
|
relationName: "grantedByUser",
|
|
}),
|
|
}),
|
|
);
|
|
|
|
export const rolePermissionsRelations = relations(
|
|
rolePermissions,
|
|
({ one }) => ({
|
|
permission: one(permissions, {
|
|
fields: [rolePermissions.permissionId],
|
|
references: [permissions.id],
|
|
}),
|
|
}),
|
|
);
|
|
|
|
export const studiesRelations = relations(studies, ({ one, many }) => ({
|
|
createdBy: one(users, {
|
|
fields: [studies.createdBy],
|
|
references: [users.id],
|
|
}),
|
|
members: many(studyMembers),
|
|
experiments: many(experiments),
|
|
participants: many(participants),
|
|
consentForms: many(consentForms),
|
|
plugins: many(studyPlugins),
|
|
activityLogs: many(activityLogs),
|
|
comments: many(comments),
|
|
attachments: many(attachments),
|
|
exportJobs: many(exportJobs),
|
|
sharedResources: many(sharedResources),
|
|
}));
|
|
|
|
export const studyMembersRelations = relations(studyMembers, ({ one }) => ({
|
|
study: one(studies, {
|
|
fields: [studyMembers.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
user: one(users, { fields: [studyMembers.userId], references: [users.id] }),
|
|
invitedBy: one(users, {
|
|
fields: [studyMembers.invitedBy],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const experimentsRelations = relations(experiments, ({ one, many }) => ({
|
|
study: one(studies, {
|
|
fields: [experiments.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
robot: one(robots, {
|
|
fields: [experiments.robotId],
|
|
references: [robots.id],
|
|
}),
|
|
createdBy: one(users, {
|
|
fields: [experiments.createdBy],
|
|
references: [users.id],
|
|
}),
|
|
trials: many(trials),
|
|
steps: many(steps),
|
|
}));
|
|
|
|
export const participantsRelations = relations(
|
|
participants,
|
|
({ one, many }) => ({
|
|
study: one(studies, {
|
|
fields: [participants.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
trials: many(trials),
|
|
consents: many(participantConsents),
|
|
}),
|
|
);
|
|
|
|
export const trialsRelations = relations(trials, ({ one, many }) => ({
|
|
experiment: one(experiments, {
|
|
fields: [trials.experimentId],
|
|
references: [experiments.id],
|
|
}),
|
|
participant: one(participants, {
|
|
fields: [trials.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
wizard: one(users, { fields: [trials.wizardId], references: [users.id] }),
|
|
events: many(trialEvents),
|
|
interventions: many(wizardInterventions),
|
|
mediaCaptures: many(mediaCaptures),
|
|
sensorData: many(sensorData),
|
|
annotations: many(annotations),
|
|
}));
|
|
|
|
export const stepsRelations = relations(steps, ({ one, many }) => ({
|
|
experiment: one(experiments, {
|
|
fields: [steps.experimentId],
|
|
references: [experiments.id],
|
|
}),
|
|
actions: many(actions),
|
|
}));
|
|
|
|
export const actionsRelations = relations(actions, ({ one, many }) => ({
|
|
step: one(steps, { fields: [actions.stepId], references: [steps.id] }),
|
|
events: many(trialEvents),
|
|
}));
|
|
|
|
export const consentFormsRelations = relations(
|
|
consentForms,
|
|
({ one, many }) => ({
|
|
study: one(studies, {
|
|
fields: [consentForms.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
createdBy: one(users, {
|
|
fields: [consentForms.createdBy],
|
|
references: [users.id],
|
|
}),
|
|
participantConsents: many(participantConsents),
|
|
}),
|
|
);
|
|
|
|
export const participantConsentsRelations = relations(
|
|
participantConsents,
|
|
({ one }) => ({
|
|
participant: one(participants, {
|
|
fields: [participantConsents.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
consentForm: one(consentForms, {
|
|
fields: [participantConsents.consentFormId],
|
|
references: [consentForms.id],
|
|
}),
|
|
}),
|
|
);
|
|
|
|
export const robotsRelations = relations(robots, ({ many }) => ({
|
|
experiments: many(experiments),
|
|
plugins: many(plugins),
|
|
}));
|
|
|
|
export const pluginsRelations = relations(plugins, ({ one, many }) => ({
|
|
robot: one(robots, { fields: [plugins.robotId], references: [robots.id] }),
|
|
studyPlugins: many(studyPlugins),
|
|
}));
|
|
|
|
export const studyPluginsRelations = relations(studyPlugins, ({ one }) => ({
|
|
study: one(studies, {
|
|
fields: [studyPlugins.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
plugin: one(plugins, {
|
|
fields: [studyPlugins.pluginId],
|
|
references: [plugins.id],
|
|
}),
|
|
installedBy: one(users, {
|
|
fields: [studyPlugins.installedBy],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const trialEventsRelations = relations(trialEvents, ({ one }) => ({
|
|
trial: one(trials, {
|
|
fields: [trialEvents.trialId],
|
|
references: [trials.id],
|
|
}),
|
|
action: one(actions, {
|
|
fields: [trialEvents.actionId],
|
|
references: [actions.id],
|
|
}),
|
|
createdBy: one(users, {
|
|
fields: [trialEvents.createdBy],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const wizardInterventionsRelations = relations(
|
|
wizardInterventions,
|
|
({ one }) => ({
|
|
trial: one(trials, {
|
|
fields: [wizardInterventions.trialId],
|
|
references: [trials.id],
|
|
}),
|
|
wizard: one(users, {
|
|
fields: [wizardInterventions.wizardId],
|
|
references: [users.id],
|
|
}),
|
|
}),
|
|
);
|
|
|
|
export const mediaCapturesRelations = relations(mediaCaptures, ({ one }) => ({
|
|
trial: one(trials, {
|
|
fields: [mediaCaptures.trialId],
|
|
references: [trials.id],
|
|
}),
|
|
}));
|
|
|
|
export const sensorDataRelations = relations(sensorData, ({ one }) => ({
|
|
trial: one(trials, { fields: [sensorData.trialId], references: [trials.id] }),
|
|
}));
|
|
|
|
export const annotationsRelations = relations(annotations, ({ one }) => ({
|
|
trial: one(trials, {
|
|
fields: [annotations.trialId],
|
|
references: [trials.id],
|
|
}),
|
|
annotator: one(users, {
|
|
fields: [annotations.annotatorId],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const activityLogsRelations = relations(activityLogs, ({ one }) => ({
|
|
study: one(studies, {
|
|
fields: [activityLogs.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
user: one(users, { fields: [activityLogs.userId], references: [users.id] }),
|
|
}));
|
|
|
|
export const commentsRelations = relations(comments, ({ one, many }) => ({
|
|
parent: one(comments, {
|
|
fields: [comments.parentId],
|
|
references: [comments.id],
|
|
}),
|
|
author: one(users, { fields: [comments.authorId], references: [users.id] }),
|
|
replies: many(comments),
|
|
}));
|
|
|
|
export const attachmentsRelations = relations(attachments, ({ one }) => ({
|
|
uploadedBy: one(users, {
|
|
fields: [attachments.uploadedBy],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const exportJobsRelations = relations(exportJobs, ({ one }) => ({
|
|
study: one(studies, {
|
|
fields: [exportJobs.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
requestedBy: one(users, {
|
|
fields: [exportJobs.requestedBy],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const sharedResourcesRelations = relations(
|
|
sharedResources,
|
|
({ one }) => ({
|
|
study: one(studies, {
|
|
fields: [sharedResources.studyId],
|
|
references: [studies.id],
|
|
}),
|
|
sharedBy: one(users, {
|
|
fields: [sharedResources.sharedBy],
|
|
references: [users.id],
|
|
}),
|
|
}),
|
|
);
|
|
|
|
export const systemSettingsRelations = relations(systemSettings, ({ one }) => ({
|
|
updatedBy: one(users, {
|
|
fields: [systemSettings.updatedBy],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
export const auditLogsRelations = relations(auditLogs, ({ one }) => ({
|
|
user: one(users, { fields: [auditLogs.userId], references: [users.id] }),
|
|
}));
|