207 lines
6.3 KiB
Go
207 lines
6.3 KiB
Go
package model
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
|
||
"spark-server/internal/module/friend/entity"
|
||
"spark-server/internal/pkg/errs"
|
||
)
|
||
|
||
// FriendRelation maps table friend_relations (one directed edge per row).
|
||
type FriendRelation struct {
|
||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增"`
|
||
UserID int64 `gorm:"column:user_id;comment:关系所属用户"`
|
||
FriendID int64 `gorm:"column:friend_id;comment:对方 user_id"`
|
||
Status int `gorm:"column:status;comment:1=好友 0=非好友(仅用于单向拉黑占位)"`
|
||
Remark string `gorm:"column:remark;comment:备注,仅自己可见"`
|
||
Blocked bool `gorm:"column:is_blocked;comment:是否拉黑对方"`
|
||
Pinned bool `gorm:"column:is_pinned;comment:会话消息置顶"`
|
||
Source int `gorm:"column:source;comment:添加来源:0=未知 1=二维码 2=邮箱 3=spark_id"`
|
||
CreatedAt time.Time `gorm:"column:created_at;comment:成为好友或建边时间"`
|
||
UpdatedAt time.Time `gorm:"column:updated_at;comment:最近更新时间"`
|
||
}
|
||
|
||
func (FriendRelation) TableName() string { return "t_friend_relations" }
|
||
|
||
func (r *FriendRelation) entity() *entity.Relation {
|
||
if r == nil {
|
||
return nil
|
||
}
|
||
return &entity.Relation{
|
||
ID: r.ID, UserID: r.UserID, FriendID: r.FriendID, Status: entity.EdgeStatus(r.Status),
|
||
Remark: r.Remark, Blocked: r.Blocked, Pinned: r.Pinned, Source: entity.Source(r.Source),
|
||
CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt,
|
||
}
|
||
}
|
||
|
||
func rowFromEdge(e *entity.Relation) FriendRelation {
|
||
return FriendRelation{
|
||
UserID: e.UserID, FriendID: e.FriendID, Status: int(e.Status),
|
||
Remark: e.Remark, Blocked: e.Blocked, Pinned: e.Pinned, Source: int(e.Source),
|
||
CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt,
|
||
}
|
||
}
|
||
|
||
func (m *Repo) GetRelation(ctx context.Context, userID, friendID int64) (*entity.Relation, error) {
|
||
db, err := m.session(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var row FriendRelation
|
||
if err := db.Where("user_id = ? AND friend_id = ?", userID, friendID).Take(&row).Error; err != nil {
|
||
return nil, mapDBErr(err)
|
||
}
|
||
return row.entity(), nil
|
||
}
|
||
|
||
func (m *Repo) ListFriendIDs(ctx context.Context, userID int64) ([]int64, error) {
|
||
db, err := m.session(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var ids []int64
|
||
err = db.Model(&FriendRelation{}).
|
||
Where("user_id = ? AND status = ?", userID, entity.StatusFriend).
|
||
Limit(entity.FriendCap).Pluck("friend_id", &ids).Error
|
||
if err != nil {
|
||
return nil, mapDBErr(err)
|
||
}
|
||
return ids, nil
|
||
}
|
||
|
||
func (m *Repo) ListBlockedPeerIDs(ctx context.Context, userID int64) ([]int64, error) {
|
||
db, err := m.session(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var mine, theirs []int64
|
||
q := db.Model(&FriendRelation{})
|
||
if err := q.Where("user_id = ? AND is_blocked = ?", userID, true).Limit(entity.FriendCap).Pluck("friend_id", &mine).Error; err != nil {
|
||
return nil, mapDBErr(err)
|
||
}
|
||
if err := db.Model(&FriendRelation{}).Where("friend_id = ? AND is_blocked = ?", userID, true).Limit(entity.FriendCap).Pluck("user_id", &theirs).Error; err != nil {
|
||
return nil, mapDBErr(err)
|
||
}
|
||
seen := make(map[int64]struct{}, len(mine)+len(theirs))
|
||
out := make([]int64, 0, len(mine)+len(theirs))
|
||
for _, id := range append(mine, theirs...) {
|
||
if id == 0 {
|
||
continue
|
||
}
|
||
if _, ok := seen[id]; ok {
|
||
continue
|
||
}
|
||
seen[id] = struct{}{}
|
||
out = append(out, id)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (m *Repo) ListFriends(ctx context.Context, userID int64, limit int) ([]*entity.Relation, error) {
|
||
db, err := m.session(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
limit = entity.ClampLimit(limit)
|
||
var rows []FriendRelation
|
||
err = db.Where("user_id = ? AND status = ?", userID, entity.StatusFriend).
|
||
Order("is_pinned DESC, created_at DESC").Limit(limit).Find(&rows).Error
|
||
if err != nil {
|
||
return nil, mapDBErr(err)
|
||
}
|
||
out := make([]*entity.Relation, 0, len(rows))
|
||
for i := range rows {
|
||
out = append(out, rows[i].entity())
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (m *Repo) SetRemark(ctx context.Context, userID, friendID int64, remark string) error {
|
||
return m.updateEdge(ctx, userID, friendID, map[string]any{"remark": remark, "updated_at": time.Now()})
|
||
}
|
||
|
||
func (m *Repo) SetPinned(ctx context.Context, userID, friendID int64, pinned bool) error {
|
||
return m.updateEdge(ctx, userID, friendID, map[string]any{"is_pinned": pinned, "updated_at": time.Now()})
|
||
}
|
||
|
||
func (m *Repo) updateEdge(ctx context.Context, userID, friendID int64, values map[string]any) error {
|
||
if userID <= 0 || friendID <= 0 {
|
||
return errs.ErrInvalidArgument
|
||
}
|
||
db, err := m.session(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
res := db.Model(&FriendRelation{}).
|
||
Where("user_id = ? AND friend_id = ? AND status = ?", userID, friendID, entity.StatusFriend).
|
||
Updates(values)
|
||
if res.Error != nil {
|
||
return mapDBErr(res.Error)
|
||
}
|
||
if res.RowsAffected == 0 {
|
||
return errs.ErrNotFound
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (m *Repo) IsBlocked(ctx context.Context, userID, targetID int64) (bool, error) {
|
||
rel, err := m.GetRelation(ctx, userID, targetID)
|
||
if err != nil {
|
||
if errors.Is(err, errs.ErrNotFound) {
|
||
return false, nil
|
||
}
|
||
return false, err
|
||
}
|
||
return rel.Blocked, nil
|
||
}
|
||
|
||
func (m *Repo) BlockedEither(ctx context.Context, a, b int64) (bool, error) {
|
||
me, err := m.IsBlocked(ctx, a, b)
|
||
if err != nil || me {
|
||
return me, err
|
||
}
|
||
return m.IsBlocked(ctx, b, a)
|
||
}
|
||
|
||
func (m *Repo) SetBlocked(ctx context.Context, userID, friendID int64, blocked bool) error {
|
||
if userID <= 0 || friendID <= 0 {
|
||
return errs.ErrInvalidArgument
|
||
}
|
||
db, err := m.session(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
now := time.Now()
|
||
return db.Transaction(func(tx *gorm.DB) error {
|
||
var row FriendRelation
|
||
err := tx.Where("user_id = ? AND friend_id = ?", userID, friendID).Take(&row).Error
|
||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return mapDBErr(err)
|
||
}
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
if !blocked {
|
||
return nil
|
||
}
|
||
row = FriendRelation{
|
||
UserID: userID, FriendID: friendID, Status: int(entity.StatusNone),
|
||
Blocked: true, CreatedAt: now, UpdatedAt: now,
|
||
}
|
||
if err := tx.Create(&row).Error; err != nil {
|
||
return mapDBErr(err)
|
||
}
|
||
} else {
|
||
if err := tx.Model(&row).Updates(map[string]any{"is_blocked": blocked, "updated_at": now}).Error; err != nil {
|
||
return mapDBErr(err)
|
||
}
|
||
}
|
||
if blocked {
|
||
return rejectPairRequests(tx, userID, friendID, now)
|
||
}
|
||
return nil
|
||
})
|
||
}
|