feat: complete im p2p relationship onboarding
This commit is contained in:
@@ -319,9 +319,12 @@ type TenantBusinessAdapter interface {
|
|||||||
TenantSystemNotificationMarkRead(t.TenantID, t.Uid, string, time.Time) error
|
TenantSystemNotificationMarkRead(t.TenantID, t.Uid, string, time.Time) error
|
||||||
|
|
||||||
TenantFriendRequestCreate(t.TenantID, *t.FriendRequest) (*t.FriendRequest, error)
|
TenantFriendRequestCreate(t.TenantID, *t.FriendRequest) (*t.FriendRequest, error)
|
||||||
|
TenantFriendRequestList(t.TenantID, t.Uid, string, string, string, int) ([]t.FriendRequestListItem, string, error)
|
||||||
TenantFriendRequestLatestBetween(t.TenantID, t.Uid, t.Uid) (*t.FriendRequest, error)
|
TenantFriendRequestLatestBetween(t.TenantID, t.Uid, t.Uid) (*t.FriendRequest, error)
|
||||||
|
TenantFriendRequestAccept(t.TenantID, string, t.Uid, time.Time) (*t.FriendRequest, error)
|
||||||
TenantFriendRequestResolve(t.TenantID, string, t.Uid, string, time.Time) (*t.FriendRequest, error)
|
TenantFriendRequestResolve(t.TenantID, string, t.Uid, string, time.Time) (*t.FriendRequest, error)
|
||||||
TenantFriendshipUpsertPair(t.TenantID, t.Uid, t.Uid, string, time.Time) error
|
TenantFriendshipUpsertPair(t.TenantID, t.Uid, t.Uid, string, time.Time) error
|
||||||
|
TenantFriendshipList(t.TenantID, t.Uid, string, string, string, int) ([]t.FriendListItem, string, error)
|
||||||
TenantFriendshipGet(t.TenantID, t.Uid, t.Uid) (*t.Friendship, error)
|
TenantFriendshipGet(t.TenantID, t.Uid, t.Uid) (*t.Friendship, error)
|
||||||
TenantFriendshipDeletePair(t.TenantID, t.Uid, t.Uid, time.Time) error
|
TenantFriendshipDeletePair(t.TenantID, t.Uid, t.Uid, time.Time) error
|
||||||
TenantUserBlockGet(t.TenantID, t.Uid, t.Uid) (*t.UserBlock, error)
|
TenantUserBlockGet(t.TenantID, t.Uid, t.Uid) (*t.UserBlock, error)
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ func (a *adapter) TenantUserGetAll(id t.TenantID, uids ...t.Uid) ([]t.User, erro
|
|||||||
if err := validTenant(id); err != nil {
|
if err := validTenant(id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ctx, cancel := a.getContext()
|
||||||
|
if cancel != nil {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
return a.tenantUserGetAllTx(ctx, nil, id, uids...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adapter) tenantUserGetAllTx(ctx context.Context, tx *sqlx.Tx, id t.TenantID, uids ...t.Uid) ([]t.User, error) {
|
||||||
if len(uids) == 0 {
|
if len(uids) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -100,11 +108,14 @@ func (a *adapter) TenantUserGetAll(id t.TenantID, uids ...t.Uid) ([]t.User, erro
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
q, params, _ := sqlx.In("SELECT * FROM users WHERE tenant_id=? AND id IN (?) AND state!=?", id, args, t.StateDeleted)
|
q, params, _ := sqlx.In("SELECT * FROM users WHERE tenant_id=? AND id IN (?) AND state!=?", id, args, t.StateDeleted)
|
||||||
ctx, cancel := a.getContext()
|
query := a.db.Rebind(q)
|
||||||
if cancel != nil {
|
var rows *sqlx.Rows
|
||||||
defer cancel()
|
var err error
|
||||||
|
if tx != nil {
|
||||||
|
rows, err = tx.QueryxContext(ctx, query, params...)
|
||||||
|
} else {
|
||||||
|
rows, err = a.db.QueryxContext(ctx, query, params...)
|
||||||
}
|
}
|
||||||
rows, err := a.db.QueryxContext(ctx, a.db.Rebind(q), params...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -2534,6 +2545,125 @@ func (a *adapter) TenantFriendRequestCreate(id t.TenantID, req *t.FriendRequest)
|
|||||||
return a.friendRequestByRequestID(ctx, id, req.RequestID)
|
return a.friendRequestByRequestID(ctx, id, req.RequestID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) TenantFriendRequestList(id t.TenantID, uid t.Uid, direction, status, cursor string, limit int) ([]t.FriendRequestListItem, string, error) {
|
||||||
|
if err := validTenant(id); err != nil || uid.IsZero() {
|
||||||
|
return nil, "", t.ErrMalformed
|
||||||
|
}
|
||||||
|
if limit <= 0 || limit > 100 {
|
||||||
|
limit = 20
|
||||||
|
}
|
||||||
|
where := []string{"fr.tenant_id=?"}
|
||||||
|
args := []any{id}
|
||||||
|
decodedUID := decodeUID(uid)
|
||||||
|
switch direction {
|
||||||
|
case "", "inbound":
|
||||||
|
where = append(where, "fr.target_id=?")
|
||||||
|
args = append(args, decodedUID)
|
||||||
|
case "outbound":
|
||||||
|
where = append(where, "fr.applicant_id=?")
|
||||||
|
args = append(args, decodedUID)
|
||||||
|
case "all":
|
||||||
|
where = append(where, "(fr.applicant_id=? OR fr.target_id=?)")
|
||||||
|
args = append(args, decodedUID, decodedUID)
|
||||||
|
default:
|
||||||
|
return nil, "", t.ErrMalformed
|
||||||
|
}
|
||||||
|
if status != "" && status != "all" {
|
||||||
|
where = append(where, "fr.status=?")
|
||||||
|
args = append(args, status)
|
||||||
|
}
|
||||||
|
if cursor != "" {
|
||||||
|
parsed, err := strconv.ParseInt(cursor, 10, 64)
|
||||||
|
if err != nil || parsed <= 0 {
|
||||||
|
return nil, "", t.ErrMalformed
|
||||||
|
}
|
||||||
|
where = append(where, "fr.id<?")
|
||||||
|
args = append(args, parsed)
|
||||||
|
}
|
||||||
|
args = append(args, limit+1)
|
||||||
|
query := `SELECT fr.*,u.public,u.trusted
|
||||||
|
FROM im_friend_requests AS fr
|
||||||
|
JOIN users AS u ON u.tenant_id=fr.tenant_id AND u.id=IF(fr.applicant_id=?,fr.target_id,fr.applicant_id)
|
||||||
|
WHERE ` + strings.Join(where, " AND ") + `
|
||||||
|
ORDER BY fr.id DESC LIMIT ?`
|
||||||
|
args = append([]any{decodedUID}, args...)
|
||||||
|
ctx, cancel := a.getContext()
|
||||||
|
if cancel != nil {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
rows, err := a.db.QueryxContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := make([]t.FriendRequestListItem, 0, limit)
|
||||||
|
nextCursor := ""
|
||||||
|
for rows.Next() {
|
||||||
|
row := struct {
|
||||||
|
ID int64
|
||||||
|
TenantID t.TenantID `db:"tenant_id"`
|
||||||
|
RequestID string `db:"request_id"`
|
||||||
|
ApplicantID int64 `db:"applicant_id"`
|
||||||
|
TargetID int64 `db:"target_id"`
|
||||||
|
Message string
|
||||||
|
Source string
|
||||||
|
SourceTopic string `db:"source_topic"`
|
||||||
|
Status string
|
||||||
|
IdempotencyKey string `db:"idempotency_key"`
|
||||||
|
ExpiresAt time.Time `db:"expires_at"`
|
||||||
|
HandledAt *time.Time `db:"handled_at"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
Public any
|
||||||
|
Trusted any
|
||||||
|
}{}
|
||||||
|
if err = rows.StructScan(&row); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if len(items) == limit {
|
||||||
|
nextCursor = strconv.FormatInt(row.ID, 10)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
applicant := store.EncodeUid(row.ApplicantID)
|
||||||
|
target := store.EncodeUid(row.TargetID)
|
||||||
|
peerID := target
|
||||||
|
direction := "outbound"
|
||||||
|
if target == uid {
|
||||||
|
peerID = applicant
|
||||||
|
direction = "inbound"
|
||||||
|
}
|
||||||
|
items = append(items, t.FriendRequestListItem{
|
||||||
|
Request: t.FriendRequest{
|
||||||
|
ID: row.ID,
|
||||||
|
TenantID: row.TenantID,
|
||||||
|
RequestID: row.RequestID,
|
||||||
|
ApplicantID: applicant,
|
||||||
|
TargetID: target,
|
||||||
|
Message: row.Message,
|
||||||
|
Source: row.Source,
|
||||||
|
SourceTopic: row.SourceTopic,
|
||||||
|
Status: row.Status,
|
||||||
|
IdempotencyKey: row.IdempotencyKey,
|
||||||
|
ExpiresAt: row.ExpiresAt,
|
||||||
|
HandledAt: row.HandledAt,
|
||||||
|
CreatedAt: row.CreatedAt,
|
||||||
|
UpdatedAt: row.UpdatedAt,
|
||||||
|
},
|
||||||
|
Peer: &t.User{
|
||||||
|
ObjHeader: t.ObjHeader{Id: peerID.String()},
|
||||||
|
TenantID: row.TenantID,
|
||||||
|
Public: common.FromJSON(row.Public),
|
||||||
|
Trusted: common.FromJSON(row.Trusted),
|
||||||
|
},
|
||||||
|
Direction: direction,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return items, nextCursor, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *adapter) TenantFriendRequestLatestBetween(id t.TenantID, uid1, uid2 t.Uid) (*t.FriendRequest, error) {
|
func (a *adapter) TenantFriendRequestLatestBetween(id t.TenantID, uid1, uid2 t.Uid) (*t.FriendRequest, error) {
|
||||||
if err := validTenant(id); err != nil || uid1.IsZero() || uid2.IsZero() {
|
if err := validTenant(id); err != nil || uid1.IsZero() || uid2.IsZero() {
|
||||||
return nil, t.ErrMalformed
|
return nil, t.ErrMalformed
|
||||||
@@ -2547,6 +2677,151 @@ func (a *adapter) TenantFriendRequestLatestBetween(id t.TenantID, uid1, uid2 t.U
|
|||||||
id, decodeUID(uid1), decodeUID(uid2), decodeUID(uid2), decodeUID(uid1))
|
id, decodeUID(uid1), decodeUID(uid2), decodeUID(uid2), decodeUID(uid1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) TenantFriendRequestAccept(id t.TenantID, requestID string, actor t.Uid, handledAt time.Time) (*t.FriendRequest, error) {
|
||||||
|
if err := validTenant(id); err != nil || requestID == "" || actor.IsZero() {
|
||||||
|
return nil, t.ErrMalformed
|
||||||
|
}
|
||||||
|
ctx, cancel := a.getContextForTx()
|
||||||
|
if cancel != nil {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
tx, err := a.db.BeginTxx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
row := struct {
|
||||||
|
ID int64
|
||||||
|
TenantID t.TenantID `db:"tenant_id"`
|
||||||
|
RequestID string `db:"request_id"`
|
||||||
|
ApplicantID int64 `db:"applicant_id"`
|
||||||
|
TargetID int64 `db:"target_id"`
|
||||||
|
Message string
|
||||||
|
Source string
|
||||||
|
SourceTopic string `db:"source_topic"`
|
||||||
|
Status string
|
||||||
|
IdempotencyKey string `db:"idempotency_key"`
|
||||||
|
ExpiresAt time.Time `db:"expires_at"`
|
||||||
|
HandledAt *time.Time `db:"handled_at"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}{}
|
||||||
|
err = tx.GetContext(ctx, &row, "SELECT * FROM im_friend_requests WHERE tenant_id=? AND request_id=? FOR UPDATE", id, requestID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, t.ErrNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if store.EncodeUid(row.TargetID) != actor {
|
||||||
|
return nil, t.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
if row.Status != t.FriendRequestPending && row.Status != t.FriendRequestAccepted {
|
||||||
|
return &t.FriendRequest{
|
||||||
|
ID: row.ID,
|
||||||
|
TenantID: row.TenantID,
|
||||||
|
RequestID: row.RequestID,
|
||||||
|
ApplicantID: store.EncodeUid(row.ApplicantID),
|
||||||
|
TargetID: store.EncodeUid(row.TargetID),
|
||||||
|
Message: row.Message,
|
||||||
|
Source: row.Source,
|
||||||
|
SourceTopic: row.SourceTopic,
|
||||||
|
Status: row.Status,
|
||||||
|
IdempotencyKey: row.IdempotencyKey,
|
||||||
|
ExpiresAt: row.ExpiresAt,
|
||||||
|
HandledAt: row.HandledAt,
|
||||||
|
CreatedAt: row.CreatedAt,
|
||||||
|
UpdatedAt: row.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
applicant := store.EncodeUid(row.ApplicantID)
|
||||||
|
target := store.EncodeUid(row.TargetID)
|
||||||
|
topic := applicant.P2PName(target)
|
||||||
|
if topic == "" {
|
||||||
|
return nil, t.ErrMalformed
|
||||||
|
}
|
||||||
|
if row.Status == t.FriendRequestPending {
|
||||||
|
if _, err = tx.ExecContext(ctx,
|
||||||
|
"UPDATE im_friend_requests SET status=?,handled_at=?,updated_at=? WHERE tenant_id=? AND request_id=? AND status=?",
|
||||||
|
t.FriendRequestAccepted, handledAt, handledAt, id, requestID, t.FriendRequestPending); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
row.Status = t.FriendRequestAccepted
|
||||||
|
row.HandledAt = &handledAt
|
||||||
|
row.UpdatedAt = handledAt
|
||||||
|
}
|
||||||
|
for _, pair := range [][2]t.Uid{{applicant, target}, {target, applicant}} {
|
||||||
|
if _, err = tx.ExecContext(ctx,
|
||||||
|
"INSERT INTO im_friendships(tenant_id,user_id,peer_id,status,topic,created_at,updated_at) VALUES(?,?,?,?,?,?,?) "+
|
||||||
|
"ON DUPLICATE KEY UPDATE status=VALUES(status),topic=VALUES(topic),updated_at=VALUES(updated_at)",
|
||||||
|
id, decodeUID(pair[0]), decodeUID(pair[1]), t.RelationshipFriend, topic, handledAt, handledAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
users, err := a.tenantUserGetAllTx(ctx, tx, id, applicant, target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(users) != 2 {
|
||||||
|
return nil, t.ErrUserNotFound
|
||||||
|
}
|
||||||
|
userByID := map[t.Uid]t.User{}
|
||||||
|
for _, user := range users {
|
||||||
|
userByID[user.Uid()] = user
|
||||||
|
}
|
||||||
|
applicantUser, ok1 := userByID[applicant]
|
||||||
|
targetUser, ok2 := userByID[target]
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return nil, t.ErrUserNotFound
|
||||||
|
}
|
||||||
|
applicantSub := &t.Subscription{
|
||||||
|
ObjHeader: t.ObjHeader{CreatedAt: handledAt, UpdatedAt: handledAt},
|
||||||
|
TenantID: id,
|
||||||
|
User: applicant.String(),
|
||||||
|
Topic: topic,
|
||||||
|
ModeWant: t.ModeCP2P,
|
||||||
|
ModeGiven: t.ModeCP2P,
|
||||||
|
}
|
||||||
|
applicantSub.SetTouchedAt(handledAt)
|
||||||
|
applicantSub.SetPublic(targetUser.Public)
|
||||||
|
applicantSub.SetTrusted(targetUser.Trusted)
|
||||||
|
targetSub := &t.Subscription{
|
||||||
|
ObjHeader: t.ObjHeader{CreatedAt: handledAt, UpdatedAt: handledAt},
|
||||||
|
TenantID: id,
|
||||||
|
User: target.String(),
|
||||||
|
Topic: topic,
|
||||||
|
ModeWant: t.ModeCP2P,
|
||||||
|
ModeGiven: t.ModeCP2P,
|
||||||
|
}
|
||||||
|
targetSub.SetTouchedAt(handledAt)
|
||||||
|
targetSub.SetPublic(applicantUser.Public)
|
||||||
|
targetSub.SetTrusted(applicantUser.Trusted)
|
||||||
|
if err = createSubscription(tx, applicantSub, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = createSubscription(tx, targetSub, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
topicRow := &t.Topic{TenantID: id, ObjHeader: t.ObjHeader{Id: topic, CreatedAt: handledAt, UpdatedAt: handledAt}, TouchedAt: handledAt}
|
||||||
|
if err = tenantTopicCreate(tx, topicRow); err != nil {
|
||||||
|
if !isDupe(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = tx.ExecContext(ctx, "UPDATE topics SET updatedat=?,touchedat=?,state=? WHERE tenant_id=? AND name=?",
|
||||||
|
handledAt, handledAt, t.StateOK, id, topic); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return a.friendRequestByRequestID(ctx, id, requestID)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *adapter) TenantFriendRequestResolve(id t.TenantID, requestID string, actor t.Uid, status string, handledAt time.Time) (*t.FriendRequest, error) {
|
func (a *adapter) TenantFriendRequestResolve(id t.TenantID, requestID string, actor t.Uid, status string, handledAt time.Time) (*t.FriendRequest, error) {
|
||||||
if err := validTenant(id); err != nil || requestID == "" || actor.IsZero() {
|
if err := validTenant(id); err != nil || requestID == "" || actor.IsZero() {
|
||||||
return nil, t.ErrMalformed
|
return nil, t.ErrMalformed
|
||||||
@@ -2595,6 +2870,96 @@ func (a *adapter) TenantFriendshipUpsertPair(id t.TenantID, uid1, uid2 t.Uid, to
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, initial, cursor string, limit int) ([]t.FriendListItem, string, error) {
|
||||||
|
if err := validTenant(id); err != nil || uid.IsZero() {
|
||||||
|
return nil, "", t.ErrMalformed
|
||||||
|
}
|
||||||
|
if limit <= 0 || limit > 100 {
|
||||||
|
limit = 50
|
||||||
|
}
|
||||||
|
where := []string{"fs.tenant_id=?", "fs.user_id=?", "fs.status=?"}
|
||||||
|
args := []any{id, decodeUID(uid), t.RelationshipFriend}
|
||||||
|
if cursor != "" {
|
||||||
|
parsed, err := strconv.ParseInt(cursor, 10, 64)
|
||||||
|
if err != nil || parsed <= 0 {
|
||||||
|
return nil, "", t.ErrMalformed
|
||||||
|
}
|
||||||
|
where = append(where, "fs.id>?")
|
||||||
|
args = append(args, parsed)
|
||||||
|
}
|
||||||
|
if q := strings.TrimSpace(queryText); q != "" {
|
||||||
|
like := "%" + q + "%"
|
||||||
|
where = append(where, "(fs.remark LIKE ? OR CAST(u.public AS CHAR) LIKE ? OR CAST(u.tags AS CHAR) LIKE ?)")
|
||||||
|
args = append(args, like, like, like)
|
||||||
|
}
|
||||||
|
if initial = strings.TrimSpace(initial); initial != "" {
|
||||||
|
like := initial + "%"
|
||||||
|
where = append(where, "(fs.remark LIKE ? OR CAST(u.public AS CHAR) LIKE ?)")
|
||||||
|
args = append(args, like, like)
|
||||||
|
}
|
||||||
|
args = append(args, limit+1)
|
||||||
|
ctx, cancel := a.getContext()
|
||||||
|
if cancel != nil {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
rows, err := a.db.QueryxContext(ctx, `SELECT fs.*,u.public,u.trusted
|
||||||
|
FROM im_friendships AS fs
|
||||||
|
JOIN users AS u ON u.tenant_id=fs.tenant_id AND u.id=fs.peer_id
|
||||||
|
WHERE `+strings.Join(where, " AND ")+`
|
||||||
|
ORDER BY fs.id ASC LIMIT ?`, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := make([]t.FriendListItem, 0, limit)
|
||||||
|
nextCursor := ""
|
||||||
|
for rows.Next() {
|
||||||
|
row := struct {
|
||||||
|
ID int64
|
||||||
|
TenantID t.TenantID `db:"tenant_id"`
|
||||||
|
UserID int64 `db:"user_id"`
|
||||||
|
PeerID int64 `db:"peer_id"`
|
||||||
|
Status string
|
||||||
|
Remark string
|
||||||
|
Topic string
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
Public any
|
||||||
|
Trusted any
|
||||||
|
}{}
|
||||||
|
if err = rows.StructScan(&row); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if len(items) == limit {
|
||||||
|
nextCursor = strconv.FormatInt(row.ID, 10)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
peerID := store.EncodeUid(row.PeerID)
|
||||||
|
items = append(items, t.FriendListItem{
|
||||||
|
Friendship: t.Friendship{
|
||||||
|
TenantID: row.TenantID,
|
||||||
|
UserID: store.EncodeUid(row.UserID),
|
||||||
|
PeerID: peerID,
|
||||||
|
Status: row.Status,
|
||||||
|
Remark: row.Remark,
|
||||||
|
Topic: row.Topic,
|
||||||
|
CreatedAt: row.CreatedAt,
|
||||||
|
UpdatedAt: row.UpdatedAt,
|
||||||
|
},
|
||||||
|
Peer: &t.User{
|
||||||
|
ObjHeader: t.ObjHeader{Id: peerID.String()},
|
||||||
|
TenantID: row.TenantID,
|
||||||
|
Public: common.FromJSON(row.Public),
|
||||||
|
Trusted: common.FromJSON(row.Trusted),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return items, nextCursor, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *adapter) TenantFriendshipGet(id t.TenantID, userID, peerID t.Uid) (*t.Friendship, error) {
|
func (a *adapter) TenantFriendshipGet(id t.TenantID, userID, peerID t.Uid) (*t.Friendship, error) {
|
||||||
if err := validTenant(id); err != nil || userID.IsZero() || peerID.IsZero() {
|
if err := validTenant(id); err != nil || userID.IsZero() || peerID.IsZero() {
|
||||||
return nil, t.ErrMalformed
|
return nil, t.ErrMalformed
|
||||||
|
|||||||
+143
-2
@@ -1014,10 +1014,39 @@ func serveIMConversationContext(wrt http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func serveIMFriendRequests(wrt http.ResponseWriter, req *http.Request) {
|
func serveIMFriendRequests(wrt http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method != http.MethodPost {
|
switch req.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
serveIMFriendRequestList(wrt, req)
|
||||||
|
case http.MethodPost:
|
||||||
|
serveIMFriendRequestCreate(wrt, req)
|
||||||
|
default:
|
||||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveIMFriendRequestList(wrt http.ResponseWriter, req *http.Request) {
|
||||||
|
principal, ok := authenticateIMHTTPBearer(wrt, req)
|
||||||
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
limit := parseIMLimit(req.URL.Query().Get("limit"), 20, 100)
|
||||||
|
items, nextCursor, err := store.Relations.ListFriendRequests(principal.TenantID, principal.Uid,
|
||||||
|
strings.TrimSpace(req.URL.Query().Get("direction")),
|
||||||
|
strings.TrimSpace(req.URL.Query().Get("status")),
|
||||||
|
strings.TrimSpace(req.URL.Query().Get("cursor")),
|
||||||
|
limit)
|
||||||
|
if err != nil {
|
||||||
|
writeIMRelationError(wrt, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload := make([]map[string]any, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
payload = append(payload, friendRequestListItemPayload(item))
|
||||||
|
}
|
||||||
|
writeIMSuccess(wrt, req, map[string]any{"items": payload, "next_cursor": nextCursor})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveIMFriendRequestCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||||
principal, ok := authenticateIMHTTPBearer(wrt, req)
|
principal, ok := authenticateIMHTTPBearer(wrt, req)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -1060,6 +1089,32 @@ func serveIMFriendRequests(wrt http.ResponseWriter, req *http.Request) {
|
|||||||
writeIMSuccess(wrt, req, friendRequestPayload(created))
|
writeIMSuccess(wrt, req, friendRequestPayload(created))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveIMFriends(wrt http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Method != http.MethodGet {
|
||||||
|
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
principal, ok := authenticateIMHTTPBearer(wrt, req)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit := parseIMLimit(req.URL.Query().Get("limit"), 50, 100)
|
||||||
|
items, nextCursor, err := store.Relations.ListFriends(principal.TenantID, principal.Uid,
|
||||||
|
strings.TrimSpace(req.URL.Query().Get("query")),
|
||||||
|
strings.TrimSpace(req.URL.Query().Get("initial")),
|
||||||
|
strings.TrimSpace(req.URL.Query().Get("cursor")),
|
||||||
|
limit)
|
||||||
|
if err != nil {
|
||||||
|
writeIMRelationError(wrt, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload := make([]map[string]any, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
payload = append(payload, friendListItemPayload(item))
|
||||||
|
}
|
||||||
|
writeIMSuccess(wrt, req, map[string]any{"items": payload, "next_cursor": nextCursor})
|
||||||
|
}
|
||||||
|
|
||||||
func serveIMFriendRequestAction(wrt http.ResponseWriter, req *http.Request) {
|
func serveIMFriendRequestAction(wrt http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method != http.MethodPost {
|
if req.Method != http.MethodPost {
|
||||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||||
@@ -1615,7 +1670,9 @@ func friendRequestPayload(req *types.FriendRequest) map[string]any {
|
|||||||
"status": req.Status,
|
"status": req.Status,
|
||||||
"created_at": req.CreatedAt.UTC().Format(time.RFC3339Nano),
|
"created_at": req.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||||
"expires_at": req.ExpiresAt.UTC().Format(time.RFC3339Nano),
|
"expires_at": req.ExpiresAt.UTC().Format(time.RFC3339Nano),
|
||||||
"topic": req.ApplicantID.P2PName(req.TargetID),
|
}
|
||||||
|
if topic := req.ApplicantID.P2PName(req.TargetID); topic != "" {
|
||||||
|
data["topic"] = topic
|
||||||
}
|
}
|
||||||
if req.SourceTopic != "" {
|
if req.SourceTopic != "" {
|
||||||
data["source_topic"] = req.SourceTopic
|
data["source_topic"] = req.SourceTopic
|
||||||
@@ -1626,6 +1683,90 @@ func friendRequestPayload(req *types.FriendRequest) map[string]any {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func friendRequestListItemPayload(item types.FriendRequestListItem) map[string]any {
|
||||||
|
data := friendRequestPayload(&item.Request)
|
||||||
|
data["direction"] = item.Direction
|
||||||
|
if item.Peer != nil {
|
||||||
|
data["peer"] = userSummaryPayload(item.Peer, "")
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func friendListItemPayload(item types.FriendListItem) map[string]any {
|
||||||
|
data := map[string]any{
|
||||||
|
"user_id": item.Friendship.PeerID.UserId(),
|
||||||
|
"relationship": item.Friendship.Status,
|
||||||
|
"remark": item.Friendship.Remark,
|
||||||
|
"topic": item.Friendship.Topic,
|
||||||
|
"online_visibility": "unknown",
|
||||||
|
"created_at": item.Friendship.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||||
|
"updated_at": item.Friendship.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||||
|
"capabilities": map[string]any{"can_send": item.Friendship.Status == types.RelationshipFriend, "can_call": item.Friendship.Status == types.RelationshipFriend},
|
||||||
|
"display_name_source": "profile",
|
||||||
|
}
|
||||||
|
if item.Friendship.Remark != "" {
|
||||||
|
data["display_name"] = item.Friendship.Remark
|
||||||
|
data["display_name_source"] = "remark"
|
||||||
|
}
|
||||||
|
if item.Peer != nil {
|
||||||
|
for key, value := range userSummaryPayload(item.Peer, item.Friendship.Remark) {
|
||||||
|
_, exists := data[key]
|
||||||
|
if !exists || key == "avatar_file_id" || (key == "display_name" && item.Friendship.Remark == "") {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data["initial"] = displayInitial(data["display_name"])
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func userSummaryPayload(user *types.User, remark string) map[string]any {
|
||||||
|
uid := types.ParseUid(user.Id)
|
||||||
|
userID := uid.UserId()
|
||||||
|
if uid.IsZero() {
|
||||||
|
userID = user.Id
|
||||||
|
}
|
||||||
|
data := map[string]any{"user_id": userID}
|
||||||
|
if remark != "" {
|
||||||
|
data["display_name"] = remark
|
||||||
|
}
|
||||||
|
if public, ok := user.Public.(map[string]any); ok {
|
||||||
|
copyUserPublicSummary(data, public)
|
||||||
|
} else if public, ok := user.Public.(types.KVMap); ok {
|
||||||
|
copyUserPublicSummary(data, map[string]any(public))
|
||||||
|
}
|
||||||
|
if _, ok := data["display_name"]; !ok {
|
||||||
|
data["display_name"] = userID
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyUserPublicSummary(data map[string]any, public map[string]any) {
|
||||||
|
for _, key := range []string{"display_name", "fn", "name", "nickname"} {
|
||||||
|
if value, ok := public[key]; ok && strings.TrimSpace(fmt.Sprint(value)) != "" {
|
||||||
|
data["display_name"] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, key := range []string{"avatar_file_id", "photo", "avatar"} {
|
||||||
|
if value, ok := public[key]; ok && strings.TrimSpace(fmt.Sprint(value)) != "" {
|
||||||
|
data["avatar_file_id"] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayInitial(value any) string {
|
||||||
|
name := strings.TrimSpace(fmt.Sprint(value))
|
||||||
|
if name == "" {
|
||||||
|
return "#"
|
||||||
|
}
|
||||||
|
for _, r := range name {
|
||||||
|
return strings.ToUpper(string(r))
|
||||||
|
}
|
||||||
|
return "#"
|
||||||
|
}
|
||||||
|
|
||||||
func writeIMRelationError(wrt http.ResponseWriter, req *http.Request, err error) {
|
func writeIMRelationError(wrt http.ResponseWriter, req *http.Request, err error) {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, types.ErrNotFound):
|
case errors.Is(err, types.ErrNotFound):
|
||||||
|
|||||||
+96
-6
@@ -34,12 +34,14 @@ type testVerificationStore struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type testRelationsStore struct {
|
type testRelationsStore struct {
|
||||||
context *types.RelationshipContext
|
context *types.RelationshipContext
|
||||||
created *types.FriendRequest
|
created *types.FriendRequest
|
||||||
blocked *types.UserBlock
|
blocked *types.UserBlock
|
||||||
deleted bool
|
deleted bool
|
||||||
canSend *bool
|
canSend *bool
|
||||||
reason string
|
reason string
|
||||||
|
friends []types.FriendListItem
|
||||||
|
friendRequests []types.FriendRequestListItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testRelationsStore) CreateFriendRequest(_ types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error) {
|
func (s *testRelationsStore) CreateFriendRequest(_ types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error) {
|
||||||
@@ -47,6 +49,10 @@ func (s *testRelationsStore) CreateFriendRequest(_ types.TenantID, req *types.Fr
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *testRelationsStore) ListFriendRequests(_ types.TenantID, _ types.Uid, _, _, _ string, _ int) ([]types.FriendRequestListItem, string, error) {
|
||||||
|
return s.friendRequests, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *testRelationsStore) ResolveFriendRequest(_ types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
|
func (s *testRelationsStore) ResolveFriendRequest(_ types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
|
||||||
return &types.FriendRequest{
|
return &types.FriendRequest{
|
||||||
TenantID: 7,
|
TenantID: 7,
|
||||||
@@ -61,6 +67,10 @@ func (s *testRelationsStore) ResolveFriendRequest(_ types.TenantID, requestID st
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *testRelationsStore) ListFriends(_ types.TenantID, _ types.Uid, _, _, _ string, _ int) ([]types.FriendListItem, string, error) {
|
||||||
|
return s.friends, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *testRelationsStore) GetRelationshipContext(_ types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error) {
|
func (s *testRelationsStore) GetRelationshipContext(_ types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error) {
|
||||||
if s.context != nil {
|
if s.context != nil {
|
||||||
return s.context, nil
|
return s.context, nil
|
||||||
@@ -984,6 +994,86 @@ func TestServeIMFriendRequestCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServeIMFriendRequestList(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||||
|
oldRelations := store.Relations
|
||||||
|
now := time.Now().UTC().Round(time.Millisecond)
|
||||||
|
relations := &testRelationsStore{friendRequests: []types.FriendRequestListItem{{
|
||||||
|
Request: types.FriendRequest{
|
||||||
|
TenantID: 7,
|
||||||
|
RequestID: "frq_1",
|
||||||
|
ApplicantID: 2,
|
||||||
|
TargetID: 1,
|
||||||
|
Message: "hi",
|
||||||
|
Source: "search",
|
||||||
|
Status: types.FriendRequestPending,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
ExpiresAt: now.Add(time.Hour),
|
||||||
|
},
|
||||||
|
Peer: &types.User{ObjHeader: types.ObjHeader{Id: types.Uid(2).String()}, TenantID: 7, Public: map[string]any{"display_name": "Alice"}},
|
||||||
|
Direction: "inbound",
|
||||||
|
}}}
|
||||||
|
store.Relations = relations
|
||||||
|
defer func() { store.Relations = oldRelations }()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/im/friend-requests?direction=inbound&status=pending", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer token-1")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
serveIMFriendRequests(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||||
|
items := data["items"].([]any)
|
||||||
|
if len(items) != 1 || items[0].(map[string]any)["direction"] != "inbound" {
|
||||||
|
t.Fatalf("unexpected friend request list: %#v", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServeIMFriendsList(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||||
|
oldRelations := store.Relations
|
||||||
|
now := time.Now().UTC().Round(time.Millisecond)
|
||||||
|
peer := types.Uid(2)
|
||||||
|
relations := &testRelationsStore{friends: []types.FriendListItem{{
|
||||||
|
Friendship: types.Friendship{
|
||||||
|
TenantID: 7,
|
||||||
|
UserID: 1,
|
||||||
|
PeerID: peer,
|
||||||
|
Status: types.RelationshipFriend,
|
||||||
|
Remark: "小明",
|
||||||
|
Topic: types.Uid(1).P2PName(peer),
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
},
|
||||||
|
Peer: &types.User{ObjHeader: types.ObjHeader{Id: peer.String()}, TenantID: 7, Public: map[string]any{"display_name": "Ming"}},
|
||||||
|
}}}
|
||||||
|
store.Relations = relations
|
||||||
|
defer func() { store.Relations = oldRelations }()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/im/friends?query=ming", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer token-1")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
serveIMFriends(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||||
|
items := data["items"].([]any)
|
||||||
|
if len(items) != 1 || items[0].(map[string]any)["display_name"] != "小明" {
|
||||||
|
t.Fatalf("unexpected friends list: %#v", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ptrTime(t time.Time) *time.Time {
|
func ptrTime(t time.Time) *time.Time {
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -797,6 +797,7 @@ func main() {
|
|||||||
mux.HandleFunc(config.ApiPath+"v1/im/friend-requests", serveIMFriendRequests)
|
mux.HandleFunc(config.ApiPath+"v1/im/friend-requests", serveIMFriendRequests)
|
||||||
mux.HandleFunc(config.ApiPath+"v1/im/friend-requests/", serveIMFriendRequestAction)
|
mux.HandleFunc(config.ApiPath+"v1/im/friend-requests/", serveIMFriendRequestAction)
|
||||||
mux.HandleFunc(config.ApiPath+"v1/im/blocks/", serveIMBlockUser)
|
mux.HandleFunc(config.ApiPath+"v1/im/blocks/", serveIMBlockUser)
|
||||||
|
mux.HandleFunc(config.ApiPath+"v1/im/friends", serveIMFriends)
|
||||||
mux.HandleFunc(config.ApiPath+"v1/im/friends/", serveIMFriendResource)
|
mux.HandleFunc(config.ApiPath+"v1/im/friends/", serveIMFriendResource)
|
||||||
// Handle long polling clients. Enable compression.
|
// Handle long polling clients. Enable compression.
|
||||||
mux.Handle(config.ApiPath+"v0/channels/lp", gh.CompressHandler(http.HandlerFunc(serveLongPoll)))
|
mux.Handle(config.ApiPath+"v0/channels/lp", gh.CompressHandler(http.HandlerFunc(serveLongPoll)))
|
||||||
|
|||||||
+43
-5
@@ -997,7 +997,9 @@ func (messagesMapper) Edit(tenantID types.TenantID, topic string, seq int, actor
|
|||||||
|
|
||||||
type RelationsPersistenceInterface interface {
|
type RelationsPersistenceInterface interface {
|
||||||
CreateFriendRequest(tenantID types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error)
|
CreateFriendRequest(tenantID types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error)
|
||||||
|
ListFriendRequests(tenantID types.TenantID, uid types.Uid, direction, status, cursor string, limit int) ([]types.FriendRequestListItem, string, error)
|
||||||
ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error)
|
ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error)
|
||||||
|
ListFriends(tenantID types.TenantID, uid types.Uid, query, initial, cursor string, limit int) ([]types.FriendListItem, string, error)
|
||||||
GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error)
|
GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error)
|
||||||
CanSendP2P(tenantID types.TenantID, userID, peerID types.Uid) (bool, string, error)
|
CanSendP2P(tenantID types.TenantID, userID, peerID types.Uid) (bool, string, error)
|
||||||
BlockUser(tenantID types.TenantID, blockerID, blockedID types.Uid, now time.Time) (*types.UserBlock, error)
|
BlockUser(tenantID types.TenantID, blockerID, blockedID types.Uid, now time.Time) (*types.UserBlock, error)
|
||||||
@@ -1020,6 +1022,30 @@ func (relationsMapper) CreateFriendRequest(tenantID types.TenantID, req *types.F
|
|||||||
return tenantAdp.TenantFriendRequestCreate(tenantID, req)
|
return tenantAdp.TenantFriendRequestCreate(tenantID, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (relationsMapper) ListFriendRequests(tenantID types.TenantID, uid types.Uid, direction, status, cursor string, limit int) ([]types.FriendRequestListItem, string, error) {
|
||||||
|
if uid.IsZero() {
|
||||||
|
return nil, "", types.ErrMalformed
|
||||||
|
}
|
||||||
|
switch direction {
|
||||||
|
case "", "inbound", "outbound", "all":
|
||||||
|
default:
|
||||||
|
return nil, "", types.ErrMalformed
|
||||||
|
}
|
||||||
|
switch status {
|
||||||
|
case "", "all", types.FriendRequestPending, types.FriendRequestAccepted, types.FriendRequestRejected, types.FriendRequestExpired:
|
||||||
|
default:
|
||||||
|
return nil, "", types.ErrMalformed
|
||||||
|
}
|
||||||
|
if limit <= 0 || limit > 100 {
|
||||||
|
limit = 20
|
||||||
|
}
|
||||||
|
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return tenantAdp.TenantFriendRequestList(tenantID, uid, direction, status, cursor, limit)
|
||||||
|
}
|
||||||
|
|
||||||
func (relationsMapper) ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
|
func (relationsMapper) ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
|
||||||
if strings.TrimSpace(requestID) == "" || actor.IsZero() || (status != types.FriendRequestAccepted && status != types.FriendRequestRejected) {
|
if strings.TrimSpace(requestID) == "" || actor.IsZero() || (status != types.FriendRequestAccepted && status != types.FriendRequestRejected) {
|
||||||
return nil, types.ErrMalformed
|
return nil, types.ErrMalformed
|
||||||
@@ -1028,18 +1054,30 @@ func (relationsMapper) ResolveFriendRequest(tenantID types.TenantID, requestID s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if status == types.FriendRequestAccepted {
|
||||||
|
return tenantAdp.TenantFriendRequestAccept(tenantID, requestID, actor, handledAt)
|
||||||
|
}
|
||||||
req, err := tenantAdp.TenantFriendRequestResolve(tenantID, requestID, actor, status, handledAt)
|
req, err := tenantAdp.TenantFriendRequestResolve(tenantID, requestID, actor, status, handledAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if status == types.FriendRequestAccepted {
|
|
||||||
if err := tenantAdp.TenantFriendshipUpsertPair(tenantID, req.ApplicantID, req.TargetID, "", handledAt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (relationsMapper) ListFriends(tenantID types.TenantID, uid types.Uid, query, initial, cursor string, limit int) ([]types.FriendListItem, string, error) {
|
||||||
|
if uid.IsZero() {
|
||||||
|
return nil, "", types.ErrMalformed
|
||||||
|
}
|
||||||
|
if limit <= 0 || limit > 100 {
|
||||||
|
limit = 50
|
||||||
|
}
|
||||||
|
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return tenantAdp.TenantFriendshipList(tenantID, uid, query, initial, cursor, limit)
|
||||||
|
}
|
||||||
|
|
||||||
func (relationsMapper) GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error) {
|
func (relationsMapper) GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error) {
|
||||||
if userID.IsZero() || peerID.IsZero() || userID == peerID {
|
if userID.IsZero() || peerID.IsZero() || userID == peerID {
|
||||||
return nil, types.ErrMalformed
|
return nil, types.ErrMalformed
|
||||||
|
|||||||
@@ -63,3 +63,14 @@ type RelationshipContext struct {
|
|||||||
PendingStatus string `json:"pending_status,omitempty"`
|
PendingStatus string `json:"pending_status,omitempty"`
|
||||||
Peer *User `json:"peer,omitempty"`
|
Peer *User `json:"peer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FriendListItem struct {
|
||||||
|
Friendship Friendship
|
||||||
|
Peer *User
|
||||||
|
}
|
||||||
|
|
||||||
|
type FriendRequestListItem struct {
|
||||||
|
Request FriendRequest
|
||||||
|
Peer *User
|
||||||
|
Direction string
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user