Compare commits

..

37 Commits

Author SHA1 Message Date
ea28d93f7d 모듈 업데이트 2023-09-08 18:19:42 +09:00
374992d55f 내 파티에서 파티원을 추방 못하는 문제 수정 2023-09-08 16:15:06 +09:00
a9b0cf2493 파티 초대 accept와 deny 분리 2023-09-08 16:09:12 +09:00
3dde7ccaf5 bson을 json으로 통일(일단은) 2023-09-08 15:29:01 +09:00
1d14fb659d 모듈 업데이트 2023-09-08 11:50:52 +09:00
ce50657734 WebsocketApiHandler로 변경 2023-09-08 11:37:50 +09:00
4a51f7d433 서버간 api 호출 간소화 2023-09-05 17:14:07 +09:00
6410056c87 모듈 업데이트 2023-09-04 15:31:35 +09:00
f7173a4f49 파티를 옮길 때 기존 파티에 남아있는 거 처럼 보이는 문제 수정 2023-09-04 10:24:17 +09:00
9b0c4a121a body와 query 분리, 모듈 업데이트 2023-09-01 12:33:22 +09:00
e0504f688a 모듈 업데이트 2023-08-31 21:17:51 +09:00
fd1502e52a session consumer로 교체 2023-08-31 21:02:19 +09:00
cb5cd280b9 모듈 업데이트 2023-08-23 23:02:37 +09:00
a4923fa0a1 모듈 업데이트 2023-08-23 22:44:55 +09:00
2534aa2a36 파티 초대 실패 수정 2023-08-23 20:06:21 +09:00
a404764abf 모듈 업데이트 2023-08-14 21:42:56 +09:00
9a734f9f4d 타입 변경 2023-08-14 15:34:55 +09:00
884fb0080f UpdateChannelDocument 제거 2023-08-14 14:44:56 +09:00
a08353a920 chat document update 2023-08-12 15:26:32 +09:00
5e953d6131 모듈 업데이트 2023-08-11 19:26:44 +09:00
3d2ed40b1e config.json 추가 2023-08-02 17:34:09 +09:00
9de686e828 BroadcastMessageOnChannel api 추가 2023-08-02 16:22:18 +09:00
12ddd2cbfb 쫓아내기 버그 수정 / 채팅 채널 expire되는 문제 수정 2023-08-02 00:55:36 +09:00
2b0e60a06a 모듈 업데이트 2023-08-01 14:19:25 +09:00
922f55740b 모듈 업데이트 2023-07-28 15:36:39 +09:00
b6262515e0 대상 채널 선택 2023-07-27 21:57:26 +09:00
90d0fd319d 채팅 채널 입장 추가 2023-07-27 17:45:51 +09:00
310397dd2b party로 그룹 변경 2023-07-25 18:11:02 +09:00
bb6a741d63 멤버 접속종료 알림 2023-07-20 01:36:55 +09:00
4f1c79d3b7 접속 종료를 그룹에 알림 2023-07-19 17:02:49 +09:00
07cb4848fe gocommon master로 변경 2023-07-19 10:12:22 +09:00
8dded8b907 Squashed commit of the following:
commit 8e1b232d57
Author: mountain <mountain@action2quare.com>
Date:   Wed Jul 19 09:37:02 2023 +0900

    InMemory 그룹을 redis로 변경

commit 01da5bb3a4
Author: mountain <mountain@action2quare.com>
Date:   Tue Jul 18 01:31:39 2023 +0900

    body를 marshaling하고 클라이언트에서 flatten함

commit ba61a11659
Author: mountain <mountain@action2quare.com>
Date:   Mon Jul 17 17:47:07 2023 +0900

    gob 등록

commit 67cca13326
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 18:41:24 2023 +0900

    모듈 업데이트

commit 272c696c59
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 17:29:21 2023 +0900

    json value 다시 되돌림

commit aa568ec3fa
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 17:26:19 2023 +0900

    SetOption 타입 변경

commit b9c4d8b21b
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 17:15:08 2023 +0900

    objvalue marshalling 수정

commit 99834c1461
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 17:01:06 2023 +0900

    objlen 수정

commit 592112219e
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 16:38:05 2023 +0900

    gocommon 업데이트

commit 62485b6d54
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 15:36:20 2023 +0900

    redis json 마이그레이션 완료

commit d36dd13bb7
Author: mountain <mountain@action2quare.com>
Date:   Sun Jul 16 02:51:41 2023 +0900

    redis stack 사용
2023-07-19 09:37:40 +09:00
454aae5294 멤버 도큐먼트를 직접 메시지 보내는 방식으로 변경 2023-07-14 15:09:20 +09:00
30005ea0e3 binary message는 커맨드 2023-07-14 01:27:11 +09:00
9df68a4d07 sendCloseMessage대신 LeaveRoomMessage 2023-07-13 17:09:47 +09:00
8f2860165b 모듈 업데이트 2023-07-13 15:41:47 +09:00
3a1d0da531 gocommon master로 변경 2023-07-11 17:42:26 +09:00
13 changed files with 1513 additions and 2337 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.vscode/
__debug_bin.exe
*.log
config.json

View File

@ -1,65 +1,44 @@
{
"region_storage" : {
"default" : {
"mongo" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
"redis" : {
"url" : "redis://192.168.8.94:6379",
"offset" : {
"cache" : 0,
"session" : 1,
"ranking" : 2
"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"
}
}
},
"dev" : {
"mongo" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
"redis" : {
"url" : "redis://192.168.8.94:6379",
"offset" : {
"cache" : 0,
"session" : 1,
"ranking" : 2
}
}
}
},
"maingate_mongodb_url" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
"maingate_service_url" : "http://localhost/maingate",
"maingate_api_token" : "63d08aa34f0162622c11284b",
"maingate_session_storage": "redis://192.168.8.94:6380/1",
"maingate_session_ttl" : 3600,
"maingate_api_token": "63d08aa34f0162622c11284b",
"tavern_service_url" : "http://localhost/tavern",
"tavern_group_types" : {
"subjugate" : {
"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
"tavern_service_url": "http://localhost/tavern",
"tavern_group_types": {
"party": {
"max_member": 3,
"invite_ttl": 30
},
"lobby" : {
"max_member" : 3,
"invitee_exlusive" : true,
"invitee_is_member" : true,
"transient" : true,
"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)"
}
},
"ws_sync_pipeline" : "redis://192.168.8.94:6379/3",
"services" : {
"kingdom" : {
"개발중" : {
"url" :"http://localhost/warehouse/dev",
"development" : true
},
"개인서버" : {
"url" : "http://localhost/warehouse/private",
"development" : false
}
}
}

View File

@ -1,750 +0,0 @@
package core
import (
"context"
"encoding/json"
"io"
"net/http"
common "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"github.com/go-redis/redis/v8"
"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 {
tidobj, err = group.Join(gidobj, midobj, tidobj, doc)
}
if err == nil {
json.NewEncoder(w).Encode(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.Error("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.Error("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")
tid, ok := common.ReadObjectIDFormValue(r.Form, "tid")
if !ok {
logger.Println("CancelInvitation failed. form value 'tid' is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
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
}
gidbytes, err := group.AcceptInvitation(gid, mid, tid, member)
if err != nil {
logger.Error("AcceptInvitation failed. group.AcceptInvitation returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte(gidbytes.Hex()))
}
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) QueryOnlineGroup(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename]
if group == nil {
logger.Println("QueryOnlineGroup failed. group type is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
var cmd *redis.StringSliceCmd
scoreStart, _ := common.ReadStringFormValue(r.Form, "score_start")
scoreStop, _ := common.ReadStringFormValue(r.Form, "score_stop")
if len(scoreStart) > 0 || len(scoreStop) > 0 {
if len(scoreStart) == 0 {
scoreStart = "-inf"
}
if len(scoreStop) == 0 {
scoreStop = "+inf"
}
cmd = sub.wsh.RedisSync.ZRangeArgs(context.Background(), redis.ZRangeArgs{
Key: onlineGroupQueryKey(typename),
ByScore: true,
Start: scoreStart,
Stop: scoreStop,
Rev: true,
Count: 1,
})
} else {
// 아무거나
cmd = sub.wsh.RedisSync.ZRandMember(context.Background(), onlineGroupQueryKey(typename), 1, false)
}
result, err := cmd.Result()
if err != nil {
logger.Error("QueryOnlineGroup failed. redid.ZRandMember returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
writeBsonDoc(w, bson.M{"r": result})
}
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("json 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("json marshal failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
// QueryGroupMembers : 그룹내 멤버 조회
// - type : 그룹 타입
// - 그룹 타입에 맞는 키(주로 _id)
// - projection : select할 필드. ,로 구분
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. group type is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("QueryGroupMembers failed. _id is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
midobj, _ := common.ReadObjectIDFormValue(r.Form, "mid")
var after primitive.Timestamp
if ts, ok := common.ReadStringFormValue(r.Form, "after"); ok && ts != "0.0" {
after = common.DotStringToTimestamp(ts)
}
projection, _ := common.ReadStringFormValue(r.Form, "projection")
result, err := group.QueryMembers(gidobj, midobj, projection, after)
if err != nil {
logger.Error("QueryGroupMembers failed. FindAll err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if result == nil {
return
}
if err := writeBsonDoc(w, result); err != nil {
logger.Error("QueryGroupMembers failed. writeBsonArr err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (sub *subTavern) QueryGroupMember(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename]
if group == nil {
logger.Println("QueryGroupMember failed. group type is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("QueryGroupMember failed. gid is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
mid, midok := common.ReadObjectIDFormValue(r.Form, "mid")
tid, tidok := common.ReadObjectIDFormValue(r.Form, "tid")
if !midok && !tidok {
// 둘 중 하나는 있어야지
logger.Println("QueryGroupMember failed. tid and mid are both missing")
w.WriteHeader(http.StatusBadRequest)
return
}
projection, _ := common.ReadStringFormValue(r.Form, "projection")
result, err := group.QueryMember(gid, mid, tid, projection)
if err != nil {
logger.Println("QueryGroupMember failed. group.QueryMember returns err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if result == nil {
return
}
if err := writeBsonDoc(w, result); err != nil {
logger.Error("QueryGroupMember failed. writeBsonDoc err :", 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")
tid, tidok := common.ReadObjectIDFormValue(r.Form, "tid")
if !midok && !tidok {
// 둘 중 하나는 있어야지
logger.Println("LeaveGroup failed. tid and mid are both missing")
w.WriteHeader(http.StatusBadRequest)
return
}
if err := group.Leave(gid, mid, tid); 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
}
body, err := io.ReadAll(r.Body)
if err != nil {
logger.Error("UpdateGroupDocument failed. readBsonDoc err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if err := group.UpdateGroupDocument(gid, body); err != nil {
logger.Error("UpdateGroupDocument failed. group.UpdateGroupDocument returns err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
func (sub *subTavern) PauseGroupMember(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
}
group.PauseMember(gidobj, midobj)
}
func (sub *subTavern) DropPausedMember(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename]
if group == nil {
logger.Println("DropDeadMember failed. type is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("DropDeadMember failed. gid is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("DropDeadMember failed. mid is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
if err := group.DropPausedMember(gid, mid); err != nil {
logger.Error("DropDeadMember failed. group.DropDeadMember returns err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
// func (sub *subTavern) deliveryMessageHandler(deliveryChan <-chan wshandler.DeliveryMessage) {
// defer func() {
// r := recover()
// if r != nil {
// logger.Error(r)
// }
// }()
// redisSync := sub.wsh.RedisSync
// for msg := range deliveryChan {
// mid := msg.Alias
// if msg.Body != nil {
// buffer := msg.Body
// var channame string
// for i, ch := range buffer {
// if ch == 0 {
// channame = string(buffer[:i])
// buffer = buffer[i+1:]
// break
// }
// }
// if len(channame) == 0 {
// continue
// }
// buffer = append(mid[:], buffer...)
// _, err := redisSync.Publish(context.Background(), channame, buffer).Result()
// if err != nil {
// logger.Error(err)
// }
// }
// if len(msg.Command) > 0 {
// switch msg.Command {
// case "pause":
// gidtype := msg.Conn.GetTag("gid")
// if len(gidtype) > 0 {
// tokens := strings.SplitN(gidtype, "@", 2)
// gidobj, _ := primitive.ObjectIDFromHex(tokens[0])
// gtype := tokens[1]
// group := sub.groups[gtype]
// if group != nil {
// group.PauseMember(gidobj, msg.Alias, msg.Conn)
// }
// }
// }
// }
// }
// logger.Println("delivery chan fin")
// }

View File

@ -1,46 +1,3 @@
package core
import (
"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, ticketID primitive.ObjectID, doc bson.M) (newTicketID primitive.ObjectID, err error)
FindTicketID(groupID primitive.ObjectID, memberID primitive.ObjectID) primitive.ObjectID
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, ticketID primitive.ObjectID, member bson.M) (primitive.ObjectID, 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)
QueryMembers(groupID primitive.ObjectID, requesterID primitive.ObjectID, projection string, after primitive.Timestamp) (map[string]bson.M, error)
QueryMember(groupID primitive.ObjectID, memberID primitive.ObjectID, ticketID primitive.ObjectID, projection string) (bson.M, error)
Leave(groupID primitive.ObjectID, memberID primitive.ObjectID, ticketID primitive.ObjectID) error
DropPausedMember(groupID primitive.ObjectID, memberID primitive.ObjectID) error
PauseMember(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, body []byte) error
}
type configDocument map[string]any

402
core/group_chat.go Normal file
View File

@ -0,0 +1,402 @@
package core
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/go-redis/redis/v8"
"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"`
emptyJson 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))
_, 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
}
}
return nil
}
func (gc *groupChat) ClientConnected(ctx wshandler.ApiCallContext) {
gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel", map[string]any{})
}
func (gc *groupChat) ClientDisconnected(ctx wshandler.ApiCallContext) {
docs, _ := gc.rh.JSONGetDocuments(ctx.CallBy.Accid.Hex(), "$.channel")
if len(docs) > 0 {
for k, v := range docs[0] {
typename := k
chanid := v.(string)
gc.leaveRoom(chanid, ctx.CallBy.Accid)
if k == "public" {
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
} else {
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"sender": ctx.CallBy.Alias, "typename": typename},
Tag: []string{"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.JSONGetInt64(chanid, "$.size")
if err != nil || len(size) == 0 {
logger.Println("JSONGetInt64 failed :", chanid, err)
} else if size[0] < cfg.Capacity {
// 입장
newsize, err := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
if err == nil {
gc.enterRoom(chanid, ctx.CallBy.Accid)
gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel.public", chanid)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"size": newsize[0]},
Tag: []string{"ChattingChannelProperties"},
})
}
} 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)
}
}
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)
} else {
// 내가 이미 private channel에 있다는 것을 다른 사람들에게 알려주기 위함
}
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + channel,
Body: map[string]any{
"sender": ctx.CallBy.Alias,
"msg": reason,
"typename": typename,
},
Tag: []string{"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, "typename": typename},
Tag: []string{"LeavePrivateChannel"},
})
}
}
// func (gc *groupChat) ClientMessageReceived(sender *wshandler.Sender, mt wshandler.WebSocketMessageType, message any) {
// if mt == wshandler.Disconnected {
// if _, err := gc.rh.Del(gc.rh.Context(), accidHex(sender.Accid)).Result(); err != nil {
// logger.Println(err)
// }
// } else if mt == wshandler.BinaryMessage {
// commandline := message.([]any)
// cmd := commandline[0].(string)
// args := commandline[1:]
// switch cmd {
// case "EnterPublicChannel":
// chanid := args[0].(string)
// if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
// size, err := gc.rh.JSONGetInt64(chanid, "$.size")
// if err != nil || len(size) == 0 {
// logger.Println("JSONGetInt64 failed :", chanid, err)
// } else if size[0] < cfg.Capacity {
// // 입장
// newsize, err := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
// if err == nil {
// gc.enterRoom(chanid, sender.Accid)
// sender.RegistDisconnectedCallback(chanid, func() {
// size, err := gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
// if err == nil {
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
// Target: "#" + chanid,
// Body: map[string]any{"size": size},
// Tag: []string{"ChattingChannelProperties"},
// })
// }
// })
// gc.rh.HSet(gc.rh.Context(), accidHex(sender.Accid), "cc_pub", chanid)
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
// Target: "#" + chanid,
// Body: map[string]any{"size": newsize[0]},
// Tag: []string{"ChattingChannelProperties"},
// })
// }
// } else {
// // 풀방
// logger.Println("chatting channel is full :", chanid, size, cfg.Capacity)
// }
// } else {
// logger.Println("chatting channel not valid :", chanid)
// }
// case "LeavePublicChannel":
// chanid := args[0].(string)
// gc.rh.HDel(gc.rh.Context(), accidHex(sender.Accid), "cc_pub")
// gc.leaveRoom(chanid, sender.Accid)
// if f := sender.PopDisconnectedCallback(chanid); f != nil {
// f()
// }
// case "TextMessage":
// chanid := args[0].(string)
// msg := args[1].(string)
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
// Target: "#" + chanid,
// Body: map[string]any{"sender": sender.Alias, "msg": msg},
// Tag: []string{"TextMessage"},
// })
// case "EnterPrivateChannel":
// typename := args[0].(string)
// channel := args[1].(string)
// var reason string
// if len(args) > 2 {
// reason = args[2].(string)
// }
// if len(reason) > 0 {
// // 수락
// // 이거 HSet 하면 안되겠는데? JSONSet해야할 듯?
// ok, err := gc.rh.HSetNX(gc.rh.Context(), accidHex(sender.Accid), "cc_"+typename, channel).Result()
// if err != nil || !ok {
// // 이미 다른 private channel 참여 중
// logger.Println("EnterPrivateChannel failed. HSetNX return err :", err, sender.Accid.Hex(), typename, channel)
// return
// }
// gc.enterRoom(channel, sender.Accid)
// sender.RegistDisconnectedCallback(channel, func() {
// gc.rh.JSONDel(channel, "$."+sender.Accid.Hex())
// // 이거 HDel 하면 안되겠는데? JSONDel해야할 듯?
// cnt, _ := gc.rh.HDel(gc.rh.Context(), accidHex(sender.Accid), "cc_"+typename).Result()
// if cnt > 0 {
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
// Target: "#" + channel,
// Body: map[string]any{"sender": sender.Alias, "typename": typename},
// Tag: []string{"LeavePrivateChannel"},
// })
// }
// })
// } else {
// // 내가 이미 private channel에 있다는 것을 다른 사람들에게 알려주기 위함
// }
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
// Target: "#" + channel,
// Body: map[string]any{
// "sender": sender.Alias,
// "msg": reason,
// "typename": typename,
// },
// Tag: []string{"EnterPrivateChannel"},
// })
// case "LeavePrivateChannel":
// channel := args[1].(string)
// gc.leaveRoom(channel, sender.Accid)
// if f := sender.PopDisconnectedCallback(channel); f != nil {
// f()
// }
// }
// }
// }
func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Request) {
var data struct {
Prefix string `bson:"prefix"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &data); err != nil {
logger.Println("FetchChattingChannels failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
prefix := data.Prefix
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 {
rows = append(rows, onechan.(string))
}
}
if len(rows) == 0 {
w.Write([]byte("[]"))
} else if len(rows) == 1 {
w.Write([]byte(rows[0]))
} else {
first := rows[0]
w.Write([]byte(first[:len(first)-1]))
for i := 1; i < len(rows); i++ {
mid := rows[i]
w.Write([]byte(","))
w.Write([]byte(mid[1 : len(mid)-1]))
}
w.Write([]byte("]"))
}
}
func (gc *groupChat) QueryPlayerChattingChannel(w http.ResponseWriter, r *http.Request) {
var data struct {
Accid string `bson:"accid"`
Typename string `bson:"typename"`
}
err := gocommon.ReadJsonDocumentFromBody(r.Body, &data)
if err != nil {
logger.Println("QueryPlayerChattingChannel failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
accid := data.Accid
sub, err := gc.rh.JSONGetDocuments(accid, "$.channel")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if len(sub) > 0 {
enc := json.NewEncoder(w)
enc.Encode(sub[0])
}
}
func (gc *groupChat) SendMessageOnChannel(w http.ResponseWriter, r *http.Request) {
var msg wshandler.UpstreamMessage
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &msg); err != nil {
logger.Println("SendMessageOnChannel failed. ReadJsonDocumentFromBody return err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
gc.sendUpstreamMessage(&msg)
}

File diff suppressed because it is too large Load Diff

844
core/group_party.go Normal file
View File

@ -0,0 +1,844 @@
package core
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-redis/redis/v8"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type accountID = primitive.ObjectID
type groupID = primitive.ObjectID
func makeTid(gid groupID, in accountID) string {
var out primitive.ObjectID
for i := range in {
out[12-i-1] = gid[i] ^ in[12-i-1]
}
return out.Hex()
}
func midFromTid(gid groupID, in string) accountID {
h, _ := primitive.ObjectIDFromHex(in)
var out accountID
for i := range h {
out[12-i-1] = gid[i] ^ h[12-i-1]
}
return out
}
type Invitation struct {
GroupID groupID `json:"_gid"`
TicketID string `json:"_tid"`
Inviter bson.M `json:"_inviter"` // memberDoc.Body
ExpireAtUTC int64 `json:"_expire_at_utc"`
}
// 플레이어한테 공유하는 멤버 정보
type memberDoc struct {
Body bson.M `json:"_body"`
Invite bool `json:"_invite"`
InviteExpire int64 `json:"_invite_exp"`
}
type InvitationFail bson.M
type 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(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 *groupDoc) 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 *groupDoc) 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 *groupDoc) removeMember(mid accountID) error {
return gd.removeMemberByTid(gd.tid(mid))
}
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 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 `bson:"gid"`
Mid primitive.ObjectID `bson:"mid"`
Character bson.M `bson:"character"`
First bool `bson:"first"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &data); err != nil {
logger.Println("JoinParty failed. ReadJsonDocumentFromBody 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.Write([]byte("{}"))
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"},
})
enc := json.NewEncoder(w)
enc.Encode(map[string]string{
"gid": gid.Hex(),
"tid": gd.tid(mid),
})
} 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 `bson:"gid"`
Mid primitive.ObjectID `bson:"mid"`
Targetid primitive.ObjectID `bson:"targetid"`
Inviter bson.M `bson:"inviter"`
Invitee bson.M `bson:"invitee"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &doc); err != nil {
logger.Println("InviteToParty failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
targetid := doc.Targetid
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 {
gd, err = gp.createGroup(gid, mid, doc.Inviter)
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"},
})
w.Write([]byte(gd.strid()))
}
func (gp *groupParty) AcceptPartyInvitation(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid primitive.ObjectID `bson:"gid"`
Mid primitive.ObjectID `bson:"mid"`
Tid string `bson:"tid"`
Character bson.M `bson:"character"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &doc); err != nil {
logger.Println("AcceptPartyInvitation failed. ReadJsonDocumentFromBody 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.Write([]byte("expired"))
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 := &groupDoc{
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 := &groupDoc{
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 data struct {
Mid primitive.ObjectID `bson:"mid"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &data); err != nil {
logger.Println("DenyPartyInvitation failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
mid := data.Mid
states, err := gp.rh.JSONGetString(mid.Hex(), "$.party.state")
if err == redis.Nil {
return
}
if len(states) > 0 && len(states[0]) > 0 {
w.Write([]byte(states[0]))
} else {
w.Write([]byte("connected"))
}
}
func (gp *groupParty) updateMemberDocument(gid groupID, mid accountID, doc bson.M) error {
gd := &groupDoc{
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 := groupDoc{
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 `bson:"gid"`
Doc bson.M `bson:"doc"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &data); err != nil {
logger.Println("UpdatePartyDocument failed. ReadJsonDocumentFromBody 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 data struct {
Gid primitive.ObjectID `bson:"gid"`
}
if err := gocommon.ReadJsonDocumentFromBody(r.Body, &data); err != nil {
logger.Println("QueryPartyMembers failed. ReadJsonDocumentFromBody returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gid := data.Gid
gd := groupDoc{
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
}
enc := json.NewEncoder(w)
if err := enc.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) (*groupDoc, error) {
tid := makeTid(newid, charge)
gd := &groupDoc{
Members: map[string]any{
tid: &memberDoc{
Body: chargeDoc,
Invite: false,
InviteExpire: 0,
},
},
InCharge: tid,
rh: gp.rh,
id: newid,
}
_, err := gp.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
if err != nil {
return nil, err
}
return gd, nil
}
func (gp *groupParty) find(id groupID) (*groupDoc, 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 &groupDoc{
rh: gp.rh,
id: id,
}, nil
}
func (gp *groupParty) memberDisconnected(room string, mid primitive.ObjectID) {
gid, err := primitive.ObjectIDFromHex(room)
if err != nil {
return
}
gd := &groupDoc{
id: gid,
rh: gp.rh,
}
gd.removeMember(mid)
// 퇴장을 알림
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + room,
Body: bson.M{
gd.tid(mid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
}
func (gp *groupParty) ClientDisconnected(ctx wshandler.ApiCallContext) {
gids, _ := gp.rh.JSONGetString(ctx.CallBy.Accid.Hex(), "$.party.id")
if len(gids) > 0 && len(gids[0]) > 0 {
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
gidstr := gids[0]
gid, _ := primitive.ObjectIDFromHex(gidstr)
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ctx.CallBy.Accid.Hex(),
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gidstr},
})
// gid에는 제거 메시지 보냄
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
Body: bson.M{
makeTid(gid, ctx.CallBy.Accid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
gp.leaveRoom(gid, ctx.CallBy.Accid)
}
}
func (gp *groupParty) UpdatePartyMemberDocumentDirect(ctx wshandler.ApiCallContext) {
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
doc := ctx.Arguments[1].(map[string]any)
gp.updateMemberDocument(gidobj, ctx.CallBy.Accid, doc)
}
func (gp *groupParty) UpdatePartyDocumentDirect(ctx wshandler.ApiCallContext) {
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
doc := ctx.Arguments[1].(map[string]any)
gp.updatePartyDocument(gidobj, doc)
}
func (gp *groupParty) LeaveParty(ctx wshandler.ApiCallContext) {
gids, _ := gp.rh.JSONGetString(ctx.CallBy.Accid.Hex(), "$.party.id")
if len(gids) == 0 || len(gids[0]) == 0 {
return
}
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
gidstr := gids[0]
gid, _ := primitive.ObjectIDFromHex(gidstr)
mid := ctx.CallBy.Accid
tid := ctx.Arguments[0].(string)
gd := groupDoc{
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 := groupDoc{
id: gid,
rh: gp.rh,
}
gd.removeMember(mid)
}

View File

@ -1,97 +0,0 @@
package core
import (
"fmt"
"strings"
"sync"
)
type connection struct {
locker sync.Mutex
alias string
tags []string
onClose map[string]func()
}
func (rc *connection) addTag(name, val string) {
rc.locker.Lock()
defer rc.locker.Unlock()
prefix := name + "="
for i, tag := range rc.tags {
if strings.HasPrefix(tag, prefix) {
rc.tags[i] = prefix + val
return
}
}
rc.tags = append(rc.tags, prefix+val)
}
func (rc *connection) removeTag(name string, val string) {
rc.locker.Lock()
defer rc.locker.Unlock()
whole := fmt.Sprintf("%s=%s", name, val)
for i, tag := range rc.tags {
if tag == whole {
if i == 0 && len(rc.tags) == 1 {
rc.tags = nil
} else {
lastidx := len(rc.tags) - 1
if i < lastidx {
rc.tags[i] = rc.tags[lastidx]
}
rc.tags = rc.tags[:lastidx]
}
return
}
}
}
func (rc *connection) registOnCloseFunc(name string, f func()) {
rc.locker.Lock()
defer rc.locker.Unlock()
if rc.onClose == nil {
f()
return
}
rc.onClose[name] = f
}
func (rc *connection) hasOnCloseFunc(name string) bool {
rc.locker.Lock()
defer rc.locker.Unlock()
if rc.onClose == nil {
return false
}
_, ok := rc.onClose[name]
return ok
}
func (rc *connection) unregistOnCloseFunc(name string) (out func()) {
rc.locker.Lock()
defer rc.locker.Unlock()
if rc.onClose == nil {
return
}
out = rc.onClose[name]
delete(rc.onClose, name)
return
}
func (rc *connection) cleanup() {
rc.locker.Lock()
defer rc.locker.Unlock()
cp := rc.onClose
rc.onClose = nil
go func() {
for _, f := range cp {
f()
}
}()
}

View File

@ -6,74 +6,24 @@ import (
"io"
"net"
"net/http"
"reflect"
"strings"
"sync"
"time"
"github.com/go-redis/redis/v8"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/session"
"repositories.action2quare.com/ayo/gocommon/wshandler"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/primitive"
)
const (
defaultMaxMemory = 32 << 10 // 32 KB
)
func writeBsonArr(w io.Writer, src []bson.M) error {
return writeBsonDoc(w, bson.M{
"r": src,
})
}
func onlineGroupQueryKey(prefix string) string {
return prefix + "_olg"
}
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
}
var devflag = flagx.Bool("dev", false, "")
type TavernConfig struct {
gocommon.RegionStorageConfig `json:",inline"`
GroupTypes map[string]*groupConfig `json:"tavern_group_types"`
session.SessionConfig `json:",inline"`
Group map[string]configDocument `json:"tavern_group_types"`
MaingateApiToken string `json:"maingate_api_token"`
RedisURL string `json:"tavern_redis_url"`
macAddr string
@ -81,55 +31,11 @@ type TavernConfig struct {
var config TavernConfig
type connectionMap struct {
sync.Mutex
conns map[primitive.ObjectID]*connection
}
func (cm *connectionMap) add(accid accountID, alias string) {
cm.Lock()
defer cm.Unlock()
old := cm.conns[accid]
if old != nil {
old.cleanup()
}
cm.conns[accid] = &connection{
alias: alias,
onClose: make(map[string]func()),
}
}
func (cm *connectionMap) remove(accid accountID) {
cm.Lock()
defer cm.Unlock()
old := cm.conns[accid]
if old != nil {
delete(cm.conns, accid)
old.cleanup()
}
}
func (cm *connectionMap) get(accid accountID) *connection {
cm.Lock()
defer cm.Unlock()
return cm.conns[accid]
}
type Tavern struct {
subTaverns []*subTavern
wsh *wshandler.WebsocketHandler
}
type subTavern struct {
mongoClient gocommon.MongoClient
wsh *wshandler.WebsocketHandler
region string
groups map[string]group
methods map[string]reflect.Method
cm connectionMap
redison *gocommon.RedisonHandler
httpApiBorker gocommon.HttpApiHandlerContainer
}
func getMacAddr() (string, error) {
@ -149,16 +55,11 @@ func getMacAddr() (string, error) {
}
// New :
func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *TavernConfig) (*Tavern, error) {
if inconfig == nil {
var loaded TavernConfig
if err := gocommon.LoadConfig(&loaded); err != nil {
func New(context context.Context, wsh *wshandler.WebsocketHandler) (*Tavern, error) {
if err := gocommon.LoadConfig(&config); err != nil {
return nil, err
}
inconfig = &loaded
}
config = *inconfig
macaddr, err := getMacAddr()
if err != nil {
return nil, err
@ -177,104 +78,92 @@ func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *Tav
}
func (tv *Tavern) Cleanup() {
for _, st := range tv.subTaverns {
st.mongoClient.Close()
}
tv.mongoClient.Close()
}
// func (tv *Tavern) SocketMessageReceived(accid primitive.ObjectID, alias string, messageType wshandler.WebSocketMessageType, body io.Reader) {
// switch messageType {
// case wshandler.Connected:
// case wshandler.Disconnected:
// }
// // gidtype := msg.Conn.GetTag("gid")
// // if len(gidtype) > 0 {
// // tokens := strings.SplitN(gidtype, "@", 2)
// // gidobj, _ := primitive.ObjectIDFromHex(tokens[0])
// // gtype := tokens[1]
// // group := sub.groups[gtype]
// // if group != nil {
// // group.PauseMember(gidobj, msg.Alias, msg.Conn)
// // }
// }
func (tv *Tavern) prepare(ctx context.Context) error {
for region := range config.RegionStorage {
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
}
sub := &subTavern{
wsh: tv.wsh,
mongoClient: dbconn,
region: region,
methods: methods,
cm: connectionMap{
conns: make(map[primitive.ObjectID]*connection),
},
}
groups := make(map[string]group)
for typename, cfg := range config.GroupTypes {
cfg.Name = typename
if cfg.Transient {
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)
}
redisClient, err := gocommon.NewRedisClient(config.RedisURL)
if err != nil {
return err
}
groups[typename] = groupinstance
}
sub.groups = groups
tv.subTaverns = append(tv.subTaverns, sub)
tv.redison = gocommon.NewRedisonHandler(redisClient.Context(), redisClient)
tv.wsh.RegisterApiHandler(wshandler.MakeWebsocketApiHandler(tv, "tv"))
if cfg, ok := config.Group["chat"]; ok {
chat := new(groupChat)
if err := chat.Initialize(tv, cfg); err != nil {
return err
}
tv.httpApiBorker.RegisterApiHandler(gocommon.MakeHttpApiHandler(chat, "chat"))
tv.wsh.RegisterApiHandler(wshandler.MakeWebsocketApiHandler(chat, "chat"))
}
if cfg, ok := config.Group["party"]; ok {
party := new(groupParty)
if err := party.Initialize(tv, cfg); err != nil {
return err
}
tv.httpApiBorker.RegisterApiHandler(gocommon.MakeHttpApiHandler(party, "party"))
tv.wsh.RegisterApiHandler(wshandler.MakeWebsocketApiHandler(party, "party"))
}
return nil
}
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
for _, sub := range tv.subTaverns {
tv.wsh.RegisterReceiver(sub.region, sub.clientMessageReceived)
var pattern string
if sub.region == "default" {
pattern = gocommon.MakeHttpHandlerPattern(prefix, "api")
} else {
pattern = gocommon.MakeHttpHandlerPattern(prefix, sub.region, "api")
}
serveMux.HandleFunc(pattern, sub.api)
}
// tv.wsh.RegisterReceiver(tv)
pattern := gocommon.MakeHttpHandlerPattern(prefix, "api")
serveMux.HandleFunc(pattern, tv.api)
return nil
}
func (sub *subTavern) clientMessageReceived(sender *wshandler.Sender, messageType wshandler.WebSocketMessageType, body io.Reader) {
if messageType == wshandler.Connected {
sub.cm.add(sender.Accid, sender.Alias)
} else if messageType == wshandler.Disconnected {
sub.cm.remove(sender.Accid)
func (tv *Tavern) EnterChannel(ctx wshandler.ApiCallContext) {
tv.wsh.EnterRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
}
func (tv *Tavern) LeaveChannel(ctx wshandler.ApiCallContext) {
tv.wsh.LeaveRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
}
func (tv *Tavern) ClientConnected(ctx wshandler.ApiCallContext) {
logger.Println("ClientConnected :", ctx.CallBy.Alias)
tv.redison.Del(tv.redison.Context(), ctx.CallBy.Accid.Hex())
_, err := tv.redison.JSONSet(ctx.CallBy.Accid.Hex(), "$", bson.M{"_ts": time.Now().UTC().Unix()})
if err != nil {
logger.Println("OnClientMessageReceived HSet error :", err)
}
}
func (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
func (tv *Tavern) ClientDisconnected(ctx wshandler.ApiCallContext) {
tv.redison.Del(tv.redison.Context(), ctx.CallBy.Accid.Hex()).Result()
logger.Println("ClientDisconnected :", ctx.CallBy.Alias)
}
func (tv *Tavern) OnRoomCreated(name string) {
cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, 1).Result()
if err != nil && !errors.Is(err, redis.Nil) {
logger.Println("OnRoomCreated JSONSet failed :", err)
return
}
if cnt == 1 {
tv.redison.JSONSet(name, "$", map[string]any{}, gocommon.RedisonSetOptionNX)
}
}
func (tv *Tavern) OnRoomDestroyed(name string) {
cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, -1).Result()
if err != nil {
logger.Println("OnRoomDestroyed JSONNumIncrBy failed :", err)
} else if cnt == 0 {
tv.redison.Del(tv.redison.Context(), "_ref_"+name)
tv.redison.JSONDel(name, "$")
}
}
func (tv *Tavern) api(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
@ -293,29 +182,12 @@ func (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
return
}
operation := r.URL.Query().Get("operation")
if len(operation) == 0 {
funcname := r.URL.Query().Get("call")
if len(funcname) == 0 {
logger.Println("query param 'call' is missing")
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)
tv.httpApiBorker.Call(funcname, w, r)
}

113
core/tavern_test.go Normal file
View File

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

6
go.mod
View File

@ -1,17 +1,18 @@
module repositories.action2quare.com/ayo/tavern
go 1.19
go 1.20
require (
github.com/go-redis/redis/v8 v8.11.5
go.mongodb.org/mongo-driver v1.11.7
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711062613-74829b93ac1b
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908091916-23231dc6d705
)
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/snappy v0.0.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/montanaflynn/stats v0.7.1 // indirect
@ -24,4 +25,5 @@ require (
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)

30
go.sum
View File

@ -11,8 +11,9 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@ -93,8 +94,9 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -102,17 +104,13 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710053024-a842845685ee h1:Aau1j/b9wI4nyvrM7m1Q+2xkcW1Qo7i3q+QBD4Umnzg=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710053024-a842845685ee/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711003621-3bb985d0b617 h1:91mBIGIyxzcnvOaIdegUuV+i9xs8YTSRcmyRaIytzx8=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711003621-3bb985d0b617/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711005604-a42eb2888e97 h1:ARzXt3HBmiAUDyACfNm5Kvz1JMTn7+ryE03kB8x/km0=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711005604-a42eb2888e97/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711033135-d7b26608df94 h1:VrNj5gBFFN9/roWCxyBCZ2gu5k58eremNHQvQNPrfrU=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711033135-d7b26608df94/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711035757-9fd0dd00cb7d h1:RdxKmMc7kHrTk+SvTYse2IGxmdDhbEDeM0fKAUW+G+w=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711035757-9fd0dd00cb7d/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711053010-4acb81a20d9c h1:SktFqjnc/UOMjJrq/brSw5lQjW1IA+KkB5YgeovusmQ=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711053010-4acb81a20d9c/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711062613-74829b93ac1b h1:04rlgT+zeKSpekyleb8Mfi8kENIoka5DYJLuk65wqxc=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711062613-74829b93ac1b/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230906142024-eb54fa2e3a44 h1:90XY5WSLtxvfi6YktDY4Sv1CMPRViZvPLPunA1eIxZA=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230906142024-eb54fa2e3a44/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908023557-6cbf32c3868b h1:Rx6tP6IhlGlVGGgMDZ7OuIDU9cHfvm2L05L2tqF7G58=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908023557-6cbf32c3868b/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908025007-3603c0386b29 h1:Ts40m9MLMMx4uaQWko5QXkg/HX4uYQB9TGGEN6twhiU=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908025007-3603c0386b29/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908062630-46ce5f09897a h1:xKUI2xlP6LcUV5fy+4QEHoaZOhkSsMYgeIp6H5ADBCM=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908062630-46ce5f09897a/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908091916-23231dc6d705 h1:sK2mbRwqTMTZFmP9F50MIFZG9hcQ+EeW7tsGTzBgAow=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230908091916-23231dc6d705/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=

17
main.go
View File

@ -9,8 +9,9 @@ import (
"repositories.action2quare.com/ayo/gocommon/wshandler"
"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/session"
)
var prefix = flagx.String("prefix", "", "")
@ -20,23 +21,27 @@ func main() {
ctx, cancel := context.WithCancel(context.Background())
var config core.TavernConfig
if err := common.LoadConfig(&config); err != nil {
if err := gocommon.LoadConfig(&config); err != nil {
panic(err)
}
authcache, err := common.NewAuthCollectionGlobal(ctx, config.MaingateApiToken)
consumer, err := session.NewConsumerWithConfig(ctx, config.SessionConfig)
if err != nil {
panic(err)
}
wsh := wshandler.NewWebsocketHandler(authcache)
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)
} else {
serveMux := http.NewServeMux()
wsh.RegisterHandlers(serveMux, *prefix)
tv.RegisterHandlers(ctx, serveMux, *prefix)
server := common.NewHTTPServer(serveMux)
server := gocommon.NewHTTPServer(serveMux)
logger.Println("tavern is started")
wsh.Start(ctx)
server.Start()