seed.rs

  1use crate::db::{self, ChannelRole, NewUserParams};
  2
  3use anyhow::Context as _;
  4use chrono::{DateTime, Utc};
  5use db::Database;
  6use serde::{Deserialize, de::DeserializeOwned};
  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    name: Option<String>,
 20    created_at: DateTime<Utc>,
 21}
 22
 23#[derive(Deserialize)]
 24struct SeedConfig {
 25    /// Which users to create as admins.
 26    admins: Vec<String>,
 27    /// Which channels to create (all admins are invited to all channels).
 28    channels: Vec<String>,
 29}
 30
 31pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result<()> {
 32    let client = reqwest::Client::new();
 33
 34    if !db.get_all_users(0, 1).await?.is_empty() && !force {
 35        return Ok(());
 36    }
 37
 38    let seed_path = config
 39        .seed_path
 40        .as_ref()
 41        .context("called seed with no SEED_PATH")?;
 42
 43    let seed_config = load_admins(seed_path)
 44        .context(format!("failed to load {}", seed_path.to_string_lossy()))?;
 45
 46    let mut first_user = None;
 47    let mut others = vec![];
 48
 49    for admin_login in seed_config.admins {
 50        let user = fetch_github::<GithubUser>(
 51            &client,
 52            &format!("https://api.github.com/users/{admin_login}"),
 53        )
 54        .await;
 55        let user = db
 56            .create_user(
 57                &user.email.unwrap_or(format!("{admin_login}@example.com")),
 58                user.name.as_deref(),
 59                true,
 60                NewUserParams {
 61                    github_login: user.login,
 62                    github_user_id: user.id,
 63                },
 64            )
 65            .await
 66            .context("failed to create admin user")?;
 67        if first_user.is_none() {
 68            first_user = Some(user.user_id);
 69        } else {
 70            others.push(user.user_id)
 71        }
 72    }
 73
 74    for channel in seed_config.channels {
 75        let (channel, _) = db
 76            .create_channel(&channel, None, first_user.unwrap())
 77            .await
 78            .context("failed to create channel")?;
 79
 80        for user_id in &others {
 81            db.invite_channel_member(
 82                channel.id,
 83                *user_id,
 84                first_user.unwrap(),
 85                ChannelRole::Admin,
 86            )
 87            .await
 88            .context("failed to add user to channel")?;
 89        }
 90    }
 91
 92    let github_users_filepath = seed_path.parent().unwrap().join("seed/github_users.json");
 93    let github_users: Vec<GithubUser> =
 94        serde_json::from_str(&fs::read_to_string(github_users_filepath)?)?;
 95
 96    for github_user in github_users {
 97        log::info!("Seeding {:?} from GitHub", github_user.login);
 98
 99        db.update_or_create_user_by_github_account(
100            &github_user.login,
101            github_user.id,
102            github_user.email.as_deref(),
103            github_user.name.as_deref(),
104            github_user.created_at,
105            None,
106        )
107        .await
108        .expect("failed to insert user");
109    }
110
111    Ok(())
112}
113
114fn load_admins(path: impl AsRef<Path>) -> anyhow::Result<SeedConfig> {
115    let file_content = fs::read_to_string(path)?;
116    Ok(serde_json::from_str(&file_content)?)
117}
118
119async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
120    let mut request_builder = client.get(url);
121    if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
122        request_builder =
123            request_builder.header("Authorization", format!("Bearer {}", github_token));
124    }
125    let response = request_builder
126        .header("user-agent", "zed")
127        .send()
128        .await
129        .unwrap_or_else(|error| panic!("failed to fetch '{url}': {error}"));
130    let response_text = response.text().await.unwrap_or_else(|error| {
131        panic!("failed to fetch '{url}': {error}");
132    });
133    serde_json::from_str(&response_text).unwrap_or_else(|error| {
134        panic!("failed to deserialize github user from '{url}'. Error: '{error}', text: '{response_text}'");
135    })
136}