seed.rs

  1use crate::db::{self, ChannelRole, NewUserParams};
  2
  3use anyhow::Context;
  4use chrono::{DateTime, Utc};
  5use db::Database;
  6use serde::{de::DeserializeOwned, Deserialize};
  7use std::{fs, path::Path};
  8
  9use crate::Config;
 10
 11/// A GitHub user.
 12///
 13/// This representation corresponds to the entries in the `seed/github_users.json` file.
 14#[derive(Debug, Deserialize)]
 15struct GithubUser {
 16    id: i32,
 17    login: String,
 18    email: Option<String>,
 19    created_at: DateTime<Utc>,
 20}
 21
 22#[derive(Deserialize)]
 23struct SeedConfig {
 24    /// Which users to create as admins.
 25    admins: Vec<String>,
 26    /// Which channels to create (all admins are invited to all channels).
 27    channels: Vec<String>,
 28}
 29
 30pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result<()> {
 31    let client = reqwest::Client::new();
 32
 33    if !db.get_all_users(0, 1).await?.is_empty() && !force {
 34        return Ok(());
 35    }
 36
 37    let seed_path = config
 38        .seed_path
 39        .as_ref()
 40        .context("called seed with no SEED_PATH")?;
 41
 42    let seed_config = load_admins(seed_path)
 43        .context(format!("failed to load {}", seed_path.to_string_lossy()))?;
 44
 45    let mut first_user = None;
 46    let mut others = vec![];
 47
 48    let flag_names = ["remoting", "language-models"];
 49    let mut flags = Vec::new();
 50
 51    let existing_feature_flags = db.list_feature_flags().await?;
 52
 53    for flag_name in flag_names {
 54        if existing_feature_flags
 55            .iter()
 56            .any(|flag| flag.flag == flag_name)
 57        {
 58            log::info!("Flag {flag_name:?} already exists");
 59            continue;
 60        }
 61
 62        let flag = db
 63            .create_user_flag(flag_name, false)
 64            .await
 65            .unwrap_or_else(|err| panic!("failed to create flag: '{flag_name}': {err}"));
 66        flags.push(flag);
 67    }
 68
 69    for admin_login in seed_config.admins {
 70        let user = fetch_github::<GithubUser>(
 71            &client,
 72            &format!("https://api.github.com/users/{admin_login}"),
 73        )
 74        .await;
 75        let user = db
 76            .create_user(
 77                &user.email.unwrap_or(format!("{admin_login}@example.com")),
 78                true,
 79                NewUserParams {
 80                    github_login: user.login,
 81                    github_user_id: user.id,
 82                },
 83            )
 84            .await
 85            .context("failed to create admin user")?;
 86        if first_user.is_none() {
 87            first_user = Some(user.user_id);
 88        } else {
 89            others.push(user.user_id)
 90        }
 91
 92        for flag in &flags {
 93            db.add_user_flag(user.user_id, *flag)
 94                .await
 95                .context(format!(
 96                    "Unable to enable flag '{}' for user '{}'",
 97                    flag, user.user_id
 98                ))?;
 99        }
100    }
101
102    for channel in seed_config.channels {
103        let (channel, _) = db
104            .create_channel(&channel, None, first_user.unwrap())
105            .await
106            .context("failed to create channel")?;
107
108        for user_id in &others {
109            db.invite_channel_member(
110                channel.id,
111                *user_id,
112                first_user.unwrap(),
113                ChannelRole::Admin,
114            )
115            .await
116            .context("failed to add user to channel")?;
117        }
118    }
119
120    let github_users_filepath = seed_path.parent().unwrap().join("seed/github_users.json");
121    let github_users: Vec<GithubUser> =
122        serde_json::from_str(&fs::read_to_string(github_users_filepath)?)?;
123
124    for github_user in github_users {
125        log::info!("Seeding {:?} from GitHub", github_user.login);
126
127        let user = db
128            .get_or_create_user_by_github_account(
129                &github_user.login,
130                github_user.id,
131                github_user.email.as_deref(),
132                github_user.created_at,
133                None,
134            )
135            .await
136            .expect("failed to insert user");
137
138        for flag in &flags {
139            db.add_user_flag(user.id, *flag).await.context(format!(
140                "Unable to enable flag '{}' for user '{}'",
141                flag, user.id
142            ))?;
143        }
144    }
145
146    Ok(())
147}
148
149fn load_admins(path: impl AsRef<Path>) -> anyhow::Result<SeedConfig> {
150    let file_content = fs::read_to_string(path)?;
151    Ok(serde_json::from_str(&file_content)?)
152}
153
154async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
155    let response = client
156        .get(url)
157        .header("user-agent", "zed")
158        .send()
159        .await
160        .unwrap_or_else(|error| panic!("failed to fetch '{url}': {error}"));
161    response
162        .json()
163        .await
164        .unwrap_or_else(|error| panic!("failed to deserialize github user from '{url}': {error}"))
165}