397 lines
11 KiB
Go
397 lines
11 KiB
Go
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"`
|
|
Denied bool `bson:"denied,omitempty" json:"denied,omitempty"`
|
|
Canceled bool `bson:"canceled,omitempty" json:"canceled,omitempty"`
|
|
Blocked bool `bson:"blocked,omitempty" json:"-"` // From은 To에 의해 차단된 상태를 표시
|
|
}
|
|
|
|
type sneakpeekDoc struct {
|
|
From primitive.ObjectID `bson:"from,omitempty" json:"-"`
|
|
To primitive.ObjectID `bson:"to,omitempty" json:"-"`
|
|
}
|
|
|
|
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}},
|
|
}); 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": bson.M{"$exists": false},
|
|
"denied": bson.M{"$exists": 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},
|
|
"canceled": bson.M{"$exists": false},
|
|
}, &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
|
|
invId, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
|
now := time.Now().UTC().Unix()
|
|
|
|
var ivdoc invitationDoc
|
|
if err := iv.mongoClient.FindOneAndUpdateAs(invitation_collection_name, bson.M{
|
|
"_id": invId,
|
|
"from": bson.M{"$eq": ctx.CallBy.Accid},
|
|
}, bson.M{
|
|
"$set": bson.M{
|
|
"canceled": true,
|
|
"ts": now,
|
|
},
|
|
}, &ivdoc, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(false)); err != nil {
|
|
logger.Println("CancelInvitation failed :", err)
|
|
return
|
|
}
|
|
|
|
if ivdoc.Id.IsZero() {
|
|
return
|
|
}
|
|
|
|
if ivdoc.Blocked {
|
|
// 차단된 초대다. 초대한 사람은 차단된 상태를 모르기 때문에 이 초대는 삭제하고, 초대받은 사람한테는 보내지 않는다.
|
|
iv.mongoClient.Delete(invitation_collection_name, bson.M{"_id": invId})
|
|
} else {
|
|
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: ivdoc.To.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{
|
|
Id: combineObjectID(ivdoc.To, ivdoc.From),
|
|
From: ivdoc.To, // 수락한 나
|
|
To: ivdoc.From, // 상대방
|
|
ToAlias: ivdoc.FromAlias,
|
|
Timestamp: now,
|
|
}
|
|
f2 := friendDoc{
|
|
Id: combineObjectID(ivdoc.From, ivdoc.To),
|
|
From: ivdoc.From, // 상대방
|
|
To: ivdoc.To, // 나
|
|
ToAlias: ivdoc.ToAlias,
|
|
Timestamp: now,
|
|
}
|
|
|
|
// 나한테 상대방을 친구로 만든다
|
|
// SendUpstreamMessage를 먼저 해야 함
|
|
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: f1.From.Hex(),
|
|
Body: []friendDoc{f1},
|
|
Tag: friends_tag,
|
|
})
|
|
iv.f.addFriend(&f1)
|
|
|
|
// 상대방한테 나를 친구로 만듬
|
|
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: f2.From.Hex(),
|
|
Body: []friendDoc{f2},
|
|
Tag: friends_tag,
|
|
})
|
|
iv.f.addFriend(&f2)
|
|
|
|
iv.mongoClient.Delete(invitation_collection_name, bson.M{"_id": invId})
|
|
}
|
|
|
|
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,
|
|
"to": bson.M{"$eq": ctx.CallBy.Accid},
|
|
}, &ivdoc); err != nil {
|
|
logger.Println("DenyInvitation failed. addFriend(f2) err :", err)
|
|
return
|
|
}
|
|
|
|
if ivdoc.Id.IsZero() {
|
|
// 없다
|
|
return
|
|
}
|
|
|
|
iv.mongoClient.Delete(invitation_collection_name, bson.M{"_id": invId})
|
|
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: ivdoc.From.Hex(),
|
|
Body: []invitationDoc{ivdoc},
|
|
Tag: invitations_tag,
|
|
})
|
|
}
|
|
|
|
func (iv *invitation) Trim(ctx wshandler.ApiCallContext) {
|
|
ids := stringsToObjs(ctx.Arguments[2].([]any))
|
|
if len(ids) > 0 {
|
|
if len(ids) == 1 {
|
|
iv.mongoClient.Delete(block_collection_name, bson.M{"_id": ids[0]})
|
|
} else {
|
|
iv.mongoClient.DeleteMany(block_collection_name, bson.D{
|
|
{Key: "_id", Value: bson.M{"$in": ids}},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (iv *invitation) SneakPeekTarget(w http.ResponseWriter, r *http.Request) {
|
|
var ivdoc sneakpeekDoc
|
|
|
|
if err := gocommon.MakeDecoder(r).Decode(&ivdoc); err != nil {
|
|
logger.Println("InviteAsFriend failed:", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// ivdoc.To가 invdoc.From을 차단했으면 offline으로 표시
|
|
exists, err := iv.mongoClient.Exists(block_collection_name, bson.M{"_id": combineObjectID(ivdoc.To, ivdoc.From)})
|
|
if err != nil {
|
|
logger.Println("InviteAsFriend failed:", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
enc := gocommon.MakeEncoder(w, r)
|
|
if exists {
|
|
enc.Encode("offline")
|
|
} else {
|
|
exists, _ := iv.redison.Exists(iv.redison.Context(), ivdoc.To.Hex()).Result()
|
|
if exists == 0 {
|
|
enc.Encode("offline")
|
|
} else {
|
|
enc.Encode("online")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (iv *invitation) InviteAsFriend(w http.ResponseWriter, r *http.Request) {
|
|
// 내 현재 친구 숫자 + 내가 보낸 초대 숫자가 FriendsMax를 넘을 수 없다.
|
|
// TODO : 이미 친구면 초대 불가
|
|
|
|
var ivdoc invitationDoc
|
|
|
|
if err := gocommon.MakeDecoder(r).Decode(&ivdoc); err != nil {
|
|
logger.Println("InviteAsFriend failed:", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if exists, err := iv.mongoClient.Exists(friends_collection_name, bson.M{"_id": combineObjectID(ivdoc.From, ivdoc.To)}); err != nil {
|
|
logger.Println("InviteAsFriend failed:", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
} else if exists {
|
|
// 이미 친구
|
|
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("InviteAsFriend failed:", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
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 {
|
|
logger.Println("InviteAsFriend failed:", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if newid == nil {
|
|
// 이미 보낸 초대 요청
|
|
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, Canceled: 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,
|
|
},
|
|
"$unset": bson.M{
|
|
"canceled": "",
|
|
"blocked": "",
|
|
},
|
|
}, &ivdoc, options.FindOneAndUpdate().SetUpsert(false).SetReturnDocument(options.After))
|
|
if err != nil {
|
|
logger.Println("Block failed:", err)
|
|
return
|
|
}
|
|
|
|
if !ivdoc.Id.IsZero() {
|
|
// 받은 초대가 있었다.
|
|
// 나한테 알림
|
|
ivdoc.Canceled = false
|
|
iv.f.conns.writeMessage(ctx.CallBy.Accid, &wshandler.DownstreamMessage{
|
|
Body: []invitationDoc{ivdoc},
|
|
Tag: invitations_tag,
|
|
})
|
|
}
|
|
}
|