791 lines
21 KiB
Go
791 lines
21 KiB
Go
|
|
package usecase
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"backend/pkg/chat/domain/chat"
|
|||
|
|
"backend/pkg/chat/domain/entity"
|
|||
|
|
"backend/pkg/chat/domain/repository"
|
|||
|
|
"backend/pkg/chat/domain/usecase"
|
|||
|
|
"backend/pkg/library/cassandra"
|
|||
|
|
errs "backend/pkg/library/errors"
|
|||
|
|
"context"
|
|||
|
|
"fmt"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/gocql/gocql"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
defaultRoomPageSize = 20
|
|||
|
|
maxRoomPageSize = 100
|
|||
|
|
defaultRoomStatus = "active"
|
|||
|
|
defaultMemberRole = "member"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type RoomUseCaseParam struct {
|
|||
|
|
RoomRepo repository.RoomRepository
|
|||
|
|
Logger errs.Logger
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type RoomUseCase struct {
|
|||
|
|
RoomUseCaseParam
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewRoomUseCase 創建新的聊天室 UseCase
|
|||
|
|
func NewRoomUseCase(param RoomUseCaseParam) usecase.RoomUseCase {
|
|||
|
|
return &RoomUseCase{
|
|||
|
|
param,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 聊天室管理 ====================
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) CreateRoom(ctx context.Context, req usecase.CreateRoomReq) (*usecase.Room, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateCreateRoomReq(req); err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 設置預設值
|
|||
|
|
status := req.Status
|
|||
|
|
if status == "" {
|
|||
|
|
status = defaultRoomStatus
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 創建聊天室實體
|
|||
|
|
now := time.Now().UTC().UnixNano()
|
|||
|
|
room := &entity.Room{
|
|||
|
|
Name: req.Name,
|
|||
|
|
Status: status,
|
|||
|
|
CreatedAt: now,
|
|||
|
|
UpdatedAt: now,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存聊天室
|
|||
|
|
if err := uc.RoomRepo.Create(ctx, room); err != nil {
|
|||
|
|
return nil, uc.logError("RoomRepo.Create", req, err, "failed to create room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 將創建者添加為管理員
|
|||
|
|
member := &entity.RoomMember{
|
|||
|
|
RoomID: room.RoomID,
|
|||
|
|
UID: req.UID,
|
|||
|
|
Role: chat.RoomRoleAdmin.String(),
|
|||
|
|
JoinedAt: now,
|
|||
|
|
}
|
|||
|
|
if err := uc.RoomRepo.Insert(ctx, member); err != nil {
|
|||
|
|
// 如果添加成員失敗,嘗試刪除已創建的聊天室(清理)
|
|||
|
|
_ = uc.RoomRepo.RoomDelete(ctx, room.RoomID.String())
|
|||
|
|
return nil, uc.logError("RoomRepo.Insert", req, err, "failed to add creator as admin")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return uc.entityRoomToUseCase(room), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) GetRoom(ctx context.Context, req usecase.GetRoomReq) (*usecase.Room, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateGetRoomReq(req); err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查用戶是否在聊天室中
|
|||
|
|
isInRoom, err := uc.RoomRepo.IsUserInRoom(ctx, req.UID, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, uc.logError("RoomRepo.IsUserInRoom", req, err, "failed to check user in room")
|
|||
|
|
}
|
|||
|
|
if !isInRoom {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"user is not in the room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取得聊天室資訊
|
|||
|
|
room, err := uc.RoomRepo.RoomGet(ctx, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return nil, errs.ResNotFoundErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"room not found")
|
|||
|
|
}
|
|||
|
|
return nil, uc.logError("RoomRepo.RoomGet", req, err, "failed to get room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return uc.entityRoomToUseCase(room), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) UpdateRoom(ctx context.Context, req usecase.UpdateRoomReq) (*usecase.Room, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateUpdateRoomReq(req); err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查用戶是否在聊天室中且為管理員
|
|||
|
|
member, err := uc.RoomRepo.Get(ctx, req.RoomID, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"user is not in the room")
|
|||
|
|
}
|
|||
|
|
return nil, uc.logError("RoomRepo.Get", req, err, "failed to get member")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查是否為管理員
|
|||
|
|
role := chat.RoomRole(member.Role)
|
|||
|
|
if !role.IsAdmin() {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
{Key: "role", Val: member.Role},
|
|||
|
|
},
|
|||
|
|
"only admin can update room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取得現有聊天室資訊
|
|||
|
|
room, err := uc.RoomRepo.RoomGet(ctx, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return nil, errs.ResNotFoundErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"room not found")
|
|||
|
|
}
|
|||
|
|
return nil, uc.logError("RoomRepo.RoomGet", req, err, "failed to get room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新欄位
|
|||
|
|
if req.Name != nil {
|
|||
|
|
room.Name = *req.Name
|
|||
|
|
}
|
|||
|
|
if req.Status != nil {
|
|||
|
|
room.Status = *req.Status
|
|||
|
|
}
|
|||
|
|
room.UpdatedAt = time.Now().UTC().UnixNano()
|
|||
|
|
|
|||
|
|
// 保存更新
|
|||
|
|
if err := uc.RoomRepo.RoomUpdate(ctx, room); err != nil {
|
|||
|
|
return nil, uc.logError("RoomRepo.RoomUpdate", req, err, "failed to update room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return uc.entityRoomToUseCase(room), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) DeleteRoom(ctx context.Context, req usecase.DeleteRoomReq) error {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateDeleteRoomReq(req); err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查用戶是否在聊天室中且為管理員
|
|||
|
|
member, err := uc.RoomRepo.Get(ctx, req.RoomID, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"user is not in the room")
|
|||
|
|
}
|
|||
|
|
return uc.logError("RoomRepo.Get", req, err, "failed to get member")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查是否為管理員
|
|||
|
|
role := chat.RoomRole(member.Role)
|
|||
|
|
if !role.IsAdmin() {
|
|||
|
|
return errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
{Key: "role", Val: member.Role},
|
|||
|
|
},
|
|||
|
|
"only admin can delete room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 刪除聊天室(會同時刪除相關的成員和訊息)
|
|||
|
|
if err := uc.RoomRepo.RoomDelete(ctx, req.RoomID); err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return errs.ResNotFoundErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"room not found")
|
|||
|
|
}
|
|||
|
|
return uc.logError("RoomRepo.RoomDelete", req, err, "failed to delete room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) ListRooms(ctx context.Context, req usecase.ListRoomsReq) ([]usecase.Room, string, int64, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateListRoomsReq(req); err != nil {
|
|||
|
|
return nil, "", 0, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 設置預設值
|
|||
|
|
pageSize := req.PageSize
|
|||
|
|
if pageSize <= 0 {
|
|||
|
|
pageSize = defaultRoomPageSize
|
|||
|
|
}
|
|||
|
|
if pageSize > maxRoomPageSize {
|
|||
|
|
pageSize = maxRoomPageSize
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查詢用戶所在的聊天室
|
|||
|
|
userRooms, err := uc.RoomRepo.GetUserRooms(ctx, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, "", 0, uc.logError("RoomRepo.GetUserRooms", req, err, "failed to get user rooms")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果沒有聊天室,直接返回
|
|||
|
|
if len(userRooms) == 0 {
|
|||
|
|
return []usecase.Room{}, "", 0, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取得聊天室 ID 列表
|
|||
|
|
roomIDs := make([]string, 0, len(userRooms))
|
|||
|
|
for _, ur := range userRooms {
|
|||
|
|
roomIDs = append(roomIDs, ur.RoomID.String())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根據 ID 列表查詢聊天室詳細資訊
|
|||
|
|
rooms, err := uc.RoomRepo.RoomGetByID(ctx, roomIDs)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, "", 0, uc.logError("RoomRepo.RoomGetByID", req, err, "failed to get rooms by id")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 篩選狀態
|
|||
|
|
if req.Status != "" {
|
|||
|
|
filtered := make([]entity.Room, 0, len(rooms))
|
|||
|
|
for _, room := range rooms {
|
|||
|
|
if room.Status == req.Status {
|
|||
|
|
filtered = append(filtered, room)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
rooms = filtered
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 轉換為 usecase.Room
|
|||
|
|
result := make([]usecase.Room, 0, len(rooms))
|
|||
|
|
for _, room := range rooms {
|
|||
|
|
result = append(result, *uc.entityRoomToUseCase(&room))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 計算總數(僅第一頁)
|
|||
|
|
var total int64
|
|||
|
|
if req.LastID == "" {
|
|||
|
|
total = int64(len(result))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回最後一個 ID(用於分頁)
|
|||
|
|
lastID := ""
|
|||
|
|
if len(result) > 0 {
|
|||
|
|
lastID = result[len(result)-1].RoomID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result, lastID, total, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) IsUserInRoom(ctx context.Context, req usecase.IsUserInRoomReq) (bool, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateIsUserInRoomReq(req); err != nil {
|
|||
|
|
return false, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查用戶是否在聊天室中
|
|||
|
|
isInRoom, err := uc.RoomRepo.IsUserInRoom(ctx, req.UID, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
return false, uc.logError("RoomRepo.IsUserInRoom", req, err, "failed to check user in room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return isInRoom, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 成員管理 ====================
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) AddMember(ctx context.Context, req usecase.AddMemberReq) (*usecase.Member, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateAddMemberReq(req); err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查操作者是否在聊天室中且為管理員
|
|||
|
|
operator, err := uc.RoomRepo.Get(ctx, req.RoomID, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"operator is not in the room")
|
|||
|
|
}
|
|||
|
|
return nil, uc.logError("RoomRepo.Get", req, err, "failed to get operator")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查是否為管理員
|
|||
|
|
operatorRole := chat.RoomRole(operator.Role)
|
|||
|
|
if !operatorRole.IsAdmin() {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
{Key: "role", Val: operator.Role},
|
|||
|
|
},
|
|||
|
|
"only admin can add members")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查要添加的用戶是否已在聊天室中
|
|||
|
|
existing, err := uc.RoomRepo.Get(ctx, req.RoomID, req.MemberUID)
|
|||
|
|
if err == nil && existing != nil {
|
|||
|
|
return nil, errs.InputInvalidFormatError("user is already in the room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 設置預設角色
|
|||
|
|
role := req.Role
|
|||
|
|
if role == "" {
|
|||
|
|
role = defaultMemberRole
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 驗證角色
|
|||
|
|
roleEnum := chat.RoomRole(role)
|
|||
|
|
if !roleEnum.IsValid() || roleEnum.IsOwner() {
|
|||
|
|
return nil, errs.InputInvalidFormatError("invalid role")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 創建成員實體
|
|||
|
|
now := time.Now().UTC().UnixNano()
|
|||
|
|
roomUUID, err := gocql.ParseUUID(req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, errs.InputInvalidFormatError("invalid room_id format")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
member := &entity.RoomMember{
|
|||
|
|
RoomID: roomUUID,
|
|||
|
|
UID: req.MemberUID,
|
|||
|
|
Role: role,
|
|||
|
|
JoinedAt: now,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加到聊天室
|
|||
|
|
if err := uc.RoomRepo.Insert(ctx, member); err != nil {
|
|||
|
|
return nil, uc.logError("RoomRepo.Insert", req, err, "failed to add member")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return uc.entityMemberToUseCase(member), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) RemoveMember(ctx context.Context, req usecase.RemoveMemberReq) error {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateRemoveMemberReq(req); err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查操作者是否在聊天室中
|
|||
|
|
operator, err := uc.RoomRepo.Get(ctx, req.RoomID, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"operator is not in the room")
|
|||
|
|
}
|
|||
|
|
return uc.logError("RoomRepo.Get", req, err, "failed to get operator")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查要移除的成員
|
|||
|
|
member, err := uc.RoomRepo.Get(ctx, req.RoomID, req.MemberUID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return errs.ResNotFoundErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "member_uid", Val: req.MemberUID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"member not found")
|
|||
|
|
}
|
|||
|
|
return uc.logError("RoomRepo.Get", req, err, "failed to get member")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查權限:管理員可以移除任何人,普通成員只能移除自己
|
|||
|
|
operatorRole := chat.RoomRole(operator.Role)
|
|||
|
|
if !operatorRole.IsAdmin() {
|
|||
|
|
if req.UID != req.MemberUID {
|
|||
|
|
return errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "member_uid", Val: req.MemberUID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"only admin can remove other members")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 不能移除擁有者
|
|||
|
|
memberRole := chat.RoomRole(member.Role)
|
|||
|
|
if memberRole.IsOwner() {
|
|||
|
|
return errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "member_uid", Val: req.MemberUID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"cannot remove owner")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移除成員
|
|||
|
|
if err := uc.RoomRepo.DeleteMember(ctx, req.RoomID, req.MemberUID); err != nil {
|
|||
|
|
return uc.logError("RoomRepo.DeleteMember", req, err, "failed to remove member")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) UpdateMemberRole(ctx context.Context, req usecase.UpdateMemberRoleReq) (*usecase.Member, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateUpdateMemberRoleReq(req); err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查操作者是否在聊天室中且為管理員
|
|||
|
|
operator, err := uc.RoomRepo.Get(ctx, req.RoomID, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"operator is not in the room")
|
|||
|
|
}
|
|||
|
|
return nil, uc.logError("RoomRepo.Get", req, err, "failed to get operator")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查是否為管理員
|
|||
|
|
operatorRole := chat.RoomRole(operator.Role)
|
|||
|
|
if !operatorRole.IsAdmin() {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
{Key: "role", Val: operator.Role},
|
|||
|
|
},
|
|||
|
|
"only admin can update member role")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查要更新的成員
|
|||
|
|
member, err := uc.RoomRepo.Get(ctx, req.RoomID, req.MemberUID)
|
|||
|
|
if err != nil {
|
|||
|
|
if cassandra.IsNotFound(err) {
|
|||
|
|
return nil, errs.ResNotFoundErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "member_uid", Val: req.MemberUID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"member not found")
|
|||
|
|
}
|
|||
|
|
return nil, uc.logError("RoomRepo.Get", req, err, "failed to get member")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 驗證角色
|
|||
|
|
roleEnum := chat.RoomRole(req.Role)
|
|||
|
|
if !roleEnum.IsValid() || roleEnum.IsOwner() {
|
|||
|
|
return nil, errs.InputInvalidFormatError("invalid role")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 不能修改擁有者的角色
|
|||
|
|
memberRole := chat.RoomRole(member.Role)
|
|||
|
|
if memberRole.IsOwner() {
|
|||
|
|
return nil, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "member_uid", Val: req.MemberUID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"cannot update owner role")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新角色
|
|||
|
|
member.Role = req.Role
|
|||
|
|
if err := uc.RoomRepo.UpdateRole(ctx, member); err != nil {
|
|||
|
|
return nil, uc.logError("RoomRepo.UpdateRole", req, err, "failed to update member role")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return uc.entityMemberToUseCase(member), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) ListMembers(ctx context.Context, req usecase.ListMembersReq) ([]usecase.Member, int64, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateListMembersReq(req); err != nil {
|
|||
|
|
return nil, 0, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查用戶是否在聊天室中
|
|||
|
|
isInRoom, err := uc.RoomRepo.IsUserInRoom(ctx, req.UID, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, 0, uc.logError("RoomRepo.IsUserInRoom", req, err, "failed to check user in room")
|
|||
|
|
}
|
|||
|
|
if !isInRoom {
|
|||
|
|
return nil, 0, errs.AuthForbiddenErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "uid", Val: req.UID},
|
|||
|
|
{Key: "room_id", Val: req.RoomID},
|
|||
|
|
},
|
|||
|
|
"user is not in the room")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查詢所有成員
|
|||
|
|
members, err := uc.RoomRepo.AllMembers(ctx, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, 0, uc.logError("RoomRepo.AllMembers", req, err, "failed to list members")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 轉換為 usecase.Member
|
|||
|
|
result := make([]usecase.Member, 0, len(members))
|
|||
|
|
for _, member := range members {
|
|||
|
|
result = append(result, *uc.entityMemberToUseCase(&member))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 計算總數
|
|||
|
|
total, err := uc.RoomRepo.Count(ctx, req.RoomID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, 0, uc.logError("RoomRepo.Count", req, err, "failed to count members")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result, total, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 用戶相關 ====================
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) GetUserRooms(ctx context.Context, req usecase.GetUserRoomsReq) ([]usecase.Room, int64, error) {
|
|||
|
|
// 驗證輸入參數
|
|||
|
|
if err := uc.validateGetUserRoomsReq(req); err != nil {
|
|||
|
|
return nil, 0, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查詢用戶所在的聊天室
|
|||
|
|
userRooms, err := uc.RoomRepo.GetUserRooms(ctx, req.UID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, 0, uc.logError("RoomRepo.GetUserRooms", req, err, "failed to get user rooms")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果沒有聊天室,直接返回
|
|||
|
|
if len(userRooms) == 0 {
|
|||
|
|
return []usecase.Room{}, 0, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取得聊天室 ID 列表
|
|||
|
|
roomIDs := make([]string, 0, len(userRooms))
|
|||
|
|
for _, ur := range userRooms {
|
|||
|
|
roomIDs = append(roomIDs, ur.RoomID.String())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根據 ID 列表查詢聊天室詳細資訊
|
|||
|
|
rooms, err := uc.RoomRepo.RoomGetByID(ctx, roomIDs)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, 0, uc.logError("RoomRepo.RoomGetByID", req, err, "failed to get rooms by id")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 轉換為 usecase.Room
|
|||
|
|
result := make([]usecase.Room, 0, len(rooms))
|
|||
|
|
for _, room := range rooms {
|
|||
|
|
result = append(result, *uc.entityRoomToUseCase(&room))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 計算總數
|
|||
|
|
total := int64(len(result))
|
|||
|
|
|
|||
|
|
return result, total, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 輔助方法 ====================
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) entityRoomToUseCase(room *entity.Room) *usecase.Room {
|
|||
|
|
return &usecase.Room{
|
|||
|
|
RoomID: room.RoomID.String(),
|
|||
|
|
Name: room.Name,
|
|||
|
|
Status: room.Status,
|
|||
|
|
CreatedAt: room.CreatedAt,
|
|||
|
|
UpdatedAt: room.UpdatedAt,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) entityMemberToUseCase(member *entity.RoomMember) *usecase.Member {
|
|||
|
|
return &usecase.Member{
|
|||
|
|
RoomID: member.RoomID.String(),
|
|||
|
|
UID: member.UID,
|
|||
|
|
Role: member.Role,
|
|||
|
|
JoinedAt: member.JoinedAt,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 驗證方法 ====================
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateCreateRoomReq(req usecase.CreateRoomReq) error {
|
|||
|
|
if req.Name == "" {
|
|||
|
|
return errs.InputInvalidFormatError("name cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.Status != "" && req.Status != "active" && req.Status != "archived" {
|
|||
|
|
return errs.InputInvalidFormatError("invalid status")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateGetRoomReq(req usecase.GetRoomReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateUpdateRoomReq(req usecase.UpdateRoomReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.Name != nil && *req.Name == "" {
|
|||
|
|
return errs.InputInvalidFormatError("name cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.Status != nil && *req.Status != "active" && *req.Status != "archived" {
|
|||
|
|
return errs.InputInvalidFormatError("invalid status")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateDeleteRoomReq(req usecase.DeleteRoomReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateListRoomsReq(req usecase.ListRoomsReq) error {
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateIsUserInRoomReq(req usecase.IsUserInRoomReq) error {
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateAddMemberReq(req usecase.AddMemberReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.MemberUID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("member_uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateRemoveMemberReq(req usecase.RemoveMemberReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.MemberUID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("member_uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateUpdateMemberRoleReq(req usecase.UpdateMemberRoleReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.MemberUID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("member_uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.Role == "" {
|
|||
|
|
return errs.InputInvalidFormatError("role cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateListMembersReq(req usecase.ListMembersReq) error {
|
|||
|
|
if req.RoomID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("room_id cannot be empty")
|
|||
|
|
}
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) validateGetUserRoomsReq(req usecase.GetUserRoomsReq) error {
|
|||
|
|
if req.UID == "" {
|
|||
|
|
return errs.InputInvalidFormatError("uid cannot be empty")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 錯誤日誌 ====================
|
|||
|
|
|
|||
|
|
func (uc *RoomUseCase) logError(funcName string, req interface{}, err error, message string) error {
|
|||
|
|
return errs.DBErrorErrorL(
|
|||
|
|
uc.Logger,
|
|||
|
|
[]errs.LogField{
|
|||
|
|
{Key: "func", Val: funcName},
|
|||
|
|
{Key: "req", Val: fmt.Sprintf("%+v", req)},
|
|||
|
|
{Key: "err", Val: err.Error()},
|
|||
|
|
},
|
|||
|
|
message,
|
|||
|
|
).Wrap(err)
|
|||
|
|
}
|