819 lines
20 KiB
Go
819 lines
20 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
"github.com/gorilla/websocket"
|
|
"repositories.action2quare.com/ayo/gocommon"
|
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
|
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
type accountID = primitive.ObjectID
|
|
type groupID = primitive.ObjectID
|
|
|
|
func makeTid(gid groupID, in accountID) string {
|
|
var out primitive.ObjectID
|
|
for i := range in {
|
|
out[12-i-1] = gid[i] ^ in[12-i-1]
|
|
}
|
|
return out.Hex()
|
|
}
|
|
|
|
func midFromTid(gid groupID, in string) accountID {
|
|
h, _ := primitive.ObjectIDFromHex(in)
|
|
|
|
var out accountID
|
|
for i := range h {
|
|
out[12-i-1] = gid[i] ^ h[12-i-1]
|
|
}
|
|
return out
|
|
}
|
|
|
|
type Invitation struct {
|
|
GroupID groupID `json:"_gid"`
|
|
TicketID string `json:"_tid"`
|
|
Inviter bson.M `json:"_inviter"` // memberDoc.Body
|
|
ExpireAtUTC int64 `json:"_expire_at_utc"`
|
|
}
|
|
|
|
// 플레이어한테 공유하는 멤버 정보
|
|
type memberDoc struct {
|
|
Body bson.M `json:"_body"`
|
|
Invite bool `json:"_invite"`
|
|
InviteExpire int64 `json:"_invite_exp"`
|
|
}
|
|
|
|
type InvitationFail bson.M
|
|
|
|
type partyDoc struct {
|
|
Members map[string]any `json:"_members"`
|
|
InCharge string `json:"_incharge"`
|
|
Gid string `json:"_gid"`
|
|
|
|
rh *gocommon.RedisonHandler
|
|
id groupID
|
|
}
|
|
|
|
func (gd *partyDoc) loadMemberFull(tid string) (bson.M, error) {
|
|
full, err := gd.rh.JSONGet(gd.strid(), "$._members."+tid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bt := []byte(full.(string))
|
|
bt = bt[1 : len(bt)-1]
|
|
|
|
var doc bson.M
|
|
if err = json.Unmarshal(bt, &doc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return doc, nil
|
|
}
|
|
|
|
func (gd *partyDoc) loadFull() (doc bson.M) {
|
|
// 새 멤버에 그룹 전체를 알림
|
|
full, err := gd.rh.JSONGet(gd.strid(), "$")
|
|
if err == nil {
|
|
bt := []byte(full.(string))
|
|
bt = bt[1 : len(bt)-1]
|
|
err = json.Unmarshal(bt, &doc)
|
|
if err != nil {
|
|
logger.Println("loadFull err :", err)
|
|
}
|
|
} else {
|
|
logger.Println("loadFull err :", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (gd *partyDoc) strid() string {
|
|
if len(gd.Gid) == 0 {
|
|
gd.Gid = gd.id.Hex()
|
|
}
|
|
return gd.Gid
|
|
}
|
|
|
|
func (gd *partyDoc) tid(in accountID) string {
|
|
return makeTid(gd.id, in)
|
|
}
|
|
|
|
func (gd *partyDoc) mid(tid string) accountID {
|
|
tidobj, _ := primitive.ObjectIDFromHex(tid)
|
|
var out primitive.ObjectID
|
|
for i := range tidobj {
|
|
out[12-i-1] = gd.id[i] ^ tidobj[12-i-1]
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (gd *partyDoc) addInvite(mid accountID, body bson.M, ttl time.Duration, max int) (*memberDoc, error) {
|
|
targetmid := mid
|
|
targetbody := body
|
|
|
|
// 초대 가능한 빈 자리가 있나
|
|
tids, err := gd.rh.JSONObjKeys(gd.strid(), "$._members")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
createNewDoc := func() *memberDoc {
|
|
return &memberDoc{
|
|
Body: targetbody,
|
|
Invite: true,
|
|
InviteExpire: now.Add(ttl).Unix(),
|
|
}
|
|
}
|
|
|
|
newtid := gd.tid(targetmid)
|
|
if len(tids) < max {
|
|
// 빈자리를 찾았다.
|
|
newdoc := createNewDoc()
|
|
_, err := gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc)
|
|
return newdoc, err
|
|
}
|
|
|
|
expires, err := gd.rh.JSONGetInt64(gd.strid(), "$._members.._invite_exp")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var delpaths []string
|
|
for i, expire := range expires {
|
|
if expire < now.Unix() {
|
|
// 만료된 초대가 있네? 지우자
|
|
delpaths = append(delpaths, "$._members."+tids[i])
|
|
}
|
|
}
|
|
|
|
if len(delpaths) == 0 {
|
|
// 빈자리가 없다
|
|
return nil, nil
|
|
}
|
|
|
|
if err := gd.rh.JSONMDel(gd.strid(), delpaths); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newdoc := createNewDoc()
|
|
_, err = gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc)
|
|
return newdoc, err
|
|
}
|
|
|
|
func (gd *partyDoc) addMember(mid accountID, character bson.M) (bson.M, error) {
|
|
tid := gd.tid(mid)
|
|
prefix := "$._members." + tid
|
|
|
|
if _, err := gd.rh.JSONSet(gd.strid(), prefix+"._body", character, gocommon.RedisonSetOptionXX); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := gd.rh.JSONMDel(gd.strid(), []string{prefix + "._invite", prefix + "._invite_exp"}); err != nil {
|
|
return nil, err
|
|
}
|
|
gd.rh.Persist(gd.rh.Context(), gd.strid()).Result()
|
|
|
|
return gd.loadMemberFull(tid)
|
|
}
|
|
|
|
func (gd *partyDoc) removeMemberByTid(tid string) error {
|
|
_, err := gd.rh.JSONDel(gd.strid(), "$._members."+tid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
counts, err := gd.rh.JSONObjLen(gd.strid(), "$._members")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(counts) > 0 && counts[0] == 0 {
|
|
_, err = gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (gd *partyDoc) removeMember(mid accountID) error {
|
|
return gd.removeMemberByTid(gd.tid(mid))
|
|
}
|
|
|
|
func (gd *partyDoc) getMembers() (map[string]any, error) {
|
|
res, err := gd.rh.JSONGet(gd.strid(), "$._members")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var temp []map[string]any
|
|
err = json.Unmarshal([]byte(res.(string)), &temp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make(map[string]any)
|
|
for k, v := range temp[0] {
|
|
body := v.(map[string]any)["_body"]
|
|
out[gd.mid(k).Hex()] = body
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
type partyConfig struct {
|
|
InviteExpire int32 `json:"invite_ttl"` // 그룹이 개인에게 보낸 초대장 만료 기한
|
|
MaxMember int `json:"max_member"`
|
|
|
|
Name string
|
|
}
|
|
|
|
type groupParty struct {
|
|
partyConfig
|
|
sendUpstreamMessage func(*wshandler.UpstreamMessage)
|
|
enterRoom func(groupID, accountID)
|
|
leaveRoom func(groupID, accountID)
|
|
rh *gocommon.RedisonHandler
|
|
}
|
|
|
|
func (gp *groupParty) Initialize(tv *Tavern, cfg configDocument) error {
|
|
rem, _ := json.Marshal(cfg)
|
|
err := json.Unmarshal(rem, &gp.partyConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gp.rh = tv.redison
|
|
gp.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
|
|
tv.wsh.SendUpstreamMessage(msg)
|
|
}
|
|
gp.enterRoom = func(gid groupID, accid accountID) {
|
|
tv.wsh.EnterRoom(gid.Hex(), accid)
|
|
}
|
|
gp.leaveRoom = func(gid groupID, accid accountID) {
|
|
tv.wsh.LeaveRoom(gid.Hex(), accid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gp *groupParty) RegisterApiFunctions() {
|
|
|
|
}
|
|
|
|
// JoinParty : 그룹에 참가
|
|
// - type : 그룹 타입
|
|
// - 그룹 타입에 맞는 키(주로 _id)
|
|
// - member_id : 참가 멤버의 아이디
|
|
// - body : 멤버의 속성 bson document
|
|
func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
|
|
var data struct {
|
|
Gid primitive.ObjectID
|
|
Mid primitive.ObjectID
|
|
First bool
|
|
Character bson.M
|
|
}
|
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
|
logger.Println("JoinParty failed. DecodeGob returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
character := data.Character
|
|
gid := data.Gid
|
|
mid := data.Mid
|
|
|
|
if gid.IsZero() || mid.IsZero() {
|
|
logger.Println("JoinParty failed. mid should be exist")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
gd, err := gp.find(gid)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if gd == nil {
|
|
// 그룹이 없다. 실패
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// 내 정보 업데이트할 때에도 사용됨
|
|
if data.First {
|
|
if memdoc, err := gd.addMember(mid, character); err == nil {
|
|
// 기존 유저에게 새 유저 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gid.Hex(),
|
|
Body: map[string]any{
|
|
gd.tid(mid): memdoc,
|
|
},
|
|
Tag: []string{"MemberDocFull"},
|
|
})
|
|
|
|
gp.enterRoom(gid, mid)
|
|
|
|
// 최초 입장이라면 새 멤버에 그룹 전체를 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: mid.Hex(),
|
|
Body: gd.loadFull(),
|
|
Tag: []string{"GroupDocFull"},
|
|
})
|
|
} else if err != nil {
|
|
logger.Error("JoinParty failed :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
} else {
|
|
path := "$._members." + gd.tid(mid) + "._body"
|
|
if _, err := gd.rh.JSONSet(gd.strid(), path, character, gocommon.RedisonSetOptionXX); err != nil {
|
|
logger.Error("JoinParty failed :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// 기존 유저에게 캐릭터 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gid.Hex(),
|
|
Body: map[string]any{
|
|
gd.tid(mid): bson.M{
|
|
"_body": character,
|
|
},
|
|
},
|
|
Tag: []string{"MemberDocFragment"},
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
// InviteToParty : 초대
|
|
// - type : 초대 타입 (required)
|
|
// - from : 초대하는 자 (required)
|
|
// - to : 초대받는 자 (required)
|
|
// - timeout : 초대 유지시간(optional. 없으면 config 기본 값)
|
|
// - (body) : 검색시 노출되는 document
|
|
func (gp *groupParty) InviteToParty(w http.ResponseWriter, r *http.Request) {
|
|
var doc struct {
|
|
Gid primitive.ObjectID
|
|
Mid primitive.ObjectID
|
|
Target primitive.ObjectID
|
|
Inviter bson.M
|
|
Invitee bson.M
|
|
InitialPartyDoc bson.M
|
|
}
|
|
|
|
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
|
logger.Println("InviteToParty failed. DecodeGob returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
targetid := doc.Target
|
|
gid := doc.Gid
|
|
mid := doc.Mid
|
|
|
|
// targetid에 초대한 mid가 들어있다.
|
|
success, err := gp.rh.SetNX(context.Background(), "inv."+targetid.Hex(), mid.Hex(), time.Duration(gp.InviteExpire)*time.Second).Result()
|
|
if err != nil {
|
|
logger.Println("InviteToParty failed. gp.rh.SetNX() return err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if !success {
|
|
// 이미 초대 중이다.
|
|
// inviter한테 알려줘야 한다.
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: mid.Hex(),
|
|
Body: doc.Invitee,
|
|
Tag: []string{"InvitationFail"},
|
|
})
|
|
return
|
|
}
|
|
|
|
gd, err := gp.find(gid)
|
|
if err != nil {
|
|
logger.Println("InviteToParty failed. gp.find() return err :", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if gd == nil {
|
|
gid = primitive.NewObjectID()
|
|
gd, err = gp.createGroup(gid, mid, doc.Inviter, doc.InitialPartyDoc)
|
|
if err != nil {
|
|
logger.Println("InviteToParty failed. gp.createGroup() return err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// 내가 wshandler room에 입장
|
|
gp.enterRoom(gid, mid)
|
|
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
|
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: mid.Hex(),
|
|
Body: gd,
|
|
Tag: []string{"GroupDocFull"},
|
|
})
|
|
}
|
|
|
|
newdoc, err := gd.addInvite(targetid, doc.Invitee, time.Duration(gp.InviteExpire+1)*time.Second, gp.MaxMember)
|
|
if err != nil {
|
|
logger.Println("InviteToParty failed. gp.addInvite() return err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// invitee에게 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: targetid.Hex(),
|
|
Body: Invitation{
|
|
GroupID: gid,
|
|
TicketID: gd.tid(targetid),
|
|
Inviter: doc.Inviter,
|
|
ExpireAtUTC: newdoc.InviteExpire,
|
|
},
|
|
Tag: []string{"Invitation"},
|
|
})
|
|
}
|
|
|
|
func (gp *groupParty) AcceptPartyInvitation(w http.ResponseWriter, r *http.Request) {
|
|
var doc struct {
|
|
Gid primitive.ObjectID
|
|
Mid primitive.ObjectID
|
|
Tid string
|
|
Character bson.M
|
|
}
|
|
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
|
logger.Println("AcceptPartyInvitation failed. DecodeGob returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
gid := doc.Gid
|
|
mid := doc.Mid
|
|
member := doc.Character
|
|
|
|
cnt, err := gp.rh.Del(context.Background(), "inv."+mid.Hex()).Result()
|
|
if err != nil {
|
|
logger.Error("AcceptPartyInvitation failed. gp.rh.Del returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if cnt == 0 {
|
|
// 만료됨
|
|
w.WriteHeader(http.StatusGatewayTimeout)
|
|
return
|
|
}
|
|
|
|
pids, err := gp.rh.JSONGetString(mid.Hex(), "$.party.id")
|
|
if err != nil {
|
|
logger.Error("AcceptPartyInvitation failed. gp.rh.JSONGetString returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if len(pids) > 0 && len(pids[0]) > 0 {
|
|
// 기존에 이미 파티에 들어가 있다.
|
|
// 기존 파티에서는 탈퇴
|
|
oldgid, _ := primitive.ObjectIDFromHex(pids[0])
|
|
oldgd := &partyDoc{
|
|
id: oldgid,
|
|
rh: gp.rh,
|
|
}
|
|
|
|
// gid에는 제거 메시지 보냄
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + oldgd.strid(),
|
|
Body: bson.M{
|
|
oldgd.tid(mid): bson.M{},
|
|
},
|
|
Tag: []string{"MemberDocFull"},
|
|
})
|
|
|
|
gp.leaveRoom(oldgid, mid)
|
|
}
|
|
|
|
gd := &partyDoc{
|
|
id: gid,
|
|
rh: gp.rh,
|
|
}
|
|
|
|
memberDoc, err := gd.addMember(mid, member)
|
|
if err == nil {
|
|
// 기존 멤버에게 새 멤버를 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gid.Hex(),
|
|
Body: map[string]any{
|
|
gd.tid(mid): memberDoc,
|
|
},
|
|
Tag: []string{"MemberDocFull"},
|
|
})
|
|
|
|
gp.enterRoom(gid, mid)
|
|
|
|
// 현재 내 파티를 기록
|
|
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
|
|
|
// 새 멤버에 그룹 전체를 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: mid.Hex(),
|
|
Body: gd.loadFull(),
|
|
Tag: []string{"GroupDocFull"},
|
|
})
|
|
} else {
|
|
logger.Println("AcceptPartyInvitation failed. group.AcceptPartyInvitation returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (gp *groupParty) QueryPartyMemberState(w http.ResponseWriter, r *http.Request) {
|
|
var mid primitive.ObjectID
|
|
if err := gocommon.MakeDecoder(r).Decode(&mid); err != nil {
|
|
logger.Println("DenyPartyInvitation failed. DecodeGob returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if cnt, _ := gp.rh.Exists(gp.rh.Context(), mid.Hex()).Result(); cnt == 0 {
|
|
return
|
|
}
|
|
|
|
states, _ := gp.rh.JSONGetString(mid.Hex(), "$.party.state")
|
|
if len(states) > 0 && len(states[0]) > 0 {
|
|
gocommon.MakeEncoder(w, r).Encode(states[0])
|
|
} else {
|
|
gocommon.MakeEncoder(w, r).Encode("connected")
|
|
}
|
|
}
|
|
|
|
func (gp *groupParty) updateMemberDocument(gid groupID, mid accountID, doc bson.M) error {
|
|
gd := &partyDoc{
|
|
id: gid,
|
|
rh: gp.rh,
|
|
}
|
|
prefixPath := fmt.Sprintf("$._members.%s.", gd.tid(mid))
|
|
err := gp.rh.JSONMSetRel(gd.strid(), prefixPath, doc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if newstate, ok := doc["_state"]; ok {
|
|
gp.rh.JSONSet(mid.Hex(), "$.party.state", newstate)
|
|
}
|
|
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gid.Hex(),
|
|
Body: map[string]any{
|
|
gd.tid(mid): doc,
|
|
},
|
|
Tag: []string{"MemberDocFragment"},
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (gp *groupParty) updatePartyDocument(gid groupID, frag bson.M) error {
|
|
gd := partyDoc{
|
|
id: gid,
|
|
rh: gp.rh,
|
|
}
|
|
if err := gp.rh.JSONMSetRel(gd.strid(), "$.", frag); err != nil {
|
|
return err
|
|
}
|
|
// 업데이트 알림
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gid.Hex(),
|
|
Body: frag,
|
|
Tag: []string{"GroupDocFragment"},
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (gp *groupParty) UpdatePartyDocument(w http.ResponseWriter, r *http.Request) {
|
|
var data struct {
|
|
Gid primitive.ObjectID
|
|
Doc bson.M
|
|
}
|
|
|
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
|
logger.Println("UpdatePartyDocument failed. DecodeGob returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
gid := data.Gid
|
|
frag := data.Doc
|
|
|
|
if err := gp.updatePartyDocument(gid, frag); err != nil {
|
|
logger.Error("UpdatePartyDocument failed. group.UpdatePartyDocument returns err :", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (gp *groupParty) QueryPartyMembers(w http.ResponseWriter, r *http.Request) {
|
|
var gid primitive.ObjectID
|
|
if err := gocommon.MakeDecoder(r).Decode(&gid); err != nil {
|
|
logger.Println("QueryPartyMembers failed. DecodeGob returns err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
gd := partyDoc{
|
|
id: gid,
|
|
rh: gp.rh,
|
|
}
|
|
|
|
members, err := gd.getMembers()
|
|
if err != nil {
|
|
logger.Error("QueryPartyMembers failed. group.QueryPartyMembers returns err :", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := gocommon.MakeEncoder(w, r).Encode(members); err != nil {
|
|
logger.Error("QueryPartyMembers failed. writeBsonDoc return err :", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (gp *groupParty) createGroup(newid groupID, charge accountID, chargeDoc bson.M, initialGroupDoc bson.M) (*partyDoc, error) {
|
|
tid := makeTid(newid, charge)
|
|
|
|
gd := &partyDoc{
|
|
Members: map[string]any{
|
|
tid: &memberDoc{
|
|
Body: chargeDoc,
|
|
Invite: false,
|
|
InviteExpire: 0,
|
|
},
|
|
},
|
|
InCharge: tid,
|
|
Gid: newid.Hex(),
|
|
|
|
rh: gp.rh,
|
|
id: newid,
|
|
}
|
|
|
|
var err error
|
|
if initialGroupDoc != nil {
|
|
initialGroupDoc["_members"] = gd.Members
|
|
initialGroupDoc["_incharge"] = gd.InCharge
|
|
initialGroupDoc["_gid"] = gd.Gid
|
|
|
|
_, err = gp.rh.JSONSet(gd.strid(), "$", initialGroupDoc, gocommon.RedisonSetOptionNX)
|
|
} else {
|
|
_, err = gp.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return gd, nil
|
|
}
|
|
|
|
func (gp *groupParty) find(id groupID) (*partyDoc, error) {
|
|
if id.IsZero() {
|
|
return nil, nil
|
|
}
|
|
|
|
_, err := gp.rh.JSONObjLen(id.Hex(), "$")
|
|
if err == redis.Nil {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &partyDoc{
|
|
rh: gp.rh,
|
|
id: id,
|
|
}, nil
|
|
}
|
|
func (gp *groupParty) ClientDisconnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
|
gids, _ := gp.rh.JSONGetString(callby.Accid.Hex(), "$.party.id")
|
|
|
|
if len(gids) > 0 && len(gids[0]) > 0 {
|
|
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
|
gidstr := gids[0]
|
|
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
|
|
|
// 나를 먼저 룸에서 빼야 나한테 메시지가 안감
|
|
gp.leaveRoom(gid, callby.Accid)
|
|
|
|
// gid에는 제거 메시지 보냄
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gidstr,
|
|
Body: bson.M{
|
|
makeTid(gid, callby.Accid): bson.M{},
|
|
},
|
|
Tag: []string{"MemberDocFull"},
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func (gp *groupParty) UpdatePartyMemberDocumentDirect(ctx wshandler.ApiCallContext) {
|
|
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
|
doc := ctx.Arguments[1].(map[string]any)
|
|
|
|
gp.updateMemberDocument(gidobj, ctx.CallBy.Accid, doc)
|
|
}
|
|
|
|
func (gp *groupParty) UpdatePartyDocumentDirect(ctx wshandler.ApiCallContext) {
|
|
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
|
doc := ctx.Arguments[1].(map[string]any)
|
|
|
|
gp.updatePartyDocument(gidobj, doc)
|
|
}
|
|
|
|
func (gp *groupParty) LeaveParty(ctx wshandler.ApiCallContext) {
|
|
gids, _ := gp.rh.JSONGetString(ctx.CallBy.Accid.Hex(), "$.party.id")
|
|
|
|
if len(gids) == 0 || len(gids[0]) == 0 {
|
|
return
|
|
}
|
|
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
|
gidstr := gids[0]
|
|
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
|
mid := ctx.CallBy.Accid
|
|
tid := ctx.Arguments[0].(string)
|
|
|
|
gd := partyDoc{
|
|
id: gid,
|
|
rh: gp.rh,
|
|
}
|
|
|
|
var err error
|
|
if len(tid) > 0 {
|
|
if tid != gd.tid(mid) {
|
|
// mid가 incharge여야 한다. 그래야 tid를 쫓아낼 수 있음
|
|
incharge, err := gp.rh.JSONGet(gd.strid(), "$._incharge")
|
|
if err != nil {
|
|
logger.Println("LeaveParty failed. gp.rh.JSONGet returns err :", err)
|
|
return
|
|
}
|
|
if !strings.Contains(incharge.(string), gd.tid(mid)) {
|
|
// incharge가 아니네?
|
|
logger.Println("LeaveParty failed. mid is not incharge")
|
|
return
|
|
}
|
|
mid = midFromTid(gd.id, tid)
|
|
}
|
|
err = gd.removeMemberByTid(tid)
|
|
} else {
|
|
err = gd.removeMember(mid)
|
|
// 내가 나갔다
|
|
gp.rh.JSONDel(mid.Hex(), "$.party.id")
|
|
}
|
|
|
|
if err != nil {
|
|
logger.Println("LeaveParty failed. gd.removeMember returns err :", err)
|
|
return
|
|
}
|
|
|
|
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: mid.Hex(),
|
|
Body: bson.M{"gid": gid},
|
|
Tag: []string{"GroupDocFull", gid.Hex()},
|
|
})
|
|
|
|
// gid에는 제거 메시지 보냄
|
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + gd.strid(),
|
|
Body: bson.M{
|
|
tid: bson.M{},
|
|
},
|
|
Tag: []string{"MemberDocFull"},
|
|
})
|
|
|
|
gp.leaveRoom(gid, mid)
|
|
}
|
|
|
|
func (gp *groupParty) DenyPartyInvitation(ctx wshandler.ApiCallContext) {
|
|
gid, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
|
mid := ctx.CallBy.Accid
|
|
|
|
gp.rh.Del(context.Background(), "inv."+mid.Hex()).Result()
|
|
gd := partyDoc{
|
|
id: gid,
|
|
rh: gp.rh,
|
|
}
|
|
gd.removeMember(mid)
|
|
}
|