UNPKG

@damourlabs/euro-clinical-trials

Version:

A Nuxt 3 application for visualizing and managing clinical trials data in Europe, built with modern web technologies.

176 lines (155 loc) 6.32 kB
// Drizzle ORM schema for User Management tables import { pgTable, uuid, varchar, timestamp, boolean, check, integer } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; import { z } from 'zod'; import type { Expect, IsSameType } from '~/types/equality'; export const usersFieldDefaultValues = { anyUUID: () => crypto.randomUUID(), institution: 'Unknown Institution', isActive: true, role: 'Patient' as const, emailVerified: false, twoFactorEnabled: false, loginAttempts: 0, } export const users = pgTable('users', { uuid: uuid('uuid').primaryKey().defaultRandom(), email: varchar('email', { length: 255 }).notNull().unique(), name: varchar('name', { length: 100 }).notNull(), phoneNumber: varchar('phone_number', { length: 20 }), institution: varchar('institution', { length: 100 }).notNull().default(usersFieldDefaultValues.institution), role: varchar('role', { length: 50 }).notNull().default(usersFieldDefaultValues.role), isActive: boolean('is_active').notNull().default(usersFieldDefaultValues.isActive), emailVerified: boolean('email_verified').notNull().default(usersFieldDefaultValues.emailVerified), // Passwordless authentication fields emailVerificationToken: varchar('email_verification_token', { length: 255 }), emailVerificationExpires: timestamp('email_verification_expires', { withTimezone: true }), magicLinkToken: varchar('magic_link_token', { length: 255 }), magicLinkExpires: timestamp('magic_link_expires', { withTimezone: true }), // Two-factor authentication twoFactorEnabled: boolean('two_factor_enabled').notNull().default(usersFieldDefaultValues.twoFactorEnabled), twoFactorSecret: varchar('two_factor_secret', { length: 255 }), twoFactorToken: varchar('two_factor_token', { length: 10 }), twoFactorExpires: timestamp('two_factor_expires', { withTimezone: true }), // Security fields loginAttempts: integer('login_attempts').notNull().default(usersFieldDefaultValues.loginAttempts), accountLockedUntil: timestamp('account_locked_until', { withTimezone: true }), lastLoginAt: timestamp('last_login_at', { withTimezone: true }), // OAuth fields githubId: varchar('github_id', { length: 100 }), // GDPR consent tracking gdprConsentGiven: boolean('gdpr_consent_given').notNull().default(false), gdprConsentDate: timestamp('gdpr_consent_date', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ check('users_email_format', sql`${table.email} ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'`), check('users_name_not_empty', sql`LENGTH(TRIM(${table.name})) > 0`), check('users_login_attempts_non_negative', sql`${table.loginAttempts} >= 0`), ]); export const usersSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the user, automatically generated if not provided") .default(usersFieldDefaultValues.anyUUID), email: z.string() .email() .describe("Email address of the user, must be unique") .max(255), name: z.string() .describe("Full name of the user") .max(100), phoneNumber: z.string() .max(20) .nullable() .describe("Phone number of the user, optional"), institution: z.string() .describe("Institution the user is affiliated with") .max(100) .default(usersFieldDefaultValues.institution), role: z.string() .describe("Role of the user in the clinical trial") .max(50) .default(usersFieldDefaultValues.role), isActive: z.boolean() .describe("Indicates if the user account is active") .default(usersFieldDefaultValues.isActive), emailVerified: z.boolean() .describe("Indicates if the user's email has been verified") .default(usersFieldDefaultValues.emailVerified), // Passwordless authentication fields emailVerificationToken: z.string() .max(255) .nullable() .describe("Token used for email verification"), emailVerificationExpires: z.date() .nullable() .describe("Expiration date for email verification token"), magicLinkToken: z.string() .max(255) .nullable() .describe("Token used for magic link authentication"), magicLinkExpires: z.date() .nullable() .describe("Expiration date for magic link token"), // Two-factor authentication twoFactorEnabled: z.boolean() .describe("Indicates if two-factor authentication is enabled") .default(usersFieldDefaultValues.twoFactorEnabled), twoFactorSecret: z.string() .max(255) .nullable() .describe("Secret key for two-factor authentication"), twoFactorToken: z.string() .max(10) .nullable() .describe("Current two-factor authentication token"), twoFactorExpires: z.date() .nullable() .describe("Expiration date for two-factor token"), // Security fields loginAttempts: z.number() .int() .min(0) .describe("Number of failed login attempts") .default(usersFieldDefaultValues.loginAttempts), accountLockedUntil: z.date() .nullable() .describe("Date until which the account is locked"), lastLoginAt: z.date() .nullable() .describe("Timestamp of last login"), // OAuth fields githubId: z.string() .max(100) .nullable() .describe("GitHub user ID for OAuth authentication"), // GDPR consent tracking gdprConsentGiven: z.boolean() .describe("Indicates if GDPR consent has been given") .default(false), gdprConsentDate: z.date() .nullable() .describe("Date when GDPR consent was given"), createdAt: z.date() .describe("Timestamp of when the user was created, automatically set to now") .default(() => new Date()), updatedAt: z.date() .describe("Timestamp of when the user was last updated, automatically set to now") .default(() => new Date()), }); export type User = z.infer<typeof usersSchema>; // Validate to check if the schema respects the table structure // If this fails, it means the schema and table structure are not in sync // i.e: // - the schema has fields that are not present in the table or vice versa // - the types of the fields do not match type _userSchemaMatchesTable = Expect<IsSameType<User, typeof users.$inferSelect>>