use crate::{ApiResponse, error::ApiError}; use actix_web::{HttpResponse, Result, web}; use service::AppService; use service::workspace::members::{ PendingInvitationInfo, WorkspaceInviteAcceptParams, WorkspaceInviteParams, WorkspaceMembersResponse, }; use session::Session; use uuid::Uuid; #[derive(serde::Deserialize, utoipa::IntoParams)] pub struct MembersQuery { pub page: Option, pub per_page: Option, } #[utoipa::path( get, path = "/api/workspaces/{slug}/members", params( ("slug" = String, Path), ("page" = Option, Query), ("per_page" = Option, Query), ), responses( (status = 200, description = "List workspace members", body = ApiResponse), (status = 401, description = "Unauthorized"), (status = 403, description = "Not a member"), (status = 404, description = "Workspace not found"), ), tag = "Workspace" )] pub async fn workspace_members( service: web::Data, session: Session, path: web::Path, query: web::Query, ) -> Result { let slug = path.into_inner(); let resp = service .workspace_members(&session, slug, query.page, query.per_page) .await?; Ok(ApiResponse::ok(resp).to_response()) } #[derive(serde::Deserialize, utoipa::ToSchema)] pub struct UpdateRoleParams { pub user_id: Uuid, pub role: String, } #[utoipa::path( patch, path = "/api/workspaces/{slug}/members/role", params(("slug" = String, Path)), request_body = UpdateRoleParams, responses( (status = 200, description = "Update member role"), (status = 401, description = "Unauthorized"), (status = 403, description = "Permission denied"), (status = 404, description = "Workspace or member not found"), ), tag = "Workspace" )] pub async fn workspace_update_member_role( service: web::Data, session: Session, path: web::Path, body: web::Json, ) -> Result { let slug = path.into_inner(); service .workspace_update_member_role(&session, slug, body.user_id, body.role.clone()) .await?; Ok(ApiResponse::ok(serde_json::json!({ "success": true })).to_response()) } #[utoipa::path( delete, path = "/api/workspaces/{slug}/members/{user_id}", params( ("slug" = String, Path), ("user_id" = Uuid, Path), ), responses( (status = 200, description = "Remove member"), (status = 401, description = "Unauthorized"), (status = 403, description = "Permission denied"), (status = 404, description = "Member not found"), ), tag = "Workspace" )] pub async fn workspace_remove_member( service: web::Data, session: Session, path: web::Path<(String, Uuid)>, ) -> Result { let (slug, user_id) = path.into_inner(); service .workspace_remove_member(&session, slug, user_id) .await?; Ok(ApiResponse::ok(serde_json::json!({ "success": true })).to_response()) } #[utoipa::path( get, path = "/api/workspaces/{slug}/invitations", params(("slug" = String, Path)), responses( (status = 200, description = "List pending invitations", body = ApiResponse>), (status = 401, description = "Unauthorized"), (status = 403, description = "Permission denied"), (status = 404, description = "Workspace not found"), ), tag = "Workspace" )] pub async fn workspace_pending_invitations( service: web::Data, session: Session, path: web::Path, ) -> Result { let slug = path.into_inner(); let resp = service .workspace_pending_invitations(&session, slug) .await?; Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( delete, path = "/api/workspaces/{slug}/invitations/{user_id}", params( ("slug" = String, Path), ("user_id" = Uuid, Path), ), responses( (status = 200, description = "Cancel invitation"), (status = 401, description = "Unauthorized"), (status = 403, description = "Permission denied"), (status = 404, description = "Invitation not found"), ), tag = "Workspace" )] pub async fn workspace_cancel_invitation( service: web::Data, session: Session, path: web::Path<(String, Uuid)>, ) -> Result { let (slug, user_id) = path.into_inner(); service .workspace_cancel_invitation(&session, slug, user_id) .await?; Ok(ApiResponse::ok(serde_json::json!({ "success": true })).to_response()) } #[utoipa::path( post, path = "/api/workspaces/{slug}/invitations", params(("slug" = String, Path)), request_body = WorkspaceInviteParams, responses( (status = 200, description = "Send invitation"), (status = 401, description = "Unauthorized"), (status = 403, description = "Permission denied"), (status = 404, description = "User not found"), (status = 409, description = "Already a member"), ), tag = "Workspace" )] pub async fn workspace_invite_member( service: web::Data, session: Session, path: web::Path, body: web::Json, ) -> Result { let slug = path.into_inner(); service .workspace_invite_member(&session, slug, body.into_inner()) .await?; Ok(ApiResponse::ok(serde_json::json!({ "success": true })).to_response()) } #[utoipa::path( post, path = "/api/workspaces/invitations/accept", request_body = WorkspaceInviteAcceptParams, responses( (status = 200, description = "Accept invitation", body = ApiResponse), (status = 400, description = "Invalid or expired token"), (status = 401, description = "Unauthorized"), (status = 409, description = "Already accepted"), ), tag = "Workspace" )] pub async fn workspace_accept_invitation( service: web::Data, session: Session, body: web::Json, ) -> Result { let ws = service .workspace_accept_invitation(&session, body.into_inner()) .await?; let resp = service.workspace_info(&session, ws.slug).await?; Ok(ApiResponse::ok(resp).to_response()) }