chore(infra): add gRPC layer, update protobufs, remove immediate module
- Add gRPC service modules: auth, channel, channel settings, member, permission - Update protobuf definitions and generated code - Remove immediate/ real-time module (superseded by IM service) - Update etcd discovery and registration - Update cache, error, config, and build infrastructure - Add ADR documentation - Update OpenAPI spec
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::pb::im::internal_auth_service_server::InternalAuthService as InternalAuthServiceTrait;
|
||||
use crate::pb::im::{AuthenticateRequest, AuthenticateResponse};
|
||||
use crate::service::internal_auth::InternalAuthService;
|
||||
|
||||
pub struct InternalAuthGrpcService {
|
||||
service: InternalAuthService,
|
||||
}
|
||||
|
||||
impl InternalAuthGrpcService {
|
||||
pub fn new(service: InternalAuthService) -> Self {
|
||||
Self { service }
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl InternalAuthServiceTrait for InternalAuthGrpcService {
|
||||
async fn authenticate(
|
||||
&self,
|
||||
request: Request<AuthenticateRequest>,
|
||||
) -> Result<Response<AuthenticateResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
|
||||
if req.api_key.is_empty() {
|
||||
return Ok(Response::new(AuthenticateResponse {
|
||||
authenticated: false,
|
||||
service_name: String::new(),
|
||||
service_id: String::new(),
|
||||
scopes: vec![],
|
||||
expires_at: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
match self.service.verify_api_key(&req.api_key).await {
|
||||
Ok(Some(identity)) => Ok(Response::new(AuthenticateResponse {
|
||||
authenticated: true,
|
||||
service_name: identity.service_name,
|
||||
service_id: identity.service_id,
|
||||
scopes: identity.scopes,
|
||||
expires_at: identity.expires_at,
|
||||
})),
|
||||
Ok(None) => Ok(Response::new(AuthenticateResponse {
|
||||
authenticated: false,
|
||||
service_name: String::new(),
|
||||
service_id: String::new(),
|
||||
scopes: vec![],
|
||||
expires_at: 0,
|
||||
})),
|
||||
Err(e) => Err(Status::internal(format!("auth verification failed: {e}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
use tonic::{Request, Response, Status};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::channels::ChannelStats;
|
||||
use crate::models::common::{ChannelKind, ChannelType, Visibility};
|
||||
use crate::models::workspaces::Workspace;
|
||||
use crate::pb::im::channel_service_server::ChannelService;
|
||||
use crate::pb::im::{
|
||||
CreateCategoryRequest, CreateCategoryResponse, CreateChannelRequest, CreateChannelResponse,
|
||||
DeleteCategoryRequest, DeleteCategoryResponse, DeleteChannelRequest, DeleteChannelResponse,
|
||||
GetChannelRequest, GetChannelResponse, GetChannelStatsRequest, GetChannelStatsResponse,
|
||||
ListCategoriesRequest, ListCategoriesResponse, ListChannelsRequest, ListChannelsResponse,
|
||||
UpdateCategoryRequest, UpdateCategoryResponse, UpdateChannelRequest, UpdateChannelResponse,
|
||||
};
|
||||
use crate::service::im::categories::{CreateCategoryParams, UpdateCategoryParams};
|
||||
use crate::service::im::channels::{ChannelListFilters, CreateChannelParams, UpdateChannelParams};
|
||||
use crate::service::im::session::ImSession;
|
||||
use crate::service::AppService;
|
||||
|
||||
pub struct ChannelGrpcService {
|
||||
service: AppService,
|
||||
}
|
||||
|
||||
impl ChannelGrpcService {
|
||||
pub fn new(service: AppService) -> Self {
|
||||
Self { service }
|
||||
}
|
||||
|
||||
fn system_session() -> ImSession {
|
||||
ImSession::new(Uuid::nil())
|
||||
}
|
||||
|
||||
fn parse_uuid(s: &str, field: &str) -> Result<Uuid, Status> {
|
||||
Uuid::parse_str(s).map_err(|e| Status::invalid_argument(format!("{field}: {e}")))
|
||||
}
|
||||
|
||||
async fn resolve_workspace_name(&self, workspace_id: Uuid) -> Result<String, Status> {
|
||||
Workspace::find_by_id(self.service.ctx.db.reader(), workspace_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?
|
||||
.map(|w| w.name)
|
||||
.ok_or_else(|| Status::not_found("workspace not found"))
|
||||
}
|
||||
|
||||
fn to_proto_timestamp(dt: chrono::DateTime<chrono::Utc>) -> prost_types::Timestamp {
|
||||
prost_types::Timestamp {
|
||||
seconds: dt.timestamp(),
|
||||
nanos: dt.timestamp_subsec_nanos() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
fn model_channel_to_proto(c: crate::models::channels::Channel) -> crate::pb::im::Channel {
|
||||
let channel_type = match c.channel_type {
|
||||
ChannelType::Public => crate::pb::im::ChannelType::Public,
|
||||
ChannelType::Private => crate::pb::im::ChannelType::Private,
|
||||
ChannelType::Direct => crate::pb::im::ChannelType::Direct,
|
||||
ChannelType::Group => crate::pb::im::ChannelType::Group,
|
||||
ChannelType::Repo => crate::pb::im::ChannelType::Repo,
|
||||
ChannelType::System => crate::pb::im::ChannelType::System,
|
||||
ChannelType::Unknown => crate::pb::im::ChannelType::Unspecified,
|
||||
};
|
||||
|
||||
let channel_kind = match c.channel_kind {
|
||||
ChannelKind::Text => crate::pb::im::ChannelKind::Text,
|
||||
ChannelKind::Voice => crate::pb::im::ChannelKind::Voice,
|
||||
ChannelKind::Stage => crate::pb::im::ChannelKind::Stage,
|
||||
ChannelKind::Forum => crate::pb::im::ChannelKind::Forum,
|
||||
ChannelKind::Announcement => crate::pb::im::ChannelKind::Announcement,
|
||||
ChannelKind::Unknown => crate::pb::im::ChannelKind::Unspecified,
|
||||
};
|
||||
|
||||
let visibility = match c.visibility {
|
||||
Visibility::Public => crate::pb::im::Visibility::Public,
|
||||
Visibility::Private => crate::pb::im::Visibility::Private,
|
||||
Visibility::Internal => crate::pb::im::Visibility::Internal,
|
||||
Visibility::Workspace => crate::pb::im::Visibility::Workspace,
|
||||
Visibility::Protected => crate::pb::im::Visibility::Protected,
|
||||
Visibility::Hidden => crate::pb::im::Visibility::Hidden,
|
||||
Visibility::Secret => crate::pb::im::Visibility::Secret,
|
||||
Visibility::Unknown => crate::pb::im::Visibility::Unspecified,
|
||||
};
|
||||
|
||||
crate::pb::im::Channel {
|
||||
id: c.id.to_string(),
|
||||
workspace_id: c.workspace_id.to_string(),
|
||||
category_id: c.category_id.map(|id| id.to_string()),
|
||||
parent_channel_id: c.parent_channel_id.map(|id| id.to_string()),
|
||||
name: c.name,
|
||||
topic: c.topic,
|
||||
description: c.description,
|
||||
channel_type: channel_type.into(),
|
||||
channel_kind: channel_kind.into(),
|
||||
visibility: visibility.into(),
|
||||
position: c.position.unwrap_or(0),
|
||||
nsfw: c.nsfw,
|
||||
read_only: c.read_only,
|
||||
archived: c.archived,
|
||||
created_by: Some(c.created_by.to_string()),
|
||||
rate_limit_per_user: c.rate_limit_per_user,
|
||||
archived_at: c.archived_at.map(Self::to_proto_timestamp),
|
||||
last_message_id: c.last_message_id.map(|id| id.to_string()),
|
||||
last_message_at: c.last_message_at.map(Self::to_proto_timestamp),
|
||||
created_at: Some(Self::to_proto_timestamp(c.created_at)),
|
||||
updated_at: Some(Self::to_proto_timestamp(c.updated_at)),
|
||||
}
|
||||
}
|
||||
|
||||
fn model_category_to_proto(
|
||||
c: crate::models::channels::ChannelCategory,
|
||||
) -> crate::pb::im::ChannelCategory {
|
||||
crate::pb::im::ChannelCategory {
|
||||
id: c.id.to_string(),
|
||||
workspace_id: c.workspace_id.to_string(),
|
||||
name: c.name,
|
||||
position: c.position,
|
||||
collapsed: c.collapsed,
|
||||
created_at: Some(Self::to_proto_timestamp(c.created_at)),
|
||||
updated_at: Some(Self::to_proto_timestamp(c.updated_at)),
|
||||
}
|
||||
}
|
||||
|
||||
fn model_stats_to_proto(s: ChannelStats) -> crate::pb::im::ChannelStats {
|
||||
crate::pb::im::ChannelStats {
|
||||
channel_id: s.channel_id.to_string(),
|
||||
members_count: s.members_count as i32,
|
||||
messages_count: s.messages_count as i32,
|
||||
threads_count: s.threads_count as i32,
|
||||
reactions_count: s.reactions_count as i32,
|
||||
mentions_count: s.mentions_count as i32,
|
||||
files_count: s.files_count as i32,
|
||||
last_activity_at: s.last_activity_at.map(Self::to_proto_timestamp),
|
||||
updated_at: Some(Self::to_proto_timestamp(s.updated_at)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_category_workspace(&self, category_id: Uuid) -> Result<String, Status> {
|
||||
let workspace_id: Uuid = sqlx::query_scalar(
|
||||
"SELECT workspace_id FROM channel_category WHERE id = $1",
|
||||
)
|
||||
.bind(category_id)
|
||||
.fetch_optional(self.service.ctx.db.reader())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?
|
||||
.ok_or_else(|| Status::not_found("category not found"))?;
|
||||
|
||||
self.resolve_workspace_name(workspace_id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl ChannelService for ChannelGrpcService {
|
||||
async fn get_channel(
|
||||
&self,
|
||||
request: Request<GetChannelRequest>,
|
||||
) -> Result<Response<GetChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let session = Self::system_session();
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let wk_name = self.resolve_workspace_name(channel.workspace_id).await?;
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.channel_get(&session, &wk_name, channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(GetChannelResponse {
|
||||
channel: Some(Self::model_channel_to_proto(channel)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_channels(
|
||||
&self,
|
||||
request: Request<ListChannelsRequest>,
|
||||
) -> Result<Response<ListChannelsResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let session = Self::system_session();
|
||||
|
||||
let channel_type = req.channel_type()
|
||||
.as_str_name()
|
||||
.strip_prefix("CHANNEL_TYPE_")
|
||||
.map(|s| s.to_lowercase())
|
||||
.filter(|s| s != "unspecified");
|
||||
let channel_kind = req.channel_kind()
|
||||
.as_str_name()
|
||||
.strip_prefix("CHANNEL_KIND_")
|
||||
.map(|s| s.to_lowercase())
|
||||
.filter(|s| s != "unspecified");
|
||||
|
||||
let filters = ChannelListFilters {
|
||||
channel_type,
|
||||
channel_kind,
|
||||
category_id: req.category_id.as_deref().and_then(|s| Uuid::parse_str(s).ok()),
|
||||
archived: None,
|
||||
};
|
||||
|
||||
let channels = self
|
||||
.service
|
||||
.im
|
||||
.channel_list(
|
||||
&session,
|
||||
&req.workspace_name,
|
||||
filters,
|
||||
req.limit as i64,
|
||||
req.offset as i64,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let total = channels.len() as i32;
|
||||
let proto_channels: Vec<_> = channels
|
||||
.into_iter()
|
||||
.map(Self::model_channel_to_proto)
|
||||
.collect();
|
||||
|
||||
Ok(Response::new(ListChannelsResponse {
|
||||
channels: proto_channels,
|
||||
total,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn create_channel(
|
||||
&self,
|
||||
request: Request<CreateChannelRequest>,
|
||||
) -> Result<Response<CreateChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let session = Self::system_session();
|
||||
|
||||
let params = CreateChannelParams {
|
||||
name: req.name,
|
||||
topic: req.topic,
|
||||
description: req.description,
|
||||
channel_type: req.channel_type,
|
||||
channel_kind: req.channel_kind,
|
||||
visibility: req.visibility,
|
||||
category_id: req.category_id.as_deref().and_then(|s| Uuid::parse_str(s).ok()),
|
||||
parent_channel_id: req.parent_channel_id.as_deref().and_then(|s| Uuid::parse_str(s).ok()),
|
||||
nsfw: None,
|
||||
rate_limit_per_user: req.rate_limit_per_user,
|
||||
};
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.channel_create(&session, &req.workspace_name, params, Uuid::nil())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(CreateChannelResponse {
|
||||
channel: Some(Self::model_channel_to_proto(channel)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn update_channel(
|
||||
&self,
|
||||
request: Request<UpdateChannelRequest>,
|
||||
) -> Result<Response<UpdateChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let session = Self::system_session();
|
||||
|
||||
let existing = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
let wk_name = self.resolve_workspace_name(existing.workspace_id).await?;
|
||||
|
||||
let params = UpdateChannelParams {
|
||||
name: req.name,
|
||||
topic: req.topic,
|
||||
description: req.description,
|
||||
visibility: req.visibility,
|
||||
category_id: req.category_id.as_deref().and_then(|s| Uuid::parse_str(s).ok()),
|
||||
position: req.position,
|
||||
nsfw: req.nsfw,
|
||||
rate_limit_per_user: req.rate_limit_per_user,
|
||||
archived: req.archived,
|
||||
read_only: req.read_only,
|
||||
};
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.channel_update(&session, &wk_name, channel_id, params, Uuid::nil())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateChannelResponse {
|
||||
channel: Some(Self::model_channel_to_proto(channel)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn delete_channel(
|
||||
&self,
|
||||
request: Request<DeleteChannelRequest>,
|
||||
) -> Result<Response<DeleteChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let session = Self::system_session();
|
||||
|
||||
let existing = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
let wk_name = self.resolve_workspace_name(existing.workspace_id).await?;
|
||||
|
||||
self.service
|
||||
.im
|
||||
.channel_delete(&session, &wk_name, channel_id, Uuid::nil())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(DeleteChannelResponse {}))
|
||||
}
|
||||
|
||||
async fn get_channel_stats(
|
||||
&self,
|
||||
request: Request<GetChannelStatsRequest>,
|
||||
) -> Result<Response<GetChannelStatsResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
|
||||
let stats = sqlx::query_as::<_, ChannelStats>(
|
||||
"SELECT * FROM channel_stats WHERE channel_id = $1",
|
||||
)
|
||||
.bind(channel_id)
|
||||
.fetch_optional(self.service.ctx.db.reader())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
match stats {
|
||||
Some(s) => Ok(Response::new(GetChannelStatsResponse {
|
||||
stats: Some(Self::model_stats_to_proto(s)),
|
||||
})),
|
||||
None => Err(Status::not_found("Channel stats not found")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_categories(
|
||||
&self,
|
||||
request: Request<ListCategoriesRequest>,
|
||||
) -> Result<Response<ListCategoriesResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let session = Self::system_session();
|
||||
|
||||
let categories = self
|
||||
.service
|
||||
.im
|
||||
.category_list(&session, &req.workspace_name)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let proto_categories: Vec<_> = categories
|
||||
.into_iter()
|
||||
.map(Self::model_category_to_proto)
|
||||
.collect();
|
||||
|
||||
Ok(Response::new(ListCategoriesResponse {
|
||||
categories: proto_categories,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn create_category(
|
||||
&self,
|
||||
request: Request<CreateCategoryRequest>,
|
||||
) -> Result<Response<CreateCategoryResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let session = Self::system_session();
|
||||
|
||||
let params = CreateCategoryParams {
|
||||
name: req.name,
|
||||
position: req.position,
|
||||
};
|
||||
|
||||
let category = self
|
||||
.service
|
||||
.im
|
||||
.category_create(&session, &req.workspace_name, params)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(CreateCategoryResponse {
|
||||
category: Some(Self::model_category_to_proto(category)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn update_category(
|
||||
&self,
|
||||
request: Request<UpdateCategoryRequest>,
|
||||
) -> Result<Response<UpdateCategoryResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let category_id = Self::parse_uuid(&req.category_id, "category_id")?;
|
||||
let session = Self::system_session();
|
||||
let wk_name = self.resolve_category_workspace(category_id).await?;
|
||||
|
||||
let params = UpdateCategoryParams {
|
||||
name: req.name,
|
||||
position: req.position,
|
||||
collapsed: req.collapsed,
|
||||
};
|
||||
|
||||
let category = self
|
||||
.service
|
||||
.im
|
||||
.category_update(&session, &wk_name, category_id, params)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateCategoryResponse {
|
||||
category: Some(Self::model_category_to_proto(category)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn delete_category(
|
||||
&self,
|
||||
request: Request<DeleteCategoryRequest>,
|
||||
) -> Result<Response<DeleteCategoryResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let category_id = Self::parse_uuid(&req.category_id, "category_id")?;
|
||||
let session = Self::system_session();
|
||||
let wk_name = self.resolve_category_workspace(category_id).await?;
|
||||
|
||||
self.service
|
||||
.im
|
||||
.category_delete(&session, &wk_name, category_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(DeleteCategoryResponse {}))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+243
@@ -0,0 +1,243 @@
|
||||
use tonic::{Request, Response, Status};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::channels::ChannelMember;
|
||||
use crate::pb::im::member_service_server::MemberService;
|
||||
use crate::pb::im::{
|
||||
ChannelMember as PbChannelMember, InviteMemberRequest, InviteMemberResponse,
|
||||
IsMemberRequest, IsMemberResponse, JoinChannelRequest, JoinChannelResponse,
|
||||
KickMemberRequest, KickMemberResponse, LeaveChannelRequest, LeaveChannelResponse,
|
||||
ListMembersRequest, ListMembersResponse, UpdateMemberRequest, UpdateMemberResponse,
|
||||
};
|
||||
use crate::service::im::session::ImSession;
|
||||
use crate::service::im::members::{InviteMemberParams, UpdateMemberParams};
|
||||
use crate::service::AppService;
|
||||
|
||||
pub struct MemberGrpcService {
|
||||
service: AppService,
|
||||
}
|
||||
|
||||
impl MemberGrpcService {
|
||||
pub fn new(service: AppService) -> Self {
|
||||
Self { service }
|
||||
}
|
||||
|
||||
async fn resolve_workspace_name(&self, channel_id: Uuid) -> Result<String, Status> {
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let ws_name: String = sqlx::query_scalar("SELECT name FROM workspace WHERE id = $1")
|
||||
.bind(channel.workspace_id)
|
||||
.fetch_optional(self.service.ctx.db.reader())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?
|
||||
.ok_or_else(|| Status::not_found("workspace not found"))?;
|
||||
|
||||
Ok(ws_name)
|
||||
}
|
||||
|
||||
fn parse_uuid(s: &str, field: &str) -> Result<Uuid, Status> {
|
||||
Uuid::parse_str(s).map_err(|_| Status::invalid_argument(format!("invalid {}", field)))
|
||||
}
|
||||
|
||||
fn to_timestamp(dt: chrono::DateTime<chrono::Utc>) -> prost_types::Timestamp {
|
||||
prost_types::Timestamp {
|
||||
seconds: dt.timestamp(),
|
||||
nanos: dt.timestamp_subsec_nanos() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_pb_member(m: ChannelMember) -> PbChannelMember {
|
||||
PbChannelMember {
|
||||
id: m.id.to_string(),
|
||||
channel_id: m.channel_id.to_string(),
|
||||
user_id: m.user_id.to_string(),
|
||||
role: m.role.to_string(),
|
||||
status: m.status.to_string(),
|
||||
muted: m.muted,
|
||||
pinned: m.pinned,
|
||||
last_read_message_id: m.last_read_message_id.map(|id| id.to_string()),
|
||||
last_read_at: m.last_read_at.map(Self::to_timestamp),
|
||||
joined_at: m.joined_at.map(Self::to_timestamp),
|
||||
left_at: m.left_at.map(Self::to_timestamp),
|
||||
created_at: Some(Self::to_timestamp(m.created_at)),
|
||||
updated_at: Some(Self::to_timestamp(m.updated_at)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl MemberService for MemberGrpcService {
|
||||
async fn list_members(
|
||||
&self,
|
||||
request: Request<ListMembersRequest>,
|
||||
) -> Result<Response<ListMembersResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let wk_name = self.resolve_workspace_name(channel_id).await?;
|
||||
|
||||
let session = ImSession::new(Uuid::nil());
|
||||
let members = self
|
||||
.service
|
||||
.im
|
||||
.member_list(&session, &wk_name, channel_id, req.limit as i64, req.offset as i64)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let pb_members: Vec<PbChannelMember> = members.into_iter().map(Self::to_pb_member).collect();
|
||||
let total = pb_members.len() as i32;
|
||||
|
||||
Ok(Response::new(ListMembersResponse {
|
||||
members: pb_members,
|
||||
total,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn invite_member(
|
||||
&self,
|
||||
request: Request<InviteMemberRequest>,
|
||||
) -> Result<Response<InviteMemberResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_id = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
let wk_name = self.resolve_workspace_name(channel_id).await?;
|
||||
|
||||
let params = InviteMemberParams {
|
||||
user_id,
|
||||
role: req.role,
|
||||
};
|
||||
|
||||
let session = ImSession::new(Uuid::nil());
|
||||
let member = self
|
||||
.service
|
||||
.im
|
||||
.member_invite(&session, &wk_name, channel_id, params)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(InviteMemberResponse {
|
||||
member: Some(Self::to_pb_member(member)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn update_member(
|
||||
&self,
|
||||
request: Request<UpdateMemberRequest>,
|
||||
) -> Result<Response<UpdateMemberResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_id = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
let wk_name = self.resolve_workspace_name(channel_id).await?;
|
||||
|
||||
let params = UpdateMemberParams {
|
||||
role: req.role,
|
||||
muted: req.muted,
|
||||
pinned: req.pinned,
|
||||
};
|
||||
|
||||
let session = ImSession::new(Uuid::nil());
|
||||
let member = self
|
||||
.service
|
||||
.im
|
||||
.member_update(&session, &wk_name, channel_id, user_id, params)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateMemberResponse {
|
||||
member: Some(Self::to_pb_member(member)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn kick_member(
|
||||
&self,
|
||||
request: Request<KickMemberRequest>,
|
||||
) -> Result<Response<KickMemberResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_id = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
let wk_name = self.resolve_workspace_name(channel_id).await?;
|
||||
|
||||
let session = ImSession::new(Uuid::nil());
|
||||
self.service
|
||||
.im
|
||||
.member_kick(&session, &wk_name, channel_id, user_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(KickMemberResponse {}))
|
||||
}
|
||||
|
||||
async fn join_channel(
|
||||
&self,
|
||||
request: Request<JoinChannelRequest>,
|
||||
) -> Result<Response<JoinChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_id = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
let wk_name = self.resolve_workspace_name(channel_id).await?;
|
||||
|
||||
let session = ImSession::new(user_id);
|
||||
let member = self
|
||||
.service
|
||||
.im
|
||||
.member_join(&session, &wk_name, channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(JoinChannelResponse {
|
||||
member: Some(Self::to_pb_member(member)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn leave_channel(
|
||||
&self,
|
||||
request: Request<LeaveChannelRequest>,
|
||||
) -> Result<Response<LeaveChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_id = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
let wk_name = self.resolve_workspace_name(channel_id).await?;
|
||||
|
||||
let session = ImSession::new(user_id);
|
||||
self.service
|
||||
.im
|
||||
.member_leave(&session, &wk_name, channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(LeaveChannelResponse {}))
|
||||
}
|
||||
|
||||
async fn is_member(
|
||||
&self,
|
||||
request: Request<IsMemberRequest>,
|
||||
) -> Result<Response<IsMemberResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_id = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
|
||||
let is_member = self
|
||||
.service
|
||||
.im
|
||||
.is_channel_member(channel_id, user_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let role = if is_member {
|
||||
self.service
|
||||
.im
|
||||
.channel_member_role(channel_id, user_id)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?
|
||||
.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
Ok(Response::new(IsMemberResponse { is_member, role }))
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
pub mod auth;
|
||||
pub mod channel;
|
||||
pub mod channel_settings;
|
||||
pub mod member;
|
||||
pub mod permission;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::pb::im::channel_audit_service_server::ChannelAuditServiceServer;
|
||||
use crate::pb::im::channel_invitation_service_server::ChannelInvitationServiceServer;
|
||||
use crate::pb::im::channel_repo_link_service_server::ChannelRepoLinkServiceServer;
|
||||
use crate::pb::im::channel_role_service_server::ChannelRoleServiceServer;
|
||||
use crate::pb::im::channel_service_server::ChannelServiceServer;
|
||||
use crate::pb::im::channel_slash_command_service_server::ChannelSlashCommandServiceServer;
|
||||
use crate::pb::im::channel_webhook_service_server::ChannelWebhookServiceServer;
|
||||
use crate::pb::im::custom_emoji_service_server::CustomEmojiServiceServer;
|
||||
use crate::pb::im::forum_tag_service_server::ForumTagServiceServer;
|
||||
use crate::pb::im::im_integration_service_server::ImIntegrationServiceServer;
|
||||
use crate::pb::im::internal_auth_service_server::InternalAuthServiceServer;
|
||||
use crate::pb::im::member_service_server::MemberServiceServer;
|
||||
use crate::pb::im::permission_service_server::PermissionServiceServer;
|
||||
use crate::pb::im::stage_service_server::StageServiceServer;
|
||||
use crate::pb::im::voice_service_server::VoiceServiceServer;
|
||||
use crate::service::AppService;
|
||||
|
||||
pub async fn start_grpc_server(
|
||||
addr: SocketAddr,
|
||||
service: AppService,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let auth_service = service.internal_auth.clone();
|
||||
let channel_svc = channel::ChannelGrpcService::new(service.clone());
|
||||
let member_svc = member::MemberGrpcService::new(service.clone());
|
||||
let permission_svc = permission::PermissionGrpcService::new(service.clone());
|
||||
let internal_auth_svc = auth::InternalAuthGrpcService::new(auth_service);
|
||||
|
||||
let cs = channel_settings::ChannelSettingsServices::new(service);
|
||||
|
||||
tracing::info!(%addr, "gRPC server listening");
|
||||
|
||||
tonic::transport::Server::builder()
|
||||
.add_service(InternalAuthServiceServer::new(internal_auth_svc))
|
||||
.add_service(ChannelServiceServer::new(channel_svc))
|
||||
.add_service(MemberServiceServer::new(member_svc))
|
||||
.add_service(PermissionServiceServer::new(permission_svc))
|
||||
.add_service(ChannelRoleServiceServer::new(cs.channel_role))
|
||||
.add_service(ChannelInvitationServiceServer::new(cs.channel_invitation))
|
||||
.add_service(ChannelWebhookServiceServer::new(cs.channel_webhook))
|
||||
.add_service(ChannelSlashCommandServiceServer::new(cs.channel_slash_command))
|
||||
.add_service(ChannelRepoLinkServiceServer::new(cs.channel_repo_link))
|
||||
.add_service(ImIntegrationServiceServer::new(cs.im_integration))
|
||||
.add_service(CustomEmojiServiceServer::new(cs.custom_emoji))
|
||||
.add_service(ForumTagServiceServer::new(cs.forum_tag))
|
||||
.add_service(VoiceServiceServer::new(cs.voice))
|
||||
.add_service(StageServiceServer::new(cs.stage))
|
||||
.add_service(ChannelAuditServiceServer::new(cs.channel_audit))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
use tonic::{Request, Response, Status};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::channels::ChannelPermissionOverwrite;
|
||||
use crate::models::common::{OverwriteTarget, Role};
|
||||
use crate::pb::im::permission_service_server::PermissionService;
|
||||
use crate::pb::im::{
|
||||
CheckPermissionRequest, CheckPermissionResponse, DeletePermissionOverwriteRequest,
|
||||
DeletePermissionOverwriteResponse, EnsureReadableRequest, EnsureReadableResponse,
|
||||
GetPermissionOverwritesRequest, GetPermissionOverwritesResponse, GetPermissionsRequest,
|
||||
GetPermissionsResponse, PermissionOverwrite, ResolveChannelRequest, ResolveChannelResponse,
|
||||
SetPermissionOverwriteRequest, SetPermissionOverwriteResponse,
|
||||
};
|
||||
use crate::service::util::role_level;
|
||||
use crate::service::AppService;
|
||||
|
||||
pub struct PermissionGrpcService {
|
||||
service: AppService,
|
||||
}
|
||||
|
||||
impl PermissionGrpcService {
|
||||
pub fn new(service: AppService) -> Self {
|
||||
Self { service }
|
||||
}
|
||||
|
||||
fn parse_uuid(s: &str, field: &str) -> Result<Uuid, Status> {
|
||||
Uuid::parse_str(s).map_err(|e| Status::invalid_argument(format!("{field}: {e}")))
|
||||
}
|
||||
|
||||
fn im_permission_to_str(v: i32) -> &'static str {
|
||||
match v {
|
||||
1 => "READ_CHANNEL",
|
||||
2 => "SEND_MESSAGE",
|
||||
3 => "MANAGE_THREADS",
|
||||
4 => "MANAGE_REACTIONS",
|
||||
5 => "MANAGE_PINS",
|
||||
6 => "INVITE_MEMBERS",
|
||||
7 => "KICK_MEMBERS",
|
||||
8 => "MANAGE_CHANNEL",
|
||||
9 => "MANAGE_ROLES",
|
||||
10 => "MANAGE_WEBHOOKS",
|
||||
11 => "MANAGE_EMOJIS",
|
||||
12 => "VIEW_AUDIT_LOG",
|
||||
13 => "MANAGE_INTEGRATIONS",
|
||||
14 => "SEND_TTS",
|
||||
15 => "USE_SLASH_COMMANDS",
|
||||
16 => "ATTACH_FILES",
|
||||
17 => "MENTION_EVERYONE",
|
||||
18 => "MANAGE_MESSAGES",
|
||||
19 => "ADMIN",
|
||||
_ => "UNSPECIFIED",
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_im_permission(s: &str) -> i32 {
|
||||
match s {
|
||||
"READ_CHANNEL" => 1,
|
||||
"SEND_MESSAGE" => 2,
|
||||
"MANAGE_THREADS" => 3,
|
||||
"MANAGE_REACTIONS" => 4,
|
||||
"MANAGE_PINS" => 5,
|
||||
"INVITE_MEMBERS" => 6,
|
||||
"KICK_MEMBERS" => 7,
|
||||
"MANAGE_CHANNEL" => 8,
|
||||
"MANAGE_ROLES" => 9,
|
||||
"MANAGE_WEBHOOKS" => 10,
|
||||
"MANAGE_EMOJIS" => 11,
|
||||
"VIEW_AUDIT_LOG" => 12,
|
||||
"MANAGE_INTEGRATIONS" => 13,
|
||||
"SEND_TTS" => 14,
|
||||
"USE_SLASH_COMMANDS" => 15,
|
||||
"ATTACH_FILES" => 16,
|
||||
"MENTION_EVERYONE" => 17,
|
||||
"MANAGE_MESSAGES" => 18,
|
||||
"ADMIN" => 19,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn permission_requires_role(p: i32) -> Role {
|
||||
match p {
|
||||
1 => Role::Viewer,
|
||||
2 => Role::Member,
|
||||
3 => Role::Member,
|
||||
4 => Role::Member,
|
||||
5 => Role::Moderator,
|
||||
6 => Role::Moderator,
|
||||
7 => Role::Moderator,
|
||||
8 => Role::Admin,
|
||||
9 => Role::Admin,
|
||||
10 => Role::Admin,
|
||||
11 => Role::Admin,
|
||||
12 => Role::Moderator,
|
||||
13 => Role::Admin,
|
||||
14 => Role::Member,
|
||||
15 => Role::Member,
|
||||
16 => Role::Member,
|
||||
17 => Role::Moderator,
|
||||
18 => Role::Moderator,
|
||||
19 => Role::Admin,
|
||||
_ => Role::Owner,
|
||||
}
|
||||
}
|
||||
|
||||
fn role_to_permissions(role: Role) -> Vec<i32> {
|
||||
let level = role_level(role);
|
||||
let mut perms = Vec::new();
|
||||
|
||||
if level >= role_level(Role::Viewer) {
|
||||
perms.push(1);
|
||||
}
|
||||
if level >= role_level(Role::Member) {
|
||||
perms.extend_from_slice(&[2, 3, 4, 14, 15, 16]);
|
||||
}
|
||||
if level >= role_level(Role::Moderator) {
|
||||
perms.extend_from_slice(&[5, 6, 7, 12, 17, 18]);
|
||||
}
|
||||
if level >= role_level(Role::Admin) {
|
||||
perms.extend_from_slice(&[8, 9, 10, 11, 13, 19]);
|
||||
}
|
||||
|
||||
perms.sort();
|
||||
perms.dedup();
|
||||
perms
|
||||
}
|
||||
|
||||
fn overwrite_to_proto(o: ChannelPermissionOverwrite) -> PermissionOverwrite {
|
||||
PermissionOverwrite {
|
||||
id: o.id.to_string(),
|
||||
channel_id: o.channel_id.to_string(),
|
||||
target_type: o.target_type.as_str().to_string(),
|
||||
target_id: o.target_id.to_string(),
|
||||
allow: o
|
||||
.allow
|
||||
.iter()
|
||||
.map(|p| Self::str_to_im_permission(p))
|
||||
.collect(),
|
||||
deny: o
|
||||
.deny
|
||||
.iter()
|
||||
.map(|p| Self::str_to_im_permission(p))
|
||||
.collect(),
|
||||
created_at: o.created_at.to_rfc3339(),
|
||||
updated_at: o.updated_at.to_rfc3339(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl PermissionService for PermissionGrpcService {
|
||||
async fn check_permission(
|
||||
&self,
|
||||
request: Request<CheckPermissionRequest>,
|
||||
) -> Result<Response<CheckPermissionResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_uid = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::not_found(e.to_string()))?;
|
||||
|
||||
let role = self
|
||||
.service
|
||||
.im
|
||||
.channel_member_role(channel.id, user_uid)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let required_role = Self::permission_requires_role(req.permission);
|
||||
let allowed = role_level(role) >= role_level(required_role);
|
||||
|
||||
Ok(Response::new(CheckPermissionResponse {
|
||||
allowed,
|
||||
role: role.as_str().to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_permissions(
|
||||
&self,
|
||||
request: Request<GetPermissionsRequest>,
|
||||
) -> Result<Response<GetPermissionsResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_uid = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::not_found(e.to_string()))?;
|
||||
|
||||
let role = self
|
||||
.service
|
||||
.im
|
||||
.channel_member_role(channel.id, user_uid)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let permissions = Self::role_to_permissions(role);
|
||||
|
||||
Ok(Response::new(GetPermissionsResponse {
|
||||
permissions,
|
||||
role: role.as_str().to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn set_permission_overwrite(
|
||||
&self,
|
||||
request: Request<SetPermissionOverwriteRequest>,
|
||||
) -> Result<Response<SetPermissionOverwriteResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let target_id = Self::parse_uuid(&req.target_id, "target_id")?;
|
||||
let target_type: OverwriteTarget = req.target_type.parse().unwrap_or(OverwriteTarget::Unknown);
|
||||
|
||||
let allow: Vec<String> = req
|
||||
.allow
|
||||
.into_iter()
|
||||
.map(|v| Self::im_permission_to_str(v).to_string())
|
||||
.collect();
|
||||
let deny: Vec<String> = req
|
||||
.deny
|
||||
.into_iter()
|
||||
.map(|v| Self::im_permission_to_str(v).to_string())
|
||||
.collect();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let id = Uuid::new_v4();
|
||||
|
||||
let overwrite = sqlx::query_as::<_, ChannelPermissionOverwrite>(
|
||||
"INSERT INTO channel_permission_overwrite \
|
||||
(id, channel_id, target_type, target_id, allow, deny, created_by, created_at, updated_at) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) \
|
||||
ON CONFLICT (channel_id, target_type, target_id) \
|
||||
DO UPDATE SET allow = $5, deny = $6, updated_at = $9 \
|
||||
RETURNING id, channel_id, target_type, target_id, allow, deny, created_by, created_at, updated_at",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(channel_id)
|
||||
.bind(target_type)
|
||||
.bind(target_id)
|
||||
.bind(&allow)
|
||||
.bind(&deny)
|
||||
.bind(Uuid::nil())
|
||||
.bind(now)
|
||||
.bind(now)
|
||||
.fetch_one(self.service.ctx.db.writer())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(SetPermissionOverwriteResponse {
|
||||
overwrite: Some(Self::overwrite_to_proto(overwrite)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_permission_overwrites(
|
||||
&self,
|
||||
request: Request<GetPermissionOverwritesRequest>,
|
||||
) -> Result<Response<GetPermissionOverwritesResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
|
||||
let overwrites = sqlx::query_as::<_, ChannelPermissionOverwrite>(
|
||||
"SELECT id, channel_id, target_type, target_id, allow, deny, created_by, created_at, updated_at \
|
||||
FROM channel_permission_overwrite WHERE channel_id = $1",
|
||||
)
|
||||
.bind(channel_id)
|
||||
.fetch_all(self.service.ctx.db.reader())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
let proto_overwrites: Vec<_> = overwrites
|
||||
.into_iter()
|
||||
.map(Self::overwrite_to_proto)
|
||||
.collect();
|
||||
|
||||
Ok(Response::new(GetPermissionOverwritesResponse {
|
||||
overwrites: proto_overwrites,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn delete_permission_overwrite(
|
||||
&self,
|
||||
request: Request<DeletePermissionOverwriteRequest>,
|
||||
) -> Result<Response<DeletePermissionOverwriteResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let target_id = Self::parse_uuid(&req.target_id, "target_id")?;
|
||||
let target_type: OverwriteTarget = req.target_type.parse().unwrap_or(OverwriteTarget::Unknown);
|
||||
|
||||
sqlx::query(
|
||||
"DELETE FROM channel_permission_overwrite \
|
||||
WHERE channel_id = $1 AND target_type = $2 AND target_id = $3",
|
||||
)
|
||||
.bind(channel_id)
|
||||
.bind(target_type)
|
||||
.bind(target_id)
|
||||
.execute(self.service.ctx.db.writer())
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(DeletePermissionOverwriteResponse {}))
|
||||
}
|
||||
|
||||
async fn resolve_channel(
|
||||
&self,
|
||||
request: Request<ResolveChannelRequest>,
|
||||
) -> Result<Response<ResolveChannelResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::not_found(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(ResolveChannelResponse {
|
||||
channel_id: channel.id.to_string(),
|
||||
workspace_id: channel.workspace_id.to_string(),
|
||||
name: channel.name,
|
||||
visibility: channel.visibility.as_str().to_string(),
|
||||
channel_type: channel.channel_type.as_str().to_string(),
|
||||
read_only: channel.read_only,
|
||||
archived: channel.archived,
|
||||
created_by: Some(channel.created_by.to_string()),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ensure_readable(
|
||||
&self,
|
||||
request: Request<EnsureReadableRequest>,
|
||||
) -> Result<Response<EnsureReadableResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?;
|
||||
let user_uid = Self::parse_uuid(&req.user_id, "user_id")?;
|
||||
|
||||
let channel = self
|
||||
.service
|
||||
.im
|
||||
.resolve_channel(channel_id)
|
||||
.await
|
||||
.map_err(|e| Status::not_found(e.to_string()))?;
|
||||
|
||||
let allowed = self
|
||||
.service
|
||||
.im
|
||||
.ensure_channel_readable(user_uid, &channel)
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
Ok(Response::new(EnsureReadableResponse { allowed }))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user