diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index f3ab451fda..e2dd9cdf86 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -10,7 +10,8 @@ CREATE TABLE "users" ( "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "metrics_id" TEXT, "github_user_id" INTEGER, - "accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE + "accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE, + "github_user_created_at" TIMESTAMP WITHOUT TIME ZONE ); CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login"); CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code"); diff --git a/crates/collab/migrations/20240812204045_add_github_user_created_at_to_users.sql b/crates/collab/migrations/20240812204045_add_github_user_created_at_to_users.sql new file mode 100644 index 0000000000..a5f713ef7c --- /dev/null +++ b/crates/collab/migrations/20240812204045_add_github_user_created_at_to_users.sql @@ -0,0 +1 @@ +ALTER TABLE "users" ADD COLUMN "github_user_created_at" TIMESTAMP WITHOUT TIME ZONE; diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 5504005f8d..1aadcc5cd9 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -111,6 +111,7 @@ struct AuthenticatedUserParams { github_user_id: Option, github_login: String, github_email: Option, + github_user_created_at: Option>, } #[derive(Debug, Serialize)] @@ -131,6 +132,7 @@ async fn get_authenticated_user( ¶ms.github_login, params.github_user_id, params.github_email.as_deref(), + params.github_user_created_at, initial_channel_id, ) .await?; diff --git a/crates/collab/src/api/contributors.rs b/crates/collab/src/api/contributors.rs index 5d899dbd46..277a22994c 100644 --- a/crates/collab/src/api/contributors.rs +++ b/crates/collab/src/api/contributors.rs @@ -115,6 +115,7 @@ async fn add_contributor( ¶ms.github_login, params.github_user_id, params.github_email.as_deref(), + params.github_user_created_at, initial_channel_id, ) .await diff --git a/crates/collab/src/db/queries/contributors.rs b/crates/collab/src/db/queries/contributors.rs index 703abfb035..3078de42ed 100644 --- a/crates/collab/src/db/queries/contributors.rs +++ b/crates/collab/src/db/queries/contributors.rs @@ -65,6 +65,7 @@ impl Database { github_login: &str, github_user_id: Option, github_email: Option<&str>, + github_user_created_at: Option, initial_channel_id: Option, ) -> Result<()> { self.transaction(|tx| async move { @@ -73,6 +74,7 @@ impl Database { github_login, github_user_id, github_email, + github_user_created_at.map(|time| time.naive_utc()), initial_channel_id, &tx, ) diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index 447946b7b2..6ae482c36b 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -1,3 +1,5 @@ +use chrono::NaiveDateTime; + use super::*; impl Database { @@ -99,6 +101,7 @@ impl Database { github_login: &str, github_user_id: Option, github_email: Option<&str>, + github_user_created_at: Option, initial_channel_id: Option, ) -> Result { self.transaction(|tx| async move { @@ -106,6 +109,7 @@ impl Database { github_login, github_user_id, github_email, + github_user_created_at.map(|created_at| created_at.naive_utc()), initial_channel_id, &tx, ) @@ -119,6 +123,7 @@ impl Database { github_login: &str, github_user_id: Option, github_email: Option<&str>, + github_user_created_at: Option, initial_channel_id: Option, tx: &DatabaseTransaction, ) -> Result { @@ -130,6 +135,10 @@ impl Database { { let mut user_by_github_user_id = user_by_github_user_id.into_active_model(); user_by_github_user_id.github_login = ActiveValue::set(github_login.into()); + if github_user_created_at.is_some() { + user_by_github_user_id.github_user_created_at = + ActiveValue::set(github_user_created_at); + } Ok(user_by_github_user_id.update(tx).await?) } else if let Some(user_by_github_login) = user::Entity::find() .filter(user::Column::GithubLogin.eq(github_login)) @@ -138,12 +147,17 @@ impl Database { { let mut user_by_github_login = user_by_github_login.into_active_model(); user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id)); + if github_user_created_at.is_some() { + user_by_github_login.github_user_created_at = + ActiveValue::set(github_user_created_at); + } Ok(user_by_github_login.update(tx).await?) } else { let user = user::Entity::insert(user::ActiveModel { email_address: ActiveValue::set(github_email.map(|email| email.into())), github_login: ActiveValue::set(github_login.into()), github_user_id: ActiveValue::set(Some(github_user_id)), + github_user_created_at: ActiveValue::set(github_user_created_at), admin: ActiveValue::set(false), invite_count: ActiveValue::set(0), invite_code: ActiveValue::set(None), diff --git a/crates/collab/src/db/tables/user.rs b/crates/collab/src/db/tables/user.rs index 100bb11650..e65b241338 100644 --- a/crates/collab/src/db/tables/user.rs +++ b/crates/collab/src/db/tables/user.rs @@ -1,4 +1,5 @@ use crate::db::UserId; +use chrono::NaiveDateTime; use sea_orm::entity::prelude::*; use serde::Serialize; @@ -10,6 +11,7 @@ pub struct Model { pub id: UserId, pub github_login: String, pub github_user_id: Option, + pub github_user_created_at: Option, pub email_address: Option, pub admin: bool, pub invite_code: Option, @@ -17,8 +19,8 @@ pub struct Model { pub inviter_id: Option, pub connected_once: bool, pub metrics_id: Uuid, - pub created_at: DateTime, - pub accepted_tos_at: Option, + pub created_at: NaiveDateTime, + pub accepted_tos_at: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/src/db/tests/contributor_tests.rs b/crates/collab/src/db/tests/contributor_tests.rs index 72fa5f9357..0d5c0da6e5 100644 --- a/crates/collab/src/db/tests/contributor_tests.rs +++ b/crates/collab/src/db/tests/contributor_tests.rs @@ -1,3 +1,5 @@ +use chrono::Utc; + use super::Database; use crate::{db::NewUserParams, test_both_dbs}; use std::sync::Arc; @@ -22,7 +24,8 @@ async fn test_contributors(db: &Arc) { assert_eq!(db.get_contributors().await.unwrap(), Vec::::new()); - db.add_contributor("user1", Some(1), None, None) + let user1_created_at = Utc::now(); + db.add_contributor("user1", Some(1), None, Some(user1_created_at), None) .await .unwrap(); assert_eq!( @@ -30,7 +33,8 @@ async fn test_contributors(db: &Arc) { vec!["user1".to_string()] ); - db.add_contributor("user2", Some(2), None, None) + let user2_created_at = Utc::now(); + db.add_contributor("user2", Some(2), None, Some(user2_created_at), None) .await .unwrap(); assert_eq!( diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 4f9bb035fb..eabc1a9091 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::test_both_dbs; +use chrono::Utc; use pretty_assertions::{assert_eq, assert_ne}; use std::sync::Arc; @@ -100,7 +101,13 @@ async fn test_get_or_create_user_by_github_account(db: &Arc) { .user_id; let user = db - .get_or_create_user_by_github_account("the-new-login2", Some(102), None, None) + .get_or_create_user_by_github_account( + "the-new-login2", + Some(102), + None, + Some(Utc::now()), + None, + ) .await .unwrap(); assert_eq!(user.id, user_id2); @@ -108,7 +115,13 @@ async fn test_get_or_create_user_by_github_account(db: &Arc) { assert_eq!(user.github_user_id, Some(102)); let user = db - .get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"), None) + .get_or_create_user_by_github_account( + "login3", + Some(103), + Some("user3@example.com"), + Some(Utc::now()), + None, + ) .await .unwrap(); assert_eq!(&user.github_login, "login3"); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 0420730b58..b5c6ec4920 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -4907,6 +4907,9 @@ async fn accept_terms_of_service( Ok(()) } +/// The minimum account age an account must have in order to use the LLM service. +const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30); + async fn get_llm_api_token( _request: proto::GetLlmToken, response: Response, @@ -4928,6 +4931,14 @@ async fn get_llm_api_token( Err(anyhow!("terms of service not accepted"))? } + let mut account_created_at = user.created_at; + if let Some(github_created_at) = user.github_user_created_at { + account_created_at = account_created_at.min(github_created_at); + } + if Utc::now().naive_utc() - account_created_at < MIN_ACCOUNT_AGE_FOR_LLM_USE { + Err(anyhow!("account too young"))? + } + let token = LlmTokenClaims::create( user.id, session.is_staff(), diff --git a/crates/collab/src/seed.rs b/crates/collab/src/seed.rs index b851aa239d..509b986089 100644 --- a/crates/collab/src/seed.rs +++ b/crates/collab/src/seed.rs @@ -1,6 +1,7 @@ use crate::db::{self, ChannelRole, NewUserParams}; use anyhow::Context; +use chrono::{DateTime, Utc}; use db::Database; use serde::{de::DeserializeOwned, Deserialize}; use std::{fmt::Write, fs, path::Path}; @@ -12,6 +13,7 @@ struct GitHubUser { id: i32, login: String, email: Option, + created_at: DateTime, } #[derive(Deserialize)] @@ -107,6 +109,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result &github_user.login, Some(github_user.id), github_user.email.as_deref(), + Some(github_user.created_at), None, ) .await diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index df7d6a8890..3415c974fc 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,5 +1,6 @@ use crate::{db::ChannelId, tests::TestServer}; use call::ActiveCall; +use chrono::Utc; use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext}; use rpc::proto; @@ -167,7 +168,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes server .app_state .db - .get_or_create_user_by_github_account("user_b", Some(100), None, None) + .get_or_create_user_by_github_account("user_b", Some(100), None, Some(Utc::now()), None) .await .unwrap(); @@ -265,7 +266,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes server .app_state .db - .add_contributor("user_b", Some(100), None, None) + .add_contributor("user_b", Some(100), None, Some(Utc::now()), None) .await .unwrap();