commit 80997dbca59efd4f0a7792e50fb2c0e94e49e6b1 Author: mountain Date: Tue Sep 19 18:48:56 2023 +0900 초기 커밋. 친구 초대 및 관리 완료 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42069af --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/ +__debug_bin.exe +*.log +config.json diff --git a/config_template.json b/config_template.json new file mode 100644 index 0000000..871ef54 --- /dev/null +++ b/config_template.json @@ -0,0 +1,47 @@ +{ + "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_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": { + "party": { + "max_member": 3, + "invite_ttl": 30 + }, + "chat" : { + "default_capacity" : 1000, + "channels" : { + "bazzar-1" : { + "name" : "FText(bazzar-1)" + }, + "bazzar-2" : { + "name" : "FText(bazzar-2)" + }, + "bazzar-3" : { + "name" : "FText(bazzar-3)" + }, + "bazzar-4" : { + "name" : "FText(bazzar-4)" + }, + "bazzar-5" : { + "name" : "FText(bazzar-5)" + } + } + } + } +} \ No newline at end of file diff --git a/core/friend.go b/core/friend.go new file mode 100644 index 0000000..9610363 --- /dev/null +++ b/core/friend.go @@ -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}}}) + } + } +} diff --git a/core/invitation.go b/core/invitation.go new file mode 100644 index 0000000..2789b7f --- /dev/null +++ b/core/invitation.go @@ -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 + // } +} diff --git a/core/social.go b/core/social.go new file mode 100644 index 0000000..eaba2c7 --- /dev/null +++ b/core/social.go @@ -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) +} diff --git a/core/social_test.go b/core/social_test.go new file mode 100644 index 0000000..adc8130 --- /dev/null +++ b/core/social_test.go @@ -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) { + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4350eb3 --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module repositories.action2quare.com/ayo/social + +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-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/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 + github.com/pkg/errors v0.9.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + 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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..80420fe --- /dev/null +++ b/go.sum @@ -0,0 +1,108 @@ +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/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/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= +github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs= +go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.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/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= +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-20230911034515-1af5d7281946 h1:YSvgTNuHeKis37+FfOvzVLYCaXQ0oF+CWBTy4bRqq3g= +repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f205b6b --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "net/http" + + "repositories.action2quare.com/ayo/gocommon/flagx" + "repositories.action2quare.com/ayo/gocommon/wshandler" + "repositories.action2quare.com/ayo/social/core" + + "repositories.action2quare.com/ayo/gocommon" + "repositories.action2quare.com/ayo/gocommon/logger" + "repositories.action2quare.com/ayo/gocommon/session" +) + +var prefix = flagx.String("prefix", "", "") + +func main() { + flagx.Parse() + + ctx, cancel := context.WithCancel(context.Background()) + var config core.SocialConfig + if err := gocommon.LoadConfig(&config); err != nil { + panic(err) + } + + consumer, err := session.NewConsumerWithConfig(ctx, config.SessionConfig) + if err != nil { + panic(err) + } + + 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) + so.RegisterHandlers(ctx, serveMux, *prefix) + server := gocommon.NewHTTPServer(serveMux) + logger.Println("social is started") + wsh.Start(ctx) + server.Start() + cancel() + so.Cleanup() + wsh.Cleanup() + } +}