diff --git a/db/config.go b/db/config.go index 95a2c770d..72d6e4591 100644 --- a/db/config.go +++ b/db/config.go @@ -84,6 +84,8 @@ func InitDB() { db.AutoMigrate(&WfRequest{}) db.AutoMigrate(&WfProcessingMap{}) db.AutoMigrate(&Tickets{}) + db.AutoMigrate(&ChatMessage{}) + db.AutoMigrate(&Chat{}) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/interface.go b/db/interface.go index 867786d2a..1ece64fb1 100644 --- a/db/interface.go +++ b/db/interface.go @@ -201,4 +201,8 @@ type Database interface { GetProductBrief(workspaceUuid string) (string, error) GetFeatureBrief(featureUuid string) (string, error) GetTicketsByPhaseUUID(featureUUID string, phaseUUID string) ([]Tickets, error) + AddChat(chat *Chat) (Chat, error) + GetChatByChatID(chatID string) (Chat, error) + AddChatMessage(message *ChatMessage) (ChatMessage, error) + GetChatMessagesForChatID(chatID string) ([]ChatMessage, error) } diff --git a/handlers/chat.go b/handlers/chat.go new file mode 100644 index 000000000..cd09146ce --- /dev/null +++ b/handlers/chat.go @@ -0,0 +1,156 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "github.com/rs/xid" + "net/http" + "time" + + "github.com/go-chi/chi" + "github.com/stakwork/sphinx-tribes/db" +) + +type ChatHandler struct { + httpClient *http.Client + db db.Database +} + +type ChatResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +type HistoryChatResponse struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` +} + +func NewChatHandler(httpClient *http.Client, database db.Database) *ChatHandler { + return &ChatHandler{ + httpClient: httpClient, + db: database, + } +} + +func (ch *ChatHandler) CreateChat(w http.ResponseWriter, r *http.Request) { + var request struct { + WorkspaceID string `json:"workspaceId"` + Title string `json:"title"` + } + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ChatResponse{ + Success: false, + Message: "Invalid request body", + }) + return + } + + chat := &db.Chat{ + ID: xid.New().String(), + WorkspaceID: request.WorkspaceID, + Title: request.Title, + } + + createdChat, err := ch.db.AddChat(chat) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ChatResponse{ + Success: false, + Message: fmt.Sprintf("Failed to create chat: %v", err), + }) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(ChatResponse{ + Success: true, + Message: "Chat created successfully", + Data: createdChat, + }) +} + +func (ch *ChatHandler) SendMessage(w http.ResponseWriter, r *http.Request) { + var request struct { + ChatID string `json:"chatId"` + Message string `json:"message"` + ContextTags []struct { + Type string `json:"type"` + ID string `json:"id"` + } `json:"contextTags"` + } + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ChatResponse{ + Success: false, + Message: "Invalid request body", + }) + return + } + + message := &db.ChatMessage{ + ID: xid.New().String(), + ChatID: request.ChatID, + Message: request.Message, + Role: "user", + Timestamp: time.Now(), + Status: "sending", + } + + createdMessage, err := ch.db.AddChatMessage(message) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ChatResponse{ + Success: false, + Message: fmt.Sprintf("Failed to send message: %v", err), + }) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(ChatResponse{ + Success: true, + Message: "Message sent successfully", + Data: createdMessage, + }) +} + +func (ch *ChatHandler) GetChatHistory(w http.ResponseWriter, r *http.Request) { + chatID := chi.URLParam(r, "uuid") + if chatID == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ChatResponse{ + Success: false, + Message: "Chat ID is required", + }) + return + } + + messages, err := ch.db.GetChatMessagesForChatID(chatID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ChatResponse{ + Success: false, + Message: fmt.Sprintf("Failed to fetch chat history: %v", err), + }) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(HistoryChatResponse{ + Success: true, + Data: messages, + }) +} + +func (ch *ChatHandler) ProcessChatResponse(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(ChatResponse{ + Success: true, + Message: "Stubbed out - process chat response", + }) +} diff --git a/routes/chat.go b/routes/chat.go new file mode 100644 index 000000000..095069d0e --- /dev/null +++ b/routes/chat.go @@ -0,0 +1,25 @@ +package routes + +import ( + "github.com/go-chi/chi" + "github.com/stakwork/sphinx-tribes/auth" + "github.com/stakwork/sphinx-tribes/db" + "github.com/stakwork/sphinx-tribes/handlers" + "net/http" +) + +func ChatRoutes() chi.Router { + r := chi.NewRouter() + chatHandler := handlers.NewChatHandler(http.DefaultClient, db.DB) + + r.Post("/response", chatHandler.ProcessChatResponse) + + r.Group(func(r chi.Router) { + r.Use(auth.PubKeyContext) + r.Post("/", chatHandler.CreateChat) + r.Post("/send", chatHandler.SendMessage) + r.Get("/history/{uuid}", chatHandler.GetChatHistory) + }) + + return r +} diff --git a/routes/index.go b/routes/index.go index 8da500084..e00df0f9a 100644 --- a/routes/index.go +++ b/routes/index.go @@ -39,6 +39,7 @@ func NewRouter() *http.Server { r.Mount("/features", FeatureRoutes()) r.Mount("/workflows", WorkflowRoutes()) r.Mount("/bounties/ticket", TicketRoutes()) + r.Mount("/hivechat", ChatRoutes()) r.Group(func(r chi.Router) { r.Get("/tribe_by_feed", tribeHandlers.GetFirstTribeByFeed)