Add a role column to the database and start using it

We cannot yet stop using `admin` because stable will continue writing
it.
This commit is contained in:
Conrad Irwin 2023-10-11 16:34:11 -06:00
parent be1800884e
commit 690d9fb971
10 changed files with 76 additions and 30 deletions

View file

@ -226,6 +226,7 @@ CREATE TABLE "channel_members" (
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE, "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"admin" BOOLEAN NOT NULL DEFAULT false, "admin" BOOLEAN NOT NULL DEFAULT false,
"role" VARCHAR,
"accepted" BOOLEAN NOT NULL DEFAULT false, "accepted" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP NOT NULL DEFAULT now "updated_at" TIMESTAMP NOT NULL DEFAULT now
); );

View file

@ -0,0 +1,4 @@
-- Add migration script here
ALTER TABLE channel_members ADD COLUMN role TEXT;
UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END;

View file

@ -80,3 +80,14 @@ id_type!(SignupId);
id_type!(UserId); id_type!(UserId);
id_type!(ChannelBufferCollaboratorId); id_type!(ChannelBufferCollaboratorId);
id_type!(FlagId); id_type!(FlagId);
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "String(None)")]
pub enum ChannelRole {
#[sea_orm(string_value = "admin")]
Admin,
#[sea_orm(string_value = "member")]
Member,
#[sea_orm(string_value = "guest")]
Guest,
}

View file

@ -74,11 +74,12 @@ impl Database {
} }
channel_member::ActiveModel { channel_member::ActiveModel {
id: ActiveValue::NotSet,
channel_id: ActiveValue::Set(channel.id), channel_id: ActiveValue::Set(channel.id),
user_id: ActiveValue::Set(creator_id), user_id: ActiveValue::Set(creator_id),
accepted: ActiveValue::Set(true), accepted: ActiveValue::Set(true),
admin: ActiveValue::Set(true), admin: ActiveValue::Set(true),
..Default::default() role: ActiveValue::Set(Some(ChannelRole::Admin)),
} }
.insert(&*tx) .insert(&*tx)
.await?; .await?;
@ -160,18 +161,19 @@ impl Database {
channel_id: ChannelId, channel_id: ChannelId,
invitee_id: UserId, invitee_id: UserId,
inviter_id: UserId, inviter_id: UserId,
is_admin: bool, role: ChannelRole,
) -> Result<()> { ) -> Result<()> {
self.transaction(move |tx| async move { self.transaction(move |tx| async move {
self.check_user_is_channel_admin(channel_id, inviter_id, &*tx) self.check_user_is_channel_admin(channel_id, inviter_id, &*tx)
.await?; .await?;
channel_member::ActiveModel { channel_member::ActiveModel {
id: ActiveValue::NotSet,
channel_id: ActiveValue::Set(channel_id), channel_id: ActiveValue::Set(channel_id),
user_id: ActiveValue::Set(invitee_id), user_id: ActiveValue::Set(invitee_id),
accepted: ActiveValue::Set(false), accepted: ActiveValue::Set(false),
admin: ActiveValue::Set(is_admin), admin: ActiveValue::Set(role == ChannelRole::Admin),
..Default::default() role: ActiveValue::Set(Some(role)),
} }
.insert(&*tx) .insert(&*tx)
.await?; .await?;
@ -417,7 +419,13 @@ impl Database {
let channels_with_admin_privileges = channel_memberships let channels_with_admin_privileges = channel_memberships
.iter() .iter()
.filter_map(|membership| membership.admin.then_some(membership.channel_id)) .filter_map(|membership| {
if membership.role == Some(ChannelRole::Admin) || membership.admin {
Some(membership.channel_id)
} else {
None
}
})
.collect(); .collect();
let graph = self let graph = self
@ -470,12 +478,12 @@ impl Database {
.await .await
} }
pub async fn set_channel_member_admin( pub async fn set_channel_member_role(
&self, &self,
channel_id: ChannelId, channel_id: ChannelId,
from: UserId, from: UserId,
for_user: UserId, for_user: UserId,
admin: bool, role: ChannelRole,
) -> Result<()> { ) -> Result<()> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, from, &*tx) self.check_user_is_channel_admin(channel_id, from, &*tx)
@ -488,7 +496,8 @@ impl Database {
.and(channel_member::Column::UserId.eq(for_user)), .and(channel_member::Column::UserId.eq(for_user)),
) )
.set(channel_member::ActiveModel { .set(channel_member::ActiveModel {
admin: ActiveValue::set(admin), admin: ActiveValue::set(role == ChannelRole::Admin),
role: ActiveValue::set(Some(role)),
..Default::default() ..Default::default()
}) })
.exec(&*tx) .exec(&*tx)
@ -516,6 +525,7 @@ impl Database {
enum QueryMemberDetails { enum QueryMemberDetails {
UserId, UserId,
Admin, Admin,
Role,
IsDirectMember, IsDirectMember,
Accepted, Accepted,
} }
@ -528,6 +538,7 @@ impl Database {
.select_only() .select_only()
.column(channel_member::Column::UserId) .column(channel_member::Column::UserId)
.column(channel_member::Column::Admin) .column(channel_member::Column::Admin)
.column(channel_member::Column::Role)
.column_as( .column_as(
channel_member::Column::ChannelId.eq(channel_id), channel_member::Column::ChannelId.eq(channel_id),
QueryMemberDetails::IsDirectMember, QueryMemberDetails::IsDirectMember,
@ -540,9 +551,10 @@ impl Database {
let mut rows = Vec::<proto::ChannelMember>::new(); let mut rows = Vec::<proto::ChannelMember>::new();
while let Some(row) = stream.next().await { while let Some(row) = stream.next().await {
let (user_id, is_admin, is_direct_member, is_invite_accepted): ( let (user_id, is_admin, channel_role, is_direct_member, is_invite_accepted): (
UserId, UserId,
bool, bool,
Option<ChannelRole>,
bool, bool,
bool, bool,
) = row?; ) = row?;
@ -558,7 +570,7 @@ impl Database {
if last_row.user_id == user_id { if last_row.user_id == user_id {
if is_direct_member { if is_direct_member {
last_row.kind = kind; last_row.kind = kind;
last_row.admin = is_admin; last_row.admin = channel_role == Some(ChannelRole::Admin) || is_admin;
} }
continue; continue;
} }
@ -566,7 +578,7 @@ impl Database {
rows.push(proto::ChannelMember { rows.push(proto::ChannelMember {
user_id, user_id,
kind, kind,
admin: is_admin, admin: channel_role == Some(ChannelRole::Admin) || is_admin,
}); });
} }

View file

@ -1,7 +1,7 @@
use crate::db::{channel_member, ChannelId, ChannelMemberId, UserId}; use crate::db::{channel_member, ChannelId, ChannelMemberId, ChannelRole, UserId};
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "channel_members")] #[sea_orm(table_name = "channel_members")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
@ -10,6 +10,8 @@ pub struct Model {
pub user_id: UserId, pub user_id: UserId,
pub accepted: bool, pub accepted: bool,
pub admin: bool, pub admin: bool,
// only optional while migrating
pub role: Option<ChannelRole>,
} }
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View file

@ -56,7 +56,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
db.invite_channel_member(zed_id, b_id, a_id, false) db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
@ -211,7 +211,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.await .await
.unwrap(); .unwrap();
db.invite_channel_member(channel, observer_id, user_id, false) db.invite_channel_member(channel, observer_id, user_id, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
db.respond_to_channel_invite(channel, observer_id, true) db.respond_to_channel_invite(channel, observer_id, true)

View file

@ -8,7 +8,7 @@ use crate::{
db::{ db::{
queries::channels::ChannelGraph, queries::channels::ChannelGraph,
tests::{graph, TEST_RELEASE_CHANNEL}, tests::{graph, TEST_RELEASE_CHANNEL},
ChannelId, Database, NewUserParams, ChannelId, ChannelRole, Database, NewUserParams,
}, },
test_both_dbs, test_both_dbs,
}; };
@ -50,7 +50,7 @@ async fn test_channels(db: &Arc<Database>) {
// Make sure that people cannot read channels they haven't been invited to // Make sure that people cannot read channels they haven't been invited to
assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none()); assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none());
db.invite_channel_member(zed_id, b_id, a_id, false) db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
@ -125,9 +125,13 @@ async fn test_channels(db: &Arc<Database>) {
); );
// Update member permissions // Update member permissions
let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await; let set_subchannel_admin = db
.set_channel_member_role(crdb_id, a_id, b_id, ChannelRole::Admin)
.await;
assert!(set_subchannel_admin.is_err()); assert!(set_subchannel_admin.is_err());
let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await; let set_channel_admin = db
.set_channel_member_role(zed_id, a_id, b_id, ChannelRole::Admin)
.await;
assert!(set_channel_admin.is_ok()); assert!(set_channel_admin.is_ok());
let result = db.get_channels_for_user(b_id).await.unwrap(); let result = db.get_channels_for_user(b_id).await.unwrap();
@ -284,13 +288,13 @@ async fn test_channel_invites(db: &Arc<Database>) {
let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap(); let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap();
db.invite_channel_member(channel_1_1, user_2, user_1, false) db.invite_channel_member(channel_1_1, user_2, user_1, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
db.invite_channel_member(channel_1_2, user_2, user_1, false) db.invite_channel_member(channel_1_2, user_2, user_1, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
db.invite_channel_member(channel_1_1, user_3, user_1, true) db.invite_channel_member(channel_1_1, user_3, user_1, ChannelRole::Admin)
.await .await
.unwrap(); .unwrap();

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
db::{Database, MessageId, NewUserParams}, db::{ChannelRole, Database, MessageId, NewUserParams},
test_both_dbs, test_both_dbs,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -155,7 +155,7 @@ async fn test_channel_message_new_notification(db: &Arc<Database>) {
let channel_2 = db.create_channel("channel-2", None, user).await.unwrap(); let channel_2 = db.create_channel("channel-2", None, user).await.unwrap();
db.invite_channel_member(channel_1, observer, user, false) db.invite_channel_member(channel_1, observer, user, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
@ -163,7 +163,7 @@ async fn test_channel_message_new_notification(db: &Arc<Database>) {
.await .await
.unwrap(); .unwrap();
db.invite_channel_member(channel_2, observer, user, false) db.invite_channel_member(channel_2, observer, user, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();

View file

@ -3,8 +3,8 @@ mod connection_pool;
use crate::{ use crate::{
auth, auth,
db::{ db::{
self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, self, BufferId, ChannelId, ChannelRole, ChannelsForUser, Database, MessageId, ProjectId,
ServerId, User, UserId, RoomId, ServerId, User, UserId,
}, },
executor::Executor, executor::Executor,
AppState, Result, AppState, Result,
@ -2282,7 +2282,12 @@ async fn invite_channel_member(
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let invitee_id = UserId::from_proto(request.user_id); let invitee_id = UserId::from_proto(request.user_id);
db.invite_channel_member(channel_id, invitee_id, session.user_id, request.admin) let role = if request.admin {
ChannelRole::Admin
} else {
ChannelRole::Member
};
db.invite_channel_member(channel_id, invitee_id, session.user_id, role)
.await?; .await?;
let (channel, _) = db let (channel, _) = db
@ -2342,7 +2347,12 @@ async fn set_channel_member_admin(
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let member_id = UserId::from_proto(request.user_id); let member_id = UserId::from_proto(request.user_id);
db.set_channel_member_admin(channel_id, session.user_id, member_id, request.admin) let role = if request.admin {
ChannelRole::Admin
} else {
ChannelRole::Member
};
db.set_channel_member_role(channel_id, session.user_id, member_id, role)
.await?; .await?;
let (channel, has_accepted) = db let (channel, has_accepted) = db

View file

@ -1,3 +1,5 @@
use crate::db::ChannelRole;
use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan}; use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
@ -50,7 +52,7 @@ impl RandomizedTest for RandomChannelBufferTest {
.await .await
.unwrap(); .unwrap();
for user in &users[1..] { for user in &users[1..] {
db.invite_channel_member(id, user.user_id, users[0].user_id, false) db.invite_channel_member(id, user.user_id, users[0].user_id, ChannelRole::Member)
.await .await
.unwrap(); .unwrap();
db.respond_to_channel_invite(id, user.user_id, true) db.respond_to_channel_invite(id, user.user_id, true)