fix: chat UI

This commit is contained in:
Krispire Admin
2026-08-29 17:30:14 +05:30
parent 7a730cd42e
commit 44120efdec
7 changed files with 513 additions and 19 deletions
+11
View File
@@ -96,6 +96,17 @@ type openDMReq struct {
Handle string `json:"handle"`
}
// Unread — GET /chat/unread. Drives the nav badge, so it stays a single
// count rather than anything the caller has to aggregate.
func (h *ChatHandler) Unread(w http.ResponseWriter, r *http.Request) {
n, err := h.svc.UnreadCount(r.Context(), middleware.UserID(r.Context()))
if err != nil {
writeChatError(w, err)
return
}
response.JSON(w, http.StatusOK, map[string]int{"count": n})
}
// OpenDM — POST /chat/conversations/dm. Idempotent: returns the existing
// thread when there is one, so the client can call it every time it opens a
// profile without checking first.
+33 -6
View File
@@ -1,6 +1,7 @@
package handler
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
@@ -41,11 +42,29 @@ type SoketiWebhookHandler struct {
appKey string
appSecret string
feed *repository.FeedRepo
logger *zap.Logger
// chat records presence so a message doesn't push to someone who is
// already looking at the app. Optional.
chat ChatPresence
logger *zap.Logger
}
func NewSoketiWebhookHandler(appKey, appSecret string, feed *repository.FeedRepo, logger *zap.Logger) *SoketiWebhookHandler {
return &SoketiWebhookHandler{appKey: appKey, appSecret: appSecret, feed: feed, logger: logger}
// ChatPresence is the slice of ChatService this handler needs — narrow so the
// webhook doesn't depend on the whole chat surface.
type ChatPresence interface {
MarkPresence(ctx context.Context, userID string, online bool)
}
func (h *SoketiWebhookHandler) presence(ctx context.Context, userID string, online bool) {
if h.chat == nil {
return
}
h.chat.MarkPresence(ctx, userID, online)
}
func NewSoketiWebhookHandler(appKey, appSecret string, feed *repository.FeedRepo, chat ChatPresence, logger *zap.Logger) *SoketiWebhookHandler {
return &SoketiWebhookHandler{
appKey: appKey, appSecret: appSecret, feed: feed, chat: chat, logger: logger,
}
}
type soketiWebhookBody struct {
@@ -91,12 +110,20 @@ func (h *SoketiWebhookHandler) Handle(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
cleared := 0
for _, e := range payload.Events {
// A per-user notifications channel emptying = that user is offline.
if e.Name != "channel_vacated" {
uid, ok := userIDFromNotifChannel(e.Channel)
if !ok {
continue
}
if uid, ok := userIDFromNotifChannel(e.Channel); ok {
switch e.Name {
case "channel_occupied":
// This user has a live connection, so chat push is suppressed
// for them — they will see the message in the app.
h.presence(ctx, uid, true)
case "channel_vacated":
// Their last tab closed: they are offline. Evict the feed
// seen-set and let chat push resume.
h.feed.Reset(ctx, uid)
h.presence(ctx, uid, false)
cleared++
}
}