1000f8a80d
- 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
1283 lines
41 KiB
Rust
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),
|
|
}
|
|
}
|
|
}
|