chat.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package handlers
  2. import (
  3. "log/slog"
  4. "net/http"
  5. "strings"
  6. "github.com/mk6i/open-oscar-server/state"
  7. )
  8. // ChatHandler handles Web API chat endpoints
  9. type ChatHandler struct {
  10. SessionManager *state.WebAPISessionManager
  11. ChatManager *state.WebAPIChatManager
  12. Logger *slog.Logger
  13. }
  14. // CreateAndJoinChat creates (if needed) and joins a chat room
  15. // GET /chat/createAndJoinChat
  16. func (h *ChatHandler) CreateAndJoinChat(w http.ResponseWriter, r *http.Request) {
  17. // Extract parameters
  18. aimsid := r.URL.Query().Get("aimsid")
  19. roomID := r.URL.Query().Get("roomId")
  20. roomName := r.URL.Query().Get("roomName")
  21. // Validate session
  22. session, err := h.SessionManager.GetSession(r.Context(), aimsid)
  23. if err != nil {
  24. h.Logger.Error("invalid session", "aimsid", aimsid, "error", err)
  25. SendError(w, http.StatusUnauthorized, "Authentication Required")
  26. return
  27. }
  28. // Validate parameters - exactly one of roomId or roomName must be provided
  29. if (roomID == "" && roomName == "") || (roomID != "" && roomName != "") {
  30. SendError(w, http.StatusBadRequest, "Exactly one of roomId or roomName must be provided")
  31. return
  32. }
  33. // Create or join the chat room
  34. chatSession, room, err := h.ChatManager.CreateAndJoinChat(r.Context(), aimsid, roomID, roomName, string(session.ScreenName))
  35. if err != nil {
  36. h.Logger.Error("failed to create/join chat", "error", err, "aimsid", aimsid)
  37. // Determine appropriate error code
  38. statusCode := http.StatusInternalServerError
  39. message := "Internal Server Error"
  40. if strings.Contains(err.Error(), "maximum capacity") {
  41. statusCode = http.StatusServiceUnavailable
  42. message = "Room is at maximum capacity"
  43. } else if strings.Contains(err.Error(), "must be provided") {
  44. statusCode = http.StatusBadRequest
  45. message = err.Error()
  46. }
  47. SendError(w, statusCode, message)
  48. return
  49. }
  50. // Build response
  51. roomData := map[string]interface{}{
  52. "roomName": room.RoomName,
  53. "roomId": room.RoomID,
  54. "instanceId": room.InstanceID,
  55. "description": room.Description,
  56. "roomType": string(room.RoomType),
  57. }
  58. // Add category ID if present
  59. if room.CategoryID != "" {
  60. roomData["categoryId"] = room.CategoryID
  61. }
  62. response := BaseResponse{
  63. Response: ResponseBody{
  64. StatusCode: 200,
  65. StatusText: "OK",
  66. Data: map[string]interface{}{
  67. "chatsid": chatSession.ChatSID,
  68. "room": roomData,
  69. },
  70. },
  71. }
  72. // Send response
  73. SendResponse(w, r, response, h.Logger)
  74. h.Logger.Info("user joined chat room",
  75. "screenName", session.ScreenName,
  76. "roomName", room.RoomName,
  77. "roomID", room.RoomID,
  78. "chatsid", chatSession.ChatSID)
  79. }
  80. // SendMessage sends a message to a chat room
  81. // GET /chat/sendMessage
  82. func (h *ChatHandler) SendMessage(w http.ResponseWriter, r *http.Request) {
  83. // Extract parameters
  84. aimsid := r.URL.Query().Get("aimsid")
  85. chatsid := r.URL.Query().Get("chatsid")
  86. message := r.URL.Query().Get("message")
  87. whisperTarget := r.URL.Query().Get("whisperTarget")
  88. // Validate session
  89. _, err := h.SessionManager.GetSession(r.Context(), aimsid)
  90. if err != nil {
  91. h.Logger.Error("invalid session", "aimsid", aimsid, "error", err)
  92. SendError(w, http.StatusUnauthorized, "Authentication Required")
  93. return
  94. }
  95. // Validate required parameters
  96. if chatsid == "" {
  97. SendError(w, http.StatusBadRequest, "chatsid is required")
  98. return
  99. }
  100. if message == "" {
  101. SendError(w, http.StatusBadRequest, "message is required")
  102. return
  103. }
  104. // Send the message
  105. err = h.ChatManager.SendMessage(r.Context(), chatsid, message, whisperTarget)
  106. if err != nil {
  107. h.Logger.Error("failed to send message", "error", err, "chatsid", chatsid)
  108. // Determine appropriate error code
  109. statusCode := http.StatusInternalServerError
  110. message := "Internal Server Error"
  111. if strings.Contains(err.Error(), "invalid chat session") || strings.Contains(err.Error(), "user has left") {
  112. statusCode = http.StatusNotFound
  113. message = "Chat session not found"
  114. }
  115. SendError(w, statusCode, message)
  116. return
  117. }
  118. // Build response
  119. response := BaseResponse{
  120. Response: ResponseBody{
  121. StatusCode: 200,
  122. StatusText: "OK",
  123. Data: map[string]interface{}{},
  124. },
  125. }
  126. // Send response
  127. SendResponse(w, r, response, h.Logger)
  128. logMsg := "message sent to chat room"
  129. if whisperTarget != "" {
  130. logMsg = "whisper sent in chat room"
  131. }
  132. h.Logger.Debug(logMsg, "chatsid", chatsid, "whisperTarget", whisperTarget)
  133. }
  134. // SetTyping sets typing status for a chat room
  135. // GET /chat/setTyping
  136. func (h *ChatHandler) SetTyping(w http.ResponseWriter, r *http.Request) {
  137. // Extract parameters
  138. aimsid := r.URL.Query().Get("aimsid")
  139. chatsid := r.URL.Query().Get("chatsid")
  140. typingStatus := r.URL.Query().Get("typingStatus")
  141. // Validate session
  142. _, err := h.SessionManager.GetSession(r.Context(), aimsid)
  143. if err != nil {
  144. h.Logger.Error("invalid session", "aimsid", aimsid, "error", err)
  145. SendError(w, http.StatusUnauthorized, "Authentication Required")
  146. return
  147. }
  148. // Validate required parameters
  149. if chatsid == "" {
  150. SendError(w, http.StatusBadRequest, "chatsid is required")
  151. return
  152. }
  153. if typingStatus == "" {
  154. SendError(w, http.StatusBadRequest, "typingStatus is required")
  155. return
  156. }
  157. // Validate typing status value
  158. validStatuses := map[string]bool{
  159. "none": true,
  160. "typing": true,
  161. "typed": true,
  162. }
  163. if !validStatuses[typingStatus] {
  164. SendError(w, http.StatusBadRequest, "Invalid typingStatus value")
  165. return
  166. }
  167. // Set typing status
  168. err = h.ChatManager.SetTyping(r.Context(), chatsid, typingStatus)
  169. if err != nil {
  170. h.Logger.Error("failed to set typing status", "error", err, "chatsid", chatsid)
  171. // Determine appropriate error code
  172. statusCode := http.StatusInternalServerError
  173. errMessage := "Internal Server Error"
  174. if strings.Contains(err.Error(), "invalid chat session") || strings.Contains(err.Error(), "user has left") {
  175. statusCode = http.StatusNotFound
  176. errMessage = "Chat session not found"
  177. }
  178. SendError(w, statusCode, errMessage)
  179. return
  180. }
  181. // Build response
  182. response := BaseResponse{
  183. Response: ResponseBody{
  184. StatusCode: 200,
  185. StatusText: "OK",
  186. Data: map[string]interface{}{},
  187. },
  188. }
  189. // Send response
  190. SendResponse(w, r, response, h.Logger)
  191. h.Logger.Debug("typing status updated", "chatsid", chatsid, "status", typingStatus)
  192. }
  193. // LeaveChat leaves the current chat room
  194. // GET /chat/leaveChat
  195. func (h *ChatHandler) LeaveChat(w http.ResponseWriter, r *http.Request) {
  196. // Extract parameters
  197. aimsid := r.URL.Query().Get("aimsid")
  198. chatsid := r.URL.Query().Get("chatsid")
  199. // Validate session
  200. session, err := h.SessionManager.GetSession(r.Context(), aimsid)
  201. if err != nil {
  202. h.Logger.Error("invalid session", "aimsid", aimsid, "error", err)
  203. SendError(w, http.StatusUnauthorized, "Authentication Required")
  204. return
  205. }
  206. // Validate required parameters
  207. if chatsid == "" {
  208. SendError(w, http.StatusBadRequest, "chatsid is required")
  209. return
  210. }
  211. // Leave the chat room
  212. err = h.ChatManager.LeaveChat(r.Context(), chatsid)
  213. if err != nil {
  214. h.Logger.Error("failed to leave chat", "error", err, "chatsid", chatsid)
  215. // Determine appropriate error code
  216. statusCode := http.StatusInternalServerError
  217. message := "Internal Server Error"
  218. if strings.Contains(err.Error(), "invalid chat session") {
  219. statusCode = http.StatusNotFound
  220. message = "Chat session not found"
  221. }
  222. SendError(w, statusCode, message)
  223. return
  224. }
  225. // Build response
  226. response := BaseResponse{
  227. Response: ResponseBody{
  228. StatusCode: 200,
  229. StatusText: "OK",
  230. Data: map[string]interface{}{},
  231. },
  232. }
  233. // Send response
  234. SendResponse(w, r, response, h.Logger)
  235. h.Logger.Info("user left chat room",
  236. "screenName", session.ScreenName,
  237. "chatsid", chatsid)
  238. }