feat(api): expand API endpoints for repo, PR, user, workspace management

- Add git operation endpoints: archive, compare branches, diff, tree,
  repository extras
- Add repo endpoints: contributors, delete fork, get branch/commit
  status/deploy key/invitation/member/release/tag/webhook, topics,
  release assets, webhook deliveries/retry
- Add PR endpoints: review requests, templates
- Add user endpoints: block/unblock, follow/unfollow, presence,
  personal access tokens, account restore
- Add workspace endpoints: billing history, approvals, domains,
  integrations, invitations, members, webhooks, restore
- Add internal API, notification API, IM API modules
- Update route configuration and OpenAPI spec
This commit is contained in:
zhenyi
2026-06-10 18:49:27 +08:00
parent 4586b79cb8
commit cec6dce955
161 changed files with 7522 additions and 349 deletions
+51
View File
@@ -0,0 +1,51 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelCategory;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::categories::CreateCategoryParams;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
}
#[utoipa::path(
post,
path = "/api/v1/im/workspaces/{workspace_name}/categories",
tag = "IM",
operation_id = "imCategoryCreate",
params(PathParams),
request_body(
content = CreateCategoryParams,
description = "Category creation parameters",
content_type = "application/json"
),
responses(
(status = 201, description = "Category created successfully", body = ApiResponse<ChannelCategory>),
(status = 400, description = "Invalid parameters", body = ApiErrorResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn category_create(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
params: web::Json<CreateCategoryParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.category_create(&im_session, &path.workspace_name, params.into_inner())
.await?;
Ok(HttpResponse::Created().json(ApiResponse::new(result)))
}
+44
View File
@@ -0,0 +1,44 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiEmptyResponse, ApiErrorResponse};
use crate::error::AppError;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub category_id: uuid::Uuid,
}
/// Delete a category
#[utoipa::path(
delete,
path = "/api/v1/im/workspaces/{workspace_name}/categories/{category_id}",
tag = "IM",
operation_id = "imCategoryDelete",
params(PathParams),
responses(
(status = 200, description = "Category deleted successfully", body = ApiEmptyResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or category not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn category_delete(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
service
.im
.category_delete(&im_session, &path.workspace_name, path.category_id)
.await?;
Ok(HttpResponse::Ok().json(ApiEmptyResponse::ok("Category deleted")))
}
+44
View File
@@ -0,0 +1,44 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelCategory;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
}
/// List categories
#[utoipa::path(
get,
path = "/api/v1/im/workspaces/{workspace_name}/categories",
tag = "IM",
operation_id = "imCategoryList",
params(PathParams),
responses(
(status = 200, description = "Categories listed successfully", body = ApiResponse<Vec<ChannelCategory>>),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn category_list(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.category_list(&im_session, &path.workspace_name)
.await?;
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
}
+58
View File
@@ -0,0 +1,58 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelCategory;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::categories::UpdateCategoryParams;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub category_id: uuid::Uuid,
}
/// Update a category
#[utoipa::path(
put,
path = "/api/v1/im/workspaces/{workspace_name}/categories/{category_id}",
tag = "IM",
operation_id = "imCategoryUpdate",
params(PathParams),
request_body(
content = UpdateCategoryParams,
description = "Category update parameters",
content_type = "application/json"
),
responses(
(status = 200, description = "Category updated successfully", body = ApiResponse<ChannelCategory>),
(status = 400, description = "Invalid parameters", body = ApiErrorResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or category not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn category_update(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
params: web::Json<UpdateCategoryParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.category_update(
&im_session,
&path.workspace_name,
path.category_id,
params.into_inner(),
)
.await?;
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
}
+65
View File
@@ -0,0 +1,65 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::base_info::resolve_users;
use crate::models::channels::ChannelDetail;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::channels::CreateChannelParams;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
}
/// Create a channel
#[utoipa::path(
post,
path = "/api/v1/im/workspaces/{workspace_name}/channels",
tag = "IM",
operation_id = "imChannelCreate",
params(PathParams),
request_body(
content = CreateChannelParams,
description = "Channel creation parameters",
content_type = "application/json"
),
responses(
(status = 201, description = "Channel created successfully", body = ApiResponse<ChannelDetail>),
(status = 400, description = "Invalid parameters", body = ApiErrorResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn channel_create(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
params: web::Json<CreateChannelParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let request_id = uuid::Uuid::now_v7();
let channel = service
.im
.channel_create(
&im_session,
&path.workspace_name,
params.into_inner(),
request_id,
)
.await?;
let db = &service.ctx.db;
let users = resolve_users(db, &[channel.created_by]).await?;
let creator = users.get(&channel.created_by).cloned().unwrap_or_default();
let detail = channel.into_detail(creator);
Ok(HttpResponse::Created().json(ApiResponse::new(detail)))
}
+50
View File
@@ -0,0 +1,50 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiEmptyResponse, ApiErrorResponse};
use crate::error::AppError;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
/// Delete a channel
#[utoipa::path(
delete,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}",
tag = "IM",
operation_id = "imChannelDelete",
params(PathParams),
responses(
(status = 200, description = "Channel deleted successfully", body = ApiEmptyResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn channel_delete(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let request_id = uuid::Uuid::now_v7();
service
.im
.channel_delete(
&im_session,
&path.workspace_name,
path.channel_id,
request_id,
)
.await?;
Ok(HttpResponse::Ok().json(ApiEmptyResponse::ok("Channel deleted")))
}
+52
View File
@@ -0,0 +1,52 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::base_info::resolve_users;
use crate::models::channels::ChannelDetail;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
/// Get a channel
#[utoipa::path(
get,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}",
tag = "IM",
operation_id = "imChannelGet",
params(PathParams),
responses(
(status = 200, description = "Channel retrieved successfully", body = ApiResponse<ChannelDetail>),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn channel_get(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let channel = service
.im
.channel_get(&im_session, &path.workspace_name, path.channel_id)
.await?;
let db = &service.ctx.db;
let users = resolve_users(db, &[channel.created_by]).await?;
let creator = users.get(&channel.created_by).cloned().unwrap_or_default();
let detail = channel.into_detail(creator);
Ok(HttpResponse::Ok().json(ApiResponse::new(detail)))
}
+82
View File
@@ -0,0 +1,82 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use uuid::Uuid;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::base_info::resolve_users;
use crate::models::channels::ChannelDetail;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::channels::ChannelListFilters;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct QueryParams {
pub channel_type: Option<String>,
pub channel_kind: Option<String>,
pub category_id: Option<uuid::Uuid>,
pub archived: Option<bool>,
pub limit: Option<i64>,
pub offset: Option<i64>,
}
/// List channels
#[utoipa::path(
get,
path = "/api/v1/im/workspaces/{workspace_name}/channels",
tag = "IM",
operation_id = "imChannelList",
params(PathParams, QueryParams),
responses(
(status = 200, description = "Channels listed successfully", body = ApiResponse<Vec<ChannelDetail>>),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn channel_list(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
query: web::Query<QueryParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let filters = ChannelListFilters {
channel_type: query.channel_type.clone(),
channel_kind: query.channel_kind.clone(),
category_id: query.category_id,
archived: query.archived,
};
let result = service
.im
.channel_list(
&im_session,
&path.workspace_name,
filters,
query.limit.unwrap_or(50),
query.offset.unwrap_or(0),
)
.await?;
let db = &service.ctx.db;
let creator_ids: Vec<Uuid> = result.iter().map(|c| c.created_by).collect();
let users = resolve_users(db, &creator_ids).await?;
let details: Vec<ChannelDetail> = result
.into_iter()
.map(|c| {
let creator = users.get(&c.created_by).cloned().unwrap_or_default();
c.into_detail(creator)
})
.collect();
Ok(HttpResponse::Ok().json(ApiResponse::new(details)))
}
+67
View File
@@ -0,0 +1,67 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::base_info::resolve_users;
use crate::models::channels::ChannelDetail;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::channels::UpdateChannelParams;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
/// Update a channel
#[utoipa::path(
put,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}",
tag = "IM",
operation_id = "imChannelUpdate",
params(PathParams),
request_body(
content = UpdateChannelParams,
description = "Channel update parameters",
content_type = "application/json"
),
responses(
(status = 200, description = "Channel updated successfully", body = ApiResponse<ChannelDetail>),
(status = 400, description = "Invalid parameters", body = ApiErrorResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn channel_update(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
params: web::Json<UpdateChannelParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let request_id = uuid::Uuid::now_v7();
let channel = service
.im
.channel_update(
&im_session,
&path.workspace_name,
path.channel_id,
params.into_inner(),
request_id,
)
.await?;
let db = &service.ctx.db;
let users = resolve_users(db, &[channel.created_by]).await?;
let creator = users.get(&channel.created_by).cloned().unwrap_or_default();
let detail = channel.into_detail(creator);
Ok(HttpResponse::Ok().json(ApiResponse::new(detail)))
}
+58
View File
@@ -0,0 +1,58 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelMember;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::members::InviteMemberParams;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
/// Invite a member
#[utoipa::path(
post,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}/members",
tag = "IM",
operation_id = "imMemberInvite",
params(PathParams),
request_body(
content = InviteMemberParams,
description = "Invitation parameters",
content_type = "application/json"
),
responses(
(status = 201, description = "Member invited successfully", body = ApiResponse<ChannelMember>),
(status = 400, description = "Invalid parameters", body = ApiErrorResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn member_invite(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
params: web::Json<InviteMemberParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.member_invite(
&im_session,
&path.workspace_name,
path.channel_id,
params.into_inner(),
)
.await?;
Ok(HttpResponse::Created().json(ApiResponse::new(result)))
}
+45
View File
@@ -0,0 +1,45 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelMember;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
/// Join a channel
#[utoipa::path(
post,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}/join",
tag = "IM",
operation_id = "imMemberJoin",
params(PathParams),
responses(
(status = 200, description = "Joined channel successfully", body = ApiResponse<ChannelMember>),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn member_join(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.member_join(&im_session, &path.workspace_name, path.channel_id)
.await?;
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
}
+50
View File
@@ -0,0 +1,50 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiEmptyResponse, ApiErrorResponse};
use crate::error::AppError;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
pub user_id: uuid::Uuid,
}
/// Kick a member
#[utoipa::path(
delete,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}/members/{user_id}",
tag = "IM",
operation_id = "imMemberKick",
params(PathParams),
responses(
(status = 200, description = "Member kicked successfully", body = ApiEmptyResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace, channel or member not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn member_kick(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
service
.im
.member_kick(
&im_session,
&path.workspace_name,
path.channel_id,
path.user_id,
)
.await?;
Ok(HttpResponse::Ok().json(ApiEmptyResponse::ok("Member kicked")))
}
+44
View File
@@ -0,0 +1,44 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiEmptyResponse, ApiErrorResponse};
use crate::error::AppError;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
/// Leave a channel
#[utoipa::path(
post,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}/leave",
tag = "IM",
operation_id = "imMemberLeave",
params(PathParams),
responses(
(status = 200, description = "Left channel successfully", body = ApiEmptyResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn member_leave(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
service
.im
.member_leave(&im_session, &path.workspace_name, path.channel_id)
.await?;
Ok(HttpResponse::Ok().json(ApiEmptyResponse::ok("Left channel")))
}
+58
View File
@@ -0,0 +1,58 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelMember;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct QueryParams {
pub limit: Option<i64>,
pub offset: Option<i64>,
}
/// List channel members
#[utoipa::path(
get,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}/members",
tag = "IM",
operation_id = "imMemberList",
params(PathParams, QueryParams),
responses(
(status = 200, description = "Members listed successfully", body = ApiResponse<Vec<ChannelMember>>),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace or channel not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn member_list(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
query: web::Query<QueryParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.member_list(
&im_session,
&path.workspace_name,
path.channel_id,
query.limit.unwrap_or(50),
query.offset.unwrap_or(0),
)
.await?;
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
}
+60
View File
@@ -0,0 +1,60 @@
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::IntoParams;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::models::channels::ChannelMember;
use crate::service::AppService;
use crate::service::im::ImSession;
use crate::service::im::members::UpdateMemberParams;
use crate::session::Session;
#[derive(Debug, Deserialize, IntoParams)]
pub struct PathParams {
pub workspace_name: String,
pub channel_id: uuid::Uuid,
pub user_id: uuid::Uuid,
}
/// Update member role
#[utoipa::path(
put,
path = "/api/v1/im/workspaces/{workspace_name}/channels/{channel_id}/members/{user_id}",
tag = "IM",
operation_id = "imMemberUpdate",
params(PathParams),
request_body(
content = UpdateMemberParams,
description = "Member update parameters",
content_type = "application/json"
),
responses(
(status = 200, description = "Member updated successfully", body = ApiResponse<ChannelMember>),
(status = 400, description = "Invalid parameters", body = ApiErrorResponse),
(status = 401, description = "Authentication required", body = ApiErrorResponse),
(status = 404, description = "Workspace, channel or member not found", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
),
security(("session_cookie" = []))
)]
pub async fn member_update(
service: web::Data<AppService>,
session: Session,
path: web::Path<PathParams>,
params: web::Json<UpdateMemberParams>,
) -> Result<HttpResponse, AppError> {
let user_id = session.user().ok_or(AppError::Unauthorized)?;
let im_session = ImSession::new(user_id);
let result = service
.im
.member_update(
&im_session,
&path.workspace_name,
path.channel_id,
path.user_id,
params.into_inner(),
)
.await?;
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
}
+77
View File
@@ -0,0 +1,77 @@
pub mod category_create;
pub mod category_delete;
pub mod category_list;
pub mod category_update;
pub mod channel_create;
pub mod channel_delete;
pub mod channel_get;
pub mod channel_list;
pub mod channel_update;
pub mod member_invite;
pub mod member_join;
pub mod member_kick;
pub mod member_leave;
pub mod member_list;
pub mod member_update;
use actix_web::web;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/im/workspaces/{workspace_name}")
// Channels
.route("/channels", web::get().to(channel_list::channel_list))
.route("/channels", web::post().to(channel_create::channel_create))
.route(
"/channels/{channel_id}",
web::get().to(channel_get::channel_get),
)
.route(
"/channels/{channel_id}",
web::put().to(channel_update::channel_update),
)
.route(
"/channels/{channel_id}",
web::delete().to(channel_delete::channel_delete),
)
// Members
.route(
"/channels/{channel_id}/members",
web::get().to(member_list::member_list),
)
.route(
"/channels/{channel_id}/members",
web::post().to(member_invite::member_invite),
)
.route(
"/channels/{channel_id}/members/{user_id}",
web::put().to(member_update::member_update),
)
.route(
"/channels/{channel_id}/members/{user_id}",
web::delete().to(member_kick::member_kick),
)
.route(
"/channels/{channel_id}/join",
web::post().to(member_join::member_join),
)
.route(
"/channels/{channel_id}/leave",
web::post().to(member_leave::member_leave),
)
// Categories
.route("/categories", web::get().to(category_list::category_list))
.route(
"/categories",
web::post().to(category_create::category_create),
)
.route(
"/categories/{category_id}",
web::put().to(category_update::category_update),
)
.route(
"/categories/{category_id}",
web::delete().to(category_delete::category_delete),
),
);
}