Compare commits

..

41 Commits

Author SHA1 Message Date
e18a5eafe0 친구 초대 및 관리 완료 2023-09-19 18:23:14 +09:00
ad185b8d01 UpdatePartyDocumentDirect를 PartyOwner만 동작하도록 함 2023-09-11 19:54:19 +09:00
d3614392c2 모듈 업데이트 2023-09-11 12:48:11 +09:00
19eacf0d4c gob로 변경 2023-09-11 12:47:27 +09:00
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
15 changed files with 1139 additions and 2453 deletions

1
.gitignore vendored
View File

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

View File

@ -1 +0,0 @@
{}

View File

@ -1,65 +1,46 @@
{
"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
}
}
},
"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
}
"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"
}
}
},
"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",
"social_redis_url": "redis://192.168.8.94:6380/4",
"social_storage_url" : "mongodb://192.168.8.94:27017/social?replicaSet=repl01&retrywrites=false",
"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
}
},
"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
"chat" : {
"default_capacity" : 1000,
"channels" : {
"bazzar-1" : {
"name" : "FText(bazzar-1)"
},
"bazzar-2" : {
"name" : "FText(bazzar-2)"
},
"bazzar-3" : {
"name" : "FText(bazzar-3)"
},
"bazzar-4" : {
"name" : "FText(bazzar-4)"
},
"bazzar-5" : {
"name" : "FText(bazzar-5)"
}
}
}
}

View File

@ -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")
// }

491
core/friend.go Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

383
core/invitation.go Normal file
View File

@ -0,0 +1,383 @@
package core
import (
"context"
"encoding/gob"
"net/http"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler"
)
const (
invitation_collection_name = gocommon.CollectionName("invitation")
)
var invitation_sent_tag = []string{"social.InvitationsSent"}
var invitation_received_tag = []string{"social.InvitationsReceived"}
var friends_tag = []string{"social.Friends"}
type invitation struct {
mongoClient gocommon.MongoClient
redison *gocommon.RedisonHandler
wsh *wshandler.WebsocketHandler
f *friends
}
type invitationDoc struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
From primitive.ObjectID `bson:"from,omitempty" json:"-"`
To primitive.ObjectID `bson:"to,omitempty" json:"-"`
FromAlias string `bson:"falias,omitempty" json:"from"`
ToAlias string `bson:"talias,omitempty" json:"to"`
Timestamp int64 `bson:"ts" json:"ts"`
Deleted bool `bson:"deleted,omitempty" json:"deleted,omitempty"`
}
func init() {
gob.Register([]invitationDoc{})
}
func makeInvitation(ctx context.Context, s *Social, f *friends) (*invitation, error) {
if err := s.mongoClient.MakeUniqueIndices(invitation_collection_name, map[string]bson.D{
"fromto": {{Key: "from", Value: 1}, {Key: "to", Value: 1}},
}); err != nil {
return nil, err
}
// 내가 받은거
if err := s.mongoClient.MakeIndices(invitation_collection_name, map[string]bson.D{
"received": {{Key: "to", Value: 1}, {Key: "ts", Value: -1}},
}); err != nil {
return nil, err
}
return &invitation{
mongoClient: s.mongoClient,
redison: s.redison,
wsh: s.wsh,
f: f,
}, nil
}
func (iv *invitation) QueryReceivedInvitations(ctx wshandler.ApiCallContext) {
// 내가 받은 초대 목록
queryfrom := int64(ctx.Arguments[0].(float64))
var receives []*invitationDoc
err := iv.mongoClient.FindAllAs(invitation_collection_name, bson.M{
"to": ctx.CallBy.Accid,
"ts": bson.M{"$gt": queryfrom},
}, &receives)
if err != nil {
logger.Println("QueryReceivedInvitations failed. FindAllAs err :", err)
}
if len(receives) > 0 {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ctx.CallBy.Accid.Hex(),
Body: receives,
Tag: invitation_received_tag,
})
}
}
func (iv *invitation) QuerySentInvitations(ctx wshandler.ApiCallContext) {
// 내가 보낸 초대 목록
queryfrom := int64(ctx.Arguments[0].(float64))
var receives []*invitationDoc
err := iv.mongoClient.FindAllAs(invitation_collection_name, bson.M{
"from": ctx.CallBy.Accid,
"ts": bson.M{"$gt": queryfrom},
"falias": bson.M{"$exists": true},
}, &receives)
if err != nil {
logger.Println("QueryReceivedInvitations failed. FindAllAs err :", err)
}
if len(receives) > 0 {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ctx.CallBy.Accid.Hex(),
Body: receives,
Tag: invitation_sent_tag,
})
}
}
func (iv *invitation) CancelInvitation(ctx wshandler.ApiCallContext) {
// ctx.CallBy.Accid
id, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var ivdoc invitationDoc
if err := iv.mongoClient.FindOneAs(invitation_collection_name, bson.M{
"_id": id,
}, &ivdoc); err != nil {
logger.Println("CancelInvitation failed:", err)
return
}
if ivdoc.From != ctx.CallBy.Accid {
return
}
ivdoc.Deleted = true
if _, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": id,
}, bson.M{
"$set": bson.M{
"falias": "",
"deleted": true,
"ts": time.Now().UTC().Unix(),
},
}); err != nil {
logger.Println("CancelInvitation failed:", err)
return
}
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.To.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitation_received_tag,
})
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.From.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitation_sent_tag,
})
}
func (iv *invitation) AcceptInvitation(ctx wshandler.ApiCallContext) {
invId, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var ivdoc invitationDoc
if err := iv.mongoClient.FindOneAs(invitation_collection_name, bson.M{
"_id": invId,
}, &ivdoc); err != nil {
logger.Println("AcceptInvitation failed:", err)
return
}
if ivdoc.Id != invId {
// 초대가 없다
return
}
if ivdoc.To != ctx.CallBy.Accid {
// 내가 받은 초대가 아니네?
return
}
now := time.Now().UTC().Unix()
f1 := friendDoc{
From: ivdoc.To, // 수락한 나
To: ivdoc.From, // 상대방
ToAlias: ivdoc.FromAlias,
Timestamp: now,
}
f2 := friendDoc{
From: ivdoc.From, // 상대방
To: ivdoc.To, // 나
ToAlias: ivdoc.ToAlias,
Timestamp: now,
}
// 나한테 상대방을 친구로 만들고
if err := iv.f.addFriend(&f1); err == nil {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: f1.From.Hex(),
Body: []friendDoc{f1},
Tag: friends_tag,
})
} else {
logger.Println("AcceptInvitation failed. addFriend(f1) err :", err)
return
}
// 상대방한테 나를 친구로 만듬
if err := iv.f.addFriend(&f2); err == nil {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: f2.From.Hex(),
Body: []friendDoc{f2},
Tag: friends_tag,
})
} else {
logger.Println("AcceptInvitation failed. addFriend(f2) err :", err)
return
}
iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": invId,
}, bson.M{
"$set": bson.M{
"deleted": true,
"ts": now,
},
}, options.Update().SetUpsert(false))
}
func (iv *invitation) DenyInvitation(ctx wshandler.ApiCallContext) {
invId, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
var ivdoc invitationDoc
if err := iv.mongoClient.FindOneAs(invitation_collection_name, bson.M{
"_id": invId,
}, &ivdoc); err != nil {
logger.Println("AcceptInvitation failed:", err)
return
}
if ivdoc.Id != invId {
// 초대가 없다
return
}
if ivdoc.To != ctx.CallBy.Accid {
// 내가 받은 초대가 아니네?
return
}
now := time.Now().UTC().Unix()
ivdoc.Timestamp = now
ivdoc.Deleted = true
if _, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"_id": invId,
}, bson.M{
"$set": bson.M{
"deleted": true,
"ts": now,
},
}, options.Update().SetUpsert(false)); err != nil {
logger.Println("DenyInvitation failed. addFriend(f2) err :", err)
return
}
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.To.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitation_received_tag,
})
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.From.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitation_sent_tag,
})
}
func (iv *invitation) Trim(ctx wshandler.ApiCallContext) {
stringsTobjs := func(in []any) (out []primitive.ObjectID) {
for _, i := range in {
p, _ := primitive.ObjectIDFromHex(i.(string))
out = append(out, p)
}
return
}
ids := stringsTobjs(ctx.Arguments[0].([]any))
ids = append(ids, stringsTobjs(ctx.Arguments[1].([]any))...)
if len(ids) > 0 {
if len(ids) == 1 {
iv.mongoClient.Delete(invitation_collection_name, bson.M{"_id": ids[0], "deleted": true})
} else {
iv.mongoClient.DeleteMany(invitation_collection_name, bson.D{
{Key: "_id", Value: bson.M{"$in": ids}},
{Key: "deleted", Value: true},
})
}
}
}
func (iv *invitation) InviteAsFriend(w http.ResponseWriter, r *http.Request) {
// 1. mongodb에 추가
// 1-1. block이 되어있다면(==이미 도큐먼트가 있다면) 마치 성공인 것처럼 아무것도 안하고 끝
// 2. mongodb에 추가가 성공하면 publish
var ivdoc invitationDoc
if err := gocommon.MakeDecoder(r).Decode(&ivdoc); err != nil {
logger.Println("IniviteAsFriend failed:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
ivdoc.Timestamp = time.Now().UTC().Unix()
_, newid, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
"from": ivdoc.From,
"to": ivdoc.To,
}, bson.M{
"$set": bson.M{
"ts": ivdoc.Timestamp,
"falias": ivdoc.FromAlias,
"talias": ivdoc.ToAlias,
},
}, options.Update().SetUpsert(true))
if err != nil {
logger.Println("IniviteAsFriend failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if newid != nil {
ivdoc.Id = newid.(primitive.ObjectID)
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.To.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitation_received_tag,
})
} else {
found, _ := iv.mongoClient.FindOne(invitation_collection_name, bson.M{
"from": ivdoc.From,
"to": ivdoc.To,
}, options.FindOne().SetProjection(bson.M{"_id": 1}))
ivdoc.Id = found["_id"].(primitive.ObjectID)
}
if !ivdoc.Id.IsZero() {
iv.wsh.SendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ivdoc.From.Hex(),
Body: []invitationDoc{ivdoc},
Tag: invitation_sent_tag,
})
}
}
func (iv *invitation) Block(w http.ResponseWriter, r *http.Request) {
// 초대가 있으면
// var bi struct {
// From primitive.ObjectID
// To primitive.ObjectID
// FromAlias string
// }
// if err := gocommon.MakeDecoder(r).Decode(&bi); err != nil {
// logger.Println("invitation.Block failed :", err)
// w.WriteHeader(http.StatusBadRequest)
// return
// }
// now := time.Now().UTC().Unix()
// // From이 To를 block했으므로 To가 From을 초대하는 것을 방지하려면 둘을 뒤집어서 문서를 만들어 놔야 함
// // 이미 존재하는 초대일 수도 있다.
// _, _, err := iv.mongoClient.Update(invitation_collection_name, bson.M{
// "from": bi.To,
// "to": bi.From,
// }, bson.M{
// "$set": invitationDoc{
// ToAlias: bi.FromAlias,
// Timestamp: now,
// },
// }, options.Update().SetUpsert(true))
// if err != nil {
// logger.Println("Block failed:", err)
// w.WriteHeader(http.StatusInternalServerError)
// return
// }
}

View File

@ -1,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()
}
}()
}

134
core/social.go Normal file
View File

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

65
core/social_test.go Normal file
View File

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

View File

@ -1,321 +0,0 @@
package core
import (
"context"
"errors"
"io"
"net"
"net/http"
"reflect"
"strings"
"sync"
"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/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
}
type TavernConfig struct {
gocommon.RegionStorageConfig `json:",inline"`
GroupTypes map[string]*groupConfig `json:"tavern_group_types"`
MaingateApiToken string `json:"maingate_api_token"`
RedisURL string `json:"tavern_redis_url"`
macAddr string
}
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
}
func getMacAddr() (string, error) {
ifas, err := net.Interfaces()
if err != nil {
return "", err
}
for _, ifa := range ifas {
a := ifa.HardwareAddr.String()
if a != "" {
a = strings.ReplaceAll(a, ":", "")
return a, nil
}
}
return "", errors.New("no net interface")
}
// New :
func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *TavernConfig) (*Tavern, error) {
if inconfig == nil {
var loaded TavernConfig
if err := gocommon.LoadConfig(&loaded); err != nil {
return nil, err
}
inconfig = &loaded
}
config = *inconfig
macaddr, err := getMacAddr()
if err != nil {
return nil, err
}
config.macAddr = macaddr
tv := &Tavern{
wsh: wsh,
}
if err = tv.prepare(context); err != nil {
logger.Println("tavern prepare() failed :", err)
return nil, err
}
return tv, nil
}
func (tv *Tavern) Cleanup() {
for _, st := range tv.subTaverns {
st.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)
}
if err != nil {
return err
}
groups[typename] = groupinstance
}
sub.groups = groups
tv.subTaverns = append(tv.subTaverns, sub)
}
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)
}
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 (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
io.Copy(io.Discard, r.Body)
r.Body.Close()
}()
// 서버에서 오는 요청만 처리
apitoken := r.Header.Get("MG-X-API-TOKEN")
if apitoken != config.MaingateApiToken {
// 서버가 보내는 쿼리만 허용
logger.Println("MG-X-API-TOKEN is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
operation := r.URL.Query().Get("operation")
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)
}

10
go.mod
View File

@ -1,18 +1,19 @@
module repositories.action2quare.com/ayo/tavern
module repositories.action2quare.com/ayo/social
go 1.19
go 1.20
require (
github.com/go-redis/redis/v8 v8.11.5
github.com/gorilla/websocket v1.5.0
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-20230911034515-1af5d7281946
)
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/gorilla/websocket v1.5.0 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pires/go-proxyproto v0.7.0 // 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
)

22
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,5 @@ 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-20230911034515-1af5d7281946 h1:YSvgTNuHeKis37+FfOvzVLYCaXQ0oF+CWBTy4bRqq3g=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=

28
main.go
View File

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