Files
relay-server/socket/socket.go
2025-02-11 23:03:31 +01:00

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
}