Files
social/core/invitation.go

395 lines
11 KiB
Go
Raw Normal View History

package core
import (
"context"
"encoding/gob"
"net/http"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler"
)
type invitation struct {
mongoClient gocommon.MongoClient
redison *gocommon.RedisonHandler
wsh *wshandler.WebsocketHandler
f *friends
}
type invitationDoc struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
From primitive.ObjectID `bson:"from,omitempty" json:"-"`
To primitive.ObjectID `bson:"to,omitempty" json:"-"`
FromAlias string `bson:"falias,omitempty" json:"from"`
ToAlias string `bson:"talias,omitempty" json:"to"`
Timestamp int64 `bson:"ts" json:"ts"`
Deleted bool `bson:"deleted,omitempty" json:"deleted,omitempty"`
Blocked bool `bson:"blocked,omitempty" json:"-"` // From은 To에 의해 차단된 상태를 표시
}
func init() {
gob.Register([]invitationDoc{})
}
func makeInvitation(ctx context.Context, s *Social, f *friends) (*invitation, error) {
if err := s.mongoClient.MakeUniqueIndices(invitation_collection_name, map[string]bson.D{
"fromts": {{Key: "from", Value: 1}, {Key: "ts", Value: -1}},
}); err != nil {
return nil, err
}
// 내가 받은거
if err := s.mongoClient.MakeIndices(invitation_collection_name, map[string]bson.D{
"tots": {{Key: "to", Value: 1}, {Key: "ts", Value: -1}, {Key: "blocked", Value: 1}},
}); err != nil {
return nil, err
}
return &invitation{
mongoClient: s.mongoClient,
redison: s.redison,
wsh: s.wsh,
f: f,
}, nil
}
func (iv *invitation) QueryInvitations(ctx wshandler.ApiCallContext) {
// 내가 받은 초대 목록
queryfrom := int64(ctx.Arguments[0].(float64))
var receives []*invitationDoc
if err := iv.mongoClient.FindAllAs(invitation_collection_name, bson.M{
"to": ctx.CallBy.Accid,
"ts": bson.M{"$gt": queryfrom},
"blocked": false,
}, &receives, options.Find().SetHint("tots")); err != nil {
logger.Println("QueryInvitations failed. FindAllAs err :", err)
}
var sents []*invitationDoc
if err := iv.mongoClient.FindAllAs(invitation_collection_name, bson.M{
"from": ctx.CallBy.Accid,
"ts": bson.M{"$gt": queryfrom},
}, &sents, options.Find().SetHint("fromts")); err != nil {
logger.Println("QueryInvitations failed. FindAllAs err :", err)
}
invitations := append(receives, sents...)
if len(invitations) > 0 {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ctx.CallBy.Accid.Hex(),
Body: invitations,
Tag: invitations_tag,
})
}
}
func (iv *invitation) CancelInvitation(ctx wshandler.ApiCallContext) {
// ctx.CallBy.Accid
id, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var ivdoc invitationDoc
if err := iv.mongoClient.FindOneAs(invitation_collection_name, bson.M{
"_id": id,
}, &ivdoc); err != nil {
logger.Println("CancelInvitation failed:", err)
return
}
if ivdoc.From != ctx.CallBy.Accid {
return
}
ivdoc.Deleted = true
if _, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": id,
}, bson.M{
"$set": bson.M{
"falias": "",
"deleted": true,
"ts": time.Now().UTC().Unix(),
},
}); err != nil {
logger.Println("CancelInvitation failed:", err)
return
}
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.To.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.From.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
}
func (iv *invitation) AcceptInvitation(ctx wshandler.ApiCallContext) {
invId, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var ivdoc invitationDoc
if err := iv.mongoClient.FindOneAs(invitation_collection_name, bson.M{
"_id": invId,
}, &ivdoc); err != nil {
logger.Println("AcceptInvitation failed:", err)
return
}
if ivdoc.Id != invId {
// 초대가 없다
return
}
if ivdoc.To != ctx.CallBy.Accid {
// 내가 받은 초대가 아니네?
return
}
now := time.Now().UTC().Unix()
f1 := friendDoc{
From: ivdoc.To, // 수락한 나
To: ivdoc.From, // 상대방
ToAlias: ivdoc.FromAlias,
Timestamp: now,
}
f2 := friendDoc{
From: ivdoc.From, // 상대방
To: ivdoc.To, // 나
ToAlias: ivdoc.ToAlias,
Timestamp: now,
}
// 나한테 상대방을 친구로 만들고
if err := iv.f.addFriend(&f1); err == nil {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: f1.From.Hex(),
Body: []friendDoc{f1},
Tag: friends_tag,
})
} else {
logger.Println("AcceptInvitation failed. addFriend(f1) err :", err)
return
}
// 상대방한테 나를 친구로 만듬
if err := iv.f.addFriend(&f2); err == nil {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: f2.From.Hex(),
Body: []friendDoc{f2},
Tag: friends_tag,
})
} else {
logger.Println("AcceptInvitation failed. addFriend(f2) err :", err)
return
}
iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": invId,
}, bson.M{
"$set": bson.M{
"deleted": true,
"ts": now,
},
}, options.Update().SetUpsert(false))
}
func (iv *invitation) DenyInvitation(ctx wshandler.ApiCallContext) {
invId, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var ivdoc invitationDoc
if err := iv.mongoClient.FindOneAs(invitation_collection_name, bson.M{
"_id": invId,
}, &ivdoc); err != nil {
logger.Println("AcceptInvitation failed:", err)
return
}
if ivdoc.Id != invId {
// 초대가 없다
return
}
if ivdoc.To != ctx.CallBy.Accid {
// 내가 받은 초대가 아니네?
return
}
now := time.Now().UTC().Unix()
ivdoc.Timestamp = now
ivdoc.Deleted = true
if _, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": invId,
}, bson.M{
"$set": bson.M{
"deleted": true,
"ts": now,
},
}, options.Update().SetUpsert(false)); err != nil {
logger.Println("DenyInvitation failed. addFriend(f2) err :", err)
return
}
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.To.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.From.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
}
func (iv *invitation) Trim(ctx wshandler.ApiCallContext) {
stringsTobjs := func(in []any) (out []primitive.ObjectID) {
for _, i := range in {
p, _ := primitive.ObjectIDFromHex(i.(string))
out = append(out, p)
}
return
}
ids := stringsTobjs(ctx.Arguments[0].([]any))
ids = append(ids, stringsTobjs(ctx.Arguments[1].([]any))...)
if len(ids) > 0 {
if len(ids) == 1 {
iv.mongoClient.Delete(invitation_collection_name, bson.M{"_id": ids[0], "deleted": true})
} else {
iv.mongoClient.DeleteMany(invitation_collection_name, bson.D{
{Key: "_id", Value: bson.M{"$in": ids}},
{Key: "deleted", Value: true},
})
}
}
}
func (iv *invitation) InviteAsFriend(w http.ResponseWriter, r *http.Request) {
// 1. mongodb에 추가
// 1-1. block이 되어있다면(==이미 도큐먼트가 있다면) 마치 성공인 것처럼 아무것도 안하고 끝
// 2. mongodb에 추가가 성공하면 publish
var ivdoc invitationDoc
if err := gocommon.MakeDecoder(r).Decode(&ivdoc); err != nil {
logger.Println("IniviteAsFriend failed:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// ivdoc.To가 invdoc.From을 차단했으면 표시
exists, err := iv.mongoClient.Exists(block_collection_name, bson.M{"_id": combineObjectID(ivdoc.To, ivdoc.From)})
if err != nil {
logger.Println("IniviteAsFriend failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// exists면 차단된 상태
ivdoc.Blocked = exists
ivdoc.Timestamp = time.Now().UTC().Unix()
_, newid, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": combineObjectID(ivdoc.From, ivdoc.To),
}, bson.M{"$setOnInsert": ivdoc}, options.Update().SetUpsert(true))
if err != nil || newid == nil {
logger.Println("IniviteAsFriend failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
ivdoc.Id = newid.(primitive.ObjectID)
if !ivdoc.Blocked {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.To.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
}
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.From.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
}
func (iv *invitation) Block(w http.ResponseWriter, r *http.Request) {
var block blockDoc
if err := gocommon.MakeDecoder(r).Decode(&block); err != nil {
logger.Println("Block failed:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// 차단한 상대가 나한테 보낸 초대가 있으면 차단 표시
// block.From이 block.To를 차단 -> block.To가 block.From을 초대한게 있나?
id := combineObjectID(block.To, block.From)
now := time.Now().UTC().Unix()
updated, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": id,
}, bson.M{
"$set": bson.M{
"blocked": true,
"ts": now,
},
}, options.Update().SetUpsert(false))
if err != nil {
logger.Println("Block failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if updated {
// 초대가 있었다.
// 사실은 삭제가 아니지만 초대 삭제 알림. 나중에 쿼리해도 안나옴
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: block.From.Hex(),
Body: []invitationDoc{{Id: id, Deleted: true, Timestamp: now}},
Tag: invitations_tag,
})
}
}
func (iv *invitation) Unblock(ctx wshandler.ApiCallContext) {
// From이 To를 unblock = to가 from을 block했었다. = from이 to한테 보낸 초대가 있을 수 있다.
// invitation key는 to+from이고 block key는 from+to
id, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var revertid primitive.ObjectID
copy(revertid[:6], id[6:])
copy(revertid[6:], id[:6])
now := time.Now().UTC().Unix()
var ivdoc invitationDoc
err := iv.mongoClient.FindOneAndUpdateAs(invitation_collection_name, bson.M{
"_id": revertid,
}, bson.M{
"$set": bson.M{
"ts": now,
},
}, &ivdoc, options.FindOneAndUpdate().SetUpsert(false).SetReturnDocument(options.After))
if err != nil {
logger.Println("Block failed:", err)
return
}
if !ivdoc.Id.IsZero() {
// 받은 초대가 있었다.
// 나한테 알림
iv.f.conns.writeMessage(ctx.CallBy.Accid, &wshandler.DownstreamMessage{
Alias: ctx.CallBy.Alias,
Body: []invitationDoc{ivdoc},
Tag: invitations_tag,
})
}
}