messaging-center/internal/api/messages.go

157 lines
4.0 KiB
Go

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"
}
}