Files
OpsMastery.v5/internal/handlers/chatHandler.go
2026-07-12 20:26:16 -04:00

225 lines
6.5 KiB
Go

// // filepath: /home/cody/OpsMastery.v5/internal/handlers/chatHandler.go
package handlers
import (
"fmt"
"sync"
"time"
"OpsMastery.v5/internal/database"
"OpsMastery.v5/internal/models"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
var (
clients = make(map[*websocket.Conn]uint) // map connection to user ID
broadcast = make(chan Message)
mu sync.Mutex
chatClients = make(map[uint]map[*websocket.Conn]bool) // chatID -> set of connections
)
type Message struct {
SenderID uint `json:"sender_id"`
RecipientID uint `json:"recipient_id"`
Content string `json:"content"`
}
type Chat struct {
ID uint `json:"id"`
Name string `json:"name"`
TicketID *uint `json:"ticket_id,omitempty"`
IsPrivate bool `json:"is_private"`
Users []models.User `json:"users"`
Messages []models.ChatMessage `json:"messages"`
}
// WebSocket chat endpoint
func ChatWebSocket(c *fiber.Ctx) error {
// Upgrade to WebSocket
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
}
func HandleChat(c *websocket.Conn, claims map[string]interface{}) {
senderID, _ := claims["userID"].(uint)
// Register connection
if chatClients[0] == nil {
chatClients[0] = make(map[*websocket.Conn]bool)
}
chatClients[0][c] = true
defer func() {
delete(chatClients[0], c)
c.Close()
}()
for {
var msg struct {
Content string `json:"content"`
}
if err := c.ReadJSON(&msg); err != nil {
break
}
// Save message to DB
chatMsg := models.ChatMessage{
ChatID: 0,
SenderID: senderID,
Content: msg.Content,
SentAt: time.Now(),
}
database.DB().Create(&chatMsg)
// Broadcast to all clients in the chat
for client := range chatClients[0] {
client.WriteJSON(chatMsg)
}
}
}
// Broadcast messages to all clients
func StartBroadcast() {
for {
msg := <-broadcast
mu.Lock()
for conn := range clients {
if err := conn.WriteJSON(msg); err != nil {
fmt.Println("Error broadcasting:", err)
}
}
mu.Unlock()
}
}
// GET /api/v1/chats?user1=username1&user2=username2
func GetChatHistory(c *fiber.Ctx) error {
user1 := c.Query("user1")
user2 := c.Query("user2")
var messages []models.ChatMessage
database.DB().Where(
"(sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?)",
user1, user2, user2, user1,
).Order("created_at asc").Find(&messages)
return c.JSON(messages) // Returns [] if no messages exist
}
// GET /api/v1/chat_partners?user_id=some_user_id
func GetChatPartners(c *fiber.Ctx) error {
userID := c.Query("user_id")
var partners []models.User
database.DB().Raw(`
SELECT DISTINCT u.*
FROM users u
JOIN chat_messages cm
ON (cm.sender_id = u.id OR cm.recipient_id = u.id)
WHERE (cm.sender_id = ? OR cm.recipient_id = ?) AND u.id != ?
`, userID, userID, userID).Scan(&partners)
return c.JSON(partners)
}
// DELETE /api/v1/chats/delete?user1=username1&user2=username2
func DeleteChatBetweenUsers(c *fiber.Ctx) error {
user1 := c.Query("user1")
user2 := c.Query("user2")
result := database.DB().Where(
"(sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?)",
user1, user2, user2, user1,
).Delete(&models.ChatMessage{})
if result.Error != nil {
return c.Status(500).JSON(fiber.Map{"error": result.Error.Error()})
}
return c.JSON(fiber.Map{"deleted": result.RowsAffected})
}
// Create a new chat (group or direct)
func CreateChat(c *fiber.Ctx) error {
var input struct {
Name string `json:"name"`
UserIDs []uint `json:"user_ids"`
IsPrivate bool `json:"is_private"`
TicketID *uint `json:"ticket_id"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
chat := models.Chat{
Name: input.Name,
IsPrivate: input.IsPrivate,
TicketID: input.TicketID,
}
if err := database.DB().Create(&chat).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not create chat"})
}
if len(input.UserIDs) > 0 {
var users []models.User
if err := database.DB().Where("id IN ?", input.UserIDs).Find(&users).Error; err == nil {
database.DB().Model(&chat).Association("Users").Append(users)
}
}
return c.Status(fiber.StatusCreated).JSON(chat)
}
// Add users to an existing chat
func AddUsersToChat(c *fiber.Ctx) error {
chatID := c.Params("chatId")
var input struct {
UserIDs []uint `json:"user_ids"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
var chat models.Chat
if err := database.DB().First(&chat, chatID).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Chat not found"})
}
var users []models.User
if err := database.DB().Where("id IN ?", input.UserIDs).Find(&users).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Users not found"})
}
database.DB().Model(&chat).Association("Users").Append(users)
return c.JSON(fiber.Map{"added": len(users)})
}
// Get all chats for a user (for sidebar)
func GetChatsForUser(c *fiber.Ctx) error {
userID := c.Params("userId")
var chats []models.Chat
if err := database.DB().Joins("JOIN chat_users ON chat_users.chat_id = chats.id").
Where("chat_users.user_id = ?", userID).
Preload("Users").
Preload("Messages").
Find(&chats).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not fetch chats"})
}
return c.JSON(chats)
}
// Get all messages for a chat
func GetChatMessages(c *fiber.Ctx) error {
chatID := c.Params("chatId")
var messages []models.ChatMessage
if err := database.DB().Where("chat_id = ?", chatID).Order("sent_at asc").Find(&messages).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not fetch messages"})
}
return c.JSON(messages)
}
// Delete a chat (and its messages)
func DeleteChat(c *fiber.Ctx) error {
chatID := c.Params("chatId")
if err := database.DB().Where("chat_id = ?", chatID).Delete(&models.ChatMessage{}).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not delete messages"})
}
if err := database.DB().Delete(&models.Chat{}, chatID).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not delete chat"})
}
return c.JSON(fiber.Map{"deleted": chatID})
}