-- FC database schema.
-- Full schema definition for the FC CI system.
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- projects: stores repository configurations
CREATE TABLE projects (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  name VARCHAR(255) NOT NULL UNIQUE,
  description TEXT,
  repository_url TEXT NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

-- users: accounts for authentication and personalization
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  username VARCHAR(255) NOT NULL UNIQUE,
  email VARCHAR(255) NOT NULL UNIQUE,
  full_name VARCHAR(255),
  password_hash VARCHAR(255),
  user_type VARCHAR(50) NOT NULL DEFAULT 'local',
  role VARCHAR(50) NOT NULL DEFAULT 'read-only',
  enabled BOOLEAN NOT NULL DEFAULT true,
  email_verified BOOLEAN NOT NULL DEFAULT false,
  public_dashboard BOOLEAN NOT NULL DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  last_login_at TIMESTAMP WITH TIME ZONE
);

-- remote_builders: multi-machine / multi-arch build agents
CREATE TABLE remote_builders (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  name VARCHAR(255) NOT NULL UNIQUE,
  ssh_uri TEXT NOT NULL,
  systems TEXT[] NOT NULL DEFAULT '{}',
  max_jobs INTEGER NOT NULL DEFAULT 1,
  speed_factor INTEGER NOT NULL DEFAULT 1,
  supported_features TEXT[] NOT NULL DEFAULT '{}',
  mandatory_features TEXT[] NOT NULL DEFAULT '{}',
  enabled BOOLEAN NOT NULL DEFAULT true,
  public_host_key TEXT,
  ssh_key_file TEXT,
  consecutive_failures INTEGER NOT NULL DEFAULT 0,
  disabled_until TIMESTAMP WITH TIME ZONE,
  last_failure TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

-- jobsets: build configurations for each project
CREATE TABLE jobsets (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
  name VARCHAR(255) NOT NULL,
  nix_expression TEXT NOT NULL,
  enabled BOOLEAN NOT NULL DEFAULT true,
  flake_mode BOOLEAN NOT NULL DEFAULT true,
  check_interval INTEGER NOT NULL DEFAULT 60,
  branch VARCHAR(255),
  scheduling_shares INTEGER NOT NULL DEFAULT 100,
  state VARCHAR(50) NOT NULL DEFAULT 'enabled' CHECK (
    state IN (
      'disabled',
      'enabled',
      'one_shot',
      'one_at_a_time'
    )
  ),
  last_checked_at TIMESTAMP WITH TIME ZONE,
  keep_nr INTEGER NOT NULL DEFAULT 3,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (project_id, name)
);

-- api_keys: authentication tokens with role-based access control
CREATE TABLE api_keys (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  name VARCHAR(255) NOT NULL,
  key_hash VARCHAR(128) NOT NULL UNIQUE,
  role VARCHAR(50) NOT NULL DEFAULT 'read-only' CHECK (
    role IN (
      'admin',
      'create-projects',
      'restart-jobs',
      'cancel-build',
      'bump-to-front',
      'eval-jobset',
      'read-only'
    )
  ),
  user_id UUID REFERENCES users (id) ON DELETE SET NULL,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  last_used_at TIMESTAMP WITH TIME ZONE
);

-- evaluations: Nix evaluation results for each jobset commit
CREATE TABLE evaluations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  jobset_id UUID NOT NULL REFERENCES jobsets (id) ON DELETE CASCADE,
  commit_hash VARCHAR(40) NOT NULL,
  evaluation_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  status TEXT NOT NULL CHECK (
    status IN ('pending', 'running', 'completed', 'failed')
  ),
  error_message TEXT,
  inputs_hash VARCHAR(128),
  pr_number INTEGER,
  pr_head_branch TEXT,
  pr_base_branch TEXT,
  pr_action TEXT,
  UNIQUE (jobset_id, commit_hash)
);

-- builds: individual build jobs
CREATE TABLE builds (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  evaluation_id UUID NOT NULL REFERENCES evaluations (id) ON DELETE CASCADE,
  job_name VARCHAR(255) NOT NULL,
  drv_path TEXT NOT NULL,
  status TEXT NOT NULL CHECK (
    status IN (
      'pending',
      'running',
      'succeeded',
      'failed',
      'dependency_failed',
      'aborted',
      'cancelled',
      'failed_with_output',
      'timeout',
      'cached_failure',
      'unsupported_system',
      'log_limit_exceeded',
      'nar_size_limit_exceeded',
      'non_deterministic'
    )
  ),
  started_at TIMESTAMP WITH TIME ZONE,
  completed_at TIMESTAMP WITH TIME ZONE,
  log_path TEXT,
  build_output_path TEXT,
  error_message TEXT,
  priority INTEGER NOT NULL DEFAULT 0,
  retry_count INTEGER NOT NULL DEFAULT 0,
  max_retries INTEGER NOT NULL DEFAULT 3,
  notification_pending_since TIMESTAMP WITH TIME ZONE,
  log_url TEXT,
  outputs JSONB,
  is_aggregate BOOLEAN NOT NULL DEFAULT false,
  constituents JSONB,
  builder_id UUID REFERENCES remote_builders (id),
  signed BOOLEAN NOT NULL DEFAULT false,
  system VARCHAR(50),
  keep BOOLEAN NOT NULL DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (evaluation_id, job_name)
);

-- build_products: output artifacts and metadata
CREATE TABLE build_products (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  build_id UUID NOT NULL REFERENCES builds (id) ON DELETE CASCADE,
  name VARCHAR(255) NOT NULL,
  path TEXT NOT NULL,
  sha256_hash VARCHAR(64),
  file_size BIGINT,
  content_type VARCHAR(100),
  is_directory BOOLEAN NOT NULL DEFAULT false,
  gc_root_path TEXT,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

-- build_steps: detailed build execution logs and timing
CREATE TABLE build_steps (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  build_id UUID NOT NULL REFERENCES builds (id) ON DELETE CASCADE,
  step_number INTEGER NOT NULL,
  command TEXT NOT NULL,
  output TEXT,
  error_output TEXT,
  started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  completed_at TIMESTAMP WITH TIME ZONE,
  exit_code INTEGER,
  UNIQUE (build_id, step_number)
);

-- build_dependencies: tracks inter-build dependency relationships
CREATE TABLE build_dependencies (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  build_id UUID NOT NULL REFERENCES builds (id) ON DELETE CASCADE,
  dependency_build_id UUID NOT NULL REFERENCES builds (id) ON DELETE CASCADE,
  UNIQUE (build_id, dependency_build_id)
);

-- webhook_configs: incoming push event configuration per project
CREATE TABLE webhook_configs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
  forge_type VARCHAR(50) NOT NULL CHECK (
    forge_type IN ('github', 'gitea', 'forgejo', 'gitlab')
  ),
  secret_hash VARCHAR(128),
  enabled BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (project_id, forge_type)
);

-- notification_configs: outgoing notification configuration per project
CREATE TABLE notification_configs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
  notification_type VARCHAR(50) NOT NULL CHECK (
    notification_type IN (
      'github_status',
      'gitea_status',
      'forgejo_status',
      'gitlab_status',
      'webhook',
      'email'
    )
  ),
  config JSONB NOT NULL DEFAULT '{}',
  enabled BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (project_id, notification_type)
);

-- jobset_inputs: parameterized inputs for jobsets
CREATE TABLE jobset_inputs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  jobset_id UUID NOT NULL REFERENCES jobsets (id) ON DELETE CASCADE,
  name VARCHAR(255) NOT NULL,
  input_type VARCHAR(50) NOT NULL CHECK (
    input_type IN ('git', 'string', 'boolean', 'path', 'build')
  ),
  value TEXT NOT NULL,
  revision TEXT,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (jobset_id, name)
);

-- channels: release management, tracks the latest good evaluation per jobset
CREATE TABLE channels (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
  name VARCHAR(255) NOT NULL,
  jobset_id UUID NOT NULL REFERENCES jobsets (id) ON DELETE CASCADE,
  current_evaluation_id UUID REFERENCES evaluations (id),
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (project_id, name)
);

-- starred_jobs: personalized dashboard bookmarks per user
CREATE TABLE starred_jobs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE,
  project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
  jobset_id UUID REFERENCES jobsets (id) ON DELETE CASCADE,
  job_name VARCHAR(255) NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (user_id, project_id, jobset_id, job_name)
);

-- user_sessions: persistent authentication tokens
CREATE TABLE user_sessions (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE,
  session_token_hash VARCHAR(255) NOT NULL,
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  last_used_at TIMESTAMP WITH TIME ZONE
);

-- project_members: per-project permission assignments
CREATE TABLE project_members (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
  user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE,
  role VARCHAR(50) NOT NULL DEFAULT 'member',
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (project_id, user_id)
);

-- build_metrics: timing, size, and performance metrics per build
CREATE TABLE build_metrics (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  build_id UUID NOT NULL REFERENCES builds (id) ON DELETE CASCADE,
  metric_name VARCHAR(100) NOT NULL,
  metric_value DOUBLE PRECISION NOT NULL,
  unit VARCHAR(50) NOT NULL,
  collected_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  UNIQUE (build_id, metric_name)
);

-- failed_paths_cache: prevents rebuilding known-failing derivations
CREATE TABLE failed_paths_cache (
  drv_path TEXT PRIMARY KEY,
  source_build_id UUID,
  failure_status TEXT,
  failed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

-- Indexes: projects
CREATE INDEX idx_projects_name ON projects (name);

CREATE INDEX idx_projects_created_at ON projects (created_at);

-- Indexes: users
CREATE INDEX idx_users_username ON users (username);

CREATE INDEX idx_users_email ON users (email);

CREATE INDEX idx_users_role ON users (role);

CREATE INDEX idx_users_enabled ON users (enabled);

-- Indexes: remote_builders
CREATE INDEX idx_remote_builders_enabled ON remote_builders (enabled)
WHERE
  enabled = true;

-- Indexes: jobsets
CREATE INDEX idx_jobsets_project_id ON jobsets (project_id);

CREATE INDEX idx_jobsets_enabled ON jobsets (enabled);

CREATE INDEX idx_jobsets_name ON jobsets (name);

CREATE INDEX idx_jobsets_state ON jobsets (state);

CREATE INDEX idx_jobsets_last_checked_at ON jobsets (last_checked_at);

-- Indexes: api_keys
CREATE INDEX idx_api_keys_key_hash ON api_keys (key_hash);

CREATE INDEX idx_api_keys_user_id ON api_keys (user_id);

-- Indexes: evaluations
CREATE INDEX idx_evaluations_jobset_id ON evaluations (jobset_id);

CREATE INDEX idx_evaluations_commit_hash ON evaluations (commit_hash);

CREATE INDEX idx_evaluations_status ON evaluations (status);

CREATE INDEX idx_evaluations_evaluation_time ON evaluations (evaluation_time);

CREATE INDEX idx_evaluations_inputs_hash ON evaluations (jobset_id, inputs_hash);

CREATE INDEX idx_evaluations_pr ON evaluations (jobset_id, pr_number)
WHERE
  pr_number IS NOT NULL;

-- Indexes: builds
CREATE INDEX idx_builds_evaluation_id ON builds (evaluation_id);

CREATE INDEX idx_builds_status ON builds (status);

CREATE INDEX idx_builds_job_name ON builds (job_name);

CREATE INDEX idx_builds_started_at ON builds (started_at);

CREATE INDEX idx_builds_completed_at ON builds (completed_at);

CREATE INDEX idx_builds_priority ON builds (priority DESC, created_at ASC);

CREATE INDEX idx_builds_notification_pending ON builds (notification_pending_since)
WHERE
  notification_pending_since IS NOT NULL;

CREATE INDEX idx_builds_drv_path ON builds (drv_path);

CREATE INDEX idx_builds_builder ON builds (builder_id)
WHERE
  builder_id IS NOT NULL;

CREATE INDEX idx_builds_system ON builds (system)
WHERE
  system IS NOT NULL;

CREATE INDEX idx_builds_pending_priority ON builds (status, priority DESC, created_at ASC)
WHERE
  status = 'pending';

CREATE INDEX idx_builds_drv_completed ON builds (drv_path)
WHERE
  status = 'succeeded';

-- Indexes: build_products
CREATE INDEX idx_build_products_build_id ON build_products (build_id);

CREATE INDEX idx_build_products_name ON build_products (name);

CREATE INDEX idx_build_products_path_prefix ON build_products (path text_pattern_ops);

-- Indexes: build_steps
CREATE INDEX idx_build_steps_build_id ON build_steps (build_id);

CREATE INDEX idx_build_steps_started_at ON build_steps (started_at);

-- Indexes: build_dependencies
CREATE INDEX idx_build_deps_build ON build_dependencies (build_id);

CREATE INDEX idx_build_deps_dep ON build_dependencies (dependency_build_id);

-- Indexes: webhook/notification/jobset_inputs/channels
CREATE INDEX idx_webhook_configs_project ON webhook_configs (project_id);

CREATE INDEX idx_notification_configs_project ON notification_configs (project_id);

CREATE INDEX idx_jobset_inputs_jobset ON jobset_inputs (jobset_id);

CREATE INDEX idx_channels_project ON channels (project_id);

CREATE INDEX idx_channels_jobset ON channels (jobset_id);

-- Indexes: users/sessions/members
CREATE INDEX idx_starred_jobs_user_id ON starred_jobs (user_id);

CREATE INDEX idx_starred_jobs_project_id ON starred_jobs (project_id);

CREATE INDEX idx_user_sessions_token ON user_sessions (session_token_hash);

CREATE INDEX idx_user_sessions_user_id ON user_sessions (user_id);

CREATE INDEX idx_user_sessions_expires ON user_sessions (expires_at);

CREATE INDEX idx_project_members_project_id ON project_members (project_id);

CREATE INDEX idx_project_members_user_id ON project_members (user_id);

-- Indexes: build_metrics / failed_paths_cache
CREATE INDEX idx_build_metrics_build_id ON build_metrics (build_id);

CREATE INDEX idx_build_metrics_collected_at ON build_metrics (collected_at);

CREATE INDEX idx_build_metrics_name ON build_metrics (metric_name);

CREATE INDEX idx_failed_paths_cache_failed_at ON failed_paths_cache (failed_at);

-- Trigger function: auto-update updated_at on mutation
CREATE OR REPLACE FUNCTION update_updated_at_column () RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_projects_updated_at BEFORE
UPDATE ON projects FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column ();

CREATE TRIGGER update_jobsets_updated_at BEFORE
UPDATE ON jobsets FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column ();

CREATE TRIGGER update_users_updated_at BEFORE
UPDATE ON users FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column ();

-- Trigger functions: LISTEN/NOTIFY for event-driven daemon wakeup
CREATE OR REPLACE FUNCTION notify_builds_changed () RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify('fc_builds_changed', json_build_object(
        'op', TG_OP,
        'table', TG_TABLE_NAME
    )::text);
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION notify_jobsets_changed () RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify('fc_jobsets_changed', json_build_object(
        'op', TG_OP,
        'table', TG_TABLE_NAME
    )::text);
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_builds_insert_notify
AFTER INSERT ON builds FOR EACH ROW
EXECUTE FUNCTION notify_builds_changed ();

CREATE TRIGGER trg_builds_status_notify
AFTER
UPDATE ON builds FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status)
EXECUTE FUNCTION notify_builds_changed ();

CREATE TRIGGER trg_jobsets_insert_notify
AFTER INSERT ON jobsets FOR EACH ROW
EXECUTE FUNCTION notify_jobsets_changed ();

CREATE TRIGGER trg_jobsets_update_notify
AFTER
UPDATE ON jobsets FOR EACH ROW WHEN (
  OLD.enabled IS DISTINCT FROM NEW.enabled
  OR OLD.state IS DISTINCT FROM NEW.state
  OR OLD.nix_expression IS DISTINCT FROM NEW.nix_expression
  OR OLD.check_interval IS DISTINCT FROM NEW.check_interval
)
EXECUTE FUNCTION notify_jobsets_changed ();

CREATE TRIGGER trg_jobsets_delete_notify
AFTER DELETE ON jobsets FOR EACH ROW
EXECUTE FUNCTION notify_jobsets_changed ();

-- notification_tasks: persistent notification retry queue
-- Stores notification delivery tasks with automatic retry and exponential backoff
CREATE TABLE notification_tasks (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
  notification_type VARCHAR(50) NOT NULL CHECK (
    notification_type IN (
      'webhook',
      'github_status',
      'gitea_status',
      'gitlab_status',
      'email'
    )
  ),
  payload JSONB NOT NULL,
  status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (
    status IN ('pending', 'running', 'completed', 'failed')
  ),
  attempts INTEGER NOT NULL DEFAULT 0,
  max_attempts INTEGER NOT NULL DEFAULT 5,
  next_retry_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  last_error TEXT,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  completed_at TIMESTAMP WITH TIME ZONE
);

-- Indexes: notification_tasks
CREATE INDEX idx_notification_tasks_status_next_retry ON notification_tasks (
  status,
  next_retry_at
)
WHERE
  status IN ('pending', 'running');

CREATE INDEX idx_notification_tasks_created_at ON notification_tasks (created_at);

-- Views
CREATE VIEW active_jobsets AS
SELECT
  j.id,
  j.project_id,
  j.name,
  j.nix_expression,
  j.enabled,
  j.flake_mode,
  j.check_interval,
  j.branch,
  j.scheduling_shares,
  j.created_at,
  j.updated_at,
  j.state,
  j.last_checked_at,
  j.keep_nr,
  p.name as project_name,
  p.repository_url
FROM
  jobsets j
  JOIN projects p ON j.project_id = p.id
WHERE
  j.state IN ('enabled', 'one_shot', 'one_at_a_time');

CREATE VIEW build_stats AS
SELECT
  COUNT(*) as total_builds,
  COUNT(
    CASE
      WHEN status = 'succeeded' THEN 1
    END
  ) as completed_builds,
  COUNT(
    CASE
      WHEN status = 'failed' THEN 1
    END
  ) as failed_builds,
  COUNT(
    CASE
      WHEN status = 'running' THEN 1
    END
  ) as running_builds,
  COUNT(
    CASE
      WHEN status = 'pending' THEN 1
    END
  ) as pending_builds,
  AVG(
    EXTRACT(
      EPOCH
      FROM
        (completed_at - started_at)
    )
  )::double precision as avg_duration_seconds
FROM
  builds
WHERE
  started_at IS NOT NULL;

CREATE VIEW build_metrics_summary AS
SELECT
  b.id as build_id,
  b.job_name,
  b.status,
  b.system,
  e.jobset_id,
  j.project_id,
  b.started_at,
  b.completed_at,
  EXTRACT(
    EPOCH
    FROM
      (b.completed_at - b.started_at)
  ) as duration_seconds,
  MAX(
    CASE
      WHEN bm.metric_name = 'output_size_bytes' THEN bm.metric_value
    END
  ) as output_size_bytes,
  MAX(
    CASE
      WHEN bm.metric_name = 'peak_memory_bytes' THEN bm.metric_value
    END
  ) as peak_memory_bytes,
  MAX(
    CASE
      WHEN bm.metric_name = 'nar_size_bytes' THEN bm.metric_value
    END
  ) as nar_size_bytes
FROM
  builds b
  JOIN evaluations e ON b.evaluation_id = e.id
  JOIN jobsets j ON e.jobset_id = j.id
  LEFT JOIN build_metrics bm ON b.id = bm.build_id
GROUP BY
  b.id,
  b.job_name,
  b.status,
  b.system,
  e.jobset_id,
  j.project_id,
  b.started_at,
  b.completed_at;
