Compare commits
54 Commits
toredis
...
5fc64d7471
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fc64d7471 | |||
| 21ec68d27e | |||
| 99c10986d6 | |||
| dffe1cfee5 | |||
| bd2408c66f | |||
| b9339166b9 | |||
| 8dea97d956 | |||
| 708f9d6caf | |||
| dd37659089 | |||
| bdcb86c1c7 | |||
| 6e709c9454 | |||
| b9d2451902 | |||
| e8f74bcd19 | |||
| b44a6b1fd8 | |||
| d7d7df4a28 | |||
| 2cec9b90fe | |||
| 6706d7d02e | |||
| 675bcbad9e | |||
| e18a5eafe0 | |||
| ad185b8d01 | |||
| d3614392c2 | |||
| 19eacf0d4c | |||
| ea28d93f7d | |||
| 374992d55f | |||
| a9b0cf2493 | |||
| 3dde7ccaf5 | |||
| 1d14fb659d | |||
| ce50657734 | |||
| 4a51f7d433 | |||
| 6410056c87 | |||
| f7173a4f49 | |||
| 9b0c4a121a | |||
| e0504f688a | |||
| fd1502e52a | |||
| cb5cd280b9 | |||
| a4923fa0a1 | |||
| 2534aa2a36 | |||
| a404764abf | |||
| 9a734f9f4d | |||
| 884fb0080f | |||
| a08353a920 | |||
| 5e953d6131 | |||
| 3d2ed40b1e | |||
| 9de686e828 | |||
| 12ddd2cbfb | |||
| 2b0e60a06a | |||
| 922f55740b | |||
| b6262515e0 | |||
| 90d0fd319d | |||
| 310397dd2b | |||
| bb6a741d63 | |||
| 4f1c79d3b7 | |||
| 07cb4848fe | |||
| 8dded8b907 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.vscode/
|
.vscode/
|
||||||
__debug_bin.exe
|
__debug_bin.exe
|
||||||
*.log
|
*.log
|
||||||
|
config.json
|
||||||
|
|||||||
50
config.json
50
config.json
@ -1 +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)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,68 +5,42 @@
|
|||||||
"redis": {
|
"redis": {
|
||||||
"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",
|
||||||
"ranking": "redis://192.168.8.94:6380/2",
|
"tx": "redis://192.168.8.94:6380/2",
|
||||||
"wshandler": "redis://192.168.8.94:6380/3",
|
"tavern": "redis://192.168.8.94:6380/3",
|
||||||
"tavern": "redis://192.168.8.94:6380/4"
|
"wshandler": "redis://192.168.8.94:6380/4"
|
||||||
}
|
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"mongo": "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
|
||||||
"redis": {
|
|
||||||
"cache": "redis://192.168.8.94:6380/4",
|
|
||||||
"session": "redis://192.168.8.94:6380/5",
|
|
||||||
"ranking": "redis://192.168.8.94:6380/6",
|
|
||||||
"wshandler": "redis://192.168.8.94:6380/7",
|
|
||||||
"tavern": "redis://192.168.8.94:6380/8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"maingate_mongodb_url": "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
"session_storage": "redis://192.168.8.94:6380/5",
|
||||||
"maingate_service_url": "http://localhost/maingate",
|
"session_ttl": 3600,
|
||||||
|
|
||||||
"maingate_api_token": "63d08aa34f0162622c11284b",
|
"maingate_api_token": "63d08aa34f0162622c11284b",
|
||||||
|
|
||||||
|
"tavern_redis_url": "redis://192.168.8.94:6380/7",
|
||||||
"tavern_service_url": "http://localhost/tavern",
|
"tavern_service_url": "http://localhost/tavern",
|
||||||
"tavern_group_types": {
|
"tavern_group_types": {
|
||||||
"subjugate": {
|
"party": {
|
||||||
"text_search_field": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"unique_index": [
|
|
||||||
"name,_id",
|
|
||||||
"_id,members",
|
|
||||||
"name,hidden"
|
|
||||||
],
|
|
||||||
"search_index": [
|
|
||||||
"rules"
|
|
||||||
],
|
|
||||||
"member_index": [
|
|
||||||
"_gid,candidate,luts",
|
|
||||||
"_gid,luts",
|
|
||||||
"_gid,expiring"
|
|
||||||
],
|
|
||||||
"invite_ttl": 30,
|
|
||||||
"candidate_ttl": 3600,
|
|
||||||
"invitee_exlusive": true,
|
|
||||||
"invitee_is_member": true,
|
|
||||||
"max_member": 4
|
|
||||||
},
|
|
||||||
"lobby": {
|
|
||||||
"max_member": 3,
|
"max_member": 3,
|
||||||
"invitee_exlusive": true,
|
|
||||||
"invitee_is_member": true,
|
|
||||||
"transient": true,
|
|
||||||
"invite_ttl": 30
|
"invite_ttl": 30
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"chat" : {
|
||||||
"services": {
|
"default_capacity" : 1000,
|
||||||
"kingdom": {
|
"channels" : {
|
||||||
"개발중": {
|
"bazzar-1" : {
|
||||||
"url": "http://localhost/warehouse/dev",
|
"name" : "FText(bazzar-1)"
|
||||||
"development": true
|
|
||||||
},
|
},
|
||||||
"개인서버": {
|
"bazzar-2" : {
|
||||||
"url": "http://localhost/warehouse/private",
|
"name" : "FText(bazzar-2)"
|
||||||
"development": false
|
},
|
||||||
|
"bazzar-3" : {
|
||||||
|
"name" : "FText(bazzar-3)"
|
||||||
|
},
|
||||||
|
"bazzar-4" : {
|
||||||
|
"name" : "FText(bazzar-4)"
|
||||||
|
},
|
||||||
|
"bazzar-5" : {
|
||||||
|
"name" : "FText(bazzar-5)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
526
core/apiimpl.go
526
core/apiimpl.go
@ -1,526 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
common "repositories.action2quare.com/ayo/gocommon"
|
|
||||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateGroup : 그룹 생성
|
|
||||||
// - 그룹 : 멤버와 권한을 관리할 수 있다. 그룹 타입에 따라 디비에 저장되거나 메모리에만 존재한다.
|
|
||||||
// - 생성 요청이 오면 파티를 만든다. 파티을 만들 수 있는지 여부는 서비스에서 결정할 것이고, 이 요청을 호출했다는 것은 서비스가 결정한 그룹 생성 조건을 다 통과했다는 의미이다.
|
|
||||||
// - parameter :
|
|
||||||
// - type : 그룹 종류. 그룹 종류에 따라 인덱스와 쿼리 가능 field가 다르다.
|
|
||||||
func (sub *subTavern) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
grouptype := sub.groups[typename]
|
|
||||||
if grouptype == nil {
|
|
||||||
logger.Println("CreateGroup failed. group type is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
doc := bson.M{}
|
|
||||||
if err := readBsonDoc(r.Body, &doc); err != nil {
|
|
||||||
logger.Error("CreateGroup failed. readBsonDoc returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inserted, err := grouptype.Create(r.Form, doc)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(inserted[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinGroup : 그룹에 참가
|
|
||||||
// - type : 그룹 타입
|
|
||||||
// - 그룹 타입에 맞는 키(주로 _id)
|
|
||||||
// - member_id : 참가 멤버의 아이디
|
|
||||||
// - body : 멤버의 속성 bson document
|
|
||||||
func (sub *subTavern) JoinGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("JoinGroup failed. group type is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
doc := bson.M{}
|
|
||||||
if err := readBsonDoc(r.Body, &doc); err != nil {
|
|
||||||
logger.Error("JoinGroup failed. readBsonDoc returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("JoinGroup failed. gid is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
midobj, midok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
tidobj, tidok := common.ReadObjectIDFormValue(r.Form, "tid")
|
|
||||||
if !midok && !tidok {
|
|
||||||
// 둘다 없네?
|
|
||||||
logger.Println("JoinGroup failed. tid or mid should be exist")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if candidate, ok := common.ReadBoolFormValue(r.Form, "candidate"); ok && candidate {
|
|
||||||
err = group.Candidate(gidobj, midobj, doc)
|
|
||||||
} else {
|
|
||||||
err = group.Join(gidobj, midobj, doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
writeBsonDoc(w, map[string]string{
|
|
||||||
"gid": gidobj.Hex(),
|
|
||||||
"tid": tidobj.Hex(),
|
|
||||||
})
|
|
||||||
} else if err == errGroupNotExist {
|
|
||||||
w.Write([]byte("{}"))
|
|
||||||
} else if err != nil {
|
|
||||||
logger.Error("JoinGroup failed :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invite : 초대
|
|
||||||
// - type : 초대 타입 (required)
|
|
||||||
// - from : 초대하는 자 (required)
|
|
||||||
// - to : 초대받는 자 (required)
|
|
||||||
// - timeout : 초대 유지시간(optional. 없으면 config 기본 값)
|
|
||||||
// - (body) : 검색시 노출되는 document
|
|
||||||
func (sub *subTavern) Invite(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("Invite failed. group type is missing :", r)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("Invite failed. gid is missing :", r)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("Invite failed. mid is missing :", r)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var reqdoc struct {
|
|
||||||
Inviter bson.M `bson:"inviter"`
|
|
||||||
Invitee bson.M `bson:"invitee"`
|
|
||||||
}
|
|
||||||
if err := readBsonDoc(r.Body, &reqdoc); err != nil {
|
|
||||||
logger.Println("Invite failed. readBsonDoc returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := group.Invite(gid, mid, reqdoc.Inviter, reqdoc.Invitee)
|
|
||||||
if err != nil {
|
|
||||||
logger.Println("Invite failed. group.Invite returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) CancelInvitation(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("CancelInvitation failed. group type is missing :", r)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tid, ok := common.ReadObjectIDFormValue(r.Form, "tid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("CancelInvitation failed. form value 'tid' is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("CancelInvitation failed. form value 'gid' is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.CancelInvitation(gid, tid); err != nil {
|
|
||||||
logger.Println("CancelInvitation failed. group.CancelInvitation returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("CancelInvitation failed. group type is missing :", r)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, _ := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
mid, _ := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
|
|
||||||
var member bson.M
|
|
||||||
if err := readBsonDoc(r.Body, &member); err != nil {
|
|
||||||
logger.Error("AcceptInvitation failed. readBsonDoc returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := group.AcceptInvitation(gid, mid, member)
|
|
||||||
if err != nil {
|
|
||||||
logger.Println("AcceptInvitation failed. group.AcceptInvitation returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) DenyInvitation(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("DenyInvitation failed. group type is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, _ := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
mid, _ := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
tid, ok := common.ReadObjectIDFormValue(r.Form, "tid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("DenyInvitation failed. tid is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := group.DenyInvitation(gid, mid, tid)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("DenyInvitation failed. group.DenyInvitation returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) QueryInvitations(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("QueryInvitations failed. group type is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("QueryInvitations failed. mid is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var after primitive.Timestamp
|
|
||||||
if v, ok := common.ReadStringFormValue(r.Form, "after"); ok && v != "0.0" {
|
|
||||||
after = common.DotStringToTimestamp(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := group.QueryInvitations(mid, after)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Println("QueryInvitations failed. group.QueryInvitations returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeBsonArr(w, result); err != nil {
|
|
||||||
logger.Println("QueryInvitations failed. Encode returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) SearchGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("SearchGroup failed. group type is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
projection, _ := common.ReadStringFormValue(r.Form, "projection")
|
|
||||||
|
|
||||||
var filter bson.M
|
|
||||||
if err := readBsonDoc(r.Body, &filter); err != nil {
|
|
||||||
logger.Error("SearchGroup failed. readBsonDoc returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := group.FindAll(filter, projection, primitive.Timestamp{})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("SearchGroup failed. FindAll err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeBsonArr(w, result); err != nil {
|
|
||||||
logger.Error("bson marshal failed :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) QueryOnlineState(w http.ResponseWriter, r *http.Request) {
|
|
||||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("IsOnline failed. mid is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
state := sub.wsh.GetState(sub.region, mid)
|
|
||||||
w.Write([]byte(state))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) IsOnline(w http.ResponseWriter, r *http.Request) {
|
|
||||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("IsOnline failed. mid is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if state := sub.wsh.GetState(sub.region, mid); len(state) > 0 {
|
|
||||||
w.Write([]byte("true"))
|
|
||||||
} else {
|
|
||||||
w.Write([]byte("false"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryGroup : 그룹조회
|
|
||||||
// - type : 그룹 타입
|
|
||||||
// - 그룹 타입에 맞는 키(주로 _id)
|
|
||||||
// - projection : select할 필드. ,로 구분
|
|
||||||
func (sub *subTavern) QueryGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("QueryGroup failed. group type is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "_id")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("QueryGroup failed. _id is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
projection, _ := common.ReadStringFormValue(r.Form, "projection")
|
|
||||||
|
|
||||||
after, _ := common.ReadStringFormValue(r.Form, "after")
|
|
||||||
if after != "0.0" {
|
|
||||||
projection += ",+luts"
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := group.FindOne(gid, projection)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(after) > 0 {
|
|
||||||
if luts, ok := result["luts"].(primitive.Timestamp); ok {
|
|
||||||
afterts := common.DotStringToTimestamp(after)
|
|
||||||
if primitive.CompareTimestamp(luts, afterts) < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeBsonDoc(w, result); err != nil {
|
|
||||||
logger.Error("bson marshal failed :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeaveGroup : 그룹에서 나감 or 내보냄
|
|
||||||
// - type : 그룹 타입
|
|
||||||
// - 그룹 타입에 맞는 키(주로 _id)
|
|
||||||
// - member_id : 나갈 멤버의 아이디
|
|
||||||
func (sub *subTavern) LeaveGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("LeaveGroup failed. group type is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("LeaveGroup failed. gid is missing :", r.Form)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mid, midok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
|
|
||||||
if !midok {
|
|
||||||
logger.Println("LeaveGroup failed. mid is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.Leave(gid, mid); err != nil {
|
|
||||||
// 둘 중 하나는 있어야지
|
|
||||||
logger.Println("LeaveGroup failed. group.Leave returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) UpdateMemberDocument(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("DismissGroup failed. type is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
midobj, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("UpdateMemberDocument failed. member_id is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("UpdateMemberDocument failed. _id is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatedoc bson.M
|
|
||||||
if err := readBsonDoc(r.Body, &updatedoc); err != nil {
|
|
||||||
logger.Error("UpdateMemberDocument failed. body decoding error :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.UpdateMemberDocument(gidobj, midobj, updatedoc); err != nil {
|
|
||||||
logger.Println("UpdateMemberDocument failed :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) DismissGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("DismissGroup failed. type is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("DismissGroup failed. gid is missing :")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.Dismiss(gid); err != nil {
|
|
||||||
logger.Error("DismissGroup failed. group.Dismiss returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) UpdateGroupDocument(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("UpdateGroupDocument failed. type is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("UpdateGroupDocument failed. gid is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var frag bson.M
|
|
||||||
if err := readBsonDoc(r.Body, &frag); err != nil {
|
|
||||||
logger.Error("UpdateGroupDocument failed. readBsonDoc err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.UpdateGroupDocument(gid, frag); err != nil {
|
|
||||||
logger.Error("UpdateGroupDocument failed. group.UpdateGroupDocument returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sub *subTavern) QueryGroupMembers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
|
||||||
group := sub.groups[typename]
|
|
||||||
if group == nil {
|
|
||||||
logger.Println("QueryGroupMembers failed. type is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
|
||||||
if !ok {
|
|
||||||
logger.Println("QueryGroupMembers failed. gid is missing")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
members, err := group.QueryGroupMembers(gid)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("QueryGroupMembers failed. group.QueryGroupMembers returns err :", err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeBsonDoc(w, members); err != nil {
|
|
||||||
logger.Error("QueryGroupMembers failed. writeBsonDoc return err :", err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@ -1,42 +1,3 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
type configDocument map[string]any
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
|
||||||
|
|
||||||
type groupConfig struct {
|
|
||||||
UniqueIndex []string `json:"unique_index"`
|
|
||||||
SearchIndex []string `json:"search_index"`
|
|
||||||
MemberIndex []string `json:"member_index"`
|
|
||||||
TextSearchFields []string `json:"text_search_field"`
|
|
||||||
InviteExpire int32 `json:"invite_ttl"` // 그룹이 개인에게 보낸 초대장 만료 기한
|
|
||||||
CandidateExpire int32 `json:"candidate_ttl"` // 개인이 그룹에게 보낸 신청서 만료 기한
|
|
||||||
InviteeExlusive bool `json:"invitee_exlusive"`
|
|
||||||
InviteeIsMember bool `json:"invitee_is_member"`
|
|
||||||
MaxMember int `json:"max_member"`
|
|
||||||
Transient bool `json:"transient"`
|
|
||||||
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type group interface {
|
|
||||||
Create(form url.Values, doc bson.M) (primitive.ObjectID, error)
|
|
||||||
Candidate(groupID primitive.ObjectID, memberID primitive.ObjectID, doc bson.M) error
|
|
||||||
Join(groupID primitive.ObjectID, memberID primitive.ObjectID, doc bson.M) error
|
|
||||||
Invite(groupID primitive.ObjectID, memberID primitive.ObjectID, inviterDoc bson.M, inviteeDoc bson.M) (string, error)
|
|
||||||
CancelInvitation(groupID primitive.ObjectID, ticketID primitive.ObjectID) error
|
|
||||||
AcceptInvitation(groupID primitive.ObjectID, mid primitive.ObjectID, member bson.M) error
|
|
||||||
DenyInvitation(groupID primitive.ObjectID, mid primitive.ObjectID, ticketID primitive.ObjectID) error
|
|
||||||
QueryInvitations(memberID primitive.ObjectID, after primitive.Timestamp) ([]bson.M, error)
|
|
||||||
Exist(groupID primitive.ObjectID, filter bson.M) (bool, error)
|
|
||||||
FindAll(filter bson.M, projection string, after primitive.Timestamp) ([]bson.M, error)
|
|
||||||
FindOne(groupID primitive.ObjectID, projection string) (bson.M, error)
|
|
||||||
Leave(groupID primitive.ObjectID, memberID primitive.ObjectID) error
|
|
||||||
UpdateMemberDocument(groupID primitive.ObjectID, memberID primitive.ObjectID, doc bson.M) error
|
|
||||||
Dismiss(groupID primitive.ObjectID) error
|
|
||||||
UpdateGroupDocument(groupID primitive.ObjectID, doc bson.M) error
|
|
||||||
QueryGroupMembers(groupID primitive.ObjectID) (bson.M, error)
|
|
||||||
}
|
|
||||||
|
|||||||
310
core/group_chat.go
Normal file
310
core/group_chat.go
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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 (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) {
|
||||||
|
gc.rh.JSONSet(callby.Accid.Hex(), "$.channel", map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *groupChat) ClientDisconnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||||
|
docs, _ := gc.rh.JSONGetDocuments(callby.Accid.Hex(), "$.channel")
|
||||||
|
|
||||||
|
if len(docs) > 0 {
|
||||||
|
for k, v := range docs[0] {
|
||||||
|
typename := k
|
||||||
|
chanid := v.(string)
|
||||||
|
gc.leaveRoom(chanid, callby.Accid)
|
||||||
|
if k == "public" {
|
||||||
|
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
||||||
|
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
||||||
|
cfg.inoutChan <- "-" + callby.Alias
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + chanid,
|
||||||
|
Body: map[string]any{"sender": callby.Alias},
|
||||||
|
Tag: []string{typename + ".LeavePrivateChannel"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *groupChat) EnterPublicChannel(ctx wshandler.ApiCallContext) {
|
||||||
|
chanid := ctx.Arguments[0].(string)
|
||||||
|
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
||||||
|
size, err := gc.rh.JSONObjLen(chanid, "$.members")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("JSONGetInt64 failed :", chanid, err)
|
||||||
|
} else if len(size) == 0 || size[0] < cfg.Capacity {
|
||||||
|
// 입장
|
||||||
|
_, err := gc.rh.JSONSet(chanid, "$.members."+ctx.CallBy.Alias, 1)
|
||||||
|
if err == nil {
|
||||||
|
gc.enterRoom(chanid, ctx.CallBy.Accid)
|
||||||
|
gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel.public", chanid)
|
||||||
|
cfg.inoutChan <- "+" + ctx.CallBy.Alias
|
||||||
|
|
||||||
|
members, err := gc.rh.JSONGetDocuments(chanid, "$.members")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("JSONGetDocuments failed :", chanid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toarr := make([]string, 0, len(members[0]))
|
||||||
|
for k := range members[0] {
|
||||||
|
toarr = append(toarr, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: ctx.CallBy.Accid.Hex(),
|
||||||
|
Body: map[string]any{"members": toarr},
|
||||||
|
Tag: []string{"ChattingChannelProperties"},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.Println("JSONSet $.members failed :", chanid, *ctx.CallBy, err)
|
||||||
|
logger.Println(gc.rh.JSONGet(chanid, "$"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 풀방
|
||||||
|
logger.Println("chatting channel is full :", chanid, size, cfg.Capacity)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Println("chatting channel not valid :", chanid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *groupChat) LeavePublicChannel(ctx wshandler.ApiCallContext) {
|
||||||
|
chanid := ctx.Arguments[0].(string)
|
||||||
|
cnt, _ := gc.rh.JSONDel(ctx.CallBy.Accid.Hex(), "$.channel.public")
|
||||||
|
if cnt > 0 {
|
||||||
|
gc.leaveRoom(chanid, ctx.CallBy.Accid)
|
||||||
|
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
||||||
|
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
||||||
|
cfg.inoutChan <- "-" + 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.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel."+typename, channel, gocommon.RedisonSetOptionNX)
|
||||||
|
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.JSONDel(ctx.CallBy.Accid.Hex(), "$.channel."+typename)
|
||||||
|
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.JSONGetDocuments(accid.Hex(), "$.channel")
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(sub) > 0 {
|
||||||
|
gocommon.MakeEncoder(w, r).Encode(sub[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
511
core/group_instant.go
Normal file
511
core/group_instant.go
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type instantDoc struct {
|
||||||
|
Members map[string]any `json:"_members"`
|
||||||
|
Count int64 `json:"_count"`
|
||||||
|
Body primitive.M `json:"_body"`
|
||||||
|
Gid primitive.ObjectID `json:"_gid"`
|
||||||
|
|
||||||
|
rh *gocommon.RedisonHandler
|
||||||
|
idstr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *instantDoc) 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 *instantDoc) 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 *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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *instantDoc) mid(tid string) accountID {
|
||||||
|
tidobj, _ := primitive.ObjectIDFromHex(tid)
|
||||||
|
var out primitive.ObjectID
|
||||||
|
for i := range tidobj {
|
||||||
|
out[12-i-1] = gd.Gid[i] ^ tidobj[12-i-1]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *instantDoc) addMember(mid accountID, character any) (bson.M, error) {
|
||||||
|
tid := gd.tid(mid)
|
||||||
|
if _, err := gd.rh.JSONSet(gd.strid(), "$._members."+tid, character); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
counts, err := gd.rh.JSONNumIncrBy(gd.strid(), "$._count", 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gd.Count = counts[0]
|
||||||
|
|
||||||
|
return gd.loadMemberFull(tid)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *instantDoc) getMembers() (map[primitive.ObjectID]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[primitive.ObjectID]any)
|
||||||
|
for k, v := range temp[0] {
|
||||||
|
out[gd.mid(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, 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) RegisterApiFunctions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) join(gd *instantDoc, mid primitive.ObjectID, character any) error {
|
||||||
|
// 내 정보 업데이트할 때에도 사용됨
|
||||||
|
memdoc, err := gd.addMember(mid, character)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(memdoc, "_id")
|
||||||
|
|
||||||
|
// 기존 유저에게 새 유저 알림
|
||||||
|
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gd.strid(),
|
||||||
|
Body: map[string]any{
|
||||||
|
gd.tid(mid): memdoc,
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gi.rh.JSONSet(mid.Hex(), "$.instant", bson.M{"id": gd.strid()})
|
||||||
|
|
||||||
|
full := gd.loadFull()
|
||||||
|
if f, ok := full["_members"]; ok {
|
||||||
|
members := f.(map[string]any)
|
||||||
|
for _, char := range members {
|
||||||
|
delete(char.(map[string]any), "_id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최초 입장이라면 새 멤버에 그룹 전체를 알림
|
||||||
|
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: mid.Hex(),
|
||||||
|
Body: full,
|
||||||
|
Tag: []string{"GroupDocFull"},
|
||||||
|
})
|
||||||
|
gi.enterRoom(gd.Gid, mid)
|
||||||
|
|
||||||
|
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) Join(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Gid primitive.ObjectID
|
||||||
|
Mid primitive.ObjectID
|
||||||
|
Character primitive.M
|
||||||
|
}
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||||
|
logger.Println("JoinParty failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Gid.IsZero() || data.Mid.IsZero() {
|
||||||
|
logger.Error("groupInstant.Join failed. gid or mid is zero")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gd, err := gi.find(data.Gid)
|
||||||
|
if err != nil || gd == nil {
|
||||||
|
logger.Error("groupInstant.Join failed. gi find return err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gi.join(gd, data.Mid, data.Character); err != nil {
|
||||||
|
logger.Error("groupInstant.Join failed :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
gocommon.MakeEncoder(w, r).Encode(gd.Count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) Create(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Mid primitive.ObjectID
|
||||||
|
Body primitive.M
|
||||||
|
Character primitive.M
|
||||||
|
}
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||||
|
logger.Println("CreateParty failed. Decode returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gd, err := gi.createInstantGroup(data.Mid, data.Character, data.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("groupInstant.Create failed. gp.createInstantGroup() return err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 내가 wshandler room에 입장
|
||||||
|
gi.enterRoom(gd.Gid, data.Mid)
|
||||||
|
gi.rh.JSONSet(data.Mid.Hex(), "$.instant", bson.M{"id": gd.strid()})
|
||||||
|
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: data.Mid.Hex(),
|
||||||
|
Body: gd,
|
||||||
|
Tag: []string{"GroupDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gocommon.MakeEncoder(w, r).Encode(gd.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var gid primitive.ObjectID
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&gid); err != nil {
|
||||||
|
logger.Println("CreateParty failed. Decode returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) leave(gd *instantDoc, mid primitive.ObjectID) error {
|
||||||
|
if err := gd.removeMember(mid); err != nil {
|
||||||
|
if err == errGroupAlreadyDestroyed {
|
||||||
|
// 정상
|
||||||
|
gd.Count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.rh.JSONDel(mid.Hex(), "$.instant.id")
|
||||||
|
|
||||||
|
// gid에는 제거 메시지 보냄
|
||||||
|
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gd.strid(),
|
||||||
|
Body: bson.M{
|
||||||
|
gd.tid(mid): bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gi.leaveRoom(gd.Gid, mid)
|
||||||
|
|
||||||
|
if gd.Count == 0 {
|
||||||
|
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) Leave(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Gid primitive.ObjectID
|
||||||
|
Mid primitive.ObjectID
|
||||||
|
}
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||||
|
logger.Println("RemoveFromParty failed. Decode returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gd := instantDoc{
|
||||||
|
Gid: data.Gid,
|
||||||
|
rh: gi.rh,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gi.leave(&gd, data.Mid); err != nil {
|
||||||
|
logger.Println("groupInstant.Leave failed. gd.removeMember returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gocommon.MakeEncoder(w, r).Encode(gd.Count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) Merge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
From primitive.ObjectID
|
||||||
|
Into primitive.ObjectID
|
||||||
|
Max int64
|
||||||
|
}
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||||
|
logger.Println("RemoveFromParty failed. Decode returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// From에 있는 mid를 Into로 옮김
|
||||||
|
gdinto, err := gi.find(data.Into)
|
||||||
|
if err != nil || gdinto == nil {
|
||||||
|
logger.Println("groupInstant.Merge failed. gd.getMembers returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdfrom := instantDoc{
|
||||||
|
Gid: data.From,
|
||||||
|
rh: gi.rh,
|
||||||
|
}
|
||||||
|
fromMembers, err := gdfrom.getMembers()
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("groupInstant.Merge failed. gd.getMembers returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var movedmids []primitive.ObjectID
|
||||||
|
for mid, doc := range fromMembers {
|
||||||
|
gi.join(gdinto, mid, doc)
|
||||||
|
gi.leaveRoom(gdfrom.Gid, mid)
|
||||||
|
movedmids = append(movedmids, mid)
|
||||||
|
|
||||||
|
if gdinto.Count == data.Max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(movedmids) == int(gdfrom.Count) {
|
||||||
|
gi.rh.JSONDel(gdfrom.strid(), "$")
|
||||||
|
} else {
|
||||||
|
for _, mid := range movedmids {
|
||||||
|
gdfrom.removeMember(mid)
|
||||||
|
|
||||||
|
// gid에는 제거 메시지 보냄
|
||||||
|
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gdfrom.strid(),
|
||||||
|
Body: bson.M{
|
||||||
|
gdfrom.tid(mid): bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gocommon.MakeEncoder(w, r).Encode(struct {
|
||||||
|
From int64
|
||||||
|
Into int64
|
||||||
|
}{From: gdfrom.Count, Into: gdinto.Count})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) createInstantGroup(firstAcc primitive.ObjectID, firstChar primitive.M, instDoc primitive.M) (*instantDoc, error) {
|
||||||
|
newid := primitive.NewObjectID()
|
||||||
|
tid := makeTid(newid, firstAcc)
|
||||||
|
|
||||||
|
gd := &instantDoc{
|
||||||
|
Members: map[string]any{
|
||||||
|
tid: firstChar,
|
||||||
|
},
|
||||||
|
Body: instDoc,
|
||||||
|
Count: 1,
|
||||||
|
|
||||||
|
rh: gi.rh,
|
||||||
|
Gid: newid,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) find(id groupID) (*instantDoc, error) {
|
||||||
|
if id.IsZero() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := gi.rh.JSONObjLen(id.Hex(), "$")
|
||||||
|
if err == redis.Nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &instantDoc{
|
||||||
|
rh: gi.rh,
|
||||||
|
Gid: id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *groupInstant) ClientDisconnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||||
|
gids, _ := gi.rh.JSONGetString(callby.Accid.Hex(), "$.instant.id")
|
||||||
|
|
||||||
|
if len(gids) > 0 && len(gids[0]) > 0 {
|
||||||
|
gidstr := gids[0]
|
||||||
|
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
||||||
|
|
||||||
|
gd := instantDoc{
|
||||||
|
Gid: gid,
|
||||||
|
rh: gi.rh,
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.rh.JSONDel(callby.Accid.Hex(), "$.instant.id")
|
||||||
|
|
||||||
|
if err := gd.removeMember(callby.Accid); err != nil {
|
||||||
|
if err == errGroupAlreadyDestroyed {
|
||||||
|
// 정상
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Println("ClientDisconnected failed. gd.removeMember returns err :", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// gid에는 제거 메시지 보냄
|
||||||
|
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gd.strid(),
|
||||||
|
Body: bson.M{
|
||||||
|
gd.tid(callby.Accid): bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gi.leaveRoom(gd.Gid, callby.Accid)
|
||||||
|
|
||||||
|
if gd.Count == 0 {
|
||||||
|
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,553 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"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 ticketID = primitive.ObjectID
|
|
||||||
type groupID = primitive.ObjectID
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
gob.Register(memberDoc{})
|
|
||||||
gob.Register(groupDoc{})
|
|
||||||
gob.Register(Invitation{})
|
|
||||||
gob.Register(InvitationFail{})
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 groupDoc struct {
|
|
||||||
Members map[string]any `json:"_members"`
|
|
||||||
InCharge string `json:"_incharge"`
|
|
||||||
Gid string `json:"_gid"`
|
|
||||||
|
|
||||||
rh *gocommon.RedisonHandler
|
|
||||||
id groupID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *groupDoc) 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 *groupDoc) 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 *groupDoc) strid() string {
|
|
||||||
if len(gd.Gid) == 0 {
|
|
||||||
gd.Gid = gd.id.Hex()
|
|
||||||
}
|
|
||||||
return gd.Gid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *groupDoc) tid(in accountID) string {
|
|
||||||
return makeTid(gd.id, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *groupDoc) 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 *groupDoc) addInvite(inviteeDoc bson.M, ttl time.Duration, max int) (*memberDoc, error) {
|
|
||||||
targetmid := inviteeDoc["_mid"].(accountID)
|
|
||||||
targetbody := inviteeDoc["body"].(bson.M)
|
|
||||||
|
|
||||||
// 초대 가능한 빈 자리가 있나
|
|
||||||
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 *groupDoc) addMember(mid accountID, doc bson.M) (bson.M, error) {
|
|
||||||
tid := gd.tid(mid)
|
|
||||||
prefix := "$._members." + tid
|
|
||||||
|
|
||||||
if _, err := gd.rh.JSONMerge(gd.strid(), prefix+"._body", doc, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
return gd.loadMemberFull(tid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *groupDoc) removeMember(mid accountID) error {
|
|
||||||
_, err := gd.rh.JSONDel(gd.strid(), "$._members."+gd.tid(mid))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *groupDoc) 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 groupInMemory struct {
|
|
||||||
*groupConfig
|
|
||||||
sendUpstreamMessage func(*wshandler.UpstreamMessage)
|
|
||||||
sendEnterRoomMessage func(groupID, accountID)
|
|
||||||
sendLeaveRoomMessage func(groupID, accountID)
|
|
||||||
rh *gocommon.RedisonHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) createGroup(newid groupID, charge accountID, chargeDoc bson.M) (*groupDoc, error) {
|
|
||||||
tid := makeTid(newid, charge)
|
|
||||||
|
|
||||||
gd := &groupDoc{
|
|
||||||
Members: map[string]any{
|
|
||||||
tid: &memberDoc{
|
|
||||||
Body: chargeDoc,
|
|
||||||
Invite: false,
|
|
||||||
InviteExpire: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
InCharge: tid,
|
|
||||||
|
|
||||||
rh: gm.rh,
|
|
||||||
id: newid,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := gm.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return gd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) find(id groupID) (*groupDoc, error) {
|
|
||||||
if id.IsZero() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := gm.rh.JSONObjLen(id.Hex(), "$")
|
|
||||||
if err == redis.Nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &groupDoc{
|
|
||||||
rh: gm.rh,
|
|
||||||
id: id,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) Create(form url.Values, base bson.M) (groupID, error) {
|
|
||||||
return primitive.NilObjectID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) Candidate(gid groupID, mid accountID, doc bson.M) error {
|
|
||||||
logger.Error("not implemented func : Canidate")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errGroupNotExist = errors.New("group does not exist")
|
|
||||||
|
|
||||||
func (gm *groupInMemory) Join(gid groupID, mid accountID, doc bson.M) error {
|
|
||||||
gd, err := gm.find(gid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if gd == nil {
|
|
||||||
// 그룹이 없다. 실패
|
|
||||||
return errGroupNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// 내 정보 업데이트할 때에도 사용됨
|
|
||||||
if memdoc, err := gd.addMember(mid, doc); err == nil {
|
|
||||||
// 기존 유저에게 새 유저 알림
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "#" + gid.Hex(),
|
|
||||||
Body: map[string]any{
|
|
||||||
gd.tid(mid): memdoc,
|
|
||||||
},
|
|
||||||
Tag: []string{"MemberDocFull"},
|
|
||||||
})
|
|
||||||
|
|
||||||
gm.sendEnterRoomMessage(gid, mid)
|
|
||||||
|
|
||||||
// 새 멤버에 그룹 전체를 알림
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "@" + mid.Hex(),
|
|
||||||
Body: gd.loadFull(),
|
|
||||||
Tag: []string{"GroupDocFull"},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInviteeDocMidMissing = errors.New("inviteeDoc must have '_mid' field")
|
|
||||||
var errAlreadyInvited = errors.New("this target is already invited by someone or me")
|
|
||||||
|
|
||||||
func (gm *groupInMemory) Invite(gid groupID, mid accountID, inviterDoc bson.M, inviteeDoc bson.M) (string, error) {
|
|
||||||
targetid, ok := inviteeDoc["_mid"].(accountID)
|
|
||||||
if !ok {
|
|
||||||
return "", errInviteeDocMidMissing
|
|
||||||
}
|
|
||||||
|
|
||||||
// targetid에 초대한 mid가 들어있다.
|
|
||||||
already, err := gm.rh.Get(context.Background(), targetid.Hex()).Result()
|
|
||||||
if err != nil && err != redis.Nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(already) > 0 {
|
|
||||||
if already != mid.Hex() {
|
|
||||||
// 이미 초대 중이다.
|
|
||||||
// inviter한테 알려줘야 한다.
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "@" + mid.Hex(),
|
|
||||||
Body: inviteeDoc,
|
|
||||||
Tag: []string{"InvitationFail"},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return "", errAlreadyInvited
|
|
||||||
}
|
|
||||||
|
|
||||||
gd, err := gm.find(gid)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if gd == nil {
|
|
||||||
gd, err = gm.createGroup(gid, mid, inviterDoc)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// 내가 wshandler room에 입장
|
|
||||||
gm.sendEnterRoomMessage(gid, mid)
|
|
||||||
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "@" + mid.Hex(),
|
|
||||||
Body: gd,
|
|
||||||
Tag: []string{"GroupDocFull"},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
newdoc, err := gd.addInvite(inviteeDoc, time.Duration(gm.InviteExpire+1)*time.Second, gm.MaxMember)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 초대 중 표시
|
|
||||||
_, err = gm.rh.SetNX(context.Background(), targetid.Hex(), mid.Hex(), time.Duration(gm.InviteExpire)*time.Second).Result()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// invitee에게 알림
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "@" + targetid.Hex(),
|
|
||||||
Body: Invitation{
|
|
||||||
GroupID: gid,
|
|
||||||
TicketID: gd.tid(targetid),
|
|
||||||
Inviter: inviterDoc,
|
|
||||||
ExpireAtUTC: newdoc.InviteExpire,
|
|
||||||
},
|
|
||||||
Tag: []string{"Invitation"},
|
|
||||||
})
|
|
||||||
|
|
||||||
return gd.strid(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) CancelInvitation(gid groupID, tid ticketID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvitationExpired = errors.New("invitation is already expired")
|
|
||||||
|
|
||||||
func (gm *groupInMemory) AcceptInvitation(gid groupID, mid accountID, member bson.M) error {
|
|
||||||
cnt, err := gm.rh.Del(context.Background(), mid.Hex()).Result()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cnt == 0 {
|
|
||||||
// 만료됨
|
|
||||||
return errInvitationExpired
|
|
||||||
}
|
|
||||||
|
|
||||||
gd := &groupDoc{
|
|
||||||
id: gid,
|
|
||||||
rh: gm.rh,
|
|
||||||
}
|
|
||||||
|
|
||||||
memberDoc, err := gd.addMember(mid, member)
|
|
||||||
if err == nil {
|
|
||||||
// 기존 멤버에게 새 멤버를 알림
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "#" + gid.Hex(),
|
|
||||||
Body: map[string]any{
|
|
||||||
gd.tid(mid): memberDoc,
|
|
||||||
},
|
|
||||||
Tag: []string{"MemberDocFull"},
|
|
||||||
})
|
|
||||||
|
|
||||||
gm.sendEnterRoomMessage(gid, mid)
|
|
||||||
|
|
||||||
// 새 멤버에 그룹 전체를 알림
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "@" + mid.Hex(),
|
|
||||||
Body: gd.loadFull(),
|
|
||||||
Tag: []string{"GroupDocFull"},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 실패
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) QueryGroupMembers(gid groupID) (bson.M, error) {
|
|
||||||
gd := groupDoc{
|
|
||||||
id: gid,
|
|
||||||
rh: gm.rh,
|
|
||||||
}
|
|
||||||
return gd.getMembers()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) DenyInvitation(gid groupID, mid accountID, tid ticketID) error {
|
|
||||||
gm.rh.Del(context.Background(), mid.Hex()).Result()
|
|
||||||
gd := groupDoc{
|
|
||||||
id: gid,
|
|
||||||
rh: gm.rh,
|
|
||||||
}
|
|
||||||
return gd.removeMember(mid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) QueryInvitations(mid accountID, after primitive.Timestamp) ([]bson.M, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (gm *groupInMemory) Exist(gid groupID, filter bson.M) (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
func (gm *groupInMemory) FindAll(filter bson.M, projection string, after primitive.Timestamp) ([]bson.M, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (gm *groupInMemory) FindOne(gid groupID, projection string) (bson.M, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (gm *groupInMemory) Leave(gid groupID, mid accountID) error {
|
|
||||||
gd := groupDoc{
|
|
||||||
id: gid,
|
|
||||||
rh: gm.rh,
|
|
||||||
}
|
|
||||||
if err := gd.removeMember(mid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 나한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "@" + mid.Hex(),
|
|
||||||
Body: bson.M{"gid": gid},
|
|
||||||
Tag: []string{"GroupDocFull", gid.Hex()},
|
|
||||||
})
|
|
||||||
gm.sendLeaveRoomMessage(gid, mid)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) UpdateMemberDocument(gid groupID, mid accountID, doc bson.M) error {
|
|
||||||
gd := &groupDoc{
|
|
||||||
id: gid,
|
|
||||||
rh: gm.rh,
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixPath := fmt.Sprintf("$._members.%s.", gd.tid(mid))
|
|
||||||
err := gm.rh.JSONMSetRel(gd.strid(), prefixPath, doc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "#" + gid.Hex(),
|
|
||||||
Body: map[string]any{
|
|
||||||
gd.tid(mid): doc,
|
|
||||||
},
|
|
||||||
Tag: []string{"MemberDocFragment"},
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) Dismiss(gid groupID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gm *groupInMemory) UpdateGroupDocument(gid groupID, frag bson.M) error {
|
|
||||||
gd := groupDoc{
|
|
||||||
id: gid,
|
|
||||||
rh: gm.rh,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := gm.rh.JSONMSetRel(gd.strid(), "$.", frag); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 업데이트 알림
|
|
||||||
gm.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
||||||
Target: "#" + gid.Hex(),
|
|
||||||
Body: frag,
|
|
||||||
Tag: []string{"GroupDocFragment"},
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *groupConfig) prepareInMemory(ctx context.Context, typename string, sub *subTavern) (group, error) {
|
|
||||||
// group document
|
|
||||||
// member document
|
|
||||||
region := sub.region
|
|
||||||
wsh := sub.wsh
|
|
||||||
storage := config.RegionStorage[sub.region]
|
|
||||||
redisClient, err := gocommon.NewRedisClient(storage.Redis["tavern"])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// 여기서는 subscribe channel
|
|
||||||
// 각 함수에서는 publish
|
|
||||||
gm := &groupInMemory{
|
|
||||||
groupConfig: cfg,
|
|
||||||
rh: gocommon.NewRedisonHandler(ctx, redisClient),
|
|
||||||
sendUpstreamMessage: func(msg *wshandler.UpstreamMessage) {
|
|
||||||
wsh.SendUpstreamMessage(region, msg)
|
|
||||||
},
|
|
||||||
sendEnterRoomMessage: func(gid groupID, accid accountID) {
|
|
||||||
wsh.EnterRoom(region, gid.Hex(), accid)
|
|
||||||
},
|
|
||||||
sendLeaveRoomMessage: func(gid groupID, accid accountID) {
|
|
||||||
wsh.LeaveRoom(region, gid.Hex(), accid)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return gm, nil
|
|
||||||
}
|
|
||||||
838
core/group_party.go
Normal file
838
core/group_party.go
Normal file
@ -0,0 +1,838 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type accountID = primitive.ObjectID
|
||||||
|
type groupID = primitive.ObjectID
|
||||||
|
|
||||||
|
func makeTid(gid groupID, in accountID) string {
|
||||||
|
var out primitive.ObjectID
|
||||||
|
for i := range in {
|
||||||
|
out[12-i-1] = gid[i] ^ in[12-i-1]
|
||||||
|
}
|
||||||
|
return out.Hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
func midFromTid(gid groupID, in string) accountID {
|
||||||
|
h, _ := primitive.ObjectIDFromHex(in)
|
||||||
|
|
||||||
|
var out accountID
|
||||||
|
for i := range h {
|
||||||
|
out[12-i-1] = gid[i] ^ h[12-i-1]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
type Invitation struct {
|
||||||
|
GroupID groupID `json:"_gid"`
|
||||||
|
TicketID string `json:"_tid"`
|
||||||
|
Inviter bson.M `json:"_inviter"` // memberDoc.Body
|
||||||
|
ExpireAtUTC int64 `json:"_expire_at_utc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 플레이어한테 공유하는 멤버 정보
|
||||||
|
type memberDoc struct {
|
||||||
|
Body bson.M `json:"_body"`
|
||||||
|
Invite bool `json:"_invite"`
|
||||||
|
InviteExpire int64 `json:"_invite_exp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvitationFail bson.M
|
||||||
|
|
||||||
|
type partyDoc struct {
|
||||||
|
Members map[string]any `json:"_members"`
|
||||||
|
InCharge string `json:"_incharge"`
|
||||||
|
Gid string `json:"_gid"`
|
||||||
|
|
||||||
|
rh *gocommon.RedisonHandler
|
||||||
|
id groupID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) loadMemberFull(tid string) (bson.M, error) {
|
||||||
|
full, err := gd.rh.JSONGet(gd.strid(), "$._members."+tid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bt := []byte(full.(string))
|
||||||
|
bt = bt[1 : len(bt)-1]
|
||||||
|
|
||||||
|
var doc bson.M
|
||||||
|
if err = json.Unmarshal(bt, &doc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) loadFull() (doc bson.M) {
|
||||||
|
// 새 멤버에 그룹 전체를 알림
|
||||||
|
full, err := gd.rh.JSONGet(gd.strid(), "$")
|
||||||
|
if err == nil {
|
||||||
|
bt := []byte(full.(string))
|
||||||
|
bt = bt[1 : len(bt)-1]
|
||||||
|
err = json.Unmarshal(bt, &doc)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("loadFull err :", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Println("loadFull err :", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) strid() string {
|
||||||
|
if len(gd.Gid) == 0 {
|
||||||
|
gd.Gid = gd.id.Hex()
|
||||||
|
}
|
||||||
|
return gd.Gid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) tid(in accountID) string {
|
||||||
|
return makeTid(gd.id, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) mid(tid string) accountID {
|
||||||
|
tidobj, _ := primitive.ObjectIDFromHex(tid)
|
||||||
|
var out primitive.ObjectID
|
||||||
|
for i := range tidobj {
|
||||||
|
out[12-i-1] = gd.id[i] ^ tidobj[12-i-1]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) addInvite(mid accountID, body bson.M, ttl time.Duration, max int) (*memberDoc, error) {
|
||||||
|
targetmid := mid
|
||||||
|
targetbody := body
|
||||||
|
|
||||||
|
// 초대 가능한 빈 자리가 있나
|
||||||
|
tids, err := gd.rh.JSONObjKeys(gd.strid(), "$._members")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
createNewDoc := func() *memberDoc {
|
||||||
|
return &memberDoc{
|
||||||
|
Body: targetbody,
|
||||||
|
Invite: true,
|
||||||
|
InviteExpire: now.Add(ttl).Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newtid := gd.tid(targetmid)
|
||||||
|
if len(tids) < max {
|
||||||
|
// 빈자리를 찾았다.
|
||||||
|
newdoc := createNewDoc()
|
||||||
|
_, err := gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc)
|
||||||
|
return newdoc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expires, err := gd.rh.JSONGetInt64(gd.strid(), "$._members.._invite_exp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var delpaths []string
|
||||||
|
for i, expire := range expires {
|
||||||
|
if expire < now.Unix() {
|
||||||
|
// 만료된 초대가 있네? 지우자
|
||||||
|
delpaths = append(delpaths, "$._members."+tids[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(delpaths) == 0 {
|
||||||
|
// 빈자리가 없다
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gd.rh.JSONMDel(gd.strid(), delpaths); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newdoc := createNewDoc()
|
||||||
|
_, err = gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc)
|
||||||
|
return newdoc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) addMember(mid accountID, character bson.M) (bson.M, error) {
|
||||||
|
tid := gd.tid(mid)
|
||||||
|
prefix := "$._members." + tid
|
||||||
|
|
||||||
|
if _, err := gd.rh.JSONSet(gd.strid(), prefix+"._body", character, gocommon.RedisonSetOptionXX); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gd.rh.JSONMDel(gd.strid(), []string{prefix + "._invite", prefix + "._invite_exp"}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gd.rh.Persist(gd.rh.Context(), gd.strid()).Result()
|
||||||
|
|
||||||
|
return gd.loadMemberFull(tid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) removeMemberByTid(tid string) error {
|
||||||
|
_, err := gd.rh.JSONDel(gd.strid(), "$._members."+tid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
counts, err := gd.rh.JSONObjLen(gd.strid(), "$._members")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(counts) > 0 && counts[0] == 0 {
|
||||||
|
_, err = gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) removeMember(mid accountID) error {
|
||||||
|
return gd.removeMemberByTid(gd.tid(mid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *partyDoc) getMembers() (map[string]any, error) {
|
||||||
|
res, err := gd.rh.JSONGet(gd.strid(), "$._members")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var temp []map[string]any
|
||||||
|
err = json.Unmarshal([]byte(res.(string)), &temp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]any)
|
||||||
|
for k, v := range temp[0] {
|
||||||
|
body := v.(map[string]any)["_body"]
|
||||||
|
out[gd.mid(k).Hex()] = body
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type partyConfig struct {
|
||||||
|
InviteExpire int32 `json:"invite_ttl"` // 그룹이 개인에게 보낸 초대장 만료 기한
|
||||||
|
MaxMember int `json:"max_member"`
|
||||||
|
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type groupParty struct {
|
||||||
|
partyConfig
|
||||||
|
sendUpstreamMessage func(*wshandler.UpstreamMessage)
|
||||||
|
enterRoom func(groupID, accountID)
|
||||||
|
leaveRoom func(groupID, accountID)
|
||||||
|
rh *gocommon.RedisonHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) Initialize(tv *Tavern, cfg configDocument) error {
|
||||||
|
rem, _ := json.Marshal(cfg)
|
||||||
|
err := json.Unmarshal(rem, &gp.partyConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gp.rh = tv.redison
|
||||||
|
gp.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
|
||||||
|
tv.wsh.SendUpstreamMessage(msg)
|
||||||
|
}
|
||||||
|
gp.enterRoom = func(gid groupID, accid accountID) {
|
||||||
|
tv.wsh.EnterRoom(gid.Hex(), accid)
|
||||||
|
}
|
||||||
|
gp.leaveRoom = func(gid groupID, accid accountID) {
|
||||||
|
tv.wsh.LeaveRoom(gid.Hex(), accid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) RegisterApiFunctions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinParty : 그룹에 참가
|
||||||
|
// - type : 그룹 타입
|
||||||
|
// - 그룹 타입에 맞는 키(주로 _id)
|
||||||
|
// - member_id : 참가 멤버의 아이디
|
||||||
|
// - body : 멤버의 속성 bson document
|
||||||
|
func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Gid primitive.ObjectID
|
||||||
|
Mid primitive.ObjectID
|
||||||
|
First bool
|
||||||
|
Character bson.M
|
||||||
|
}
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||||
|
logger.Println("JoinParty failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
character := data.Character
|
||||||
|
gid := data.Gid
|
||||||
|
mid := data.Mid
|
||||||
|
|
||||||
|
if gid.IsZero() || mid.IsZero() {
|
||||||
|
logger.Println("JoinParty failed. mid should be exist")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gd, err := gp.find(gid)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if gd == nil {
|
||||||
|
// 그룹이 없다. 실패
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 내 정보 업데이트할 때에도 사용됨
|
||||||
|
if data.First {
|
||||||
|
if memdoc, err := gd.addMember(mid, character); err == nil {
|
||||||
|
// 기존 유저에게 새 유저 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gid.Hex(),
|
||||||
|
Body: map[string]any{
|
||||||
|
gd.tid(mid): memdoc,
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gp.enterRoom(gid, mid)
|
||||||
|
|
||||||
|
// 최초 입장이라면 새 멤버에 그룹 전체를 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: mid.Hex(),
|
||||||
|
Body: gd.loadFull(),
|
||||||
|
Tag: []string{"GroupDocFull"},
|
||||||
|
})
|
||||||
|
} else if err != nil {
|
||||||
|
logger.Error("JoinParty failed :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path := "$._members." + gd.tid(mid) + "._body"
|
||||||
|
if _, err := gd.rh.JSONSet(gd.strid(), path, character, gocommon.RedisonSetOptionXX); err != nil {
|
||||||
|
logger.Error("JoinParty failed :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 유저에게 캐릭터 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gid.Hex(),
|
||||||
|
Body: map[string]any{
|
||||||
|
gd.tid(mid): bson.M{
|
||||||
|
"_body": character,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFragment"},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteToParty : 초대
|
||||||
|
// - type : 초대 타입 (required)
|
||||||
|
// - from : 초대하는 자 (required)
|
||||||
|
// - to : 초대받는 자 (required)
|
||||||
|
// - timeout : 초대 유지시간(optional. 없으면 config 기본 값)
|
||||||
|
// - (body) : 검색시 노출되는 document
|
||||||
|
func (gp *groupParty) InviteToParty(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var doc struct {
|
||||||
|
Gid primitive.ObjectID
|
||||||
|
Mid primitive.ObjectID
|
||||||
|
Target primitive.ObjectID
|
||||||
|
Inviter bson.M
|
||||||
|
Invitee bson.M
|
||||||
|
InitialPartyDoc bson.M
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
||||||
|
logger.Println("InviteToParty failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetid := doc.Target
|
||||||
|
gid := doc.Gid
|
||||||
|
mid := doc.Mid
|
||||||
|
|
||||||
|
// targetid에 초대한 mid가 들어있다.
|
||||||
|
success, err := gp.rh.SetNX(context.Background(), "inv."+targetid.Hex(), mid.Hex(), time.Duration(gp.InviteExpire)*time.Second).Result()
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("InviteToParty failed. gp.rh.SetNX() return err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
// 이미 초대 중이다.
|
||||||
|
// inviter한테 알려줘야 한다.
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: mid.Hex(),
|
||||||
|
Body: doc.Invitee,
|
||||||
|
Tag: []string{"InvitationFail"},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gd, err := gp.find(gid)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("InviteToParty failed. gp.find() return err :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if gd == nil {
|
||||||
|
gid = primitive.NewObjectID()
|
||||||
|
gd, err = gp.createGroup(gid, mid, doc.Inviter, doc.InitialPartyDoc)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("InviteToParty failed. gp.createGroup() return err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 내가 wshandler room에 입장
|
||||||
|
gp.enterRoom(gid, mid)
|
||||||
|
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
||||||
|
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: mid.Hex(),
|
||||||
|
Body: gd,
|
||||||
|
Tag: []string{"GroupDocFull"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
newdoc, err := gd.addInvite(targetid, doc.Invitee, time.Duration(gp.InviteExpire+1)*time.Second, gp.MaxMember)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("InviteToParty failed. gp.addInvite() return err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// invitee에게 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: targetid.Hex(),
|
||||||
|
Body: Invitation{
|
||||||
|
GroupID: gid,
|
||||||
|
TicketID: gd.tid(targetid),
|
||||||
|
Inviter: doc.Inviter,
|
||||||
|
ExpireAtUTC: newdoc.InviteExpire,
|
||||||
|
},
|
||||||
|
Tag: []string{"Invitation"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 그룹에게 알림
|
||||||
|
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) AcceptPartyInvitation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var doc struct {
|
||||||
|
Gid primitive.ObjectID
|
||||||
|
Mid primitive.ObjectID
|
||||||
|
Tid string
|
||||||
|
Character bson.M
|
||||||
|
}
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
||||||
|
logger.Println("AcceptPartyInvitation failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gid := doc.Gid
|
||||||
|
mid := doc.Mid
|
||||||
|
member := doc.Character
|
||||||
|
|
||||||
|
cnt, err := gp.rh.Del(context.Background(), "inv."+mid.Hex()).Result()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("AcceptPartyInvitation failed. gp.rh.Del returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 {
|
||||||
|
// 만료됨
|
||||||
|
w.WriteHeader(http.StatusGatewayTimeout)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pids, err := gp.rh.JSONGetString(mid.Hex(), "$.party.id")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("AcceptPartyInvitation failed. gp.rh.JSONGetString returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pids) > 0 && len(pids[0]) > 0 {
|
||||||
|
// 기존에 이미 파티에 들어가 있다.
|
||||||
|
// 기존 파티에서는 탈퇴
|
||||||
|
oldgid, _ := primitive.ObjectIDFromHex(pids[0])
|
||||||
|
oldgd := &partyDoc{
|
||||||
|
id: oldgid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
|
||||||
|
// gid에는 제거 메시지 보냄
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + oldgd.strid(),
|
||||||
|
Body: bson.M{
|
||||||
|
oldgd.tid(mid): bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gp.leaveRoom(oldgid, mid)
|
||||||
|
}
|
||||||
|
|
||||||
|
gd := &partyDoc{
|
||||||
|
id: gid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
|
||||||
|
memberDoc, err := gd.addMember(mid, member)
|
||||||
|
if err == nil {
|
||||||
|
// 기존 멤버에게 새 멤버를 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gid.Hex(),
|
||||||
|
Body: map[string]any{
|
||||||
|
gd.tid(mid): memberDoc,
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gp.enterRoom(gid, mid)
|
||||||
|
|
||||||
|
// 현재 내 파티를 기록
|
||||||
|
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
||||||
|
|
||||||
|
// 새 멤버에 그룹 전체를 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: mid.Hex(),
|
||||||
|
Body: gd.loadFull(),
|
||||||
|
Tag: []string{"GroupDocFull"},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.Println("AcceptPartyInvitation failed. group.AcceptPartyInvitation returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) QueryPartyMemberState(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var mid primitive.ObjectID
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&mid); err != nil {
|
||||||
|
logger.Println("DenyPartyInvitation failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt, _ := gp.rh.Exists(gp.rh.Context(), mid.Hex()).Result(); cnt == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
states, _ := gp.rh.JSONGetString(mid.Hex(), "$.party.state")
|
||||||
|
if len(states) > 0 && len(states[0]) > 0 {
|
||||||
|
gocommon.MakeEncoder(w, r).Encode(states[0])
|
||||||
|
} else {
|
||||||
|
gocommon.MakeEncoder(w, r).Encode("connected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) updateMemberDocument(gid groupID, mid accountID, doc bson.M) error {
|
||||||
|
gd := &partyDoc{
|
||||||
|
id: gid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
prefixPath := fmt.Sprintf("$._members.%s.", gd.tid(mid))
|
||||||
|
err := gp.rh.JSONMSetRel(gd.strid(), prefixPath, doc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newstate, ok := doc["_state"]; ok {
|
||||||
|
gp.rh.JSONSet(mid.Hex(), "$.party.state", newstate)
|
||||||
|
}
|
||||||
|
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gid.Hex(),
|
||||||
|
Body: map[string]any{
|
||||||
|
gd.tid(mid): doc,
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFragment"},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) updatePartyDocument(gid groupID, frag bson.M) error {
|
||||||
|
gd := partyDoc{
|
||||||
|
id: gid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
if err := gp.rh.JSONMSetRel(gd.strid(), "$.", frag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 업데이트 알림
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gid.Hex(),
|
||||||
|
Body: frag,
|
||||||
|
Tag: []string{"GroupDocFragment"},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) UpdatePartyDocument(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Gid primitive.ObjectID
|
||||||
|
Doc bson.M
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||||
|
logger.Println("UpdatePartyDocument failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gid := data.Gid
|
||||||
|
frag := data.Doc
|
||||||
|
|
||||||
|
if err := gp.updatePartyDocument(gid, frag); err != nil {
|
||||||
|
logger.Error("UpdatePartyDocument failed. group.UpdatePartyDocument returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) QueryPartyMembers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var gid primitive.ObjectID
|
||||||
|
if err := gocommon.MakeDecoder(r).Decode(&gid); err != nil {
|
||||||
|
logger.Println("QueryPartyMembers failed. DecodeGob returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gd := partyDoc{
|
||||||
|
id: gid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
|
||||||
|
members, err := gd.getMembers()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("QueryPartyMembers failed. group.QueryPartyMembers returns err :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gocommon.MakeEncoder(w, r).Encode(members); err != nil {
|
||||||
|
logger.Error("QueryPartyMembers failed. writeBsonDoc return err :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) createGroup(newid groupID, charge accountID, chargeDoc bson.M, initialGroupDoc bson.M) (*partyDoc, error) {
|
||||||
|
tid := makeTid(newid, charge)
|
||||||
|
|
||||||
|
gd := &partyDoc{
|
||||||
|
Members: map[string]any{
|
||||||
|
tid: &memberDoc{
|
||||||
|
Body: chargeDoc,
|
||||||
|
Invite: false,
|
||||||
|
InviteExpire: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InCharge: tid,
|
||||||
|
Gid: newid.Hex(),
|
||||||
|
|
||||||
|
rh: gp.rh,
|
||||||
|
id: newid,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if initialGroupDoc != nil {
|
||||||
|
initialGroupDoc["_members"] = gd.Members
|
||||||
|
initialGroupDoc["_incharge"] = gd.InCharge
|
||||||
|
initialGroupDoc["_gid"] = gd.Gid
|
||||||
|
|
||||||
|
_, err = gp.rh.JSONSet(gd.strid(), "$", initialGroupDoc, gocommon.RedisonSetOptionNX)
|
||||||
|
} else {
|
||||||
|
_, err = gp.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) find(id groupID) (*partyDoc, error) {
|
||||||
|
if id.IsZero() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := gp.rh.JSONObjLen(id.Hex(), "$")
|
||||||
|
if err == redis.Nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &partyDoc{
|
||||||
|
rh: gp.rh,
|
||||||
|
id: id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (gp *groupParty) ClientDisconnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||||
|
gids, _ := gp.rh.JSONGetString(callby.Accid.Hex(), "$.party.id")
|
||||||
|
|
||||||
|
if len(gids) > 0 && len(gids[0]) > 0 {
|
||||||
|
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
||||||
|
gidstr := gids[0]
|
||||||
|
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
||||||
|
|
||||||
|
// 나를 먼저 룸에서 빼야 나한테 메시지가 안감
|
||||||
|
gp.leaveRoom(gid, callby.Accid)
|
||||||
|
|
||||||
|
// gid에는 제거 메시지 보냄
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gidstr,
|
||||||
|
Body: bson.M{
|
||||||
|
makeTid(gid, callby.Accid): bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) UpdatePartyMemberDocumentDirect(ctx wshandler.ApiCallContext) {
|
||||||
|
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
||||||
|
doc := ctx.Arguments[1].(map[string]any)
|
||||||
|
|
||||||
|
gp.updateMemberDocument(gidobj, ctx.CallBy.Accid, doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) UpdatePartyDocumentDirect(ctx wshandler.ApiCallContext) {
|
||||||
|
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
||||||
|
doc := ctx.Arguments[1].(map[string]any)
|
||||||
|
|
||||||
|
gp.updatePartyDocument(gidobj, doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) LeaveParty(ctx wshandler.ApiCallContext) {
|
||||||
|
gids, _ := gp.rh.JSONGetString(ctx.CallBy.Accid.Hex(), "$.party.id")
|
||||||
|
|
||||||
|
if len(gids) == 0 || len(gids[0]) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
||||||
|
gidstr := gids[0]
|
||||||
|
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
||||||
|
mid := ctx.CallBy.Accid
|
||||||
|
tid := ctx.Arguments[0].(string)
|
||||||
|
|
||||||
|
gd := partyDoc{
|
||||||
|
id: gid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if len(tid) > 0 {
|
||||||
|
if tid != gd.tid(mid) {
|
||||||
|
// mid가 incharge여야 한다. 그래야 tid를 쫓아낼 수 있음
|
||||||
|
incharge, err := gp.rh.JSONGet(gd.strid(), "$._incharge")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LeaveParty failed. gp.rh.JSONGet returns err :", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(incharge.(string), gd.tid(mid)) {
|
||||||
|
// incharge가 아니네?
|
||||||
|
logger.Println("LeaveParty failed. mid is not incharge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mid = midFromTid(gd.id, tid)
|
||||||
|
}
|
||||||
|
err = gd.removeMemberByTid(tid)
|
||||||
|
} else {
|
||||||
|
err = gd.removeMember(mid)
|
||||||
|
// 내가 나갔다
|
||||||
|
gp.rh.JSONDel(mid.Hex(), "$.party.id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LeaveParty failed. gd.removeMember returns err :", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: mid.Hex(),
|
||||||
|
Body: bson.M{"gid": gid},
|
||||||
|
Tag: []string{"GroupDocFull", gid.Hex()},
|
||||||
|
})
|
||||||
|
|
||||||
|
// gid에는 제거 메시지 보냄
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gd.strid(),
|
||||||
|
Body: bson.M{
|
||||||
|
tid: bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
|
||||||
|
gp.leaveRoom(gid, mid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *groupParty) DenyPartyInvitation(ctx wshandler.ApiCallContext) {
|
||||||
|
gid, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
||||||
|
mid := ctx.CallBy.Accid
|
||||||
|
|
||||||
|
gp.rh.Del(context.Background(), "inv."+mid.Hex()).Result()
|
||||||
|
gd := partyDoc{
|
||||||
|
id: gid,
|
||||||
|
rh: gp.rh,
|
||||||
|
}
|
||||||
|
gd.removeMember(mid)
|
||||||
|
|
||||||
|
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||||
|
Target: "#" + gd.strid(),
|
||||||
|
Body: bson.M{
|
||||||
|
gd.tid(mid): bson.M{},
|
||||||
|
},
|
||||||
|
Tag: []string{"MemberDocFull"},
|
||||||
|
})
|
||||||
|
}
|
||||||
264
core/tavern.go
264
core/tavern.go
@ -2,76 +2,29 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"repositories.action2quare.com/ayo/gocommon"
|
"repositories.action2quare.com/ayo/gocommon"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/flagx"
|
||||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/session"
|
||||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var devflag = flagx.Bool("dev", false, "")
|
||||||
defaultMaxMemory = 32 << 10 // 32 KB
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeBsonArr(w io.Writer, src []bson.M) error {
|
|
||||||
return writeBsonDoc(w, bson.M{
|
|
||||||
"r": src,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeBsonDoc[T any](w io.Writer, src T) error {
|
|
||||||
rw, err := bsonrw.NewBSONValueWriter(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := bson.NewEncoder(rw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return enc.Encode(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBsonDoc(r io.Reader, src any) error {
|
|
||||||
body, err := io.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(body) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := bson.NewDecoder(bsonrw.NewBSONDocumentReader(body))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = decoder.Decode(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TavernConfig struct {
|
type TavernConfig struct {
|
||||||
gocommon.RegionStorageConfig `json:",inline"`
|
session.SessionConfig `json:",inline"`
|
||||||
|
Group map[string]configDocument `json:"tavern_group_types"`
|
||||||
GroupTypes map[string]*groupConfig `json:"tavern_group_types"`
|
|
||||||
MaingateApiToken string `json:"maingate_api_token"`
|
MaingateApiToken string `json:"maingate_api_token"`
|
||||||
RedisURL string `json:"tavern_redis_url"`
|
RedisURL string `json:"tavern_redis_url"`
|
||||||
macAddr string
|
macAddr string
|
||||||
@ -80,17 +33,10 @@ type TavernConfig struct {
|
|||||||
var config TavernConfig
|
var config TavernConfig
|
||||||
|
|
||||||
type Tavern struct {
|
type Tavern struct {
|
||||||
subTaverns []*subTavern
|
|
||||||
wsh *wshandler.WebsocketHandler
|
wsh *wshandler.WebsocketHandler
|
||||||
}
|
|
||||||
|
|
||||||
type subTavern struct {
|
|
||||||
mongoClient gocommon.MongoClient
|
mongoClient gocommon.MongoClient
|
||||||
redisClient *redis.Client
|
redison *gocommon.RedisonHandler
|
||||||
wsh *wshandler.WebsocketHandler
|
httpApiBorker gocommon.HttpApiBroker
|
||||||
region string
|
|
||||||
groups map[string]group
|
|
||||||
methods map[string]reflect.Method
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMacAddr() (string, error) {
|
func getMacAddr() (string, error) {
|
||||||
@ -110,19 +56,14 @@ func getMacAddr() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New :
|
// New :
|
||||||
func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *TavernConfig) (*Tavern, error) {
|
func New(context context.Context, wsh *wshandler.WebsocketHandler) (*Tavern, error) {
|
||||||
if inconfig == nil {
|
if err := gocommon.LoadConfig(&config); err != nil {
|
||||||
var loaded TavernConfig
|
return nil, logger.ErrorWithCallStack(err)
|
||||||
if err := gocommon.LoadConfig(&loaded); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
inconfig = &loaded
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config = *inconfig
|
|
||||||
macaddr, err := getMacAddr()
|
macaddr, err := getMacAddr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, logger.ErrorWithCallStack(err)
|
||||||
}
|
}
|
||||||
config.macAddr = macaddr
|
config.macAddr = macaddr
|
||||||
tv := &Tavern{
|
tv := &Tavern{
|
||||||
@ -131,141 +72,104 @@ func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *Tav
|
|||||||
|
|
||||||
if err = tv.prepare(context); err != nil {
|
if err = tv.prepare(context); err != nil {
|
||||||
logger.Println("tavern prepare() failed :", err)
|
logger.Println("tavern prepare() failed :", err)
|
||||||
return nil, err
|
return nil, logger.ErrorWithCallStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tv, nil
|
return tv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tv *Tavern) Cleanup() {
|
func (tv *Tavern) Cleanup() {
|
||||||
for _, st := range tv.subTaverns {
|
tv.mongoClient.Close()
|
||||||
st.mongoClient.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tv *Tavern) prepare(ctx context.Context) error {
|
func (tv *Tavern) prepare(ctx context.Context) error {
|
||||||
for region, addr := range config.RegionStorage {
|
redisClient, err := gocommon.NewRedisClient(config.RedisURL)
|
||||||
var dbconn gocommon.MongoClient
|
|
||||||
var err error
|
|
||||||
var groupinstance group
|
|
||||||
|
|
||||||
var tmp *subTavern
|
|
||||||
methods := make(map[string]reflect.Method)
|
|
||||||
tp := reflect.TypeOf(tmp)
|
|
||||||
for i := 0; i < tp.NumMethod(); i++ {
|
|
||||||
method := tp.Method(i)
|
|
||||||
methods[method.Name] = method
|
|
||||||
}
|
|
||||||
|
|
||||||
redisClient, err := gocommon.NewRedisClient(addr.Redis["tavern"])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return logger.ErrorWithCallStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sub := &subTavern{
|
tv.redison = gocommon.NewRedisonHandler(redisClient.Context(), redisClient)
|
||||||
wsh: tv.wsh,
|
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(tv, "tv"))
|
||||||
mongoClient: dbconn,
|
|
||||||
redisClient: redisClient,
|
if cfg, ok := config.Group["chat"]; ok {
|
||||||
region: region,
|
chat := new(groupChat)
|
||||||
methods: methods,
|
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"))
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := make(map[string]group)
|
if cfg, ok := config.Group["party"]; ok {
|
||||||
for typename, cfg := range config.GroupTypes {
|
party := new(groupParty)
|
||||||
cfg.Name = typename
|
if err := party.Initialize(tv, cfg); err != nil {
|
||||||
if cfg.Transient {
|
return logger.ErrorWithCallStack(err)
|
||||||
groupinstance, err = cfg.prepareInMemory(ctx, typename, sub)
|
|
||||||
//} else {
|
|
||||||
// TODO : db
|
|
||||||
// if !dbconn.Connected() {
|
|
||||||
// dbconn, err = gocommon.NewMongoClient(ctx, url.Mongo, region)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// groupinstance, err = cfg.preparePersistent(ctx, region, dbconn, tv.wsh)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(party, "party"))
|
||||||
return err
|
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(party, "party"))
|
||||||
}
|
}
|
||||||
groups[typename] = groupinstance
|
|
||||||
}
|
|
||||||
sub.groups = groups
|
|
||||||
|
|
||||||
tv.subTaverns = append(tv.subTaverns, sub)
|
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"))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
|
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
|
||||||
for _, sub := range tv.subTaverns {
|
// tv.wsh.RegisterReceiver(tv)
|
||||||
tv.wsh.RegisterReceiver(sub.region, sub)
|
pattern := gocommon.MakeHttpHandlerPattern(prefix, "api")
|
||||||
var pattern string
|
serveMux.HandleFunc(pattern, tv.api)
|
||||||
if sub.region == "default" {
|
|
||||||
pattern = gocommon.MakeHttpHandlerPattern(prefix, "api")
|
|
||||||
} else {
|
|
||||||
pattern = gocommon.MakeHttpHandlerPattern(prefix, sub.region, "api")
|
|
||||||
}
|
|
||||||
serveMux.HandleFunc(pattern, sub.api)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sub *subTavern) OnClientMessageReceived(sender *wshandler.Sender, messageType wshandler.WebSocketMessageType, body io.Reader) {
|
func (tv *Tavern) EnterChannel(ctx wshandler.ApiCallContext) {
|
||||||
if messageType == wshandler.Connected {
|
tv.wsh.EnterRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
|
||||||
logger.Println("OnClientMessageReceived : connected ", sender.Accid.Hex())
|
|
||||||
} else if messageType == wshandler.Disconnected {
|
|
||||||
logger.Println("OnClientMessageReceived : disconnected ", sender.Accid.Hex())
|
|
||||||
} else if messageType == wshandler.BinaryMessage {
|
|
||||||
var msg map[string][]any
|
|
||||||
dec := json.NewDecoder(body)
|
|
||||||
if err := dec.Decode(&msg); err == nil {
|
|
||||||
for cmd, args := range msg {
|
|
||||||
switch cmd {
|
|
||||||
case "EnterChannel":
|
|
||||||
sub.wsh.EnterRoom(sub.region, args[0].(string), sender.Accid)
|
|
||||||
|
|
||||||
case "LeaveChannel":
|
|
||||||
sub.wsh.LeaveRoom(sub.region, args[0].(string), sender.Accid)
|
|
||||||
|
|
||||||
case "UpdateGroupMemberDocument":
|
|
||||||
typename := args[0].(string)
|
|
||||||
gidobj, _ := primitive.ObjectIDFromHex(args[1].(string))
|
|
||||||
doc := args[2].(map[string]any)
|
|
||||||
if group := sub.groups[typename]; group != nil {
|
|
||||||
group.UpdateMemberDocument(gidobj, sender.Accid, doc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "UpdateGroupDocument":
|
func (tv *Tavern) LeaveChannel(ctx wshandler.ApiCallContext) {
|
||||||
typename := args[0].(string)
|
tv.wsh.LeaveRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
|
||||||
gidobj, _ := primitive.ObjectIDFromHex(args[1].(string))
|
|
||||||
doc := args[2].(map[string]any)
|
|
||||||
if group := sub.groups[typename]; group != nil {
|
|
||||||
group.UpdateGroupDocument(gidobj, doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sub *subTavern) OnRoomCreated(region, name string) {
|
func (tv *Tavern) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||||
_, err := sub.redisClient.Persist(context.Background(), name).Result()
|
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 {
|
if err != nil {
|
||||||
logger.Println("OnRoomCreate Persist failed :", err)
|
logger.Println("OnClientMessageReceived HSet error :", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sub *subTavern) OnRoomDestroyed(region, name string) {
|
func (tv *Tavern) ClientDisconnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||||
_, err := sub.redisClient.Expire(context.Background(), name, 3600*time.Second).Result()
|
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 {
|
if err != nil {
|
||||||
logger.Println("OnRoomDestroyed Persist failed :", err)
|
logger.Println("OnRoomDestroyed JSONNumIncrBy failed :", err)
|
||||||
|
} else if cnt == 0 {
|
||||||
|
tv.redison.Del(tv.redison.Context(), "_ref_"+name)
|
||||||
|
tv.redison.JSONDel(name, "$")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
|
func (tv *Tavern) api(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
s := recover()
|
s := recover()
|
||||||
if s != nil {
|
if s != nil {
|
||||||
@ -284,29 +188,5 @@ func (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
operation := r.URL.Query().Get("operation")
|
tv.httpApiBorker.Call(w, r)
|
||||||
if len(operation) == 0 {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
method, ok := sub.methods[operation]
|
|
||||||
if !ok {
|
|
||||||
// 없는 operation
|
|
||||||
logger.Println("fail to call api. operation is not valid :", operation)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.PostForm == nil {
|
|
||||||
r.ParseMultipartForm(defaultMaxMemory)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []reflect.Value{
|
|
||||||
reflect.ValueOf(sub),
|
|
||||||
reflect.ValueOf(w),
|
|
||||||
reflect.ValueOf(r),
|
|
||||||
}
|
|
||||||
|
|
||||||
method.Func.Call(args)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -30,11 +31,46 @@ func TestPubSub(t *testing.T) {
|
|||||||
msg, err := pubsub.ReceiveMessage(context.Background())
|
msg, err := pubsub.ReceiveMessage(context.Background())
|
||||||
fmt.Println(msg.Payload, err)
|
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) {
|
func TestReJSON(t *testing.T) {
|
||||||
rc := redis.NewClient(&redis.Options{Addr: "192.168.8.94:6380"})
|
rc := redis.NewClient(&redis.Options{Addr: "192.168.8.94:6380"})
|
||||||
rh := gocommon.NewRedisonHandler(context.Background(), rc)
|
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{
|
testDoc := map[string]any{
|
||||||
"members": map[string]any{
|
"members": map[string]any{
|
||||||
"mid2": map[string]any{
|
"mid2": map[string]any{
|
||||||
@ -48,7 +84,7 @@ func TestReJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
gd := groupDoc{
|
gd := partyDoc{
|
||||||
id: primitive.NewObjectID(),
|
id: primitive.NewObjectID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +100,7 @@ func TestReJSON(t *testing.T) {
|
|||||||
logger.Println(rh.JSONGetInt64("jsontest", "$.members..exp"))
|
logger.Println(rh.JSONGetInt64("jsontest", "$.members..exp"))
|
||||||
logger.Println(rh.JSONObjKeys("jsontest", "$.members"))
|
logger.Println(rh.JSONObjKeys("jsontest", "$.members"))
|
||||||
|
|
||||||
err := rh.JSONMSet("jsontest", map[string]any{
|
err = rh.JSONMSet("jsontest", map[string]any{
|
||||||
"$.members.mid1.key": "newval",
|
"$.members.mid1.key": "newval",
|
||||||
"$.members.mid2.key": "newval",
|
"$.members.mid2.key": "newval",
|
||||||
})
|
})
|
||||||
|
|||||||
4
go.mod
4
go.mod
@ -4,8 +4,9 @@ go 1.20
|
|||||||
|
|
||||||
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-20230719003337-29b2f258507d
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20231016010153-0f302437251f
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -13,7 +14,6 @@ require (
|
|||||||
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.4 // 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/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
|
||||||
|
|||||||
36
go.sum
36
go.sum
@ -104,31 +104,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
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-20230715080833-f0f459332d1a h1:n2FF/GQYtCsi57Lh5m9LyQ2IZQ8pIppscBzhpvugmZg=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946 h1:YSvgTNuHeKis37+FfOvzVLYCaXQ0oF+CWBTy4bRqq3g=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230715080833-f0f459332d1a/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230716073702-8f6c87a8aeb8 h1:+wfozysATxEl9NOm03gUF7/kpAr3Chxjn5wjCnJsfQw=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230919095017-49a3722a7e09 h1:KYpRt/gjwYePPIWLit+PVCF7V3FGyZXQ+0U7bTKH+sE=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230716073702-8f6c87a8aeb8/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230919095017-49a3722a7e09/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230716093911-66aea48fb732 h1:Aq4E8kn1mN5z4ZpRYo5VFj2KektVNrTTuk0HocYMDCk=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20231012030311-0fb806bfda1e h1:WQmrM33bgcAJ1PRz/uaz1tyZxpSDOuuGl0ICJdh4K9U=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230716093911-66aea48fb732/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20231012030311-0fb806bfda1e/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230717084540-29843802ff0e h1:/eG6tAQzEaN178Aib+/erjHrE/+IjIVLRSmP4gx6D7E=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20231016010153-0f302437251f h1:S7J6nGOwe5c+JEXbdI0rZshVvEXZCFjUAY9e7Wd91tY=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230717084540-29843802ff0e/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20231016010153-0f302437251f/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718004527-4b35e0e6386b h1:K3YQXnVP/W6LzwGzqOxwKmFUD5IrrNPEWYcN/fSinck=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718004527-4b35e0e6386b/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718005518-289af24a8ffa h1:YmzJ1YccK3BxC/NbfB11SEUG1S6Lkz6ejg4kS3q/3Qc=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718005518-289af24a8ffa/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718020415-82abcddb497b h1:baO9csa0Esnp7UW+L8zJW/ygpjGHRve4rU2/1pVvXQg=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718020415-82abcddb497b/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718020838-c21017d2cd8b h1:FqLKDrFji0+giFwAJ3oV6dIOR6Sd/aaay76WgWIEVR8=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718020838-c21017d2cd8b/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718032106-40a603522d40 h1:VyFfS0d6pTX2HbZoDHOxJwag4aVSLOh/LrQXqfSJLBg=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718032106-40a603522d40/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718083804-d724cc84fa94 h1:iQPrRcZ6XfFblpVHxe/CIoWyTj7imF+3edIGSX6ZMM8=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718083804-d724cc84fa94/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718084512-89fa9e4ac585 h1:Wy6qjZ0uHfp02/H688zotRfzYGRPjun7Qay0Z9B/hSg=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718084512-89fa9e4ac585/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718105124-72a683fed2c0 h1:8LmRo2nKaLi4QCmO/agSpNTmCD0EdwFycjHjOweQJp8=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230718105124-72a683fed2c0/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230719003101-256bfd030c29 h1:ADScrqJgmk/TfyOu/6oXD3WkSH8sh3Bw360O8GKuEV8=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230719003101-256bfd030c29/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230719003337-29b2f258507d h1:eMzrvVkQfbs5X5dcw80TGGKtJ+6XELl7zNsWiuq4gzs=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230719003337-29b2f258507d/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
|
|
||||||
|
|||||||
16
main.go
16
main.go
@ -9,8 +9,9 @@ import (
|
|||||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||||
"repositories.action2quare.com/ayo/tavern/core"
|
"repositories.action2quare.com/ayo/tavern/core"
|
||||||
|
|
||||||
common "repositories.action2quare.com/ayo/gocommon"
|
"repositories.action2quare.com/ayo/gocommon"
|
||||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
var prefix = flagx.String("prefix", "", "")
|
var prefix = flagx.String("prefix", "", "")
|
||||||
@ -20,22 +21,27 @@ func main() {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
var config core.TavernConfig
|
var config core.TavernConfig
|
||||||
if err := common.LoadConfig(&config); err != nil {
|
if err := gocommon.LoadConfig(&config); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wsh, err := wshandler.NewWebsocketHandler()
|
consumer, err := session.NewConsumerWithConfig(ctx, config.SessionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tv, err := core.New(ctx, wsh, &config); err != nil {
|
wsh, err := wshandler.NewWebsocketHandler(consumer, config.RedisURL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
tv.RegisterHandlers(ctx, serveMux, *prefix)
|
tv.RegisterHandlers(ctx, serveMux, *prefix)
|
||||||
server := common.NewHTTPServer(serveMux)
|
server := gocommon.NewHTTPServer(serveMux)
|
||||||
logger.Println("tavern is started")
|
logger.Println("tavern is started")
|
||||||
wsh.Start(ctx)
|
wsh.Start(ctx)
|
||||||
server.Start()
|
server.Start()
|
||||||
|
|||||||
Reference in New Issue
Block a user