331 lines
10 KiB
Go
331 lines
10 KiB
Go
package socket
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"log"
|
|
"relay-server/database"
|
|
"relay-server/utils"
|
|
|
|
socketio "github.com/googollee/go-socket.io"
|
|
)
|
|
|
|
type JoinRoomResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// 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(server *socketio.Server) error {
|
|
// Middleware for authentication
|
|
server.OnConnect("/", func(s socketio.Conn) error {
|
|
s.SetContext("")
|
|
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
|
|
}
|
|
log.Println("CONVERSATIONS", conversations)
|
|
// Join all conversations
|
|
for _, conv := range conversations {
|
|
s.Join(conv)
|
|
}
|
|
s.Join(userID)
|
|
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"}
|
|
}
|
|
|
|
msg, err := database.RemoveUserFromGroup(database.DB, data.GroupID, data.UserID)
|
|
if msg != "" {
|
|
return SocketResponse{Status: "error", Message: msg}
|
|
}
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: err.Error()}
|
|
}
|
|
|
|
// Remove user from room
|
|
server.ForEach("/", data.GroupID.String(), func(c socketio.Conn) {
|
|
// Get the context and check user ID
|
|
if ctxData, ok := c.Context().(map[string]interface{}); ok {
|
|
if userIDFromCtx, ok := ctxData["user_id"].(string); ok {
|
|
if userIDFromCtx == data.UserID.String() {
|
|
c.Leave(data.GroupID.String())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
groupIDstr := data.GroupID.String()
|
|
userIDstr := data.UserID.String()
|
|
|
|
server.BroadcastToRoom("", groupIDstr, "left group", data)
|
|
server.BroadcastToRoom("", userIDstr, "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{})
|
|
userIDstr := ctx["user_id"].(string)
|
|
|
|
userID, err := uuid.Parse(userIDstr)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: "Invalid user id"}
|
|
}
|
|
if data.GroupID == uuid.Nil {
|
|
return SocketResponse{Status: "error", Message: "No conversation id provided"}
|
|
}
|
|
if data.UserID == uuid.Nil {
|
|
return SocketResponse{Status: "error", Message: "No user id provided"}
|
|
}
|
|
|
|
isAdmin, err := database.IsAdmin(database.DB, data.UserID, data.GroupID)
|
|
if err != nil || !isAdmin {
|
|
return SocketResponse{Status: "error", Message: "You are not an administrator"}
|
|
}
|
|
|
|
msg, err := database.AddAdministrator(database.DB, data.GroupID, data.UserID, userID)
|
|
if err != nil {
|
|
return SocketResponse{Status: "error", Message: err.Error()}
|
|
}
|
|
if msg != "" {
|
|
return SocketResponse{Status: "error", Message: msg}
|
|
}
|
|
|
|
groupIDstr := data.GroupID.String()
|
|
server.BroadcastToRoom("", groupIDstr, "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) 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.ConversationID == uuid.Nil || data.MessageID == 0 {
|
|
return SocketResponse{Status: "error", Message: "Invalid conversation or message id"}
|
|
}
|
|
|
|
err = database.UpdateContactStatus(database.DB, userID, data.ConversationID, data.MessageID)
|
|
if err != nil {
|
|
log.Printf("Failed to update message read status: %v\n", err)
|
|
}
|
|
return SocketResponse{Status: "ok", Message: "Successfully updated message read status"}
|
|
})
|
|
|
|
server.OnEvent("/", "join room", func(s socketio.Conn, conversationIDstr string, callback func(JoinRoomResponse)) {
|
|
// Get user data from context
|
|
ctx := s.Context().(map[string]interface{})
|
|
userIDstr := ctx["user_id"].(string)
|
|
username := ctx["username"].(string)
|
|
log.Printf("JOIN ROOM SDJKLSDFJKLSDFJKLSDFJKLSDFSDFJKLSLDFJKJLSDFKSDFJKLSDFJKLSDFJKLSDFJLKSFDLJKSDFJKLSDFJLKSDFJLKSDFJLKSDFJLSDFKSDFJKLSDFJKLJKL")
|
|
userID, err := uuid.Parse(userIDstr)
|
|
if err != nil {
|
|
callback(JoinRoomResponse{
|
|
Status: "error",
|
|
Message: "Invalid user id",
|
|
})
|
|
return
|
|
}
|
|
|
|
conversationID, err := uuid.Parse(conversationIDstr)
|
|
if err != nil {
|
|
callback(JoinRoomResponse{
|
|
Status: "error",
|
|
Message: "Invalid conversation id",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Check if user is member of the conversation
|
|
isMember, err := database.IsMember(database.DB, userID, conversationID)
|
|
if err != nil {
|
|
callback(JoinRoomResponse{
|
|
Status: "error",
|
|
Message: "Failed to check conversation membership",
|
|
})
|
|
return
|
|
}
|
|
|
|
if isMember {
|
|
log.Printf("Join room for: %s, room: %s", username, conversationID)
|
|
s.Join(conversationIDstr)
|
|
callback(JoinRoomResponse{
|
|
Status: "ok",
|
|
Message: "Successfully joined to conversation",
|
|
})
|
|
} else {
|
|
callback(JoinRoomResponse{
|
|
Status: "error",
|
|
Message: "You are not member of this group",
|
|
})
|
|
}
|
|
})
|
|
|
|
server.OnEvent("/", "*", func(s socketio.Conn, event string, data interface{}) {
|
|
log.Printf("Received event: %s, data: %v\n", event, data)
|
|
})
|
|
|
|
server.OnError("/", func(s socketio.Conn, e error) {
|
|
log.Printf("Error: %v\n", e)
|
|
})
|
|
|
|
// Handle disconnection
|
|
server.OnDisconnect("/", func(s socketio.Conn, reason string) {
|
|
log.Printf("(socket) %s disconnected due to: %s\n", s.ID(), reason)
|
|
})
|
|
return nil
|
|
}
|