Skip to main content

Overview

XyraPanel uses Drizzle ORM with PostgreSQL for type-safe database operations. All schemas are defined in server/database/schema.ts and provide full type inference throughout the application.

Authentication Tables

Users

Stores user accounts with authentication and profile information:
export const users = pgTable(
  'users',
  {
    id: text('id').primaryKey(),
    username: text('username').notNull(),
    displayUsername: text('display_username'),
    email: text('email').notNull(),
    password: text('password').notNull(),
    nameFirst: text('name_first'),
    nameLast: text('name_last'),
    language: text('language').notNull().default('en'),
    rootAdmin: boolean('root_admin').notNull().default(false),
    role: text('role').notNull().default('user'),
    emailVerified: timestamp('email_verified', { mode: 'string' }),
    image: text('image'),
    suspended: boolean('suspended').notNull().default(false),
    suspendedAt: timestamp('suspended_at', { mode: 'string' }),
    suspensionReason: text('suspension_reason'),
    passwordResetRequired: boolean('password_reset_required').notNull().default(false),
    banned: boolean('banned'),
    banReason: text('ban_reason'),
    banExpires: timestamp('ban_expires', { mode: 'string' }),
    
    // Two-Factor Authentication
    useTotp: boolean('use_totp').notNull().default(false),
    totpSecret: text('totp_secret'),
    totpAuthenticatedAt: timestamp('totp_authenticated_at', { mode: 'string' }),
    twoFactorEnabled: boolean('two_factor_enabled'),
    rememberToken: text('remember_token'),
    
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
  },
  (table) => [
    uniqueIndex('users_username_unique').on(table.username),
    uniqueIndex('users_email_unique').on(table.email),
    index('users_role_index').on(table.role),
  ],
);
Role System: Users have both a role field (user/admin) and a rootAdmin boolean. Root admins have unrestricted access to all panel features.

Sessions

Manages active user sessions with metadata:
export const sessions = pgTable(
  'sessions',
  {
    id: text('id').primaryKey(),
    sessionToken: text('session_token').notNull().unique(),
    userId: text('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    expires: timestamp('expires', { mode: 'string' }).notNull(),
    expiresAt: timestamp('expires_at', { mode: 'string' }),
    ipAddress: text('ip_address'),
    userAgent: text('user_agent'),
    createdAt: timestamp('created_at', { mode: 'string' }),
    updatedAt: timestamp('updated_at', { mode: 'string' }),
    impersonatedBy: text('impersonated_by').references(() => users.id, { onDelete: 'set null' }),
  },
  (table) => [
    index('sessions_user_id_index').on(table.userId),
    index('sessions_expires_index').on(table.expires),
    index('sessions_token_index').on(table.sessionToken),
  ],
);

Session Metadata

Tracks detailed session information for security monitoring:
export const sessionMetadata = pgTable(
  'session_metadata',
  {
    sessionToken: text('session_token')
      .primaryKey()
      .references(() => sessions.sessionToken, { onDelete: 'cascade' }),
    firstSeenAt: timestamp('first_seen_at', { mode: 'string' }),
    lastSeenAt: timestamp('last_seen_at', { mode: 'string' }),
    ipAddress: text('ip_address'),
    userAgent: text('user_agent'),
    deviceName: text('device_name'),
    browserName: text('browser_name'),
    osName: text('os_name'),
  },
  (table) => [index('session_metadata_last_seen_index').on(table.lastSeenAt)],
);

Accounts

Supports OAuth providers and account linking:
export const accounts = pgTable(
  'accounts',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    type: text('type').notNull(),
    provider: text('provider').notNull(),
    providerId: text('provider_id'),
    providerAccountId: text('provider_account_id').notNull(),
    accountId: text('account_id'),
    password: text('password'),
    refreshToken: text('refresh_token'),
    accessToken: text('access_token'),
    refreshTokenExpiresIn: integer('refresh_token_expires_in'),
    expiresAt: timestamp('expires_at', { mode: 'string' }),
    accessTokenExpiresAt: timestamp('access_token_expires_at', { mode: 'string' }),
    refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { mode: 'string' }),
    tokenType: text('token_type'),
    scope: text('scope'),
    idToken: text('id_token'),
    sessionState: text('session_state'),
    oauthTokenSecret: text('oauth_token_secret'),
    oauthToken: text('oauth_token'),
    createdAt: timestamp('created_at', { mode: 'string' }),
    updatedAt: timestamp('updated_at', { mode: 'string' }),
  },
  (table) => [
    uniqueIndex('accounts_provider_provider_account_id_index').on(
      table.provider,
      table.providerAccountId,
    ),
    index('accounts_user_id_index').on(table.userId),
  ],
);

API Keys

Provides programmatic API access with rate limiting:
export const apiKeys = pgTable(
  'apikey',
  {
    id: text('id').primaryKey(),
    identifier: text('identifier'),
    memo: text('memo'),
    name: text('name'),
    start: text('start'),
    prefix: text('prefix'),
    key: text('key').notNull(),
    userId: text('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    refillInterval: integer('refill_interval'),
    refillAmount: integer('refill_amount'),
    lastRefillAt: timestamp('last_refill_at', { mode: 'string' }),
    lastUsedAt: timestamp('last_used_at', { mode: 'string' }),
    enabled: boolean('enabled').notNull().default(true),
    rateLimitEnabled: boolean('rate_limit_enabled').notNull().default(true),
    rateLimitTimeWindow: integer('rate_limit_time_window'),
    rateLimitMax: integer('rate_limit_max'),
    requestCount: integer('request_count').notNull().default(0),
    remaining: integer('remaining'),
    lastRequest: timestamp('last_request', { mode: 'string' }),
    expiresAt: timestamp('expires_at', { mode: 'string' }),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
    permissions: text('permissions'),
    metadata: text('metadata'),
  },
  (table) => [index('api_key_user_id_index').on(table.userId)],
);

Two-Factor Authentication

Stores TOTP secrets and backup codes:
export const twoFactor = pgTable(
  'two_factor',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    secret: text('secret'),
    backupCodes: text('backup_codes'),
  },
  (table) => [
    index('two_factor_secret_idx').on(table.secret),
    index('two_factor_user_id_idx').on(table.userId),
  ],
);

Server Management Tables

Wings Nodes

Defines Wings daemon nodes that host game servers:
export const wingsNodes = pgTable(
  'wings_nodes',
  {
    id: text('id').primaryKey(),
    uuid: text('uuid').notNull(),
    name: text('name').notNull(),
    description: text('description'),
    baseUrl: text('base_url').notNull(),
    fqdn: text('fqdn').notNull(),
    scheme: text('scheme', { enum: ['http', 'https'] }).notNull(),
    public: boolean('public').notNull().default(true),
    maintenanceMode: boolean('maintenance_mode').notNull().default(false),
    allowInsecure: boolean('allow_insecure').notNull().default(false),
    behindProxy: boolean('behind_proxy').notNull().default(false),
    memory: integer('memory').notNull(),
    memoryOverallocate: integer('memory_overallocate').notNull().default(0),
    disk: integer('disk').notNull(),
    diskOverallocate: integer('disk_overallocate').notNull().default(0),
    uploadSize: integer('upload_size').notNull().default(100),
    daemonBase: text('daemon_base').notNull(),
    daemonListen: integer('daemon_listen').notNull().default(8080),
    daemonSftp: integer('daemon_sftp').notNull().default(2022),
    tokenIdentifier: text('token_identifier').notNull(),
    tokenSecret: text('token_secret').notNull(),
    apiToken: text('api_token').notNull(),
    locationId: text('location_id').references(() => locations.id),
    lastSeenAt: timestamp('last_seen_at', { mode: 'string' }),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
  },
  (table) => [
    uniqueIndex('wings_nodes_base_url_unique').on(table.baseUrl),
    uniqueIndex('wings_nodes_uuid_unique').on(table.uuid),
  ],
);

Servers

Core server instances:
export const servers = pgTable(
  'servers',
  {
    id: text('id').primaryKey(),
    uuid: text('uuid').notNull(),
    identifier: text('identifier').notNull(),
    externalId: text('external_id'),
    name: text('name').notNull(),
    description: text('description'),
    status: text('status'),
    suspended: boolean('suspended').notNull().default(false),
    skipScripts: boolean('skip_scripts').notNull().default(false),
    ownerId: text('owner_id').references(() => users.id),
    nodeId: text('node_id').references(() => wingsNodes.id),
    allocationId: text('allocation_id').references(() => serverAllocations.id),
    nestId: text('nest_id').references(() => nests.id),
    eggId: text('egg_id').references(() => eggs.id),
    startup: text('startup'),
    image: text('image'),
    dockerImage: text('docker_image'),
    allocationLimit: integer('allocation_limit'),
    databaseLimit: integer('database_limit'),
    backupLimit: integer('backup_limit').notNull().default(0),
    oomDisabled: boolean('oom_disabled').notNull().default(true),
    installedAt: timestamp('installed_at', { mode: 'string' }),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
  },
  (table) => [
    uniqueIndex('servers_uuid_unique').on(table.uuid),
    uniqueIndex('servers_identifier_unique').on(table.identifier),
    uniqueIndex('servers_external_id_unique').on(table.externalId),
    index('servers_owner_id_index').on(table.ownerId),
    index('servers_node_id_index').on(table.nodeId),
    index('servers_status_index').on(table.status),
  ],
);

Server Limits

Resource constraints for each server:
export const serverLimits = pgTable('server_limits', {
  serverId: text('server_id')
    .primaryKey()
    .notNull()
    .references(() => servers.id, { onDelete: 'cascade' }),
  memory: integer('memory'),
  memoryOverallocate: integer('memory_overallocate'),
  disk: integer('disk'),
  diskOverallocate: integer('disk_overallocate'),
  swap: integer('swap'),
  io: integer('io'),
  cpu: integer('cpu'),
  threads: text('threads'),
  oomDisabled: boolean('oom_disabled').notNull().default(true),
  databaseLimit: integer('database_limit'),
  allocationLimit: integer('allocation_limit'),
  backupLimit: integer('backup_limit'),
  createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
  updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
});

Server Allocations

Network allocations (IP:Port) for servers:
export const serverAllocations = pgTable(
  'server_allocations',
  {
    id: text('id').primaryKey(),
    nodeId: text('node_id')
      .notNull()
      .references(() => wingsNodes.id, { onDelete: 'cascade' }),
    serverId: text('server_id'),
    ip: text('ip').notNull(),
    port: integer('port').notNull(),
    isPrimary: boolean('is_primary').notNull().default(false),
    ipAlias: text('ip_alias'),
    notes: text('notes'),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
  },
  (table) => [uniqueIndex('server_allocations_unique').on(table.nodeId, table.ip, table.port)],
);

Nest & Egg System

Nests

Game categories (e.g., Minecraft, Source Engine):
export const nests = pgTable('nests', {
  id: text('id').primaryKey(),
  uuid: text('uuid').notNull().unique(),
  author: text('author').notNull(),
  name: text('name').notNull(),
  description: text('description'),
  createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
  updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
});

Eggs

Server configurations within nests:
export const eggs = pgTable('eggs', {
  id: text('id').primaryKey(),
  uuid: text('uuid').notNull().unique(),
  nestId: text('nest_id')
    .notNull()
    .references(() => nests.id, { onDelete: 'cascade' }),
  author: text('author').notNull(),
  name: text('name').notNull(),
  description: text('description'),
  features: text('features'),
  fileDenylist: text('file_denylist'),
  updateUrl: text('update_url'),
  dockerImage: text('docker_image').notNull(),
  dockerImages: text('docker_images'),
  startup: text('startup').notNull(),
  configFiles: text('config_files'),
  configStartup: text('config_startup'),
  configStop: text('config_stop'),
  configLogs: text('config_logs'),
  scriptContainer: text('script_container'),
  scriptEntry: text('script_entry'),
  scriptInstall: text('script_install'),
  copyScriptFrom: text('copy_script_from'),
  createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
  updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
});

Egg Variables

Environment variables for eggs:
export const eggVariables = pgTable('egg_variables', {
  id: text('id').primaryKey(),
  eggId: text('egg_id')
    .notNull()
    .references(() => eggs.id, { onDelete: 'cascade' }),
  name: text('name').notNull(),
  description: text('description'),
  envVariable: text('env_variable').notNull(),
  defaultValue: text('default_value'),
  userViewable: boolean('user_viewable').notNull().default(true),
  userEditable: boolean('user_editable').notNull().default(true),
  rules: text('rules'),
  createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
  updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
});

Additional Features

Audit Events

Tracks all administrative actions:
export const auditEvents = pgTable(
  'audit_events',
  {
    id: text('id').primaryKey(),
    occurredAt: timestamp('occurred_at', { mode: 'string' }).notNull(),
    actor: text('actor').notNull(),
    actorType: text('actor_type').notNull(),
    action: text('action').notNull(),
    targetType: text('target_type').notNull(),
    targetId: text('target_id'),
    metadata: text('metadata'),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
  },
  (table) => [
    uniqueIndex('audit_events_occurred_id').on(table.occurredAt, table.id),
    index('audit_events_actor_index').on(table.actor),
    index('audit_events_action_index').on(table.action),
    index('audit_events_occurred_at_index').on(table.occurredAt),
  ],
);

Server Backups

export const serverBackups = pgTable(
  'server_backups',
  {
    id: text('id').primaryKey(),
    serverId: text('server_id')
      .notNull()
      .references(() => servers.id, { onDelete: 'cascade' }),
    uuid: text('uuid').notNull(),
    name: text('name').notNull(),
    ignoredFiles: text('ignored_files'),
    disk: text('disk', { enum: ['wings', 's3'] })
      .notNull()
      .default('wings'),
    checksum: text('checksum'),
    bytes: integer('bytes').notNull().default(0),
    isSuccessful: boolean('is_successful').notNull().default(false),
    isLocked: boolean('is_locked').notNull().default(false),
    completedAt: timestamp('completed_at', { mode: 'string' }),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
  },
  (table) => [
    uniqueIndex('server_backups_uuid_unique').on(table.uuid),
    index('server_backups_server_id_index').on(table.serverId),
  ],
);

Server Schedules

export const serverSchedules = pgTable(
  'server_schedules',
  {
    id: text('id').primaryKey(),
    serverId: text('server_id')
      .notNull()
      .references(() => servers.id, { onDelete: 'cascade' }),
    name: text('name').notNull(),
    cron: text('cron').notNull(),
    action: text('action').notNull(),
    nextRunAt: timestamp('next_run_at', { mode: 'string' }),
    lastRunAt: timestamp('last_run_at', { mode: 'string' }),
    enabled: boolean('enabled').notNull().default(true),
    createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
    updatedAt: timestamp('updated_at', { mode: 'string' }).notNull(),
  },
  (table) => [
    index('server_schedules_enabled_next_run_index').on(table.enabled, table.nextRunAt),
    index('server_schedules_server_id_index').on(table.serverId),
  ],
);

Type Inference

Drizzle provides automatic type inference for all database operations:
// Type inference examples
export type UserRow = typeof users.$inferSelect;
export type ServerRow = typeof servers.$inferSelect;
export type WingsNodeRow = typeof wingsNodes.$inferSelect;

// Usage in queries
const db = useDrizzle();

const user: UserRow = await db
  .select()
  .from(tables.users)
  .where(eq(tables.users.id, userId))
  .limit(1)
  .then(results => results[0]);

const servers: ServerRow[] = await db
  .select()
  .from(tables.servers)
  .where(eq(tables.servers.ownerId, userId));
All timestamps use { mode: 'string' } to ensure consistent handling across the application. Dates are stored as ISO 8601 strings.

Relationships

Cascade Deletes

Many tables use onDelete: 'cascade' to automatically clean up related data:
  • Deleting a user cascades to: sessions, accounts, API keys, SSH keys, owned servers
  • Deleting a server cascades to: allocations, backups, schedules, databases, subusers
  • Deleting a node cascades to: allocations, mounted servers

Foreign Keys

Key relationships in the schema:
// Server ownership
servers.ownerId -> users.id

// Server location
servers.nodeId -> wingsNodes.id
servers.allocationId -> serverAllocations.id

// Server configuration
servers.nestId -> nests.id
servers.eggId -> eggs.id

// Node location
wingsNodes.locationId -> locations.id

// Session tracking
sessions.userId -> users.id
sessions.impersonatedBy -> users.id

Next Steps

Wings Integration

Learn about Wings integration

Authentication

Explore the authentication system

Build docs developers (and LLMs) love