Compare commits

..

52 Commits

Author SHA1 Message Date
7d77de67cb 모듈 업데이트 2025-08-05 21:49:09 +09:00
645a54146f 모듈 업데이트 2025-08-05 21:45:34 +09:00
6df3242f1a 파티에서 랜덤매칭 가능 2024-05-10 18:23:25 +09:00
8e34d64d07 종료 순서 변경 2024-02-16 17:37:28 +09:00
e821eb7433 $.size 제거 2024-02-16 14:58:24 +09:00
1922b9152b 그룹챗 redis 키 분리 2024-02-15 12:07:37 +09:00
7b2aa046bd gob.Register 누락 2024-02-14 18:18:02 +09:00
ef98a6bc54 EnterPublicChannel이 왜 여러번 불림??? 방어코드 추가 2024-02-14 17:20:05 +09:00
d80760b177 파티 나갈때 파티 doc 갱신 수정 2024-02-02 17:27:40 +09:00
e4b0f569e8 접속 종료시 pending 처리 2024-01-24 18:16:26 +09:00
2cf1ebb88f 모듈 업데이트 2024-01-22 17:16:26 +09:00
ddcfaf7968 거래채널 참가인원 수 문제 수정 2024-01-18 15:57:02 +09:00
a81549863a 방장 종료 처리 2024-01-18 14:40:39 +09:00
3f1e9ef5c9 voicechat 옮김 2024-01-08 20:25:49 +09:00
a503fe7f30 voice chat을 gocommon으로 옮김 2024-01-08 20:22:54 +09:00
5c9116461a eos 음성채팅 지원 추가 2024-01-04 19:04:48 +09:00
cacab9350d 인스턴트 그룹 단순화.한번에 빌드하는 방식으로. 2023-12-01 00:28:44 +09:00
7d707c4ee3 모듈 업데이트 2023-11-30 14:49:11 +09:00
c1cb226309 종료 안되는 문제 수정 2023-11-29 13:07:51 +09:00
168acce56e 종료 로그 추가 2023-11-28 22:34:18 +09:00
723bd44079 로비 ccu metric 가져옴 2023-11-28 01:18:50 +09:00
b20de99db2 모듈 업데이트 2023-11-28 00:58:07 +09:00
ae0b23f950 party member document 계층 오류 수정 2023-11-27 18:11:52 +09:00
2e2f7a151e tavern ccu 메트릭 수집 2023-11-24 16:30:47 +09:00
e169e72f66 모듈 업데이트 2023-11-24 00:33:00 +09:00
6af4c90280 모듈 업데이트 2023-11-24 00:18:32 +09:00
4ee3b70a46 UpdatePartyMemberDocument http api 추가 2023-11-21 17:06:27 +09:00
5025d41128 totalCCUWriter 제거 2023-11-20 16:09:58 +09:00
f96058057d ccu metric 추가 2023-11-16 19:59:25 +09:00
be15f3f854 모듈 업데이트 2023-11-16 19:59:13 +09:00
91d7eb612e 파티 참가 결과로 파티 아이디를 내려보냄 2023-11-15 16:02:22 +09:00
22ac7c391c error로그를 println으로 변경 2023-11-15 10:37:32 +09:00
e8ea58cea0 파티 재참여 로직 수정 2023-11-10 16:41:40 +09:00
ca2f3b8794 Disconnected 시그니처 변경 2023-11-10 16:41:18 +09:00
5fc64d7471 파티 초대를 그룹에 알림 2023-11-09 00:24:13 +09:00
21ec68d27e 파티 초대 거절시 멤버 아웃 메시지 보냄 2023-11-06 17:00:23 +09:00
99c10986d6 Leave시에 groupDocFull은 안보냄 2023-10-31 21:24:39 +09:00
dffe1cfee5 초기 파티 document 설정 가능 2023-10-26 20:53:32 +09:00
bd2408c66f http api broker 변경 적용 2023-10-26 11:25:31 +09:00
b9339166b9 모듈 업데이트 2023-10-16 10:03:33 +09:00
8dea97d956 로그 추가 2023-10-16 10:02:35 +09:00
708f9d6caf 로그 추가 2023-10-13 18:11:48 +09:00
dd37659089 채팅 멤버 목록 전파 2023-10-12 14:46:54 +09:00
bdcb86c1c7 모듈 업데이트 2023-10-12 12:05:48 +09:00
6e709c9454 instant그룹에서 _id를 클라이언트에는 감춤 2023-10-10 20:32:22 +09:00
b9d2451902 instantdoc 업데이트 api 추가 2023-10-09 22:27:33 +09:00
e8f74bcd19 그룹 머지 기능 추가 2023-10-09 17:59:15 +09:00
b44a6b1fd8 랜덤 매칭 정지 2023-10-07 14:51:12 +09:00
d7d7df4a28 인스턴트 그룹 추가(랜덤매칭용) 2023-10-06 11:13:03 +09:00
2cec9b90fe 모듈 업데이트 2023-09-19 18:52:49 +09:00
6706d7d02e ClientConnect signature 변경 2023-09-19 18:46:41 +09:00
675bcbad9e Revert "친구 초대 및 관리 완료"
This reverts commit e18a5eafe0.
2023-09-19 18:38:56 +09:00
16 changed files with 1997 additions and 1111 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
__debug_bin.exe __debug_bin.exe
*.log *.log
config.json config.json
tavern.exe

49
config.json Normal file
View File

@ -0,0 +1,49 @@
{
"region_storage": {
"default": {
"mongo": "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
"redis": {
"cache": "redis://192.168.8.94:6380/0",
"session": "redis://192.168.8.94:6380/1",
"tx": "redis://192.168.8.94:6380/2",
"tavern": "redis://192.168.8.94:6380/3",
"wshandler": "redis://192.168.8.94:6380/4"
}
}
},
"session_storage": "redis://192.168.8.94:6380/3",
"session_ttl": 3600,
"maingate_session_storage": "redis://192.168.8.94:6380/1",
"maingate_session_ttl" : 3600,
"maingate_api_token": "63d08aa34f0162622c11284b",
"tavern_redis_url": "redis://192.168.8.94:6380/4",
"tavern_service_url": "http://localhost/tavern",
"tavern_group_types": {
"party": {
"max_member": 3,
"invite_ttl": 30
},
"chat" : {
"default_capacity" : 1000,
"channels" : {
"bazzar-1" : {
"name" : "FText(bazzar-1)"
},
"bazzar-2" : {
"name" : "FText(bazzar-2)"
},
"bazzar-3" : {
"name" : "FText(bazzar-3)"
},
"bazzar-4" : {
"name" : "FText(bazzar-4)"
},
"bazzar-5" : {
"name" : "FText(bazzar-5)"
}
}
}
}
}

View File

@ -6,17 +6,17 @@
"cache": "redis://192.168.8.94:6380/0", "cache": "redis://192.168.8.94:6380/0",
"session": "redis://192.168.8.94:6380/1", "session": "redis://192.168.8.94:6380/1",
"tx": "redis://192.168.8.94:6380/2", "tx": "redis://192.168.8.94:6380/2",
"tavern": "redis://192.168.8.94:6380/3" "tavern": "redis://192.168.8.94:6380/3",
"wshandler": "redis://192.168.8.94:6380/4"
} }
} }
}, },
"maingate_session_storage": "redis://192.168.8.94:6380/1", "session_storage": "redis://192.168.8.94:6380/5",
"maingate_session_ttl" : 3600, "session_ttl": 3600,
"maingate_api_token": "63d08aa34f0162622c11284b", "maingate_api_token": "63d08aa34f0162622c11284b",
"social_redis_url": "redis://192.168.8.94:6380/4", "tavern_redis_url": "redis://192.168.8.94:6380/7",
"social_storage_url" : "mongodb://192.168.8.94:27017/social?replicaSet=repl01&retrywrites=false",
"tavern_service_url": "http://localhost/tavern", "tavern_service_url": "http://localhost/tavern",
"tavern_group_types": { "tavern_group_types": {
"party": { "party": {

View File

@ -1,491 +0,0 @@
package core
import (
"context"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"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 (
friends_collection_name = gocommon.CollectionName("friends")
monitoring_center_count = 100
state_online = "online"
state_offline = "offline"
)
var friend_state_tag = []string{"social.FriendState"}
type friendDoc struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
From primitive.ObjectID `bson:"from" json:"-"`
To primitive.ObjectID `bson:"to" json:"-"`
ToAlias string `bson:"talias" json:"to"`
Timestamp int64 `bson:"ts" json:"ts"`
Deleted bool `bson:"deleted,omitempty" json:"deleted,omitempty"`
}
type registerListener struct {
src primitive.ObjectID
alias string
l *listener
}
type monitoringCenter struct {
regChan chan registerListener
publishState func(string, string, string)
}
type connWithFriends struct {
c *websocket.Conn
friends []*friendDoc
initialized bool
}
type connections struct {
connLock sync.Mutex
conns map[primitive.ObjectID]*connWithFriends
}
func (cs *connections) new(accid primitive.ObjectID, conn *websocket.Conn) {
cs.connLock.Lock()
defer cs.connLock.Unlock()
cs.conns[accid] = &connWithFriends{
c: conn,
initialized: false,
}
}
func (cs *connections) delete(accid primitive.ObjectID) {
cs.connLock.Lock()
defer cs.connLock.Unlock()
delete(cs.conns, accid)
}
func (cs *connections) conn(accid primitive.ObjectID) *websocket.Conn {
cs.connLock.Lock()
defer cs.connLock.Unlock()
if cf, ok := cs.conns[accid]; ok {
return cf.c
}
return nil
}
func (cs *connections) addFriend(accid primitive.ObjectID, fdoc *friendDoc) bool {
cs.connLock.Lock()
defer cs.connLock.Unlock()
if cf, ok := cs.conns[accid]; ok {
if cf.initialized {
cf.friends = append(cf.friends, fdoc)
return true
}
}
return false
}
func (cs *connections) initFriends(accid primitive.ObjectID, fdocs []*friendDoc) {
cs.connLock.Lock()
defer cs.connLock.Unlock()
if cf, ok := cs.conns[accid]; ok {
cf.friends = fdocs
cf.initialized = true
}
}
func (cs *connections) clearFriends(accid primitive.ObjectID) (out []*friendDoc) {
cs.connLock.Lock()
defer cs.connLock.Unlock()
if cf, ok := cs.conns[accid]; ok {
out = cf.friends
cf.friends = nil
cf.initialized = false
}
return
}
type friends struct {
mongoClient gocommon.MongoClient
redison *gocommon.RedisonHandler
wsh *wshandler.WebsocketHandler
moncen []monitoringCenter
conns connections
}
type listener struct {
c *websocket.Conn
me primitive.ObjectID
}
type listenerMap struct {
listeners map[primitive.ObjectID]*listener
connected bool
online []byte
offline []byte
}
func init() {
gob.Register([]friendDoc{})
}
// per channel
// src(alias) - listener(objectid) : socket
// - listener(objectid) : socket
// - listener(objectid) : socket
func makeSrcMap(src string, connected bool) *listenerMap {
online, _ := json.Marshal(wshandler.DownstreamMessage{
Body: bson.M{
"from": src,
"state": state_online,
},
Tag: friend_state_tag,
})
offline, _ := json.Marshal(wshandler.DownstreamMessage{
Body: bson.M{
"from": src,
"state": state_offline,
},
Tag: friend_state_tag,
})
return &listenerMap{
listeners: make(map[primitive.ObjectID]*listener),
connected: connected,
online: online,
offline: offline,
}
}
func makeFriends(ctx context.Context, so *Social) (*friends, error) {
if err := so.mongoClient.MakeUniqueIndices(friends_collection_name, map[string]bson.D{
"fromto": {{Key: "from", Value: 1}, {Key: "to", Value: 1}},
}); err != nil {
return nil, err
}
var moncen []monitoringCenter
for i := 0; i < monitoring_center_count; i++ {
subChannel := fmt.Sprintf("_soc_fr_monitor_ch_%d_%d", i, so.redison.Options().DB)
regChan := make(chan registerListener)
moncen = append(moncen, monitoringCenter{
regChan: regChan,
publishState: func(src, alias, state string) {
so.redison.Publish(ctx, subChannel, src+alias+":"+state).Result()
},
})
go func(subChannel string, regChan chan registerListener) {
pubsub := so.redison.Subscribe(ctx, subChannel)
listeners := make(map[primitive.ObjectID]*listenerMap)
for {
select {
case reg := <-regChan:
// 내가 관심있는 애들 등록
srcmap, online := listeners[reg.src]
if !online {
srcmap = makeSrcMap(reg.alias, false)
listeners[reg.src] = srcmap
}
if reg.l.c == nil {
// 등록 해제. 모니터링 종료
// listener목록에서 나(reg.l.me)를 제거
delete(srcmap.listeners, reg.l.me)
online = false
logger.Println("regChan unregistered :", reg.src.Hex(), reg.l.me.Hex())
} else if oldl, ok := srcmap.listeners[reg.l.me]; ok {
// 내가 이미 리스너로 등록되어 있다.
// 상대방이 나를 차단했을 경우에는 기존 리스너가 nil임
online = oldl != nil
logger.Println("regChan registered :", reg.src.Hex(), reg.l.me.Hex(), "old", online)
} else {
logger.Println("regChan registered :", reg.src.Hex(), reg.l.me.Hex())
srcmap.listeners[reg.l.me] = reg.l
}
if online && srcmap != nil {
logger.Println("regChan send online :", reg.l.me.Hex(), string(srcmap.online))
reg.l.c.WriteMessage(websocket.TextMessage, srcmap.online)
}
if len(srcmap.listeners) == 0 && !srcmap.connected {
delete(listeners, reg.src)
}
case msg := <-pubsub.Channel():
target, _ := primitive.ObjectIDFromHex(msg.Payload[:24])
aliasstate := strings.SplitN(msg.Payload[24:], ":", 2)
var sent []byte
if srcmap, ok := listeners[target]; ok {
if aliasstate[1] == state_online {
sent = srcmap.online
srcmap.connected = true
} else if aliasstate[1] == state_offline {
sent = srcmap.offline
srcmap.connected = false
if len(srcmap.listeners) == 0 {
delete(listeners, target)
}
}
if len(sent) > 0 {
for _, l := range srcmap.listeners {
logger.Println("state fire :", l.me, string(sent))
l.c.WriteMessage(websocket.TextMessage, sent)
}
}
} else if aliasstate[1] == state_online {
listeners[target] = makeSrcMap(aliasstate[0], true)
}
}
}
}(subChannel, regChan)
}
return &friends{
mongoClient: so.mongoClient,
redison: so.redison,
wsh: so.wsh,
moncen: moncen,
conns: connections{
conns: make(map[primitive.ObjectID]*connWithFriends),
},
}, nil
}
func (fs *friends) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
fs.conns.new(callby.Accid, conn)
// 내 로그인 상태를 알림
meidx := callby.Accid[11] % monitoring_center_count
fs.moncen[meidx].publishState(callby.Accid.Hex(), callby.Alias, state_online)
}
func (fs *friends) ClientDisconnected(conn *websocket.Conn, callby *wshandler.Sender) {
// 로그 오프 상태를 알림
meidx := callby.Accid[11] % monitoring_center_count
fs.moncen[meidx].publishState(callby.Accid.Hex(), callby.Alias, state_offline)
fs.stopMonitoringFriends(callby.Accid)
fs.conns.delete(callby.Accid)
}
func (fs *friends) writeMessage(acc primitive.ObjectID, src any) {
c := fs.conns.conn(acc)
if c == nil {
return
}
if bt, err := json.Marshal(src); err == nil {
c.WriteMessage(websocket.TextMessage, bt)
}
}
var errAddFriendFailed = errors.New("addFriend failed")
func (fs *friends) addFriend(f *friendDoc) error {
_, newid, err := fs.mongoClient.Update(friends_collection_name, bson.M{
"_id": primitive.NewObjectID(),
}, bson.M{
"$setOnInsert": f,
}, options.Update().SetUpsert(true))
if err != nil {
return err
}
if newid == nil {
return errAddFriendFailed
}
f.Id = newid.(primitive.ObjectID)
if fs.conns.addFriend(f.From, f) {
// 모니터링 중
conn := fs.conns.conn(f.From)
if conn != nil {
toidx := f.To[11] % monitoring_center_count
fs.moncen[toidx].regChan <- registerListener{
src: f.To,
alias: f.ToAlias,
l: &listener{
c: conn,
me: f.From,
},
}
}
}
return nil
}
func (fs *friends) Block(ctx wshandler.ApiCallContext) {
// BlockByMe 에 추가하고 상대의 BlockByYou를 설정한다.
// var bi struct {
// From primitive.ObjectID
// To primitive.ObjectID
// }
// if err := gocommon.MakeDecoder(r).Decode(&bi); err != nil {
// logger.Println("friends.Block failed :", err)
// w.WriteHeader(http.StatusBadRequest)
// return
// }
// logger.Println("friends.Block :", bi)
}
func (fs *friends) DeleteFriend(ctx wshandler.ApiCallContext) {
fid, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var fdoc friendDoc
if err := fs.mongoClient.FindOneAs(friends_collection_name, bson.M{
"_id": fid,
}, &fdoc, options.FindOne().SetProjection(bson.M{
"from": 1,
"to": 1,
})); err != nil {
logger.Println("DeleteFriend is failed :", err)
return
}
if fdoc.Id.IsZero() {
return
}
now := time.Now().UTC().Unix()
fdoc.Deleted = true
fdoc.Timestamp = now
// 나한테 삭제
fs.mongoClient.Update(friends_collection_name, bson.M{
"_id": fid,
}, bson.M{
"$set": bson.M{
"deleted": true,
"ts": fdoc.Timestamp,
},
}, options.Update().SetUpsert(false))
fs.writeMessage(ctx.CallBy.Accid, &wshandler.DownstreamMessage{
Body: []friendDoc{fdoc},
Tag: friends_tag,
})
// 상대방에게 삭제
var yourdoc friendDoc
if err := fs.mongoClient.FindOneAndUpdateAs(friends_collection_name, bson.M{
"from": fdoc.To,
"to": fdoc.From,
}, bson.M{
"$set": bson.M{
"deleted": true,
"ts": now,
},
}, &yourdoc, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(false)); err == nil {
fs.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: fdoc.To.Hex(),
Body: []friendDoc{yourdoc},
Tag: friends_tag,
})
}
}
func (fs *friends) StartMonitoringFriends(ctx wshandler.ApiCallContext) {
// 내 친구 목록에 나를 등록
var friends []*friendDoc
if err := fs.mongoClient.FindAllAs(friends_collection_name, bson.M{
"from": ctx.CallBy.Accid,
}, &friends, options.Find().SetProjection(bson.M{"to": 1, "talias": 1})); err != nil {
logger.Println("StartMonitoringFriends is failed :", err)
return
}
me := &listener{
c: fs.conns.conn(ctx.CallBy.Accid),
me: ctx.CallBy.Accid,
}
for _, f := range friends {
toidx := f.To[11] % monitoring_center_count
fs.moncen[toidx].regChan <- registerListener{
src: f.To,
alias: f.ToAlias,
l: me,
}
}
fs.conns.initFriends(ctx.CallBy.Accid, friends)
}
func (fs *friends) stopMonitoringFriends(accid primitive.ObjectID) {
friends := fs.conns.clearFriends(accid)
if len(friends) > 0 {
// 나를 상대방 모니터링에서 뺀다
nilListener := &listener{c: nil, me: accid}
for _, f := range friends {
toidx := f.To[11] % monitoring_center_count
fs.moncen[toidx].regChan <- registerListener{
src: f.To,
alias: f.ToAlias,
l: nilListener,
}
}
}
}
func (fs *friends) StopMonitoringFriends(ctx wshandler.ApiCallContext) {
fs.stopMonitoringFriends(ctx.CallBy.Accid)
}
func (fs *friends) QueryFriends(ctx wshandler.ApiCallContext) {
queryfrom := int64(ctx.Arguments[0].(float64))
var myfriends []friendDoc
err := fs.mongoClient.FindAllAs(friends_collection_name, bson.M{
"from": ctx.CallBy.Accid,
"ts": bson.M{"$gt": queryfrom},
}, &myfriends)
if err != nil {
logger.Println("QueryReceivedInvitations failed. FindAllAs err :", err)
}
if len(myfriends) > 0 {
fs.writeMessage(ctx.CallBy.Accid, &wshandler.DownstreamMessage{
Alias: ctx.CallBy.Alias,
Body: myfriends,
Tag: friends_tag,
})
}
}
func (fs *friends) 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[2].([]any))
if len(ids) > 0 {
if len(ids) == 1 {
fs.mongoClient.Delete(friends_collection_name, bson.M{"_id": ids[0]})
} else {
fs.mongoClient.DeleteMany(friends_collection_name, bson.D{{Key: "_id", Value: bson.M{"$in": ids}}})
}
}
}

3
core/group.go Normal file
View File

@ -0,0 +1,3 @@
package core
type configDocument map[string]any

348
core/group_chat.go Normal file
View File

@ -0,0 +1,348 @@
package core
import (
"encoding/gob"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
"go.mongodb.org/mongo-driver/bson/primitive"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler"
)
type channelID = string
type channelConfig struct {
Capacity int64 `json:"capacity"`
Size int64 `json:"size"`
Key string `json:"key"`
Members map[string]int32 `json:"members"`
emptyJson string
inoutChan chan string
}
type chatConfig struct {
DefaultCapacity int64 `json:"default_capacity"`
Channels map[string]*channelConfig `json:"channels"`
}
type groupChat struct {
chatConfig
rh *gocommon.RedisonHandler
enterRoom func(channelID, accountID)
leaveRoom func(channelID, accountID)
sendUpstreamMessage func(msg *wshandler.UpstreamMessage)
}
func init() {
gob.Register(map[string]string{})
}
func (gc *groupChat) Initialize(tv *Tavern, cfg configDocument) error {
rem, _ := json.Marshal(cfg)
if err := json.Unmarshal(rem, &gc.chatConfig); err != nil {
return err
}
gc.enterRoom = func(chanid channelID, accid accountID) {
tv.wsh.EnterRoom(string(chanid), accid)
}
gc.leaveRoom = func(chanid channelID, accid accountID) {
tv.wsh.LeaveRoom(string(chanid), accid)
}
gc.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
tv.wsh.SendUpstreamMessage(msg)
}
gc.rh = tv.redison
for name, cfg := range gc.chatConfig.Channels {
if cfg.Capacity == 0 {
cfg.Capacity = gc.chatConfig.DefaultCapacity
}
cfg.Key = name
cfg.Size = 0
jm, _ := json.Marshal(cfg)
cfg.emptyJson = fmt.Sprintf("[%s]", string(jm))
cfg.Members = make(map[string]int32)
_, err := gc.rh.JSONSet(name, "$", cfg)
if *devflag && err != nil {
gc.rh.JSONDel(name, "$")
_, err = gc.rh.JSONSet(name, "$", cfg)
}
if err != nil {
return err
}
inoutchan := make(chan string, 10)
cfg.inoutChan = inoutchan
go func(chanid string) {
var cur []string
tick := time.After(3 * time.Second)
for {
select {
case <-tick:
tick = time.After(3 * time.Second)
if len(cur) > 0 {
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"inout": cur},
Tag: []string{"ChattingChannelProperties"},
})
cur = nil
}
case m := <-inoutchan:
cur = append(cur, m)
}
}
}(name)
}
return nil
}
func (gc *groupChat) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
}
func (gc *groupChat) ClientDisconnected(msg string, callby *wshandler.Sender) {
if msg == wshandler.ForceShutdownCloseMessage {
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
return
}
chans, _ := gc.rh.HGetAll(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
for typename, chanid := range chans {
gc.leaveRoom(chanid, callby.Accid)
if typename == "public" {
cnt, err := gc.rh.JSONDel(chanid, "$.members."+callby.Alias)
if cnt > 0 {
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
cfg.inoutChan <- "-" + callby.Alias
}
} else if err != nil {
logger.Println("groupchat ClientDisconnected JSONDel err :", err, callby.Alias)
} else {
logger.Println("groupchat ClientDisconnected JSONDel cnt 0 :", callby.Alias)
}
} else {
logger.Println("groupchat ClientDisconnected leave private channel :", chanid, callby.Alias)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"sender": callby.Alias},
Tag: []string{typename + ".LeavePrivateChannel"},
})
}
}
}
func (gc *groupChat) EnterPublicChannel(ctx wshandler.ApiCallContext) {
gc.LeavePublicChannel(ctx)
chanid := ctx.Arguments[0].(string)
cfg, ok := gc.chatConfig.Channels[chanid]
if !ok {
return
}
atomicsize, _ := gc.rh.JSONObjLen(chanid, "$.members")
if len(atomicsize) == 0 {
return
}
if atomicsize[0] >= cfg.Capacity {
logger.Println("chatting channel is full :", chanid, atomicsize[0], cfg.Capacity)
return
}
cnt, _ := gc.rh.HSet(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public", chanid).Result()
if cnt == 0 {
logger.Println("HSet cnt 0 :", chanid, ctx.CallBy.Alias)
return
}
ok, err := gc.rh.JSONSet(chanid, "$.members."+ctx.CallBy.Alias, 1)
if err != nil {
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
logger.Println("JSONSet $.members failed :", err, chanid, ctx.CallBy.Alias)
return
}
if !ok {
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
logger.Println("JSONSet $.members not ok :", chanid, ctx.CallBy.Alias)
return
}
gc.enterRoom(chanid, ctx.CallBy.Accid)
cfg.inoutChan <- "+" + ctx.CallBy.Alias
members, _ := gc.rh.JSONGetDocuments(chanid, "$.members")
var toarr []string
if len(members) > 0 {
toarr = make([]string, 0, len(members[0]))
for k := range members[0] {
toarr = append(toarr, k)
}
} else {
toarr = []string{}
}
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ctx.CallBy.Accid.Hex(),
Body: map[string]any{"members": toarr},
Tag: []string{"ChattingChannelProperties"},
})
}
func (gc *groupChat) LeavePublicChannel(ctx wshandler.ApiCallContext) {
oldchan, _ := gc.rh.HGet(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
if len(oldchan) > 0 {
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public")
cnt, err := gc.rh.JSONDel(oldchan, "$.members."+ctx.CallBy.Alias)
if cnt > 0 {
gc.leaveRoom(oldchan, ctx.CallBy.Accid)
if cfg, ok := gc.chatConfig.Channels[oldchan]; ok {
cfg.inoutChan <- "-" + ctx.CallBy.Alias
}
} else if err != nil {
logger.Println("groupchat LeavePublicChannel JSONDel err :", err, oldchan, ctx.CallBy.Alias)
} else {
logger.Println("groupchat LeavePublicChannel JSONDel cnt 0 :", oldchan, ctx.CallBy.Alias)
}
}
}
func (gc *groupChat) TextMessage(ctx wshandler.ApiCallContext) {
chanid := ctx.Arguments[0].(string)
msg := ctx.Arguments[1].(string)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"sender": ctx.CallBy.Alias, "msg": msg},
Tag: []string{"TextMessage"},
})
}
func (gc *groupChat) EnterPrivateChannel(ctx wshandler.ApiCallContext) {
typename := ctx.Arguments[0].(string)
channel := ctx.Arguments[1].(string)
var reason string
if len(ctx.Arguments) > 2 {
reason = ctx.Arguments[2].(string)
}
if len(reason) > 0 {
// 수락
ok, err := gc.rh.HSetNX(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), typename, channel).Result()
if err != nil || !ok {
// 이미 다른 private channel 참여 중
logger.Println("EnterPrivateChannel failed. HSetNX return err :", err, ctx.CallBy.Accid.Hex(), typename, channel)
return
}
gc.enterRoom(channel, ctx.CallBy.Accid)
}
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + channel,
Body: map[string]any{
"sender": ctx.CallBy.Alias,
"msg": reason,
},
Tag: []string{typename + ".EnterPrivateChannel"},
})
}
func (gc *groupChat) LeavePrivateChannel(ctx wshandler.ApiCallContext) {
typename := ctx.Arguments[0].(string)
chanid := ctx.Arguments[1].(string)
cnt, _ := gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), typename).Result()
if cnt > 0 {
gc.leaveRoom(chanid, ctx.CallBy.Accid)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"sender": ctx.CallBy.Alias},
Tag: []string{typename + ".LeavePrivateChannel"},
})
}
}
func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Request) {
var prefix string
if err := gocommon.MakeDecoder(r).Decode(&prefix); err != nil {
logger.Println("FetchChattingChannels failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if len(prefix) == 0 {
logger.Println("FetchChattingChannel failed. prefix is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
var rows []string
for name, cfg := range gc.chatConfig.Channels {
if len(prefix) > 0 {
if !strings.HasPrefix(name, prefix) {
continue
}
}
onechan, err := gc.rh.JSONGet(name, "$")
if err != nil && err != redis.Nil {
logger.Println("FetchChattingChannel failed. HGetAll return err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if err == redis.Nil || onechan == nil {
rows = append(rows, cfg.emptyJson)
} else {
// json array로 나온다
rows = append(rows, strings.Trim(onechan.(string), "[]"))
}
}
gocommon.MakeEncoder(w, r).Encode(rows)
}
func (gc *groupChat) QueryPlayerChattingChannel(w http.ResponseWriter, r *http.Request) {
var accid primitive.ObjectID
if err := gocommon.MakeDecoder(r).Decode(&accid); err != nil {
logger.Println("QueryPlayerChattingChannel failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
sub, err := gc.rh.HGetAll(gc.rh.Context(), "gc-"+accid.Hex()).Result()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if sub == nil {
sub = make(map[string]string)
}
gocommon.MakeEncoder(w, r).Encode(sub)
}
func (gc *groupChat) SendMessageOnChannel(w http.ResponseWriter, r *http.Request) {
var msg wshandler.UpstreamMessage
if err := gocommon.MakeDecoder(r).Decode(&msg); err != nil {
logger.Println("SendMessageOnChannel failed. ReadJsonDocumentFromBody return err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
gc.sendUpstreamMessage(&msg)
}

243
core/group_instant.go Normal file
View File

@ -0,0 +1,243 @@
package core
import (
"errors"
"net/http"
"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 instantDoc struct {
Members map[string]primitive.M `json:"_members"`
Count int64 `json:"_count"`
Body primitive.M `json:"_body"`
Gid primitive.ObjectID `json:"_gid"`
rh *gocommon.RedisonHandler
idstr string
}
func (gd *instantDoc) strid() string {
if len(gd.idstr) == 0 {
gd.idstr = gd.Gid.Hex()
}
return gd.idstr
}
func (gd *instantDoc) tid(in accountID) string {
return makeTid(gd.Gid, in)
}
var errGroupAlreadyDestroyed = errors.New("instant group is already destroyed")
func (gd *instantDoc) removeMember(mid accountID) error {
counts, _ := gd.rh.JSONNumIncrBy(gd.strid(), "$._count", -1)
if len(counts) == 0 {
// 이미 지워진 인스턴트그룹
return errGroupAlreadyDestroyed
}
if _, err := gd.rh.JSONDel(gd.strid(), "$._members."+gd.tid(mid)); err != nil {
return err
}
gd.Count = counts[0]
return nil
}
type groupInstant struct {
sendUpstreamMessage func(*wshandler.UpstreamMessage)
enterRoom func(groupID, accountID)
leaveRoom func(groupID, accountID)
rh *gocommon.RedisonHandler
}
func (gi *groupInstant) Initialize(tv *Tavern) error {
gi.rh = tv.redison
gi.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
tv.wsh.SendUpstreamMessage(msg)
}
gi.enterRoom = func(gid groupID, accid accountID) {
tv.wsh.EnterRoom(gid.Hex(), accid)
}
gi.leaveRoom = func(gid groupID, accid accountID) {
tv.wsh.LeaveRoom(gid.Hex(), accid)
}
return nil
}
func (gi *groupInstant) UpdateInstantDocument(w http.ResponseWriter, r *http.Request) {
var data struct {
Gid primitive.ObjectID
Doc primitive.M
Result string
}
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
logger.Println("UpdateInstantDocument failed. Decode returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gd := partyDoc{
id: data.Gid,
rh: gi.rh,
}
if err := gi.rh.JSONMSetRel(gd.strid(), "$.", data.Doc); err != nil {
logger.Println("UpdateInstantDocument failed. JSONMSetRel returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// 업데이트 알림
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gd.strid(),
Body: data.Doc,
Tag: []string{"GroupDocFragment"},
})
if data.Result == "after" {
fulldoc := gd.loadFull()
if fulldoc != nil {
tids := fulldoc["_members"].(map[string]any)
mids := make(map[string]any)
for k, v := range tids {
mid := midFromTid(data.Gid, k)
mids[mid.Hex()] = v
}
fulldoc["_members"] = mids
}
gocommon.MakeEncoder(w, r).Encode(fulldoc)
}
}
func (gi *groupInstant) Build(w http.ResponseWriter, r *http.Request) {
var data struct {
Members []struct {
Mid primitive.ObjectID
Character primitive.M
}
Body primitive.M
}
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
logger.Println("JoinParty failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// data.Members가 다 살아있는지 부터 확인
var result struct {
Gid primitive.ObjectID // 실패하면 없음
Success []primitive.ObjectID
}
for _, m := range data.Members {
count, err := gi.rh.Exists(gi.rh.Context(), m.Mid.Hex()).Result()
if err != nil {
logger.Println("instant.Build failed. Exists returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if count != 0 {
result.Success = append(result.Success, m.Mid)
}
}
if len(result.Success) < len(data.Members) {
// 이탈자가 있군
// 여기서 중단
gocommon.MakeEncoder(w, r).Encode(result)
return
}
newid := primitive.NewObjectID()
members := make(map[string]primitive.M)
for _, m := range data.Members {
tid := makeTid(newid, m.Mid)
members[tid] = m.Character
}
gd := &instantDoc{
Members: members,
Body: data.Body,
Count: int64(len(members)),
rh: gi.rh,
Gid: newid,
}
_, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
if err != nil {
logger.Println("instant.Build failed. JSONSet returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
result.Gid = newid
for _, char := range members {
delete(char, "_id")
}
// 멤버에 그룹 전체를 알림
for _, m := range data.Members {
midstr := m.Mid.Hex()
gi.rh.JSONSet(midstr, "$.instant", bson.M{"id": gd.strid()})
gi.enterRoom(newid, m.Mid)
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: midstr,
Body: gd,
Tag: []string{"GroupDocFull"},
})
}
gocommon.MakeEncoder(w, r).Encode(result)
}
func (gi *groupInstant) makeClientClean(accid primitive.ObjectID) {
gids, _ := gi.rh.JSONGetString(accid.Hex(), "$.instant.id")
if len(gids) > 0 && len(gids[0]) > 0 {
gidstr := gids[0]
gid, _ := primitive.ObjectIDFromHex(gidstr)
gi.rh.JSONDel(accid.Hex(), "$.instant.id")
gi.leaveRoom(gid, accid)
// gid에는 제거 메시지 보냄
gd := instantDoc{
Gid: gid,
rh: gi.rh,
}
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gd.strid(),
Body: bson.M{
gd.tid(accid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
gd.removeMember(accid)
if gd.Count == 0 {
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
}
}
}
func (gi *groupInstant) MakeClientClean(w http.ResponseWriter, r *http.Request) {
var accid primitive.ObjectID
if err := gocommon.MakeDecoder(r).Decode(&accid); err != nil {
logger.Println("MakeClientClean failed. decode returns err :", err)
return
}
gi.makeClientClean(accid)
}
func (gi *groupInstant) ClientDisconnected(msg string, callby *wshandler.Sender) {
gi.makeClientClean(callby.Accid)
}

979
core/group_party.go Normal file
View File

@ -0,0 +1,979 @@
package core
import (
"context"
"encoding/gob"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-redis/redis/v8"
"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 init() {
gob.Register(partyDoc{})
gob.Register(Invitation{})
}
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, option string) (bson.M, error) {
tid := gd.tid(mid)
prefix := "$._members." + tid
if option == gocommon.RedisonSetOptionNX {
if _, err := gd.rh.JSONSet(gd.strid(), prefix, bson.M{"_body": character}, gocommon.RedisonSetOptionNX); err != nil {
return nil, err
}
} else {
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) getIncharge() string {
if len(gd.InCharge) == 0 {
incharge, err := gd.rh.JSONGet(gd.strid(), "$._incharge")
if err == nil && incharge != nil {
gd.InCharge = strings.Trim(incharge.(string), "[]\"")
}
}
return gd.InCharge
}
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) {
gp.rh.JSONDel(accid.Hex(), "$.party.state")
tv.wsh.LeaveRoom(gid.Hex(), accid)
}
return nil
}
// 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
Character bson.M
Option string
}
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
logger.Println("JoinParty failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// Option : ["NX" | "XX" | ""]
character := data.Character
gid := data.Gid
mid := data.Mid
option := strings.ToUpper(data.Option)
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 {
// 그룹이 없다. 없을 수도 있지
gocommon.MakeEncoder(w, r).Encode("")
return
}
// 내 정보 업데이트할 때에도 사용됨
if option == "XX" {
// 이미 멤버여야 재입장 가능
path := "$._members." + gd.tid(mid) + "._body"
if _, err := gd.rh.JSONSet(gd.strid(), path, character, gocommon.RedisonSetOptionXX); err != nil {
// 멤버가 아니네? 그새 파티장이 쫓아냈을 수도 있다.
gocommon.MakeEncoder(w, r).Encode("")
return
}
}
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
memdoc, err := gd.addMember(mid, character, option)
if err != nil {
logger.Println("JoinParty failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// 기존 유저에게 새 유저 알림
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"},
})
gocommon.MakeEncoder(w, r).Encode(gd.strid())
}
func (gp *groupParty) ForceClearPartyMember(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid primitive.ObjectID
Mid primitive.ObjectID
}
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
logger.Println("ConditionalClearPartyMember failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gd := partyDoc{
id: doc.Gid,
rh: gp.rh,
}
gd.removeMember(doc.Mid)
}
func (gp *groupParty) ConditionalClearPartyMember(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid string
Mid string
}
// accid가 접속해 있지 않으면 파티에서 나간 걸로 간주하고
// accid가 접속해 있으면 아무것도 하지 않는다.
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
logger.Println("ConditionalClearPartyMember failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
pids, err := gp.rh.JSONGetString(doc.Mid, "$.party.id")
if err != nil {
logger.Println("ConditionalClearPartyMember failed. gp.rh.JSONGetString returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
removeMember := func() {
gid, _ := primitive.ObjectIDFromHex(doc.Gid)
mid, _ := primitive.ObjectIDFromHex(doc.Mid)
gd := partyDoc{
id: gid,
rh: gp.rh,
}
if gd.getIncharge() == gd.tid(mid) {
// 방장이 나갔다.
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + doc.Gid,
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gid.Hex()},
})
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
} else {
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + doc.Gid,
Body: bson.M{
gd.tid(mid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
gd.removeMember(mid)
}
}
if len(pids) == 0 {
// 없다.
// doc.Gid에서 제거
removeMember()
} else if pids[0] != doc.Gid {
// 다른 파티? 기존 파티에서 제거
removeMember()
}
}
// 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"},
})
// 그룹에게 알림
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gid.Hex(),
Body: bson.M{
gd.tid(targetid): bson.M{
"nickname": doc.Invitee["nickname"],
"_invite": true,
},
},
Tag: []string{"MemberDocFull"},
})
}
func (gp *groupParty) UpdatePartyMemberDocument(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid string
Tid string
Fragment bson.M
}
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
logger.Println("UpdatePartyMemberDocument failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gidobj, _ := primitive.ObjectIDFromHex(doc.Gid)
mid := midFromTid(gidobj, doc.Tid)
gp.updateMemberDocument(gidobj, mid, doc.Fragment)
}
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.Println("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.Println("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, "XX")
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._body.", 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.Println("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.Println("QueryPartyMembers failed. group.QueryPartyMembers returns err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if err := gocommon.MakeEncoder(w, r).Encode(members); err != nil {
logger.Println("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(msg string, 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)
if msg != "pending" {
gd := &partyDoc{
rh: gp.rh,
id: gid,
}
if gd.getIncharge() == gd.tid(callby.Accid) {
// 방장이 나감. 방 폭파
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gidstr},
})
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
} else {
// gid에는 제거 메시지 보냄
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
Body: bson.M{
makeTid(gid, callby.Accid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
gd.removeMember(callby.Accid)
}
}
}
}
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
}
if gd.getIncharge() == gd.tid(mid) {
// 방장이 나감. 방 폭파
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gidstr},
})
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
} else {
// 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"},
})
gd.removeMember(mid)
}
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)
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gd.strid(),
Body: bson.M{
gd.tid(mid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
}

View File

@ -1,383 +0,0 @@
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
// }
}

View File

@ -1,134 +0,0 @@
package core
import (
"context"
"io"
"net/http"
"github.com/go-redis/redis/v8"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/session"
"repositories.action2quare.com/ayo/gocommon/wshandler"
)
var devflag = flagx.Bool("dev", false, "")
type SocialConfig struct {
session.SessionConfig `json:",inline"`
MaingateApiToken string `json:"maingate_api_token"`
RedisURL string `json:"social_redis_url"`
MongoURL string `json:"social_storage_url"`
}
var config SocialConfig
type Social struct {
wsh *wshandler.WebsocketHandler
mongoClient gocommon.MongoClient
redison *gocommon.RedisonHandler
httpApiBorker gocommon.HttpApiBroker
}
// New :
func New(ctx context.Context, wsh *wshandler.WebsocketHandler, inconfig *SocialConfig) (*Social, error) {
if inconfig == nil {
var loaded SocialConfig
if err := gocommon.LoadConfig(&loaded); err != nil {
return nil, err
}
inconfig = &loaded
}
config = *inconfig
opt, err := redis.ParseURL(config.RedisURL)
if err != nil {
return nil, logger.ErrorWithCallStack(err)
}
mc, err := gocommon.NewMongoClient(ctx, config.MongoURL)
if err != nil {
return nil, logger.ErrorWithCallStack(err)
}
so := &Social{
wsh: wsh,
redison: gocommon.NewRedisonHandler(ctx, redis.NewClient(opt)),
mongoClient: mc,
}
if err := so.prepare(ctx); err != nil {
logger.Println("social prepare() failed :", err)
return nil, logger.ErrorWithCallStack(err)
}
return so, nil
}
func (so *Social) Cleanup() {
so.mongoClient.Close()
}
func (so *Social) prepare(ctx context.Context) error {
redisClient, err := gocommon.NewRedisClient(config.RedisURL)
if err != nil {
return logger.ErrorWithCallStack(err)
}
so.redison = gocommon.NewRedisonHandler(redisClient.Context(), redisClient)
friends, err := makeFriends(ctx, so)
if err != nil {
return logger.ErrorWithCallStack(err)
}
so.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(friends, "social"))
so.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(friends, "social"))
invitation, err := makeInvitation(ctx, so, friends)
if err != nil {
return logger.ErrorWithCallStack(err)
}
so.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(invitation, "social"))
so.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(invitation, "social"))
return nil
}
func (so *Social) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
so.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(so, "social"))
pattern := gocommon.MakeHttpHandlerPattern(prefix, "api")
serveMux.HandleFunc(pattern, so.api)
return nil
}
func (so *Social) api(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
io.Copy(io.Discard, r.Body)
r.Body.Close()
}()
// 서버에서 오는 요청만 처리
apitoken := r.Header.Get("MG-X-API-TOKEN")
if apitoken != config.MaingateApiToken {
// 서버가 보내는 쿼리만 허용
logger.Println("MG-X-API-TOKEN is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
funcname := r.URL.Query().Get("call")
if len(funcname) == 0 {
logger.Println("query param 'call' is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
so.httpApiBorker.Call(funcname, w, r)
}

View File

@ -1,65 +0,0 @@
// warroom project main.go
package core
import (
"context"
"encoding/binary"
"fmt"
"testing"
"time"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestPubSub(t *testing.T) {
opt0, _ := redis.ParseURL("redis://192.168.8.94:6380/0")
opt1, _ := redis.ParseURL("redis://192.168.8.94:6380/1")
rc0 := redis.NewClient(opt0)
rc1 := redis.NewClient(opt1)
go func() {
time.Sleep(time.Second)
rc1.Publish(context.Background(), "__testchan", "real???")
fmt.Println("published")
}()
pubsub := rc0.Subscribe(context.Background(), "__testchan")
msg, err := pubsub.ReceiveMessage(context.Background())
fmt.Println(msg.Payload, err)
}
func makeHash(chanName string, index uint32) string {
for len(chanName) < 12 {
chanName += chanName
}
left := chanName[:6]
right := chanName[len(chanName)-6:]
base := []byte(left + right)
for i := 0; i < 12; i++ {
base[i] += base[12-i-1]
}
bs := make([]byte, 4)
binary.LittleEndian.PutUint32(bs, index)
for i, c := range bs {
base[i] ^= c
}
var gid primitive.ObjectID
copy(gid[:], base)
return gid.Hex()
}
func TestNameHash(t *testing.T) {
for i := 0; i < 10; i++ {
makeHash("Urud", uint32(i))
fmt.Printf("Urud:%d - %s\n", i, makeHash("Urud", uint32(i)))
makeHash("Sheldon", uint32(i))
fmt.Printf("Sheldon:%d - %s\n", i, makeHash("Sheldon", uint32(i)))
}
}
func TestReJSON(t *testing.T) {
}

202
core/tavern.go Normal file
View File

@ -0,0 +1,202 @@
package core
import (
"context"
"errors"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/metric"
"repositories.action2quare.com/ayo/gocommon/session"
"repositories.action2quare.com/ayo/gocommon/wshandler"
"go.mongodb.org/mongo-driver/bson"
)
var devflag = flagx.Bool("dev", false, "")
type TavernConfig struct {
session.SessionConfig `json:",inline"`
Group map[string]configDocument `json:"tavern_group_types"`
MaingateApiToken string `json:"maingate_api_token"`
RedisURL string `json:"tavern_redis_url"`
EosClientId string `json:"eos_client_id"`
EosClientSecret string `json:"eos_client_secret"`
EosDeploymentId string `json:"eos_deployment_id"`
macAddr string
}
var config TavernConfig
type Tavern struct {
wsh *wshandler.WebsocketHandler
mongoClient gocommon.MongoClient
redison *gocommon.RedisonHandler
httpApiBorker gocommon.HttpApiBroker
}
func getMacAddr() (string, error) {
ifas, err := net.Interfaces()
if err != nil {
return "", err
}
for _, ifa := range ifas {
a := ifa.HardwareAddr.String()
if a != "" {
a = strings.ReplaceAll(a, ":", "")
return a, nil
}
}
return "", errors.New("no net interface")
}
// New :
func New(context context.Context, wsh *wshandler.WebsocketHandler) (*Tavern, error) {
if err := gocommon.LoadConfig(&config); err != nil {
return nil, logger.ErrorWithCallStack(err)
}
macaddr, err := getMacAddr()
if err != nil {
return nil, logger.ErrorWithCallStack(err)
}
config.macAddr = macaddr
tv := &Tavern{
wsh: wsh,
}
if err = tv.prepare(context); err != nil {
logger.Println("tavern prepare() failed :", err)
return nil, logger.ErrorWithCallStack(err)
}
return tv, nil
}
var ccu = metric.MetricWriterNil
func (tv *Tavern) Cleanup() {
ccu.Set(0)
tv.mongoClient.Close()
}
func (tv *Tavern) prepare(ctx context.Context) error {
redisClient, err := gocommon.NewRedisClient(config.RedisURL)
if err != nil {
return logger.ErrorWithCallStack(err)
}
tv.redison = gocommon.NewRedisonHandler(redisClient.Context(), redisClient)
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(tv, "tv"))
if cfg, ok := config.Group["chat"]; ok {
chat := new(groupChat)
if err := chat.Initialize(tv, cfg); err != nil {
return logger.ErrorWithCallStack(err)
}
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(chat, "chat"))
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(chat, "chat"))
}
if cfg, ok := config.Group["party"]; ok {
party := new(groupParty)
if err := party.Initialize(tv, cfg); err != nil {
return logger.ErrorWithCallStack(err)
}
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(party, "party"))
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(party, "party"))
}
instant := new(groupInstant)
if err := instant.Initialize(tv); err != nil {
return logger.ErrorWithCallStack(err)
}
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(instant, "instant"))
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(instant, "instant"))
ccu = metric.NewMetric(metric.MetricGuage, "concurrent_user", "concurrent user count", map[string]string{"game": "lobby"})
return nil
}
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
// tv.wsh.RegisterReceiver(tv)
pattern := gocommon.MakeHttpHandlerPattern(prefix, "api")
serveMux.HandleFunc(pattern, tv.api)
return nil
}
func (tv *Tavern) EnterChannel(ctx wshandler.ApiCallContext) {
tv.wsh.EnterRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
}
func (tv *Tavern) LeaveChannel(ctx wshandler.ApiCallContext) {
tv.wsh.LeaveRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
}
func (tv *Tavern) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
ccu.Add(1)
tv.redison.Del(tv.redison.Context(), callby.Accid.Hex())
_, err := tv.redison.JSONSet(callby.Accid.Hex(), "$", bson.M{"_ts": time.Now().UTC().Unix()})
if err != nil {
logger.Println("OnClientMessageReceived HSet error :", err)
}
}
func (tv *Tavern) ClientDisconnected(msg string, callby *wshandler.Sender) {
ccu.Add(-1)
tv.redison.Del(tv.redison.Context(), callby.Accid.Hex()).Result()
}
func (tv *Tavern) OnRoomCreated(name string) {
cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, 1).Result()
if err != nil && !errors.Is(err, redis.Nil) {
logger.Println("OnRoomCreated JSONSet failed :", err)
return
}
if cnt == 1 {
tv.redison.JSONSet(name, "$", map[string]any{}, gocommon.RedisonSetOptionNX)
}
}
func (tv *Tavern) OnRoomDestroyed(name string) {
cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, -1).Result()
if err != nil {
logger.Println("OnRoomDestroyed JSONNumIncrBy failed :", err)
} else if cnt == 0 {
tv.redison.Del(tv.redison.Context(), "_ref_"+name)
tv.redison.JSONDel(name, "$")
}
}
func (tv *Tavern) api(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
io.Copy(io.Discard, r.Body)
r.Body.Close()
}()
// 서버에서 오는 요청만 처리
apitoken := r.Header.Get("MG-X-API-TOKEN")
if apitoken != config.MaingateApiToken {
// 서버가 보내는 쿼리만 허용
logger.Println("MG-X-API-TOKEN is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
tv.httpApiBorker.Call(w, r)
}

113
core/tavern_test.go Normal file
View File

@ -0,0 +1,113 @@
// warroom project main.go
package core
import (
"context"
"encoding/binary"
"fmt"
"testing"
"time"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson/primitive"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
)
func TestPubSub(t *testing.T) {
opt0, _ := redis.ParseURL("redis://192.168.8.94:6380/0")
opt1, _ := redis.ParseURL("redis://192.168.8.94:6380/1")
rc0 := redis.NewClient(opt0)
rc1 := redis.NewClient(opt1)
go func() {
time.Sleep(time.Second)
rc1.Publish(context.Background(), "__testchan", "real???")
fmt.Println("published")
}()
pubsub := rc0.Subscribe(context.Background(), "__testchan")
msg, err := pubsub.ReceiveMessage(context.Background())
fmt.Println(msg.Payload, err)
}
func makeHash(chanName string, index uint32) string {
for len(chanName) < 12 {
chanName += chanName
}
left := chanName[:6]
right := chanName[len(chanName)-6:]
base := []byte(left + right)
for i := 0; i < 12; i++ {
base[i] += base[12-i-1]
}
bs := make([]byte, 4)
binary.LittleEndian.PutUint32(bs, index)
for i, c := range bs {
base[i] ^= c
}
var gid primitive.ObjectID
copy(gid[:], base)
return gid.Hex()
}
func TestNameHash(t *testing.T) {
for i := 0; i < 10; i++ {
makeHash("Urud", uint32(i))
fmt.Printf("Urud:%d - %s\n", i, makeHash("Urud", uint32(i)))
makeHash("Sheldon", uint32(i))
fmt.Printf("Sheldon:%d - %s\n", i, makeHash("Sheldon", uint32(i)))
}
}
func TestReJSON(t *testing.T) {
rc := redis.NewClient(&redis.Options{Addr: "192.168.8.94:6380"})
rh := gocommon.NewRedisonHandler(context.Background(), rc)
success, err := rc.HSetNX(context.Background(), "setnxtest", "cap", 100).Result()
fmt.Println(success, err)
success, err = rc.HSetNX(context.Background(), "setnxtest", "cap", 100).Result()
fmt.Println(success, err)
testDoc := map[string]any{
"members": map[string]any{
"mid2": map[string]any{
"key": "val",
"exp": 20202020,
},
"mid1": map[string]any{
"key": "val",
"exp": 10101010,
},
},
}
gd := partyDoc{
id: primitive.NewObjectID(),
}
midin := primitive.NewObjectID()
tid := gd.tid(midin)
midout := gd.mid(tid)
logger.Println(midin, tid, midout)
logger.Println(rh.JSONSet("jsontest", "$", testDoc))
logger.Println(rh.JSONGet("jsontest", "$"))
logger.Println(rh.JSONResp("jsontest", "$.members"))
logger.Println(rh.JSONGetString("jsontest", "$.members..key"))
logger.Println(rh.JSONGetInt64("jsontest", "$.members..exp"))
logger.Println(rh.JSONObjKeys("jsontest", "$.members"))
err = rh.JSONMSet("jsontest", map[string]any{
"$.members.mid1.key": "newval",
"$.members.mid2.key": "newval",
})
logger.Println(err)
logger.Println(rh.JSONGet("jsontest", "$"))
logger.Println(rh.JSONMDel("jsontest", []string{"$.members.mid1", "$.members.mid2"}))
logger.Println(rh.JSONGet("jsontest", "$"))
logger.Println(rh.JSONObjLen("jsontest", "$.members"))
}

19
go.mod
View File

@ -1,29 +1,30 @@
module repositories.action2quare.com/ayo/social module repositories.action2quare.com/ayo/tavern
go 1.20 go 1.22.1
require ( require (
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/gorilla/websocket v1.5.0
go.mongodb.org/mongo-driver v1.11.7 go.mongodb.org/mongo-driver v1.11.7
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946 repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124812-38a3da271a8e
) )
require ( require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.4 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/klauspost/compress v1.16.6 // indirect github.com/klauspost/compress v1.16.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
golang.org/x/crypto v0.10.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/text v0.10.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
) )

47
go.sum
View File

@ -1,41 +1,52 @@
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -59,20 +70,21 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -80,7 +92,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -88,21 +101,23 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946 h1:YSvgTNuHeKis37+FfOvzVLYCaXQ0oF+CWBTy4bRqq3g= repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124037-fb3f0385067a h1:NkFjJVZTNO6HsivUEikISfqF4O5zHk0VRdKOEOVTOG4=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY= repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124037-fb3f0385067a/go.mod h1:q64I6gqlD61qwi9FfuPkwqy6Z6uzSHdcEjoHAJC27gQ=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124812-38a3da271a8e h1:jxfkCjnDBPg4zw5oqecEdxjclpiBysBtNvI2ENqeG4E=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124812-38a3da271a8e/go.mod h1:q64I6gqlD61qwi9FfuPkwqy6Z6uzSHdcEjoHAJC27gQ=

19
main.go
View File

@ -1,3 +1,4 @@
// warroom project main.go
package main package main
import ( import (
@ -6,7 +7,7 @@ import (
"repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/wshandler" "repositories.action2quare.com/ayo/gocommon/wshandler"
"repositories.action2quare.com/ayo/social/core" "repositories.action2quare.com/ayo/tavern/core"
"repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
@ -19,7 +20,7 @@ func main() {
flagx.Parse() flagx.Parse()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
var config core.SocialConfig var config core.TavernConfig
if err := gocommon.LoadConfig(&config); err != nil { if err := gocommon.LoadConfig(&config); err != nil {
panic(err) panic(err)
} }
@ -34,18 +35,22 @@ func main() {
panic(err) panic(err)
} }
if so, err := core.New(ctx, wsh, &config); err != nil { if tv, err := core.New(ctx, wsh); err != nil {
panic(err) panic(err)
} else { } else {
serveMux := http.NewServeMux() serveMux := http.NewServeMux()
wsh.RegisterHandlers(serveMux, *prefix) wsh.RegisterHandlers(serveMux, *prefix)
so.RegisterHandlers(ctx, serveMux, *prefix) tv.RegisterHandlers(ctx, serveMux, *prefix)
server := gocommon.NewHTTPServer(serveMux) server := gocommon.NewHTTPServer(serveMux)
logger.Println("social is started") logger.Println("tavern is started")
wsh.Start(ctx) wsh.Start(ctx)
server.Start()
if err := server.Start(); err != nil {
logger.Println(err)
}
cancel() cancel()
so.Cleanup()
wsh.Cleanup() wsh.Cleanup()
tv.Cleanup()
} }
} }