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" ) const ( invitation_collection_name = gocommon.CollectionName("invitation") ) var invitation_sent_tag = []string{"social.InvitationsSent"} var invitation_received_tag = []string{"social.InvitationsReceived"} var friends_tag = []string{"social.Friends"} 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"` } 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{ "fromto": {{Key: "from", Value: 1}, {Key: "to", Value: 1}}, }); err != nil { return nil, err } // 내가 받은거 if err := s.mongoClient.MakeIndices(invitation_collection_name, map[string]bson.D{ "received": {{Key: "to", Value: 1}, {Key: "ts", Value: -1}}, }); err != nil { return nil, err } return &invitation{ mongoClient: s.mongoClient, redison: s.redison, wsh: s.wsh, f: f, }, nil } func (iv *invitation) QueryReceivedInvitations(ctx wshandler.ApiCallContext) { // 내가 받은 초대 목록 queryfrom := int64(ctx.Arguments[0].(float64)) var receives []*invitationDoc err := iv.mongoClient.FindAllAs(invitation_collection_name, bson.M{ "to": ctx.CallBy.Accid, "ts": bson.M{"$gt": queryfrom}, }, &receives) if err != nil { logger.Println("QueryReceivedInvitations failed. FindAllAs err :", err) } if len(receives) > 0 { iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{ Target: ctx.CallBy.Accid.Hex(), Body: receives, Tag: invitation_received_tag, }) } } func (iv *invitation) QuerySentInvitations(ctx wshandler.ApiCallContext) { // 내가 보낸 초대 목록 queryfrom := int64(ctx.Arguments[0].(float64)) var receives []*invitationDoc err := iv.mongoClient.FindAllAs(invitation_collection_name, bson.M{ "from": ctx.CallBy.Accid, "ts": bson.M{"$gt": queryfrom}, "falias": bson.M{"$exists": true}, }, &receives) if err != nil { logger.Println("QueryReceivedInvitations failed. FindAllAs err :", err) } if len(receives) > 0 { iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{ Target: ctx.CallBy.Accid.Hex(), Body: receives, Tag: invitation_sent_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: invitation_received_tag, }) iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{ Target: ivdoc.From.Hex(), Body: []invitationDoc{ivdoc}, Tag: invitation_sent_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: invitation_received_tag, }) iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{ Target: ivdoc.From.Hex(), Body: []invitationDoc{ivdoc}, Tag: invitation_sent_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.Timestamp = time.Now().UTC().Unix() _, newid, err := iv.mongoClient.Update(invitation_collection_name, bson.M{ "from": ivdoc.From, "to": ivdoc.To, }, bson.M{ "$set": bson.M{ "ts": ivdoc.Timestamp, "falias": ivdoc.FromAlias, "talias": ivdoc.ToAlias, }, }, options.Update().SetUpsert(true)) if err != nil { logger.Println("IniviteAsFriend failed:", err) w.WriteHeader(http.StatusInternalServerError) return } if newid != nil { ivdoc.Id = newid.(primitive.ObjectID) iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{ Target: ivdoc.To.Hex(), Body: []invitationDoc{ivdoc}, Tag: invitation_received_tag, }) } else { found, _ := iv.mongoClient.FindOne(invitation_collection_name, bson.M{ "from": ivdoc.From, "to": ivdoc.To, }, options.FindOne().SetProjection(bson.M{"_id": 1})) ivdoc.Id = found["_id"].(primitive.ObjectID) } if !ivdoc.Id.IsZero() { iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{ Target: ivdoc.From.Hex(), Body: []invitationDoc{ivdoc}, Tag: invitation_sent_tag, }) } } func (iv *invitation) Block(w http.ResponseWriter, r *http.Request) { // 초대가 있으면 // var bi struct { // From primitive.ObjectID // To primitive.ObjectID // FromAlias string // } // if err := gocommon.MakeDecoder(r).Decode(&bi); err != nil { // logger.Println("invitation.Block failed :", err) // w.WriteHeader(http.StatusBadRequest) // return // } // now := time.Now().UTC().Unix() // // From이 To를 block했으므로 To가 From을 초대하는 것을 방지하려면 둘을 뒤집어서 문서를 만들어 놔야 함 // // 이미 존재하는 초대일 수도 있다. // _, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{ // "from": bi.To, // "to": bi.From, // }, bson.M{ // "$set": invitationDoc{ // ToAlias: bi.FromAlias, // Timestamp: now, // }, // }, options.Update().SetUpsert(true)) // if err != nil { // logger.Println("Block failed:", err) // w.WriteHeader(http.StatusInternalServerError) // return // } }