| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- package store
- import (
- "spider/internal/model"
- "strings"
- "time"
- )
- // SaveGroupMember records a group-member relationship (idempotent via unique index).
- func (s *Store) SaveGroupMember(groupUsername, memberUsername, groupTitle, sourceType string, taskID uint) {
- groupUsername = strings.TrimPrefix(strings.TrimSpace(groupUsername), "@")
- memberUsername = strings.TrimPrefix(strings.TrimSpace(memberUsername), "@")
- if groupUsername == "" || memberUsername == "" || groupUsername == memberUsername {
- return
- }
- gm := model.GroupMember{
- GroupUsername: groupUsername,
- MemberUsername: memberUsername,
- GroupTitle: groupTitle,
- SourceType: sourceType,
- TaskID: taskID,
- DiscoveredAt: time.Now(),
- }
- // Use unique index for idempotency
- s.DB.Where("group_username = ? AND member_username = ?", groupUsername, memberUsername).
- FirstOrCreate(&gm)
- }
- // BatchSaveGroupMembers saves multiple group-member relationships (idempotent).
- // Returns the count of newly created records.
- func (s *Store) BatchSaveGroupMembers(groupUsername, groupTitle, sourceType string, memberUsernames []string) int {
- groupUsername = strings.TrimPrefix(strings.TrimSpace(groupUsername), "@")
- if groupUsername == "" {
- return 0
- }
- created := 0
- for _, mu := range memberUsernames {
- mu = strings.TrimPrefix(strings.TrimSpace(mu), "@")
- if mu == "" || mu == groupUsername {
- continue
- }
- gm := model.GroupMember{
- GroupUsername: groupUsername,
- MemberUsername: mu,
- GroupTitle: groupTitle,
- SourceType: sourceType,
- DiscoveredAt: time.Now(),
- }
- result := s.DB.Where("group_username = ? AND member_username = ?", groupUsername, mu).
- FirstOrCreate(&gm)
- if result.RowsAffected > 0 {
- created++
- }
- }
- return created
- }
- // ListMembersByGroup returns all members found in a group. Supports search by member username.
- func (s *Store) ListMembersByGroup(groupUsername string, page, pageSize int, search string) ([]model.GroupMember, int64, error) {
- var items []model.GroupMember
- var total int64
- q := s.DB.Model(&model.GroupMember{}).Where("group_username = ?", groupUsername)
- if search != "" {
- like := "%" + strings.TrimPrefix(strings.TrimSpace(search), "@") + "%"
- q = q.Where("member_username LIKE ?", like)
- }
- q.Count(&total)
- offset := (page - 1) * pageSize
- err := q.Order("discovered_at DESC").Offset(offset).Limit(pageSize).Find(&items).Error
- return items, total, err
- }
- // ListGroupsByMember returns all groups a member belongs to.
- func (s *Store) ListGroupsByMember(memberUsername string) ([]model.GroupMember, error) {
- var items []model.GroupMember
- err := s.DB.Where("member_username = ?", memberUsername).
- Order("discovered_at DESC").Find(&items).Error
- return items, err
- }
- // ListGroups returns all unique groups with member counts. Supports search by group username or title.
- func (s *Store) ListGroups(page, pageSize int, search string) ([]GroupSummary, int64, error) {
- base := s.DB.Model(&model.GroupMember{})
- if search != "" {
- like := "%" + search + "%"
- base = base.Where("group_username LIKE ? OR group_title LIKE ?", like, like)
- }
- var total int64
- base.Select("group_username").Group("group_username").Count(&total)
- var summaries []GroupSummary
- offset := (page - 1) * pageSize
- q := s.DB.Model(&model.GroupMember{})
- if search != "" {
- like := "%" + search + "%"
- q = q.Where("group_username LIKE ? OR group_title LIKE ?", like, like)
- }
- err := q.Select("group_username, MAX(group_title) as group_title, COUNT(*) as member_count, MAX(source_type) as source_type").
- Group("group_username").
- Order("member_count DESC").
- Offset(offset).Limit(pageSize).
- Scan(&summaries).Error
- return summaries, total, err
- }
- // SearchMembers searches for members by username across all groups.
- func (s *Store) SearchMembers(pattern string, page, pageSize int) ([]MemberSummary, int64, error) {
- like := "%" + strings.TrimPrefix(strings.TrimSpace(pattern), "@") + "%"
- var total int64
- s.DB.Model(&model.GroupMember{}).
- Where("member_username LIKE ?", like).
- Select("member_username").
- Group("member_username").
- Count(&total)
- var summaries []MemberSummary
- offset := (page - 1) * pageSize
- err := s.DB.Model(&model.GroupMember{}).
- Where("member_username LIKE ?", like).
- Select("member_username, COUNT(DISTINCT group_username) as group_count, MAX(discovered_at) as last_seen").
- Group("member_username").
- Order("group_count DESC").
- Offset(offset).Limit(pageSize).
- Scan(&summaries).Error
- return summaries, total, err
- }
- // GroupSummary is a summary of a group with member count.
- type GroupSummary struct {
- GroupUsername string `json:"group_username"`
- GroupTitle string `json:"group_title"`
- MemberCount int64 `json:"member_count"`
- SourceType string `json:"source_type"`
- }
- // MemberSummary is a summary of a member across groups.
- type MemberSummary struct {
- MemberUsername string `json:"member_username"`
- GroupCount int64 `json:"group_count"`
- LastSeen time.Time `json:"last_seen"`
- }
|