Files
Messenger/server/internal/api/conversations.go
T

173 lines
4.7 KiB
Go
Raw Normal View History

package api
import (
"encoding/json"
"net/http"
"strconv"
"time"
"messenger/internal/realtime"
"messenger/internal/store"
)
type conversationJSON struct {
ID int64 `json:"id"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"`
LastMessage *messageJSON `json:"last_message,omitempty"`
UnreadCount int64 `json:"unread_count"`
}
type messageJSON struct {
ID int64 `json:"id"`
ConversationID int64 `json:"conversation_id"`
SenderID int64 `json:"sender_id"`
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
EditedAt *time.Time `json:"edited_at,omitempty"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
func toMessageJSON(m *store.Message) messageJSON {
return messageJSON{
ID: m.ID,
ConversationID: m.ConversationID,
SenderID: m.SenderID,
Body: m.Body,
CreatedAt: m.CreatedAt,
EditedAt: m.EditedAt,
DeletedAt: m.DeletedAt,
}
}
func (h *apiHandler) listConversations(w http.ResponseWriter, r *http.Request) {
userID, _ := userIDFrom(r.Context())
summaries, err := h.store.ListConversationsForUser(r.Context(), userID)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal server error")
return
}
out := make([]conversationJSON, 0, len(summaries))
for _, s := range summaries {
item := conversationJSON{
ID: s.ID,
Type: s.Type,
CreatedAt: s.CreatedAt,
UnreadCount: s.UnreadCount,
}
if s.LastMessage != nil {
msg := toMessageJSON(s.LastMessage)
item.LastMessage = &msg
}
out = append(out, item)
}
writeJSON(w, http.StatusOK, out)
}
func (h *apiHandler) createDM(w http.ResponseWriter, r *http.Request) {
userID, _ := userIDFrom(r.Context())
var req struct {
UserID int64 `json:"user_id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.UserID <= 0 {
writeError(w, http.StatusBadRequest, "invalid user_id")
return
}
if req.UserID == userID {
writeError(w, http.StatusBadRequest, "cannot chat with yourself")
return
}
conv, err := h.store.CreateDM(r.Context(), userID, req.UserID)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal server error")
return
}
writeJSON(w, http.StatusCreated, conversationJSON{ID: conv.ID, Type: conv.Type, CreatedAt: conv.CreatedAt})
}
func (h *apiHandler) listMessages(w http.ResponseWriter, r *http.Request) {
userID, _ := userIDFrom(r.Context())
convID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil || convID <= 0 {
writeError(w, http.StatusBadRequest, "invalid conversation id")
return
}
member, err := h.store.IsMember(r.Context(), convID, userID)
if err != nil || !member {
writeError(w, http.StatusForbidden, "not a member")
return
}
limit := 50
if v := r.URL.Query().Get("limit"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 100 {
limit = n
}
}
var before int64
if v := r.URL.Query().Get("before"); v != "" {
before, _ = strconv.ParseInt(v, 10, 64)
}
messages, err := h.store.ListMessages(r.Context(), convID, before, limit)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal server error")
return
}
out := make([]messageJSON, 0, len(messages))
for i := range messages {
out = append(out, toMessageJSON(&messages[i]))
}
writeJSON(w, http.StatusOK, out)
}
type sendMessageRequest struct {
Body string `json:"body"`
TempID string `json:"temp_id"`
}
func (h *apiHandler) sendMessage(w http.ResponseWriter, r *http.Request) {
userID, _ := userIDFrom(r.Context())
convID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil || convID <= 0 {
writeError(w, http.StatusBadRequest, "invalid conversation id")
return
}
member, err := h.store.IsMember(r.Context(), convID, userID)
if err != nil || !member {
writeError(w, http.StatusForbidden, "not a member")
return
}
var req sendMessageRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid json")
return
}
if req.Body == "" {
writeError(w, http.StatusBadRequest, "body is required")
return
}
if len(req.Body) > 4000 {
writeError(w, http.StatusBadRequest, "body too long (max 4000)")
return
}
msg, err := h.store.InsertMessage(r.Context(), convID, userID, req.Body)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal server error")
return
}
h.hub.PublishMessageCreated(convID, realtime.MessageCreated{
TempID: req.TempID,
ConversationID: convID,
MessageID: msg.ID,
SenderID: userID,
Body: msg.Body,
CreatedAt: msg.CreatedAt,
})
writeJSON(w, http.StatusCreated, toMessageJSON(msg))
}