package api import ( "encoding/json" "net/http" "strconv" "time" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/inou-ai/messaging-center/internal/core" ) // MessageResponse wraps a message for API response. type MessageResponse struct { Message *core.Message `json:"message"` } // MessagesResponse wraps a list of messages for API response. type MessagesResponse struct { Messages []core.Message `json:"messages"` Count int `json:"count"` Limit int `json:"limit"` Offset int `json:"offset"` } // CreateMessageRequest is the request body for creating a message. type CreateMessageRequest struct { Channel string `json:"channel"` To string `json:"to"` Body string `json:"body"` Subject string `json:"subject,omitempty"` Attachments []string `json:"attachments,omitempty"` // attachment IDs } // handleListMessages handles GET /api/v1/messages. func (s *Server) handleListMessages(w http.ResponseWriter, r *http.Request) { filter := core.MessageFilter{ Source: r.URL.Query().Get("source"), Direction: r.URL.Query().Get("direction"), Type: r.URL.Query().Get("type"), } if since := r.URL.Query().Get("since"); since != "" { if t, err := time.Parse(time.RFC3339, since); err == nil { filter.Since = &t } } if until := r.URL.Query().Get("until"); until != "" { if t, err := time.Parse(time.RFC3339, until); err == nil { filter.Until = &t } } if limit := r.URL.Query().Get("limit"); limit != "" { if l, err := strconv.Atoi(limit); err == nil { if l > 200 { l = 200 } filter.Limit = l } } if offset := r.URL.Query().Get("offset"); offset != "" { if o, err := strconv.Atoi(offset); err == nil { filter.Offset = o } } messages, err := s.store.ListMessages(filter) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } if messages == nil { messages = []core.Message{} } writeJSON(w, http.StatusOK, MessagesResponse{ Messages: messages, Count: len(messages), Limit: filter.Limit, Offset: filter.Offset, }) } // handleGetMessage handles GET /api/v1/messages/:id. func (s *Server) handleGetMessage(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") msg, err := s.store.GetMessage(id) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } if msg == nil { writeJSON(w, http.StatusNotFound, map[string]string{"error": "message not found"}) return } writeJSON(w, http.StatusOK, MessageResponse{Message: msg}) } // handleCreateMessage handles POST /api/v1/messages (stub). func (s *Server) handleCreateMessage(w http.ResponseWriter, r *http.Request) { var req CreateMessageRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"}) return } if req.Channel == "" || req.To == "" || req.Body == "" { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "channel, to, and body are required"}) return } // For now, just create a pending message record // Actual sending will be implemented with adapters now := time.Now() msg := &core.Message{ ID: "msg_" + uuid.New().String()[:8], Source: req.Channel, Direction: "outbound", From: core.Contact{ID: "system", Type: "system"}, To: core.Contact{ID: req.To, Type: contactType(req.Channel)}, Timestamp: now, Type: "text", Subject: req.Subject, Body: req.Body, Status: "pending", CreatedAt: now, UpdatedAt: now, } if err := s.store.CreateMessage(msg); err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusAccepted, MessageResponse{Message: msg}) } // contactType returns the contact type for a channel. func contactType(channel string) string { switch channel { case "email": return "email" case "whatsapp", "signal", "sms": return "phone" default: return "unknown" } }