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) }