feat: realtime status, mentions, push notifications
This commit is contained in:
+42
-14
@@ -18,16 +18,17 @@ const (
|
||||
|
||||
// PresenceData is the data payload for PRESENCE_UPDATE events.
|
||||
type PresenceData struct {
|
||||
UserID string `json:"user_id"`
|
||||
Status string `json:"status"`
|
||||
UserID string `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Hub maintains the set of active clients and broadcasts messages to them.
|
||||
type Hub struct {
|
||||
mu sync.RWMutex
|
||||
clients map[*Client]struct{}
|
||||
presence map[string]string // userID -> status
|
||||
lastActivity map[string]time.Time // userID -> last activity time
|
||||
presence map[string]string // userID -> status
|
||||
lastActivity map[string]time.Time // userID -> last activity time
|
||||
userServers map[string]map[string]struct{} // userID -> set of serverIDs
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
@@ -162,16 +163,7 @@ func (h *Hub) checkIdleUsers() {
|
||||
|
||||
// setUserStatus persists the user's status to the database.
|
||||
func (h *Hub) setUserStatus(userID, status string) {
|
||||
if h.db == nil {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := h.db.ExecContext(ctx, `UPDATE users SET status = $1 WHERE id = $2`, status, userID)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to update user status", "user_id", userID, "status", status, "error", err)
|
||||
}
|
||||
setUserStatus(h.db, h.logger, userID, status)
|
||||
}
|
||||
|
||||
// broadcastPresence sends a PRESENCE_UPDATE event to all connected clients.
|
||||
@@ -185,6 +177,29 @@ func (h *Hub) broadcastPresence(userID, status string) {
|
||||
})
|
||||
}
|
||||
|
||||
// SetUserStatus is the exported form of setUserStatus, used by non-gateway
|
||||
// handlers that need to persist a status change.
|
||||
func (h *Hub) SetUserStatus(userID, status string) {
|
||||
h.setUserStatus(userID, status)
|
||||
}
|
||||
|
||||
// BroadcastPresence sends a PRESENCE_UPDATE event including the username.
|
||||
func (h *Hub) BroadcastPresence(userID, username, status string) {
|
||||
h.mu.Lock()
|
||||
h.presence[userID] = status
|
||||
h.lastActivity[userID] = time.Now()
|
||||
h.mu.Unlock()
|
||||
|
||||
h.BroadcastEvent(Event{
|
||||
Type: EventPresenceUpdate,
|
||||
Data: PresenceData{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Status: status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Register queues a client for registration with the hub.
|
||||
func (h *Hub) Register(client *Client) {
|
||||
h.register <- client
|
||||
@@ -273,3 +288,16 @@ func (h *Hub) ServerIDForChannel(ctx context.Context, channelID string) (string,
|
||||
err := h.db.QueryRowContext(ctx, `SELECT server_id FROM channels WHERE id = $1`, channelID).Scan(&serverID)
|
||||
return serverID, err
|
||||
}
|
||||
|
||||
func setUserStatus(db *sql.DB, logger *slog.Logger, userID, status string) {
|
||||
if db == nil {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := db.ExecContext(ctx, `UPDATE users SET status = $1 WHERE id = $2`, status, userID)
|
||||
if err != nil {
|
||||
logger.Error("failed to update user status", "user_id", userID, "status", status, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user