code refactor, implementing sockets
This commit is contained in:
258
socket/socket.go
Normal file
258
socket/socket.go
Normal file
@@ -0,0 +1,258 @@
|
||||
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
|
||||
Reference in New Issue
Block a user