main
use ::session::AuthedUser;
use api::http::{Json, Path, Query, State};
use api::rest::{AppResponseData, NoData};
use uuid::Uuid;
use crate::errors::ApiError;
use crate::services::messages::{
BulkDeleteCmd, ForwardMessageCmd, ListMessageQuery, MessageDto, MessageEditDto, MessagePage,
MessagePath, PinCmd, SendMessageCmd, UpdateMessageCmd,
};
use crate::services::service::ChatService;
#[utoipa::path(
post,
path = "/chat/rooms/{room_id}/messages",
tag = "chat",
description = "向房间发送消息。",
params(("room_id" = Uuid, Path, description = "room id")),
request_body = SendMessageCmd,
responses(
(status = 200, description = "message sent", body = AppResponseData<MessageDto>),
(status = 400, description = "bad request", body = AppResponseData<NoData>),
(status = 403, description = "forbidden", body = AppResponseData<NoData>),
(status = 409, description = "resource already exists", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn send_message(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(room_id): Path<Uuid>,
Json(cmd): Json<SendMessageCmd>,
) -> Result<AppResponseData<MessageDto>, ApiError> {
let dto = service.send_message(uid, room_id, cmd).await?;
Ok(AppResponseData::ok(dto))
}
#[utoipa::path(
get,
path = "/chat/rooms/{room_id}/messages",
tag = "chat",
description = "按房间序号过滤并分页读取消息;items 按 seq 升序,附带 has_more / oldest_seq / newest_seq 供游标翻页。",
params(("room_id" = Uuid, Path, description = "room id"), ListMessageQuery),
responses(
(status = 200, description = "message page", body = AppResponseData<MessagePage>),
(status = 404, description = "not found", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn list_messages(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(room_id): Path<Uuid>,
Query(q): Query<ListMessageQuery>,
) -> Result<AppResponseData<MessagePage>, ApiError> {
let page = service.list_messages(uid, room_id, q).await?;
Ok(AppResponseData::ok(page))
}
#[utoipa::path(
get,
path = "/chat/rooms/{room_id}/messages/{message_id}",
tag = "chat",
description = "读取指定房间中单条未删除消息的详情。",
params(MessagePath),
responses(
(status = 200, description = "message detail", body = AppResponseData<MessageDto>),
(status = 404, description = "not found", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn get_message(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(path): Path<MessagePath>,
) -> Result<AppResponseData<MessageDto>, ApiError> {
let dto = service
.get_message(uid, path.room_id, path.message_id)
.await?;
Ok(AppResponseData::ok(dto))
}
#[utoipa::path(
patch,
path = "/chat/rooms/{room_id}/messages/{message_id}",
tag = "chat",
description = "编辑自己或空间管理员有权修改的消息,并保存历史版本。",
params(MessagePath),
request_body = UpdateMessageCmd,
responses(
(status = 200, description = "message updated", body = AppResponseData<MessageDto>),
(status = 403, description = "forbidden", body = AppResponseData<NoData>),
(status = 400, description = "request failed validation", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn update_message(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(path): Path<MessagePath>,
Json(cmd): Json<UpdateMessageCmd>,
) -> Result<AppResponseData<MessageDto>, ApiError> {
let dto = service
.update_message(uid, path.room_id, path.message_id, cmd)
.await?;
Ok(AppResponseData::ok(dto))
}
#[utoipa::path(
delete,
path = "/chat/rooms/{room_id}/messages/{message_id}",
tag = "chat",
description = "软删除指定消息并广播删除事件;作者或空间管理员可操作。",
params(MessagePath),
responses(
(status = 200, description = "message deleted", body = AppResponseData<NoData>),
(status = 403, description = "forbidden", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn delete_message(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(path): Path<MessagePath>,
) -> Result<AppResponseData<NoData>, ApiError> {
service
.delete_message(uid, path.room_id, path.message_id)
.await?;
Ok(AppResponseData::empty())
}
#[utoipa::path(
post,
path = "/chat/rooms/{room_id}/messages/bulk-delete",
tag = "chat",
description = "批量软删除同一房间内最多 100 条有权删除的消息。",
params(("room_id" = Uuid, Path, description = "room id")),
request_body = BulkDeleteCmd,
responses(
(status = 200, description = "messages deleted", body = AppResponseData<NoData>),
(status = 403, description = "forbidden", body = AppResponseData<NoData>),
(status = 409, description = "resource already exists", body = AppResponseData<NoData>),
(status = 400, description = "request failed validation", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn bulk_delete_messages(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(room_id): Path<Uuid>,
Json(cmd): Json<BulkDeleteCmd>,
) -> Result<AppResponseData<NoData>, ApiError> {
service.bulk_delete_messages(uid, room_id, cmd).await?;
Ok(AppResponseData::empty())
}
#[utoipa::path(
put,
path = "/chat/rooms/{room_id}/messages/{message_id}/pin",
tag = "chat",
description = "置顶房间消息或取消置顶,并同步房间实时事件。",
params(MessagePath),
request_body = PinCmd,
responses(
(status = 200, description = "pin updated", body = AppResponseData<MessageDto>),
(status = 404, description = "not found", body = AppResponseData<NoData>),
(status = 400, description = "request failed validation", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn set_message_pin(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(path): Path<MessagePath>,
Json(cmd): Json<PinCmd>,
) -> Result<AppResponseData<MessageDto>, ApiError> {
let dto = service
.set_message_pin(uid, path.room_id, path.message_id, cmd)
.await?;
Ok(AppResponseData::ok(dto))
}
#[utoipa::path(
get,
path = "/chat/rooms/{room_id}/pins",
tag = "chat",
description = "返回房间内当前置顶且未删除的消息列表。",
params(("room_id" = Uuid, Path, description = "room id")),
responses(
(status = 200, description = "pinned messages", body = AppResponseData<Vec<MessageDto>>),
(status = 404, description = "not found", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn list_pins(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(room_id): Path<Uuid>,
) -> Result<AppResponseData<Vec<MessageDto>>, ApiError> {
let pins = service.list_pins(uid, room_id).await?;
Ok(AppResponseData::ok(pins))
}
#[utoipa::path(
post,
path = "/chat/rooms/{room_id}/messages/{message_id}/forward",
tag = "chat",
description = "将源房间消息转发到目标房间,并按发送消息权限执行校验。",
params(MessagePath),
request_body = ForwardMessageCmd,
responses(
(status = 200, description = "message forwarded", body = AppResponseData<MessageDto>),
(status = 404, description = "not found", body = AppResponseData<NoData>),
(status = 409, description = "resource already exists", body = AppResponseData<NoData>),
(status = 400, description = "request failed validation", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn forward_message(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(path): Path<MessagePath>,
Json(cmd): Json<ForwardMessageCmd>,
) -> Result<AppResponseData<MessageDto>, ApiError> {
let dto = service
.forward_message(uid, path.room_id, path.message_id, cmd)
.await?;
Ok(AppResponseData::ok(dto))
}
#[utoipa::path(
get,
path = "/chat/rooms/{room_id}/messages/{message_id}/history",
tag = "chat",
description = "读取指定消息历次编辑保存的内容快照。",
params(MessagePath),
responses(
(status = 200, description = "edit history", body = AppResponseData<Vec<MessageEditDto>>),
(status = 404, description = "not found", body = AppResponseData<NoData>),
(status = 401, description = "authentication required", body = AppResponseData<NoData>)
)
)]
pub async fn list_message_history(
State(service): State<ChatService>,
AuthedUser(uid): AuthedUser,
Path(path): Path<MessagePath>,
) -> Result<AppResponseData<Vec<MessageEditDto>>, ApiError> {
let history = service
.list_message_history(uid, path.room_id, path.message_id)
.await?;
Ok(AppResponseData::ok(history))
}