157 lines
4.0 KiB
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"
|
|
}
|
|
}
|