Files
gitks/grpc/channel_settings.rs
T
zhenyi 1000f8a80d 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
2026-06-10 18:49:42 +08:00

1283 lines
41 KiB
Rust

use tonic::{Request, Response, Status};
use uuid::Uuid;
use crate::pb::im::channel_audit_service_server::ChannelAuditService;
use crate::pb::im::channel_invitation_service_server::ChannelInvitationService;
use crate::pb::im::channel_repo_link_service_server::ChannelRepoLinkService;
use crate::pb::im::channel_role_service_server::ChannelRoleService;
use crate::pb::im::channel_slash_command_service_server::ChannelSlashCommandService;
use crate::pb::im::channel_webhook_service_server::ChannelWebhookService;
use crate::pb::im::custom_emoji_service_server::CustomEmojiService;
use crate::pb::im::forum_tag_service_server::ForumTagService;
use crate::pb::im::im_integration_service_server::ImIntegrationService;
use crate::pb::im::stage_service_server::StageService;
use crate::pb::im::voice_service_server::VoiceService;
use crate::pb::im::{
AcceptInvitationRequest, AcceptInvitationResponse, CreateChannelRoleRequest,
CreateChannelRoleResponse, CreateCustomEmojiRequest, CreateCustomEmojiResponse,
CreateForumTagRequest, CreateForumTagResponse, CreateIntegrationRequest,
CreateIntegrationResponse, CreateInvitationRequest, CreateInvitationResponse,
CreateRepoLinkRequest, CreateRepoLinkResponse, CreateSlashCommandRequest,
CreateSlashCommandResponse, CreateStageRequest, CreateStageResponse, CreateWebhookRequest,
CreateWebhookResponse, DeleteChannelRoleRequest, DeleteChannelRoleResponse,
DeleteCustomEmojiRequest, DeleteCustomEmojiResponse, DeleteForumTagRequest,
DeleteForumTagResponse, DeleteIntegrationRequest, DeleteIntegrationResponse,
DeleteRepoLinkRequest, DeleteRepoLinkResponse, DeleteSlashCommandRequest,
DeleteSlashCommandResponse, DeleteStageRequest, DeleteStageResponse, DeleteWebhookRequest,
DeleteWebhookResponse, GetStageRequest, GetStageResponse, ListChannelEventsRequest,
ListChannelEventsResponse, ListChannelRolesRequest, ListChannelRolesResponse,
ListCustomEmojisRequest, ListCustomEmojisResponse, ListForumTagsRequest,
ListForumTagsResponse, ListIntegrationsRequest, ListIntegrationsResponse,
ListInvitationsRequest, ListInvitationsResponse, ListRepoLinksRequest,
ListRepoLinksResponse, ListSlashCommandsRequest, ListSlashCommandsResponse,
ListVoiceParticipantsRequest, ListVoiceParticipantsResponse, ListWebhooksRequest,
ListWebhooksResponse, RevokeInvitationRequest, RevokeInvitationResponse,
UpdateChannelRoleRequest, UpdateChannelRoleResponse, UpdateForumTagRequest,
UpdateForumTagResponse, UpdateIntegrationRequest, UpdateIntegrationResponse,
UpdateSlashCommandRequest, UpdateSlashCommandResponse, UpdateStageRequest,
UpdateStageResponse, UpdateVoiceStateRequest, UpdateVoiceStateResponse,
UpdateWebhookRequest, UpdateWebhookResponse,
};
use crate::service::im::channel_roles::{CreateChannelRoleParams, UpdateChannelRoleParams};
use crate::service::im::custom_emojis::CreateCustomEmojiParams;
use crate::service::im::forum_tags::{CreateForumTagParams, UpdateForumTagParams};
use crate::service::im::integrations::{CreateIntegrationParams, UpdateIntegrationParams};
use crate::service::im::invitations::CreateInvitationParams;
use crate::service::im::repo_links::CreateRepoLinkParams;
use crate::service::im::session::ImSession;
use crate::service::im::slash_commands::{CreateSlashCommandParams, UpdateSlashCommandParams};
use crate::service::im::stages::{CreateStageParams, UpdateStageParams};
use crate::service::im::voice::UpdateVoiceStateParams;
use crate::service::im::webhooks::{CreateWebhookParams, UpdateWebhookParams};
use crate::service::AppService;
fn to_proto_ts(dt: chrono::DateTime<chrono::Utc>) -> Option<prost_types::Timestamp> {
Some(prost_types::Timestamp {
seconds: dt.timestamp(),
nanos: dt.timestamp_subsec_nanos() as i32,
})
}
fn parse_uuid(s: &str, field: &str) -> Result<Uuid, Status> {
Uuid::parse_str(s).map_err(|e| Status::invalid_argument(format!("{field}: {e}")))
}
fn system_session() -> ImSession {
ImSession::new(Uuid::nil())
}
// Section: ChannelRoleGrpcService
pub struct ChannelRoleGrpcService {
service: AppService,
}
impl ChannelRoleGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
#[tonic::async_trait]
impl ChannelRoleService for ChannelRoleGrpcService {
async fn list_channel_roles(
&self,
request: Request<ListChannelRolesRequest>,
) -> Result<Response<ListChannelRolesResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let roles = self
.service
.im
.channel_role_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
let proto_roles = roles
.into_iter()
.map(|r| crate::pb::im::ChannelRole {
id: r.id.to_string(),
channel_id: r.channel_id.to_string(),
name: r.name,
permissions: r.permissions.iter().map(|p| p.to_string()).collect(),
assignable: r.assignable,
created_at: to_proto_ts(r.created_at),
updated_at: to_proto_ts(r.updated_at),
})
.collect();
Ok(Response::new(ListChannelRolesResponse {
roles: proto_roles,
}))
}
async fn create_channel_role(
&self,
request: Request<CreateChannelRoleRequest>,
) -> Result<Response<CreateChannelRoleResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = CreateChannelRoleParams {
name: req.name,
description: None,
permissions: req.permissions,
assignable: req.assignable,
};
let r = self
.service
.im
.channel_role_create(&session, channel_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateChannelRoleResponse {
role: Some(crate::pb::im::ChannelRole {
id: r.id.to_string(),
channel_id: r.channel_id.to_string(),
name: r.name,
permissions: r.permissions.iter().map(|p| p.to_string()).collect(),
assignable: r.assignable,
created_at: to_proto_ts(r.created_at),
updated_at: to_proto_ts(r.updated_at),
}),
}))
}
async fn update_channel_role(
&self,
request: Request<UpdateChannelRoleRequest>,
) -> Result<Response<UpdateChannelRoleResponse>, Status> {
let req = request.into_inner();
let role_id = parse_uuid(&req.role_id, "role_id")?;
let session = system_session();
let params = UpdateChannelRoleParams {
name: req.name,
description: None,
permissions: if req.permissions.is_empty() {
None
} else {
Some(req.permissions)
},
assignable: req.assignable,
};
let r = self
.service
.im
.channel_role_update(&session, role_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateChannelRoleResponse {
role: Some(crate::pb::im::ChannelRole {
id: r.id.to_string(),
channel_id: r.channel_id.to_string(),
name: r.name,
permissions: r.permissions.iter().map(|p| p.to_string()).collect(),
assignable: r.assignable,
created_at: to_proto_ts(r.created_at),
updated_at: to_proto_ts(r.updated_at),
}),
}))
}
async fn delete_channel_role(
&self,
request: Request<DeleteChannelRoleRequest>,
) -> Result<Response<DeleteChannelRoleResponse>, Status> {
let req = request.into_inner();
let role_id = parse_uuid(&req.role_id, "role_id")?;
let session = system_session();
self.service
.im
.channel_role_delete(&session, role_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteChannelRoleResponse {}))
}
}
// Section: ChannelInvitationGrpcService
pub struct ChannelInvitationGrpcService {
service: AppService,
}
impl ChannelInvitationGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn invitation_to_proto(inv: crate::models::channels::ChannelInvitation) -> crate::pb::im::ChannelInvitation {
let status = if inv.accepted_at.is_some() {
"accepted"
} else if inv.revoked_at.is_some() {
"revoked"
} else {
"pending"
};
crate::pb::im::ChannelInvitation {
id: inv.id.to_string(),
channel_id: inv.channel_id.to_string(),
invited_by: inv.invited_by.to_string(),
invited_user_id: inv.invited_user_id.map(|id| id.to_string()).unwrap_or_default(),
role: inv.role.to_string(),
status: status.to_string(),
created_at: to_proto_ts(inv.created_at),
updated_at: inv.accepted_at.or(inv.revoked_at).map(to_proto_ts).flatten().or_else(|| to_proto_ts(inv.created_at)),
}
}
#[tonic::async_trait]
impl ChannelInvitationService for ChannelInvitationGrpcService {
async fn list_invitations(
&self,
request: Request<ListInvitationsRequest>,
) -> Result<Response<ListInvitationsResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let invitations = self
.service
.im
.invitation_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListInvitationsResponse {
invitations: invitations.into_iter().map(invitation_to_proto).collect(),
}))
}
async fn create_invitation(
&self,
request: Request<CreateInvitationRequest>,
) -> Result<Response<CreateInvitationResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let invited_user_id = parse_uuid(&req.invited_user_id, "invited_user_id")?;
let session = system_session();
let params = CreateInvitationParams {
invited_user_id: Some(invited_user_id),
email: None,
role: Some(req.role),
expires_in_hours: None,
};
let inv = self
.service
.im
.invitation_create(&session, channel_id, Uuid::nil(), params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateInvitationResponse {
invitation: Some(invitation_to_proto(inv)),
}))
}
async fn accept_invitation(
&self,
request: Request<AcceptInvitationRequest>,
) -> Result<Response<AcceptInvitationResponse>, Status> {
let req = request.into_inner();
let invitation_id = parse_uuid(&req.invitation_id, "invitation_id")?;
let session = system_session();
let inv = self
.service
.im
.invitation_accept(&session, invitation_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(AcceptInvitationResponse {
invitation: Some(invitation_to_proto(inv)),
}))
}
async fn revoke_invitation(
&self,
request: Request<RevokeInvitationRequest>,
) -> Result<Response<RevokeInvitationResponse>, Status> {
let req = request.into_inner();
let invitation_id = parse_uuid(&req.invitation_id, "invitation_id")?;
let session = system_session();
self.service
.im
.invitation_revoke(&session, invitation_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(RevokeInvitationResponse {}))
}
}
// Section: ChannelWebhookGrpcService
pub struct ChannelWebhookGrpcService {
service: AppService,
}
impl ChannelWebhookGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn webhook_to_proto(wh: crate::models::channels::ChannelWebhook) -> crate::pb::im::ChannelWebhook {
crate::pb::im::ChannelWebhook {
id: wh.id.to_string(),
channel_id: wh.channel_id.to_string(),
name: wh.name,
url: wh.url,
secret: wh.secret_ciphertext.unwrap_or_default(),
events: wh.events.iter().map(|e| e.to_string()).collect(),
active: wh.active,
created_at: to_proto_ts(wh.created_at),
updated_at: to_proto_ts(wh.updated_at),
}
}
#[tonic::async_trait]
impl ChannelWebhookService for ChannelWebhookGrpcService {
async fn list_webhooks(
&self,
request: Request<ListWebhooksRequest>,
) -> Result<Response<ListWebhooksResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let webhooks = self
.service
.im
.webhook_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListWebhooksResponse {
webhooks: webhooks.into_iter().map(webhook_to_proto).collect(),
}))
}
async fn create_webhook(
&self,
request: Request<CreateWebhookRequest>,
) -> Result<Response<CreateWebhookResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = CreateWebhookParams {
name: req.name,
url: req.url,
secret: req.secret,
events: req.events,
};
let wh = self
.service
.im
.webhook_create(&session, channel_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateWebhookResponse {
webhook: Some(webhook_to_proto(wh)),
}))
}
async fn update_webhook(
&self,
request: Request<UpdateWebhookRequest>,
) -> Result<Response<UpdateWebhookResponse>, Status> {
let req = request.into_inner();
let webhook_id = parse_uuid(&req.webhook_id, "webhook_id")?;
let session = system_session();
let params = UpdateWebhookParams {
name: req.name,
url: req.url,
secret: req.secret,
events: if req.events.is_empty() {
None
} else {
Some(req.events)
},
active: req.active,
};
let wh = self
.service
.im
.webhook_update(&session, webhook_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateWebhookResponse {
webhook: Some(webhook_to_proto(wh)),
}))
}
async fn delete_webhook(
&self,
request: Request<DeleteWebhookRequest>,
) -> Result<Response<DeleteWebhookResponse>, Status> {
let req = request.into_inner();
let webhook_id = parse_uuid(&req.webhook_id, "webhook_id")?;
let session = system_session();
self.service
.im
.webhook_delete(&session, webhook_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteWebhookResponse {}))
}
}
// Section: ChannelSlashCommandGrpcService
pub struct ChannelSlashCommandGrpcService {
service: AppService,
}
impl ChannelSlashCommandGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn slash_command_to_proto(cmd: crate::models::channels::ChannelSlashCommand) -> crate::pb::im::ChannelSlashCommand {
crate::pb::im::ChannelSlashCommand {
id: cmd.id.to_string(),
channel_id: cmd.channel_id.map(|id| id.to_string()).unwrap_or_default(),
command: cmd.command,
description: cmd.description.unwrap_or_default(),
request_url: cmd.request_url,
scopes: cmd.scopes.iter().map(|s| s.to_string()).collect(),
created_at: to_proto_ts(cmd.created_at),
updated_at: to_proto_ts(cmd.updated_at),
}
}
#[tonic::async_trait]
impl ChannelSlashCommandService for ChannelSlashCommandGrpcService {
async fn list_slash_commands(
&self,
request: Request<ListSlashCommandsRequest>,
) -> Result<Response<ListSlashCommandsResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let commands = self
.service
.im
.slash_command_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListSlashCommandsResponse {
commands: commands.into_iter().map(slash_command_to_proto).collect(),
}))
}
async fn create_slash_command(
&self,
request: Request<CreateSlashCommandRequest>,
) -> Result<Response<CreateSlashCommandResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = CreateSlashCommandParams {
command: req.command,
description: Some(req.description),
request_url: req.request_url,
secret: None,
scopes: req.scopes,
};
let cmd = self
.service
.im
.slash_command_create(&session, channel_id, Uuid::nil(), params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateSlashCommandResponse {
command: Some(slash_command_to_proto(cmd)),
}))
}
async fn update_slash_command(
&self,
request: Request<UpdateSlashCommandRequest>,
) -> Result<Response<UpdateSlashCommandResponse>, Status> {
let req = request.into_inner();
let command_id = parse_uuid(&req.command_id, "command_id")?;
let session = system_session();
let params = UpdateSlashCommandParams {
command: None,
description: req.description,
request_url: req.request_url,
secret: None,
scopes: if req.scopes.is_empty() {
None
} else {
Some(req.scopes)
},
enabled: None,
};
let cmd = self
.service
.im
.slash_command_update(&session, command_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateSlashCommandResponse {
command: Some(slash_command_to_proto(cmd)),
}))
}
async fn delete_slash_command(
&self,
request: Request<DeleteSlashCommandRequest>,
) -> Result<Response<DeleteSlashCommandResponse>, Status> {
let req = request.into_inner();
let command_id = parse_uuid(&req.command_id, "command_id")?;
let session = system_session();
self.service
.im
.slash_command_delete(&session, command_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteSlashCommandResponse {}))
}
}
// Section: ChannelRepoLinkGrpcService
pub struct ChannelRepoLinkGrpcService {
service: AppService,
}
impl ChannelRepoLinkGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn repo_link_to_proto(link: crate::models::channels::ChannelRepoLink) -> crate::pb::im::ChannelRepoLink {
crate::pb::im::ChannelRepoLink {
id: link.id.to_string(),
channel_id: link.channel_id.to_string(),
repo_id: link.repo_id.to_string(),
link_type: link.link_type.to_string(),
events: link.notify_events.iter().map(|e| e.to_string()).collect(),
created_at: to_proto_ts(link.created_at),
updated_at: to_proto_ts(link.updated_at),
}
}
#[tonic::async_trait]
impl ChannelRepoLinkService for ChannelRepoLinkGrpcService {
async fn list_repo_links(
&self,
request: Request<ListRepoLinksRequest>,
) -> Result<Response<ListRepoLinksResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let links = self
.service
.im
.repo_link_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListRepoLinksResponse {
links: links.into_iter().map(repo_link_to_proto).collect(),
}))
}
async fn create_repo_link(
&self,
request: Request<CreateRepoLinkRequest>,
) -> Result<Response<CreateRepoLinkResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let repo_id = parse_uuid(&req.repo_id, "repo_id")?;
let session = system_session();
let params = CreateRepoLinkParams {
repo_id,
link_type: req.link_type,
notify_events: req.events,
};
let link = self
.service
.im
.repo_link_create(&session, channel_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateRepoLinkResponse {
link: Some(repo_link_to_proto(link)),
}))
}
async fn delete_repo_link(
&self,
request: Request<DeleteRepoLinkRequest>,
) -> Result<Response<DeleteRepoLinkResponse>, Status> {
let req = request.into_inner();
let link_id = parse_uuid(&req.link_id, "link_id")?;
let session = system_session();
self.service
.im
.repo_link_delete(&session, link_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteRepoLinkResponse {}))
}
}
// Section: ImIntegrationGrpcService
pub struct ImIntegrationGrpcService {
service: AppService,
}
impl ImIntegrationGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn integration_to_proto(integ: crate::models::channels::ImIntegration) -> crate::pb::im::ImIntegration {
crate::pb::im::ImIntegration {
id: integ.id.to_string(),
channel_id: integ.internal_channel_id.map(|id| id.to_string()).unwrap_or_default(),
provider: integ.provider.to_string(),
external_channel_id: integ.external_channel_id.unwrap_or_default(),
sync_direction: integ.sync_direction.to_string(),
active: integ.enabled,
created_at: to_proto_ts(integ.created_at),
updated_at: to_proto_ts(integ.updated_at),
}
}
#[tonic::async_trait]
impl ImIntegrationService for ImIntegrationGrpcService {
async fn list_integrations(
&self,
request: Request<ListIntegrationsRequest>,
) -> Result<Response<ListIntegrationsResponse>, Status> {
let req = request.into_inner();
let workspace_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let integrations = self
.service
.im
.integration_list(&session, workspace_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListIntegrationsResponse {
integrations: integrations.into_iter().map(integration_to_proto).collect(),
}))
}
async fn create_integration(
&self,
request: Request<CreateIntegrationRequest>,
) -> Result<Response<CreateIntegrationResponse>, Status> {
let req = request.into_inner();
let workspace_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = CreateIntegrationParams {
provider: req.provider,
name: String::new(),
external_workspace_id: None,
internal_channel_id: None,
external_channel_id: Some(req.external_channel_id),
bot_token: None,
webhook_url: None,
sync_direction: req.sync_direction,
};
let integ = self
.service
.im
.integration_create(&session, workspace_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateIntegrationResponse {
integration: Some(integration_to_proto(integ)),
}))
}
async fn update_integration(
&self,
request: Request<UpdateIntegrationRequest>,
) -> Result<Response<UpdateIntegrationResponse>, Status> {
let req = request.into_inner();
let integration_id = parse_uuid(&req.integration_id, "integration_id")?;
let session = system_session();
let params = UpdateIntegrationParams {
name: None,
external_channel_id: None,
webhook_url: None,
sync_direction: req.sync_direction,
enabled: req.active,
};
let integ = self
.service
.im
.integration_update(&session, integration_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateIntegrationResponse {
integration: Some(integration_to_proto(integ)),
}))
}
async fn delete_integration(
&self,
request: Request<DeleteIntegrationRequest>,
) -> Result<Response<DeleteIntegrationResponse>, Status> {
let req = request.into_inner();
let integration_id = parse_uuid(&req.integration_id, "integration_id")?;
let session = system_session();
self.service
.im
.integration_delete(&session, integration_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteIntegrationResponse {}))
}
}
// Section: CustomEmojiGrpcService
pub struct CustomEmojiGrpcService {
service: AppService,
}
impl CustomEmojiGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn emoji_to_proto(e: crate::models::channels::CustomEmoji) -> crate::pb::im::CustomEmoji {
crate::pb::im::CustomEmoji {
id: e.id.to_string(),
workspace_id: e.workspace_id.to_string(),
name: e.name,
image_url: e.url,
created_at: to_proto_ts(e.created_at),
}
}
#[tonic::async_trait]
impl CustomEmojiService for CustomEmojiGrpcService {
async fn list_custom_emojis(
&self,
request: Request<ListCustomEmojisRequest>,
) -> Result<Response<ListCustomEmojisResponse>, Status> {
let req = request.into_inner();
let workspace_id = parse_uuid(&req.workspace_id, "workspace_id")?;
let session = system_session();
let emojis = self
.service
.im
.custom_emoji_list(&session, workspace_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListCustomEmojisResponse {
emojis: emojis.into_iter().map(emoji_to_proto).collect(),
}))
}
async fn create_custom_emoji(
&self,
request: Request<CreateCustomEmojiRequest>,
) -> Result<Response<CreateCustomEmojiResponse>, Status> {
let req = request.into_inner();
let workspace_id = parse_uuid(&req.workspace_id, "workspace_id")?;
let session = system_session();
let params = CreateCustomEmojiParams {
name: req.name,
url: req.image_url,
animated: None,
};
let emoji = self
.service
.im
.custom_emoji_create(&session, workspace_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateCustomEmojiResponse {
emoji: Some(emoji_to_proto(emoji)),
}))
}
async fn delete_custom_emoji(
&self,
request: Request<DeleteCustomEmojiRequest>,
) -> Result<Response<DeleteCustomEmojiResponse>, Status> {
let req = request.into_inner();
let emoji_id = parse_uuid(&req.emoji_id, "emoji_id")?;
let session = system_session();
self.service
.im
.custom_emoji_delete(&session, emoji_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteCustomEmojiResponse {}))
}
}
// Section: ForumTagGrpcService
pub struct ForumTagGrpcService {
service: AppService,
}
impl ForumTagGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn forum_tag_to_proto(t: crate::models::channels::ForumTag) -> crate::pb::im::ForumTag {
crate::pb::im::ForumTag {
id: t.id.to_string(),
channel_id: t.channel_id.to_string(),
name: t.name,
moderated: t.moderated,
position: t.position,
created_at: to_proto_ts(t.created_at),
updated_at: to_proto_ts(t.updated_at),
}
}
#[tonic::async_trait]
impl ForumTagService for ForumTagGrpcService {
async fn list_forum_tags(
&self,
request: Request<ListForumTagsRequest>,
) -> Result<Response<ListForumTagsResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let tags = self
.service
.im
.forum_tag_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListForumTagsResponse {
tags: tags.into_iter().map(forum_tag_to_proto).collect(),
}))
}
async fn create_forum_tag(
&self,
request: Request<CreateForumTagRequest>,
) -> Result<Response<CreateForumTagResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = CreateForumTagParams {
name: req.name,
emoji_id: None,
emoji_name: None,
moderated: Some(req.moderated),
position: req.position,
};
let tag = self
.service
.im
.forum_tag_create(&session, channel_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateForumTagResponse {
tag: Some(forum_tag_to_proto(tag)),
}))
}
async fn update_forum_tag(
&self,
request: Request<UpdateForumTagRequest>,
) -> Result<Response<UpdateForumTagResponse>, Status> {
let req = request.into_inner();
let tag_id = parse_uuid(&req.tag_id, "tag_id")?;
let session = system_session();
let params = UpdateForumTagParams {
name: req.name,
emoji_id: None,
emoji_name: None,
moderated: req.moderated,
position: req.position,
};
let tag = self
.service
.im
.forum_tag_update(&session, tag_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateForumTagResponse {
tag: Some(forum_tag_to_proto(tag)),
}))
}
async fn delete_forum_tag(
&self,
request: Request<DeleteForumTagRequest>,
) -> Result<Response<DeleteForumTagResponse>, Status> {
let req = request.into_inner();
let tag_id = parse_uuid(&req.tag_id, "tag_id")?;
let session = system_session();
self.service
.im
.forum_tag_delete(&session, tag_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteForumTagResponse {}))
}
}
// Section: VoiceGrpcService
pub struct VoiceGrpcService {
service: AppService,
}
impl VoiceGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn voice_participant_to_proto(p: crate::models::channels::VoiceParticipant) -> crate::pb::im::VoiceParticipant {
crate::pb::im::VoiceParticipant {
id: p.id.to_string(),
channel_id: p.channel_id.to_string(),
user_id: p.user_id.to_string(),
muted: p.muted,
deafened: p.deafened,
joined_at: to_proto_ts(p.joined_at),
}
}
#[tonic::async_trait]
impl VoiceService for VoiceGrpcService {
async fn list_voice_participants(
&self,
request: Request<ListVoiceParticipantsRequest>,
) -> Result<Response<ListVoiceParticipantsResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let participants = self
.service
.im
.voice_participant_list(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(ListVoiceParticipantsResponse {
participants: participants
.into_iter()
.map(voice_participant_to_proto)
.collect(),
}))
}
async fn update_voice_state(
&self,
request: Request<UpdateVoiceStateRequest>,
) -> Result<Response<UpdateVoiceStateResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = UpdateVoiceStateParams {
session_id: None,
muted: req.muted,
deafened: req.deafened,
self_muted: None,
self_deafened: None,
self_video: None,
streaming: None,
};
let p = self
.service
.im
.voice_state_update(&session, channel_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateVoiceStateResponse {
participant: Some(voice_participant_to_proto(p)),
}))
}
}
// Section: StageGrpcService
pub struct StageGrpcService {
service: AppService,
}
impl StageGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn stage_to_proto(s: crate::models::channels::Stage) -> crate::pb::im::Stage {
crate::pb::im::Stage {
id: s.id.to_string(),
channel_id: s.channel_id.to_string(),
topic: s.topic,
privacy_level: s.privacy_level.to_string(),
discoverable: s.discoverable,
started_at: to_proto_ts(s.started_at),
ended_at: s.ended_at.map(to_proto_ts).flatten(),
created_at: to_proto_ts(s.created_at),
updated_at: to_proto_ts(s.updated_at),
}
}
#[tonic::async_trait]
impl StageService for StageGrpcService {
async fn get_stage(
&self,
request: Request<GetStageRequest>,
) -> Result<Response<GetStageResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let stage = self
.service
.im
.stage_get(&session, channel_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(GetStageResponse {
stage: stage.map(stage_to_proto),
}))
}
async fn create_stage(
&self,
request: Request<CreateStageRequest>,
) -> Result<Response<CreateStageResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let params = CreateStageParams {
topic: req.topic,
privacy_level: Some(req.privacy_level),
discoverable: Some(req.discoverable),
};
let stage = self
.service
.im
.stage_create(&session, channel_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(CreateStageResponse {
stage: Some(stage_to_proto(stage)),
}))
}
async fn update_stage(
&self,
request: Request<UpdateStageRequest>,
) -> Result<Response<UpdateStageResponse>, Status> {
let req = request.into_inner();
let stage_id = parse_uuid(&req.stage_id, "stage_id")?;
let session = system_session();
let params = UpdateStageParams {
topic: req.topic,
privacy_level: req.privacy_level,
discoverable: req.discoverable,
};
let stage = self
.service
.im
.stage_update(&session, stage_id, params)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(UpdateStageResponse {
stage: Some(stage_to_proto(stage)),
}))
}
async fn delete_stage(
&self,
request: Request<DeleteStageRequest>,
) -> Result<Response<DeleteStageResponse>, Status> {
let req = request.into_inner();
let stage_id = parse_uuid(&req.stage_id, "stage_id")?;
let session = system_session();
self.service
.im
.stage_delete(&session, stage_id)
.await
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(DeleteStageResponse {}))
}
}
// Section: ChannelAuditGrpcService
pub struct ChannelAuditGrpcService {
service: AppService,
}
impl ChannelAuditGrpcService {
pub fn new(service: AppService) -> Self {
Self { service }
}
}
fn audit_event_to_proto(e: crate::models::channels::ChannelEvent) -> crate::pb::im::ChannelAuditEvent {
crate::pb::im::ChannelAuditEvent {
id: e.id.to_string(),
channel_id: e.channel_id.to_string(),
actor_id: e.actor_id.map(|id| id.to_string()).unwrap_or_default(),
event_type: e.event_type.to_string(),
target_type: e.target_type.map(|t| t.to_string()).unwrap_or_default(),
target_id: e.target_id.map(|id| id.to_string()).unwrap_or_default(),
old_value: e.old_value.map(|v| v.to_string()),
new_value: e.new_value.map(|v| v.to_string()),
created_at: to_proto_ts(e.created_at),
}
}
#[tonic::async_trait]
impl ChannelAuditService for ChannelAuditGrpcService {
async fn list_channel_events(
&self,
request: Request<ListChannelEventsRequest>,
) -> Result<Response<ListChannelEventsResponse>, Status> {
let req = request.into_inner();
let channel_id = parse_uuid(&req.channel_id, "channel_id")?;
let session = system_session();
let events = self
.service
.im
.audit_list(&session, channel_id, req.limit as i64, req.offset as i64)
.await
.map_err(|e| Status::internal(e.to_string()))?;
let total = events.len() as i32;
Ok(Response::new(ListChannelEventsResponse {
events: events.into_iter().map(audit_event_to_proto).collect(),
total,
}))
}
}
pub struct ChannelSettingsServices {
pub channel_role: ChannelRoleGrpcService,
pub channel_invitation: ChannelInvitationGrpcService,
pub channel_webhook: ChannelWebhookGrpcService,
pub channel_slash_command: ChannelSlashCommandGrpcService,
pub channel_repo_link: ChannelRepoLinkGrpcService,
pub im_integration: ImIntegrationGrpcService,
pub custom_emoji: CustomEmojiGrpcService,
pub forum_tag: ForumTagGrpcService,
pub voice: VoiceGrpcService,
pub stage: StageGrpcService,
pub channel_audit: ChannelAuditGrpcService,
}
impl ChannelSettingsServices {
pub fn new(service: crate::service::AppService) -> Self {
Self {
channel_role: ChannelRoleGrpcService::new(service.clone()),
channel_invitation: ChannelInvitationGrpcService::new(service.clone()),
channel_webhook: ChannelWebhookGrpcService::new(service.clone()),
channel_slash_command: ChannelSlashCommandGrpcService::new(service.clone()),
channel_repo_link: ChannelRepoLinkGrpcService::new(service.clone()),
im_integration: ImIntegrationGrpcService::new(service.clone()),
custom_emoji: CustomEmojiGrpcService::new(service.clone()),
forum_tag: ForumTagGrpcService::new(service.clone()),
voice: VoiceGrpcService::new(service.clone()),
stage: StageGrpcService::new(service.clone()),
channel_audit: ChannelAuditGrpcService::new(service),
}
}
}