HTTP API: a chat's recent messages, and sending a message (closes #5)
check / check (push) Successful in 1m5s
check / check (push) Successful in 1m5s
`GET /api/v1/chats/{id}/messages?count=N` returns a chat's recent messages, oldest first (`count` 1 to 100, default 20), and `POST` to the same path sends a text message and returns it once the chat client has taken it. Both answer `404` for any id the chats list does not show, looked up in the same contact list. The message record is defined once, for the webhooks to reuse.
Disclosures: items are read five at a time, because one item can be many times its text and 100 at once could pass the 16 MiB read limit; text too long gets `413`, a contact who deleted the chat `409` (unverified for one still connecting); a query the server cannot read gets its own `400`.
Model: opus-5-5
This commit was merged in pull request #15.
This commit is contained in:
+71
-20
@@ -2,26 +2,55 @@ package api
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// handleChats lists the bot's chats, ordered by id. The bot talks to
|
||||
// people only one to one, so its chats are its contacts, and a chat's
|
||||
// id is its contact's.
|
||||
func (h *handlers) handleChats() http.HandlerFunc {
|
||||
type chat struct {
|
||||
ID int64 `json:"id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ContactDeleted bool `json:"contact_deleted"`
|
||||
// noSuchChat answers a request naming a chat the bot does not have.
|
||||
const noSuchChat = "no such chat"
|
||||
|
||||
// chat is how the API shows a chat. The bot talks to people only one to
|
||||
// one, so its chats are its contacts, and a chat's id is its contact's.
|
||||
type chat struct {
|
||||
ID int64 `json:"id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ContactDeleted bool `json:"contact_deleted"`
|
||||
}
|
||||
|
||||
// chats returns the bot's chats, ordered by id. GET /api/v1/chats
|
||||
// answers with this list, and chatID accepts only an id in it.
|
||||
func (h *handlers) chats(ctx context.Context) ([]chat, error) {
|
||||
contacts, err := h.client.Contacts(ctx, h.userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chats := make([]chat, 0, len(contacts))
|
||||
for _, c := range contacts {
|
||||
chats = append(chats, chat{
|
||||
ID: c.ContactID,
|
||||
DisplayName: c.Profile.DisplayName,
|
||||
ContactDeleted: c.Deleted(),
|
||||
})
|
||||
}
|
||||
|
||||
slices.SortFunc(chats, func(a, b chat) int { return cmp.Compare(a.ID, b.ID) })
|
||||
|
||||
return chats, nil
|
||||
}
|
||||
|
||||
// handleChats lists the bot's chats.
|
||||
func (h *handlers) handleChats() http.HandlerFunc {
|
||||
type response struct {
|
||||
Chats []chat `json:"chats"`
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
contacts, err := h.client.Contacts(r.Context(), h.userID)
|
||||
chats, err := h.chats(r.Context())
|
||||
if err != nil {
|
||||
h.log.Error("listing the chats", "error", err)
|
||||
h.respondError(w, http.StatusInternalServerError,
|
||||
@@ -30,17 +59,39 @@ func (h *handlers) handleChats() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
chats := make([]chat, 0, len(contacts))
|
||||
for _, c := range contacts {
|
||||
chats = append(chats, chat{
|
||||
ID: c.ContactID,
|
||||
DisplayName: c.Profile.DisplayName,
|
||||
ContactDeleted: c.Deleted(),
|
||||
})
|
||||
}
|
||||
|
||||
slices.SortFunc(chats, func(a, b chat) int { return cmp.Compare(a.ID, b.ID) })
|
||||
|
||||
h.respond(w, http.StatusOK, response{Chats: chats})
|
||||
}
|
||||
}
|
||||
|
||||
// chatID returns the chat id in the request's path, if it is one of the
|
||||
// bot's chats. Otherwise it answers the request itself, 404, or 500 if
|
||||
// the chats could not be read, and returns false.
|
||||
//
|
||||
// The id is looked up rather than passed to the chat client, which also
|
||||
// keeps contact records that are not chats, such as the bot's own
|
||||
// profile, and would read or send in them.
|
||||
func (h *handlers) chatID(w http.ResponseWriter, r *http.Request) (int64, bool) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
h.respondError(w, http.StatusNotFound, noSuchChat)
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
chats, err := h.chats(r.Context())
|
||||
if err != nil {
|
||||
h.log.Error("looking up a chat", "error", err)
|
||||
h.respondError(w, http.StatusInternalServerError,
|
||||
"the chats could not be read")
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if !slices.ContainsFunc(chats, func(c chat) bool { return c.ID == id }) {
|
||||
h.respondError(w, http.StatusNotFound, noSuchChat)
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return id, true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user