259 lines
7.8 KiB
Go
259 lines
7.8 KiB
Go
package socket
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"log"
|
|
"relay-server/database"
|
|
"relay-server/utils"
|
|
|
|
socketio "github.com/googollee/go-socket.io"
|
|
)
|
|
|
|
// Message represents the chat message structure
|
|
type Message struct {
|
|
Content string `json:"message"`
|
|
Recipient string `json:"recipient"`
|
|
RecipientID uuid.UUID `json:"recipient_id"`
|
|
AttachmentURLs []string `json:"attachment_urls"`
|
|
}
|
|
|
|
// DeleteMessageData represents the message deletion structure
|
|
type DeleteMessageData struct {
|
|
ConversationID uuid.UUID `json:"conversation_id"`
|
|
MessageID int `json:"message_id"`
|
|
}
|
|
|
|
// GroupUserData represents group-user operations data
|
|
type GroupUserData struct {
|
|
GroupID uuid.UUID `json:"group_id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
}
|
|
|
|
// MessageReadData represents message read status data
|
|
type MessageReadData struct {
|
|
ConversationID uuid.UUID `json:"conversation_id"`
|
|
MessageID int `json:"message_id"`
|
|
}
|
|
|
|
// SocketResponse represents a standard socket response
|
|
type SocketResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// InitializeSocket sets up and configures the Socket.IO server
|
|
func InitializeSocket() (*socketio.Server, error) {
|
|
server := socketio.NewServer(nil)
|
|
|
|
// Middleware for authentication
|
|
server.OnConnect("/", func(s socketio.Conn) error {
|
|
token := s.RemoteHeader().Get("Authorization")
|
|
if token == "" {
|
|
log.Println("(socket) Not logged in")
|
|
return fmt.Errorf("not logged in")
|
|
}
|
|
|
|
c, err := utils.ValidateToken(token)
|
|
if err != nil {
|
|
log.Printf("(socket) Token verification failed: %v\n", err)
|
|
return fmt.Errorf("invalid token")
|
|
}
|
|
|
|
if !utils.IsValidUsername(c.Username) {
|
|
log.Println("(socket) Invalid username")
|
|
return fmt.Errorf("invalid username")
|
|
}
|
|
|
|
s.SetContext(map[string]interface{}{
|
|
"username": c.Username,
|
|
"user_id": c.UserID,
|
|
})
|
|
|
|
log.Printf("(socket) socket id: %s, username: %s, user_id: %s\n", s.ID(), c.Username, c.UserID)
|
|
return nil
|
|
})
|
|
|
|
// Handle connection
|
|
server.OnEvent("/", "connection", func(s socketio.Conn) {
|
|
ctx := s.Context().(map[string]interface{})
|
|
username := ctx["username"].(string)
|
|
userID := ctx["user_id"].(string)
|
|
|
|
if !utils.IsValidUsername(username) {
|
|
s.Close()
|
|
return
|
|
}
|
|
|
|
conversations, err := database.GetUserConversations(database.DB, userID)
|
|
if err != nil {
|
|
log.Printf("(socket) Failed to get user conversations: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Join all conversations
|
|
for _, conv := range conversations {
|
|
s.Join(conv)
|
|
}
|
|
s.Join(userID) // Join user's personal room
|
|
log.Printf("User: %s joined to: %v\n", username, conversations)
|
|
})
|
|
|
|
// Handle chat message
|
|
server.OnEvent("/", "chat message", func(s socketio.Conn, msg Message) SocketResponse {
|
|
ctx := s.Context().(map[string]interface{})
|
|
username := ctx["username"].(string)
|
|
userIDstr := ctx["user_id"].(string)
|
|
conversationIDstr := ctx["recipient_id"].(string)
|
|
userID, err := uuid.Parse(userIDstr)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: "Invalid user id"}
|
|
}
|
|
conversationID, err := uuid.Parse(conversationIDstr)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: "Invalid conversation id"}
|
|
}
|
|
|
|
if msg.Content == "" && len(msg.AttachmentURLs) == 0 {
|
|
return SocketResponse{Status: "error", Message: "No message or attachment provided"}
|
|
}
|
|
if msg.Recipient == "" {
|
|
return SocketResponse{Status: "error", Message: "No recipient provided"}
|
|
}
|
|
|
|
insertedMsg, err := database.InsertMessage(database.DB, userID, conversationID, msg.Content, msg.AttachmentURLs)
|
|
if err != nil {
|
|
log.Printf("(socket) Failed to insert message: %v\n", err)
|
|
return SocketResponse{Status: "error", Message: "Failed to insert message"}
|
|
}
|
|
|
|
// Emit message to recipients
|
|
server.BroadcastToRoom("", msg.Recipient, "chat message", map[string]interface{}{
|
|
"sender": username,
|
|
"message": insertedMsg.Message,
|
|
"attachment_urls": msg.AttachmentURLs,
|
|
"recipient": msg.Recipient,
|
|
"message_id": insertedMsg.ID,
|
|
"sender_id": userID,
|
|
"sent_at": insertedMsg.SentAt,
|
|
"conversation_id": insertedMsg.ConversationID,
|
|
})
|
|
|
|
return SocketResponse{Status: "ok", Message: "Received message"}
|
|
})
|
|
|
|
// Handle delete message
|
|
server.OnEvent("/", "delete message", func(s socketio.Conn, data DeleteMessageData) SocketResponse {
|
|
ctx := s.Context().(map[string]interface{})
|
|
userIDstr := ctx["user_id"].(string)
|
|
|
|
userID, err := uuid.Parse(userIDstr)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: "Invalid user id"}
|
|
}
|
|
|
|
if data.MessageID == 0 {
|
|
return SocketResponse{Status: "error", Message: "No message id provided"}
|
|
}
|
|
if data.ConversationID == uuid.Nil {
|
|
return SocketResponse{Status: "error", Message: "No conversation id provided"}
|
|
}
|
|
|
|
err = database.DeleteMessage(database.DB, userID, data.ConversationID, data.MessageID)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: err.Error()}
|
|
}
|
|
|
|
conversationIDstr := data.ConversationID.String()
|
|
server.BroadcastToRoom("", conversationIDstr, "delete message", data)
|
|
return SocketResponse{Status: "ok", Message: "Successfully deleted message"}
|
|
})
|
|
|
|
// Handle remove user from group
|
|
server.OnEvent("/", "remove user from group", func(s socketio.Conn, data GroupUserData) SocketResponse {
|
|
if data.GroupID == uuid.Nil {
|
|
return SocketResponse{Status: "error", Message: "No group id provided"}
|
|
}
|
|
if data.UserID == uuid.Nil {
|
|
return SocketResponse{Status: "error", Message: "No user id provided"}
|
|
}
|
|
|
|
err := removeUserFromGroupById(data.GroupID, data.UserID)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: err.Error()}
|
|
}
|
|
|
|
// Remove user from room
|
|
sockets := server.Sockets(data.GroupID)
|
|
for _, socket := range sockets {
|
|
if socket.Context().(map[string]interface{})["user_id"] == data.UserID {
|
|
socket.Leave(data.GroupID)
|
|
}
|
|
}
|
|
|
|
server.BroadcastToRoom("", data.GroupID, "left group", data)
|
|
server.BroadcastToRoom("", data.UserID, "left group", data)
|
|
|
|
return SocketResponse{Status: "ok", Message: "Successfully removed user from group"}
|
|
})
|
|
|
|
// Handle administrator operations
|
|
server.OnEvent("/", "added administrator", func(s socketio.Conn, data GroupUserData) SocketResponse {
|
|
ctx := s.Context().(map[string]interface{})
|
|
userID := ctx["user_id"].(string)
|
|
|
|
if data.GroupID == "" {
|
|
return SocketResponse{Status: "error", Message: "No conversation id provided"}
|
|
}
|
|
if data.UserID == "" {
|
|
return SocketResponse{Status: "error", Message: "No user id provided"}
|
|
}
|
|
|
|
isAdmin, err := isAdmin(userID, data.GroupID)
|
|
if err != nil || !isAdmin {
|
|
return SocketResponse{Status: "error", Message: "You are not an administrator"}
|
|
}
|
|
|
|
err = addAdministrator(data.GroupID, data.UserID, userID)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: err.Error()}
|
|
}
|
|
|
|
server.BroadcastToRoom("", data.GroupID, "added administrator", data)
|
|
return SocketResponse{Status: "ok", Message: "Successfully added administrator"}
|
|
})
|
|
|
|
// Handle message read status
|
|
server.OnEvent("/", "message read", func(s socketio.Conn, data MessageReadData) {
|
|
ctx := s.Context().(map[string]interface{})
|
|
userID := ctx["user_id"].(string)
|
|
|
|
if data.ConversationID == "" || data.MessageID == "" {
|
|
return
|
|
}
|
|
|
|
err := updateContactStatus(userID, data.ConversationID, data.MessageID)
|
|
if err != nil {
|
|
log.Printf("Failed to update message read status: %v\n", err)
|
|
}
|
|
})
|
|
|
|
// Handle disconnection
|
|
server.OnDisconnect("/", func(s socketio.Conn, reason string) {
|
|
log.Printf("(socket) %s disconnected due to: %s\n", s.ID(), reason)
|
|
})
|
|
|
|
return server, nil
|
|
}
|
|
|
|
// The following functions would need to be implemented according to your specific needs:
|
|
// - verifyJwtToken
|
|
// - isValidUsername
|
|
// - getConversationsForUser
|
|
// - insertMessage
|
|
// - deleteMessage
|
|
// - removeUserFromGroupById
|
|
// - isAdmin
|
|
// - addAdministrator
|
|
// - updateContactStatus
|