Detailed changes
@@ -168,6 +168,7 @@ impl FakeServer {
GetPrivateUserInfoResponse {
metrics_id: "the-metrics-id".into(),
staff: false,
+ flags: Default::default(),
},
)
.await;
@@ -249,3 +249,22 @@ CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replic
CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");
+
+
+CREATE TABLE "feature_flags" (
+ "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+ "flag" TEXT NOT NULL UNIQUE
+);
+
+CREATE INDEX "index_feature_flags" ON "feature_flags" ("id");
+
+
+CREATE TABLE "user_features" (
+ "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
+ "feature_id" INTEGER NOT NULL REFERENCES feature_flags (id) ON DELETE CASCADE,
+ PRIMARY KEY (user_id, feature_id)
+);
+
+CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
+CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
+CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
@@ -0,0 +1,16 @@
+CREATE TABLE "feature_flags" (
+ "id" SERIAL PRIMARY KEY,
+ "flag" VARCHAR(255) NOT NULL UNIQUE
+);
+
+CREATE UNIQUE INDEX "index_feature_flags" ON "feature_flags" ("id");
+
+CREATE TABLE "user_features" (
+ "user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ "feature_id" INTEGER NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
+ PRIMARY KEY (user_id, feature_id)
+);
+
+CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
+CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
+CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
@@ -125,3 +125,4 @@ id_type!(ServerId);
id_type!(SignupId);
id_type!(UserId);
id_type!(ChannelBufferCollaboratorId);
+id_type!(FlagId);
@@ -240,4 +240,58 @@ impl Database {
result.push('%');
result
}
+
+ #[cfg(debug_assertions)]
+ pub async fn create_user_flag(&self, flag: &str) -> Result<FlagId> {
+ self.transaction(|tx| async move {
+ let flag = feature_flag::Entity::insert(feature_flag::ActiveModel {
+ flag: ActiveValue::set(flag.to_string()),
+ ..Default::default()
+ })
+ .exec(&*tx)
+ .await?
+ .last_insert_id;
+
+ Ok(flag)
+ })
+ .await
+ }
+
+ #[cfg(debug_assertions)]
+ pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> {
+ self.transaction(|tx| async move {
+ user_feature::Entity::insert(user_feature::ActiveModel {
+ user_id: ActiveValue::set(user),
+ feature_id: ActiveValue::set(flag),
+ })
+ .exec(&*tx)
+ .await?;
+
+ Ok(())
+ })
+ .await
+ }
+
+ pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
+ self.transaction(|tx| async move {
+ #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
+ enum QueryAs {
+ Flag,
+ }
+
+ let flags = user::Model {
+ id: user,
+ ..Default::default()
+ }
+ .find_linked(user::UserFlags)
+ .select_only()
+ .column(feature_flag::Column::Flag)
+ .into_values::<_, QueryAs>()
+ .all(&*tx)
+ .await?;
+
+ Ok(flags)
+ })
+ .await
+ }
}
@@ -7,6 +7,7 @@ pub mod channel_buffer_collaborator;
pub mod channel_member;
pub mod channel_path;
pub mod contact;
+pub mod feature_flag;
pub mod follower;
pub mod language_server;
pub mod project;
@@ -16,6 +17,7 @@ pub mod room_participant;
pub mod server;
pub mod signup;
pub mod user;
+pub mod user_feature;
pub mod worktree;
pub mod worktree_diagnostic_summary;
pub mod worktree_entry;
@@ -0,0 +1,40 @@
+use sea_orm::entity::prelude::*;
+
+use crate::db::FlagId;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "feature_flags")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: FlagId,
+ pub flag: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(has_many = "super::user_feature::Entity")]
+ UserFeature,
+}
+
+impl Related<super::user_feature::Entity> for Entity {
+ fn to() -> RelationDef {
+ Relation::UserFeature.def()
+ }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+
+pub struct FlaggedUsers;
+
+impl Linked for FlaggedUsers {
+ type FromEntity = Entity;
+
+ type ToEntity = super::user::Entity;
+
+ fn link(&self) -> Vec<RelationDef> {
+ vec![
+ super::user_feature::Relation::Flag.def().rev(),
+ super::user_feature::Relation::User.def(),
+ ]
+ }
+}
@@ -28,6 +28,8 @@ pub enum Relation {
HostedProjects,
#[sea_orm(has_many = "super::channel_member::Entity")]
ChannelMemberships,
+ #[sea_orm(has_many = "super::user_feature::Entity")]
+ UserFeatures,
}
impl Related<super::access_token::Entity> for Entity {
@@ -54,4 +56,25 @@ impl Related<super::channel_member::Entity> for Entity {
}
}
+impl Related<super::user_feature::Entity> for Entity {
+ fn to() -> RelationDef {
+ Relation::UserFeatures.def()
+ }
+}
+
impl ActiveModelBehavior for ActiveModel {}
+
+pub struct UserFlags;
+
+impl Linked for UserFlags {
+ type FromEntity = Entity;
+
+ type ToEntity = super::feature_flag::Entity;
+
+ fn link(&self) -> Vec<RelationDef> {
+ vec![
+ super::user_feature::Relation::User.def().rev(),
+ super::user_feature::Relation::Flag.def(),
+ ]
+ }
+}
@@ -0,0 +1,42 @@
+use sea_orm::entity::prelude::*;
+
+use crate::db::{FlagId, UserId};
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "user_features")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub user_id: UserId,
+ #[sea_orm(primary_key)]
+ pub feature_id: FlagId,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(
+ belongs_to = "super::feature_flag::Entity",
+ from = "Column::FeatureId",
+ to = "super::feature_flag::Column::Id"
+ )]
+ Flag,
+ #[sea_orm(
+ belongs_to = "super::user::Entity",
+ from = "Column::UserId",
+ to = "super::user::Column::Id"
+ )]
+ User,
+}
+
+impl Related<super::feature_flag::Entity> for Entity {
+ fn to() -> RelationDef {
+ Relation::Flag.def()
+ }
+}
+
+impl Related<super::user::Entity> for Entity {
+ fn to() -> RelationDef {
+ Relation::User.def()
+ }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
@@ -1,5 +1,6 @@
mod buffer_tests;
mod db_tests;
+mod feature_flag_tests;
use super::*;
use gpui::executor::Background;
@@ -0,0 +1,60 @@
+use crate::{
+ db::{Database, NewUserParams},
+ test_both_dbs,
+};
+use std::sync::Arc;
+
+test_both_dbs!(
+ test_get_user_flags,
+ test_get_user_flags_postgres,
+ test_get_user_flags_sqlite
+);
+
+async fn test_get_user_flags(db: &Arc<Database>) {
+ let user_1 = db
+ .create_user(
+ &format!("user1@example.com"),
+ false,
+ NewUserParams {
+ github_login: format!("user1"),
+ github_user_id: 1,
+ invite_count: 0,
+ },
+ )
+ .await
+ .unwrap()
+ .user_id;
+
+ let user_2 = db
+ .create_user(
+ &format!("user2@example.com"),
+ false,
+ NewUserParams {
+ github_login: format!("user2"),
+ github_user_id: 2,
+ invite_count: 0,
+ },
+ )
+ .await
+ .unwrap()
+ .user_id;
+
+ const CHANNELS_ALPHA: &'static str = "channels-alpha";
+ const NEW_SEARCH: &'static str = "new-search";
+
+ let channels_flag = db.create_user_flag(CHANNELS_ALPHA).await.unwrap();
+ let search_flag = db.create_user_flag(NEW_SEARCH).await.unwrap();
+
+ db.add_user_flag(user_1, channels_flag).await.unwrap();
+ db.add_user_flag(user_1, search_flag).await.unwrap();
+
+ db.add_user_flag(user_2, channels_flag).await.unwrap();
+
+ let mut user_1_flags = db.get_user_flags(user_1).await.unwrap();
+ user_1_flags.sort();
+ assert_eq!(user_1_flags, &[CHANNELS_ALPHA, NEW_SEARCH]);
+
+ let mut user_2_flags = db.get_user_flags(user_2).await.unwrap();
+ user_2_flags.sort();
+ assert_eq!(user_2_flags, &[CHANNELS_ALPHA]);
+}
@@ -2609,20 +2609,19 @@ async fn get_private_user_info(
response: Response<proto::GetPrivateUserInfo>,
session: Session,
) -> Result<()> {
- let metrics_id = session
- .db()
- .await
- .get_user_metrics_id(session.user_id)
- .await?;
- let user = session
- .db()
- .await
+ let db = session.db().await;
+
+ let metrics_id = db.get_user_metrics_id(session.user_id).await?;
+ let user = db
.get_user_by_id(session.user_id)
.await?
.ok_or_else(|| anyhow!("user not found"))?;
+ let flags = db.get_user_flags(session.user_id).await?;
+
response.send(proto::GetPrivateUserInfoResponse {
metrics_id,
staff: user.admin,
+ flags,
})?;
Ok(())
}
@@ -1111,6 +1111,7 @@ message GetPrivateUserInfo {}
message GetPrivateUserInfoResponse {
string metrics_id = 1;
bool staff = 2;
+ repeated string flags = 3;
}
// Entities
@@ -6,4 +6,4 @@ pub use conn::Connection;
pub use peer::*;
mod macros;
-pub const PROTOCOL_VERSION: u32 = 61;
+pub const PROTOCOL_VERSION: u32 = 62;