finished rewriting socket but it isn't working 😊

This commit is contained in:
slawk0
2025-02-11 23:03:31 +01:00
parent 248966d63f
commit e6817cb4c6
6 changed files with 214 additions and 61 deletions

View File

@@ -347,3 +347,17 @@ func ContactSuggestion(db *sql.DB, contactUsername string) ([]string, error) {
} }
return suggestions, nil return suggestions, nil
} }
func UpdateContactStatus(db *sql.DB, userID uuid.UUID, conversationID uuid.UUID, lastReadMessageID int) error {
query := `
UPDATE Memberships
SET last_read_message_id = $1
WHERE user_id = $2
AND conversation_id = $3;
`
_, err := db.Exec(query, lastReadMessageID, userID, conversationID)
if err != nil {
return fmt.Errorf("failed to update contact status: %w", err)
}
return nil
}

View File

@@ -170,6 +170,11 @@ func RemoveUserFromGroup(db *sql.DB, userID uuid.UUID, groupID uuid.UUID) (strin
WHERE conversation_id = $1 AND user_id = $2; WHERE conversation_id = $1 AND user_id = $2;
` `
removeAdministratorPermissionsQuery := `
DELETE FROM GroupAdmins
WHERE conversation_id = $1 AND user_id = $2;
`
isOwner, err := IsGroupOwner(db, userID, groupID) isOwner, err := IsGroupOwner(db, userID, groupID)
if err != nil { if err != nil {
return "Failed to remove user from group", fmt.Errorf("failed to check if user is group owner: %w", err) return "Failed to remove user from group", fmt.Errorf("failed to check if user is group owner: %w", err)
@@ -188,6 +193,12 @@ func RemoveUserFromGroup(db *sql.DB, userID uuid.UUID, groupID uuid.UUID) (strin
if rowsAffected == 0 { if rowsAffected == 0 {
return "User is not a member of the group", nil return "User is not a member of the group", nil
} }
// If user is an admin, remove admin permissions
_, err = db.Exec(removeAdministratorPermissionsQuery, groupID, userID)
if err != nil {
return "Failed to remove user from group", fmt.Errorf("failed to remove admin permissions: %w", err)
}
return "Successfully removed user from group", nil return "Successfully removed user from group", nil
} }
@@ -207,3 +218,34 @@ func IsGroupOwner(db *sql.DB, userID uuid.UUID, groupID uuid.UUID) (bool, error)
} }
return isOwner, nil return isOwner, nil
} }
func AddAdministrator(db *sql.DB, userID uuid.UUID, groupID uuid.UUID, grantedBy uuid.UUID) (string, error) {
query := `
INSERT INTO GroupAdmins (conversation_id, user_id, granted_by, is_owner)
VALUES ($1, $2, $3, false)
RETURNING granted_at;
`
isOwner, err := IsGroupOwner(db, grantedBy, groupID)
if err != nil {
return "Failed to add administrator", err
}
if !isOwner {
return "You are not the group owner", nil
}
isMember, err := IsMember(db, userID, groupID)
if err != nil {
return "Failed to add administrator", err
}
if !isMember {
return "User is not a member of the group", nil
}
var grantedAt string
err = db.QueryRow(query, groupID, userID, grantedBy).Scan(&grantedAt)
if err != nil {
return "Failed to add administrator", fmt.Errorf("failed to add administrator: %w", err)
}
return "Successfully added administrator", nil
}

46
main.go
View File

@@ -1,11 +1,14 @@
package main package main
import ( import (
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gofiber/fiber/v2/middleware/cors"
socketio "github.com/googollee/go-socket.io"
"log" "log"
"relay-server/database" "relay-server/database"
"relay-server/router" "relay-server/router"
"relay-server/socket"
"relay-server/utils" "relay-server/utils"
) )
@@ -14,14 +17,14 @@ func main() {
ErrorHandler: utils.ErrorHandler, ErrorHandler: utils.ErrorHandler,
}) })
app.Use(func(c *fiber.Ctx) error { server := socketio.NewServer(nil)
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
return c.Next()
}
return fiber.ErrUpgradeRequired
})
app.Use(cors.New(cors.Config{
AllowOrigins: "http://localhost:5173",
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
AllowMethods: "GET,POST,PUT,DELETE",
//AllowCredentials: true,
}))
db, err := database.Init() db, err := database.Init()
if err != nil { if err != nil {
log.Fatal("Failed to initialize database") log.Fatal("Failed to initialize database")
@@ -34,6 +37,29 @@ func main() {
log.Println("Database connection closed") log.Println("Database connection closed")
}() }()
router.SetupRoutes(app) err = socket.InitializeSocket(server)
app.Listen(":3000") if err != nil {
log.Println(err)
}
go func() {
err := server.Serve()
if err != nil {
log.Println(err)
}
}()
defer func(server *socketio.Server) {
err := server.Close()
if err != nil {
log.Println("disconnected from server", err)
}
}(server)
app.Use("/socket.io/*", adaptor.HTTPHandler(server))
app.Get("/socket.io/", adaptor.HTTPHandler(server))
// Setup routes
router.SetupRoutes(app)
log.Fatal(app.Listen(":3000"))
} }

View File

@@ -3,7 +3,6 @@ package model
import ( import (
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid" "github.com/google/uuid"
"time"
) )
type UserClaims struct { type UserClaims struct {
@@ -16,11 +15,11 @@ type Contact struct {
ConversationID uuid.UUID `json:"conversation_id"` ConversationID uuid.UUID `json:"conversation_id"`
UserID uuid.UUID `json:"user_id"` UserID uuid.UUID `json:"user_id"`
Username string `json:"username"` Username string `json:"username"`
LastActive *time.Time `json:"last_active"` LastActive *string `json:"last_active"`
Type string `json:"type"` Type string `json:"type"`
LastReadMessageID *int `json:"last_read_message_id"` LastReadMessageID *int `json:"last_read_message_id"`
LastMessageID *int `json:"last_message_id"` LastMessageID *int `json:"last_message_id"`
LastMessage *time.Time `json:"last_message"` LastMessage *string `json:"last_message"`
LastMessageTime *string `json:"last_message_time"` LastMessageTime *string `json:"last_message_time"`
LastMessageSender *string `json:"last_message_sender"` LastMessageSender *string `json:"last_message_sender"`
} }
@@ -32,7 +31,7 @@ type ContactSuggestion struct {
type Message struct { type Message struct {
ID int `json:"message_id"` ID int `json:"message_id"`
Message string `json:"message"` Message string `json:"message"`
SentAt time.Time `json:"sent_at"` SentAt string `json:"sent_at"`
Sender string `json:"sender"` Sender string `json:"sender"`
SenderID uuid.UUID `json:"sender_id"` SenderID uuid.UUID `json:"sender_id"`
AttachmentUrl *string `json:"attachment_url"` AttachmentUrl *string `json:"attachment_url"`

View File

@@ -39,6 +39,6 @@ func SetupRoutes(app *fiber.App) {
groups.Get("/getMembers/:groupID", handlers.GetMembers) groups.Get("/getMembers/:groupID", handlers.GetMembers)
// Socket group // Socket group
socket := chat.Group("/ws", middleware.Protected(), logger.New()) //socket := chat.Group("/ws", middleware.Protected(), logger.New())
socket.Get("/:id") //socket.Get("/:id")
} }

View File

@@ -10,6 +10,11 @@ import (
socketio "github.com/googollee/go-socket.io" socketio "github.com/googollee/go-socket.io"
) )
type JoinRoomResponse struct {
Status string `json:"status"`
Message string `json:"message"`
}
// Message represents the chat message structure // Message represents the chat message structure
type Message struct { type Message struct {
Content string `json:"message"` Content string `json:"message"`
@@ -43,11 +48,10 @@ type SocketResponse struct {
} }
// InitializeSocket sets up and configures the Socket.IO server // InitializeSocket sets up and configures the Socket.IO server
func InitializeSocket() (*socketio.Server, error) { func InitializeSocket(server *socketio.Server) error {
server := socketio.NewServer(nil)
// Middleware for authentication // Middleware for authentication
server.OnConnect("/", func(s socketio.Conn) error { server.OnConnect("/", func(s socketio.Conn) error {
s.SetContext("")
token := s.RemoteHeader().Get("Authorization") token := s.RemoteHeader().Get("Authorization")
if token == "" { if token == "" {
log.Println("(socket) Not logged in") log.Println("(socket) Not logged in")
@@ -90,12 +94,12 @@ func InitializeSocket() (*socketio.Server, error) {
log.Printf("(socket) Failed to get user conversations: %v\n", err) log.Printf("(socket) Failed to get user conversations: %v\n", err)
return return
} }
log.Println("CONVERSATIONS", conversations)
// Join all conversations // Join all conversations
for _, conv := range conversations { for _, conv := range conversations {
s.Join(conv) s.Join(conv)
} }
s.Join(userID) // Join user's personal room s.Join(userID)
log.Printf("User: %s joined to: %v\n", username, conversations) log.Printf("User: %s joined to: %v\n", username, conversations)
}) })
@@ -178,21 +182,30 @@ func InitializeSocket() (*socketio.Server, error) {
return SocketResponse{Status: "error", Message: "No user id provided"} return SocketResponse{Status: "error", Message: "No user id provided"}
} }
err := removeUserFromGroupById(data.GroupID, data.UserID) msg, err := database.RemoveUserFromGroup(database.DB, data.GroupID, data.UserID)
if msg != "" {
return SocketResponse{Status: "error", Message: msg}
}
if err != nil { if err != nil {
return SocketResponse{Status: "error", Message: err.Error()} return SocketResponse{Status: "error", Message: err.Error()}
} }
// Remove user from room // Remove user from room
sockets := server.Sockets(data.GroupID) server.ForEach("/", data.GroupID.String(), func(c socketio.Conn) {
for _, socket := range sockets { // Get the context and check user ID
if socket.Context().(map[string]interface{})["user_id"] == data.UserID { if ctxData, ok := c.Context().(map[string]interface{}); ok {
socket.Leave(data.GroupID) 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("", data.GroupID, "left group", data) server.BroadcastToRoom("", groupIDstr, "left group", data)
server.BroadcastToRoom("", data.UserID, "left group", data) server.BroadcastToRoom("", userIDstr, "left group", data)
return SocketResponse{Status: "ok", Message: "Successfully removed user from group"} return SocketResponse{Status: "ok", Message: "Successfully removed user from group"}
}) })
@@ -200,59 +213,118 @@ func InitializeSocket() (*socketio.Server, error) {
// Handle administrator operations // Handle administrator operations
server.OnEvent("/", "added administrator", func(s socketio.Conn, data GroupUserData) SocketResponse { server.OnEvent("/", "added administrator", func(s socketio.Conn, data GroupUserData) SocketResponse {
ctx := s.Context().(map[string]interface{}) ctx := s.Context().(map[string]interface{})
userID := ctx["user_id"].(string) userIDstr := ctx["user_id"].(string)
if data.GroupID == "" { 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"} return SocketResponse{Status: "error", Message: "No conversation id provided"}
} }
if data.UserID == "" { if data.UserID == uuid.Nil {
return SocketResponse{Status: "error", Message: "No user id provided"} return SocketResponse{Status: "error", Message: "No user id provided"}
} }
isAdmin, err := isAdmin(userID, data.GroupID) isAdmin, err := database.IsAdmin(database.DB, data.UserID, data.GroupID)
if err != nil || !isAdmin { if err != nil || !isAdmin {
return SocketResponse{Status: "error", Message: "You are not an administrator"} return SocketResponse{Status: "error", Message: "You are not an administrator"}
} }
err = addAdministrator(data.GroupID, data.UserID, userID) msg, err := database.AddAdministrator(database.DB, data.GroupID, data.UserID, userID)
if err != nil { if err != nil {
return SocketResponse{Status: "error", Message: err.Error()} return SocketResponse{Status: "error", Message: err.Error()}
} }
if msg != "" {
return SocketResponse{Status: "error", Message: msg}
}
server.BroadcastToRoom("", data.GroupID, "added administrator", data) groupIDstr := data.GroupID.String()
server.BroadcastToRoom("", groupIDstr, "added administrator", data)
return SocketResponse{Status: "ok", Message: "Successfully added administrator"} return SocketResponse{Status: "ok", Message: "Successfully added administrator"}
}) })
// Handle message read status // Handle message read status
server.OnEvent("/", "message read", func(s socketio.Conn, data MessageReadData) { server.OnEvent("/", "message read", func(s socketio.Conn, data MessageReadData) SocketResponse {
ctx := s.Context().(map[string]interface{}) ctx := s.Context().(map[string]interface{})
userID := ctx["user_id"].(string) userIDstr := ctx["user_id"].(string)
if data.ConversationID == "" || data.MessageID == "" { userID, err := uuid.Parse(userIDstr)
return if err != nil {
return SocketResponse{Status: "error", Message: "Invalid user id"}
} }
err := updateContactStatus(userID, data.ConversationID, data.MessageID) 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 { if err != nil {
log.Printf("Failed to update message read status: %v\n", err) 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 // Handle disconnection
server.OnDisconnect("/", func(s socketio.Conn, reason string) { server.OnDisconnect("/", func(s socketio.Conn, reason string) {
log.Printf("(socket) %s disconnected due to: %s\n", s.ID(), reason) log.Printf("(socket) %s disconnected due to: %s\n", s.ID(), reason)
}) })
return nil
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