feat(store): add thread support to contact model and collector

- Add threadID and threadType to ContactCollector.EnsureContact
- Update ContactStore.UpsertContact signature
- Update Contact type in web UI
This commit is contained in:
viettranx committed 2026-04-02 08:22:57 +07:00
1 parent 670000651d
commit bf98a32e9b
3 files changed
+14 -8

No files matched your search

+5 -4
View File
@@ -23,16 +23,17 @@ func NewContactCollector(s ContactStore, c cache.Cache[bool]) *ContactCollector
}
// EnsureContact creates or refreshes a contact entry, skipping DB if recently seen.
// contactType: "user" (individual sender) or "group" (group chat entity).
func (c *ContactCollector) EnsureContact(ctx context.Context, channelType, channelInstance, senderID, userID, displayName, username, peerKind, contactType string) {
key := channelType + ":" + senderID
// contactType: "user" (individual sender), "group" (group chat entity), or "topic" (forum topic).
// Pass empty threadID/threadType for base contacts (DM, group root).
func (c *ContactCollector) EnsureContact(ctx context.Context, channelType, channelInstance, senderID, userID, displayName, username, peerKind, contactType, threadID, threadType string) {
key := channelType + ":" + senderID + ":" + threadID
if _, ok := c.seen.Get(ctx, key); ok {
return
}
if contactType == "" {
contactType = "user"
}
if err := c.store.UpsertContact(ctx, channelType, channelInstance, senderID, userID, displayName, username, peerKind, contactType); err != nil {
if err := c.store.UpsertContact(ctx, channelType, channelInstance, senderID, userID, displayName, username, peerKind, contactType, threadID, threadType); err != nil {
slog.Warn("contact_collector.upsert_failed", "error", err, "channel", channelType, "sender", senderID)
return
}
+6 -3
View File
@@ -19,7 +19,9 @@ type ChannelContact struct {
Username *string `json:"username,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
PeerKind *string `json:"peer_kind,omitempty"`
ContactType string `json:"contact_type"` // "user" or "group"
ContactType string `json:"contact_type"` // "user", "group", or "topic"
ThreadID *string `json:"thread_id,omitempty"`
ThreadType *string `json:"thread_type,omitempty"`
MergedID *uuid.UUID `json:"merged_id,omitempty"`
FirstSeenAt time.Time `json:"first_seen_at"`
LastSeenAt time.Time `json:"last_seen_at"`
@@ -37,9 +39,10 @@ type ContactListOpts struct {
// ContactStore manages channel contacts (auto-collected user info).
type ContactStore interface {
// UpsertContact creates or updates a contact. On conflict (channel_type, sender_id),
// UpsertContact creates or updates a contact. On conflict (tenant_id, channel_type, sender_id, thread_id),
// updates display_name, username, user_id, channel_instance, and last_seen_at.
UpsertContact(ctx context.Context, channelType, channelInstance, senderID, userID, displayName, username, peerKind, contactType string) error
// Pass empty threadID/threadType for base contacts (DM, group root).
UpsertContact(ctx context.Context, channelType, channelInstance, senderID, userID, displayName, username, peerKind, contactType, threadID, threadType string) error
// ListContacts searches contacts with pagination and filters.
ListContacts(ctx context.Context, opts ContactListOpts) ([]ChannelContact, error)
+3 -1
View File
@@ -19,7 +19,9 @@ export interface ChannelContact {
username?: string;
avatar_url?: string;
peer_kind?: string;
contact_type: string; // "user" or "group"
contact_type: string; // "user", "group", or "topic"
thread_id?: string;
thread_type?: string;
merged_id?: string;
first_seen_at: string;
last_seen_at: string;