feat(bots): bot framework polish + store

- /ws/bot endpoint: bot token auth via query param, SHA-256 lookup
- Bot WS actions: SEND_MESSAGE + DELETE_MESSAGE handled in gateway
- Bot messages: bot_id on messages table, bot badge in chat (green + BOT tag)
- Bot store: /bots lists all bots with server count + add-to-server
- Bot manager moved to /bots/manage
- Fix: command routes were double-nested under /bots/{botID}/commands
- Fix: fetchServerCommands route corrected to /bots/servers/...
This commit is contained in:
2026-07-15 12:45:44 -04:00
parent 56af584ede
commit 5bdb758d23
13 changed files with 557 additions and 22 deletions
+52
View File
@@ -22,6 +22,7 @@ func NewHandler(db *sql.DB) *Handler {
// RegisterRoutes registers authenticated bot routes under the given router.
func (h *Handler) RegisterRoutes(r chi.Router) {
r.Get("/store", h.Store)
r.Post("/", h.Create)
r.Get("/", h.List)
r.Get("/{botID}", h.Get)
@@ -547,3 +548,54 @@ func (h *Handler) RegenerateToken(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, b)
}
// ---- Store (public listing) ----
type storeBotResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Avatar *string `json:"avatar"`
Description string `json:"description"`
ServerCount int `json:"server_count"`
OwnerID string `json:"owner_id"`
CreatedAt string `json:"created_at"`
}
// Store returns all bots with their server count (visible to any authenticated user).
func (h *Handler) Store(w http.ResponseWriter, r *http.Request) {
rows, err := h.db.QueryContext(r.Context(), `
SELECT b.id, b.name, b.avatar, b.description, b.owner_id, b.created_at::text,
COUNT(bs.server_id) AS server_count
FROM bots b
LEFT JOIN bot_servers bs ON bs.bot_id = b.id
GROUP BY b.id, b.name, b.avatar, b.description, b.owner_id, b.created_at
ORDER BY server_count DESC, b.name
`)
if err != nil {
writeErr(w, http.StatusInternalServerError, "server error")
return
}
defer rows.Close()
bots := make([]storeBotResponse, 0)
for rows.Next() {
var b storeBotResponse
var avatar sql.NullString
var createdAt sql.NullString
if err := rows.Scan(&b.ID, &b.Name, &avatar, &b.Description, &b.OwnerID, &createdAt, &b.ServerCount); err != nil {
writeErr(w, http.StatusInternalServerError, "server error")
return
}
if avatar.Valid {
b.Avatar = &avatar.String
}
b.CreatedAt = createdAt.String
bots = append(bots, b)
}
if err := rows.Err(); err != nil {
writeErr(w, http.StatusInternalServerError, "server error")
return
}
writeJSON(w, http.StatusOK, bots)
}