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:
+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 }))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user