Merge remote-tracking branch 'upstream/main'

pull/2237/head
withchao 2 years ago
commit e601b5fca1

@ -25,6 +25,7 @@ jobs:
- name: Comment cherry-pick command - name: Comment cherry-pick command
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
script: | script: |
const pr = context.payload.pull_request; const pr = context.payload.pull_request;
if (!pr.merged) { if (!pr.merged) {
@ -64,4 +65,3 @@ jobs:
issue_number: pr.number, issue_number: pr.number,
body: cherryPickCmd body: cherryPickCmd
}); });
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}

@ -12,23 +12,57 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
name: Github Rebot for Cherry Pick On Comment name: Github Robot for Cherry Pick On Comment
on: on:
issue_comment: issue_comment:
types: [created] types: [created]
jobs: jobs:
cherry-pick: cherry-pick:
name: Cherry Pick name: Cherry Pick
# && github.event.comment.user.login=='kubbot'
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/cherry-pick') if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/cherry-pick')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
token: ${{ secrets.BOT_GITHUB_TOKEN }} token: ${{ secrets.BOT_GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo fetch-depth: 0 # To ensure all history is available for cherry-picking
- name: Automatic Cherry Pick - name: Automatic Cherry Pick
uses: vendoo/gha-cherry-pick@v1 uses: vendoo/gha-cherry-pick@v1
with:
# Assuming the cherry-pick commit SHA is passed in the comment like '/cherry-pick sha'
commit-sha: ${{ github.event.comment.body }}
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
- name: Create a new branch for PR
run: |
PR_BRANCH="cherry-pick-${GITHUB_SHA}-to-${{ github.base_ref }}"
git checkout -b $PR_BRANCH
git push origin $PR_BRANCH
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
- name: Create Pull Request
uses: actions/github-script@v5
with:
script: |
const prTitle = "Cherry-pick to ${{ github.base_ref }}"
const prBody = "Automated cherry-pick of ${{ github.event.comment.body }}\n\n/cc @kubbot"
const base = "${{ github.base_ref }}"
const head = "cherry-pick-${{ github.sha }}-to-${{ github.base_ref }}"
const createPr = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: prTitle,
body: prBody,
head: head,
base: base,
maintainer_can_modify: true, // Allows maintainers to edit the PR
})
env: env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

@ -0,0 +1,27 @@
# Copyright © 2024 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Language Check Workflow Test
on: [pull_request]
jobs:
comment-language-detector:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Code Language Detector
uses: kubecub/comment-lang-detector@v1.0.0

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
name: OpenIM Linux System E2E Test name: OpenIM E2E And API Test
on: on:
workflow_dispatch: workflow_dispatch:
@ -82,7 +82,7 @@ jobs:
sudo make tidy sudo make tidy
sudo make tools.verify.go-gitlint sudo make tools.verify.go-gitlint
- name: Build, Start - name: Build, Start(make build && make start)
run: | run: |
sudo ./scripts/install/install.sh -i sudo ./scripts/install/install.sh -i
@ -90,9 +90,8 @@ jobs:
run: | run: |
sudo ./scripts/install/install.sh -s sudo ./scripts/install/install.sh -s
- name: Exec OpenIM API test - name: Exec OpenIM API test (make test-api)
run: | run: |
sudo make test-api
mkdir -p ./tmp mkdir -p ./tmp
touch ./tmp/test.md touch ./tmp/test.md
echo "# OpenIM Test" >> ./tmp/test.md echo "# OpenIM Test" >> ./tmp/test.md
@ -103,9 +102,10 @@ jobs:
echo "</code></pre>" >> ./tmp/test.md echo "</code></pre>" >> ./tmp/test.md
echo "</details>" >> ./tmp/test.md echo "</details>" >> ./tmp/test.md
- name: Exec OpenIM E2E Test sudo make test-api
- name: Exec OpenIM E2E Test (make test-e2e)
run: | run: |
sudo make test-e2e
echo "" >> ./tmp/test.md echo "" >> ./tmp/test.md
echo "## OpenIM E2E Test" >> ./tmp/test.md echo "## OpenIM E2E Test" >> ./tmp/test.md
echo "<details><summary>Command Output for OpenIM E2E Test</summary>" >> ./tmp/test.md echo "<details><summary>Command Output for OpenIM E2E Test</summary>" >> ./tmp/test.md
@ -114,6 +114,8 @@ jobs:
echo "</code></pre>" >> ./tmp/test.md echo "</code></pre>" >> ./tmp/test.md
echo "</details>" >> ./tmp/test.md echo "</details>" >> ./tmp/test.md
sudo make test-e2e
- name: Comment PR with file - name: Comment PR with file
uses: thollander/actions-comment-pull-request@v2 uses: thollander/actions-comment-pull-request@v2
with: with:

@ -41,6 +41,7 @@ jobs:
steps: steps:
- uses: actions/github-script@v7 # v6 - uses: actions/github-script@v7 # v6
with: with:
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
script: | script: |
if (!context.payload.pull_request.merged) { if (!context.payload.pull_request.merged) {
console.log('PR was not merged, skipping.'); console.log('PR was not merged, skipping.');
@ -56,9 +57,10 @@ jobs:
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
state: 'open', state: 'open',
sort: 'due_on', sort: 'title',
direction: 'asc' direction: 'desc'
}) })
if (milestones.data.length === 0) { if (milestones.data.length === 0) {
console.log('There are no milestones, skipping.'); console.log('There are no milestones, skipping.');
return; return;

@ -745,7 +745,7 @@ linters:
- misspell # Spelling mistakes - misspell # Spelling mistakes
- staticcheck # Static analysis - staticcheck # Static analysis
- unused # Checks for unused code - unused # Checks for unused code
- goimports # Checks if imports are correctly sorted and formatted # - goimports # Checks if imports are correctly sorted and formatted
- godot # Checks for comment punctuation - godot # Checks for comment punctuation
- bodyclose # Ensures HTTP response body is closed - bodyclose # Ensures HTTP response body is closed
- stylecheck # Style checker for Go code - stylecheck # Style checker for Go code

@ -11,6 +11,7 @@ ENV GOPROXY=$GOPROXY
# Set up the working directory # Set up the working directory
WORKDIR /openim/openim-server WORKDIR /openim/openim-server
# Copy all files to the container # Copy all files to the container
ADD . . ADD . .

@ -143,7 +143,7 @@ KAFKA_LATESTMSG_REDIS_TOPIC=${KAFKA_LATESTMSG_REDIS_TOPIC}
# MINIO_PORT # MINIO_PORT
# ---------- # ----------
# MINIO_PORT sets the port for the MinIO object storage service. # MINIO_PORT sets the port for the MinIO object storage service.
# Upon changing this port, the MinIO endpoint URLs in the `config/config.yaml` file must be updated # Upon changing this port, the MinIO endpoint URLs in the config/config.yaml file must be updated
# to reflect this change. The endpoints include both the 'endpoint' and 'signEndpoint' # to reflect this change. The endpoints include both the 'endpoint' and 'signEndpoint'
# under the MinIO configuration. # under the MinIO configuration.
# #

@ -186,23 +186,6 @@ services:
# server: # server:
# ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS:-172.28.0.8} # ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS:-172.28.0.8}
### TODO: mysql is required to deploy the openim-chat component
# mysql:
# image: mysql:${MYSQL_IMAGE_VERSION:-5.7}
# platform: linux/amd64
# ports:
# - "${MYSQL_PORT:-13306}:3306"
# container_name: mysql
# volumes:
# - "${DATA_DIR:-./}/components/mysql/data:/var/lib/mysql"
# - "/etc/localtime:/etc/localtime"
# environment:
# MYSQL_ROOT_PASSWORD: "${MYSQL_PASSWORD:-openIM123}"
# restart: always
# networks:
# server:
# ipv4_address: ${MYSQL_NETWORK_ADDRESS:-172.28.0.15}
# openim-chat: # openim-chat:
# image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-chat:${CHAT_IMAGE_VERSION:-main} # image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-chat:${CHAT_IMAGE_VERSION:-main}
# container_name: openim-chat # container_name: openim-chat

@ -96,7 +96,6 @@ We reinforce our approach to branch management and versioning with stringent tes
This document describes the maximum version skew supported between various openim components. Specific cluster deployment tools may place additional restrictions on version skew. This document describes the maximum version skew supported between various openim components. Specific cluster deployment tools may place additional restrictions on version skew.
### Supported version skew ### Supported version skew
In highly-available (HA) clusters, the newest and oldest `openim-api` instances must be within one minor version. In highly-available (HA) clusters, the newest and oldest `openim-api` instances must be within one minor version.
@ -210,6 +209,7 @@ git merge release-v3.1
# Push the updates to the main branch # Push the updates to the main branch
git push origin main git push origin main
``` ```
## Release Process ## Release Process
``` ```
@ -232,5 +232,7 @@ For more details on managing Docker image versions, visit [OpenIM Docker Images
More on multi-branch version management design and version management design at helm charts More on multi-branch version management design and version management design at helm charts
+ https://github.com/openimsdk/open-im-server/issues/1695 About Helm's version management strategy for Multiple Apps and multiple Services:
+ https://github.com/openimsdk/open-im-server/issues/1662
+ [中文版本管理文档](https://github.com/openimsdk/helm-charts/blob/main/docs/contrib/version-zh.md)
+ [English version management documents](https://github.com/openimsdk/helm-charts/blob/main/docs/contrib/version.md)

@ -3,7 +3,6 @@ go 1.19
use ( use (
. .
./test/typecheck ./test/typecheck
./tools/codescan
./tools/changelog ./tools/changelog
./tools/component ./tools/component
./tools/formitychecker ./tools/formitychecker

@ -215,25 +215,22 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
// Set the receiver ID in the message data. // Set the receiver ID in the message data.
sendMsgReq.MsgData.RecvID = req.RecvID sendMsgReq.MsgData.RecvID = req.RecvID
// Declare a variable to store the message sending status.
var status int
// Attempt to send the message using the client. // Attempt to send the message using the client.
respPb, err := m.Client.SendMsg(c, sendMsgReq) respPb, err := m.Client.SendMsg(c, sendMsgReq)
if err != nil { if err != nil {
// Set the status to failed and respond with an error if sending fails. // Set the status to failed and respond with an error if sending fails.
status = constant.MsgSendFailed
apiresp.GinError(c, err) apiresp.GinError(c, err)
return return
} }
// Set the status to successful if the message is sent. // Set the status to successful if the message is sent.
status = constant.MsgSendSuccessed var status int = constant.MsgSendSuccessed
// Attempt to update the message sending status in the system. // Attempt to update the message sending status in the system.
_, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{ _, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{
Status: int32(status), Status: int32(status),
}) })
if err != nil { if err != nil {
// Log the error if updating the status fails. // Log the error if updating the status fails.
apiresp.GinError(c, err) apiresp.GinError(c, err)

@ -141,7 +141,6 @@ func (c *UserConnContext) GetBackground() bool {
b, err := strconv.ParseBool(c.Req.URL.Query().Get(BackgroundStatus)) b, err := strconv.ParseBool(c.Req.URL.Query().Get(BackgroundStatus))
if err != nil { if err != nil {
return false return false
} else {
return b
} }
return b
} }

@ -19,7 +19,6 @@ import (
"sync" "sync"
"github.com/OpenIMSDK/tools/log" "github.com/OpenIMSDK/tools/log"
"github.com/OpenIMSDK/tools/utils"
) )
type UserMap struct { type UserMap struct {
@ -71,48 +70,65 @@ func (u *UserMap) Set(key string, v *Client) {
} }
func (u *UserMap) delete(key string, connRemoteAddr string) (isDeleteUser bool) { func (u *UserMap) delete(key string, connRemoteAddr string) (isDeleteUser bool) {
// Attempt to load the clients associated with the key.
allClients, existed := u.m.Load(key) allClients, existed := u.m.Load(key)
if existed { if !existed {
oldClients := allClients.([]*Client) // Return false immediately if the key does not exist.
var a []*Client return false
for _, client := range oldClients { }
if client.ctx.GetRemoteAddr() != connRemoteAddr {
a = append(a, client) // Convert allClients to a slice of *Client.
} oldClients := allClients.([]*Client)
} var remainingClients []*Client
if len(a) == 0 { for _, client := range oldClients {
u.m.Delete(key) // Keep clients that do not match the connRemoteAddr.
return true if client.ctx.GetRemoteAddr() != connRemoteAddr {
} else { remainingClients = append(remainingClients, client)
u.m.Store(key, a)
return false
} }
} }
return existed
// If no clients remain after filtering, delete the key from the map.
if len(remainingClients) == 0 {
u.m.Delete(key)
return true
}
// Otherwise, update the key with the remaining clients.
u.m.Store(key, remainingClients)
return false
} }
func (u *UserMap) deleteClients(key string, clients []*Client) (isDeleteUser bool) { func (u *UserMap) deleteClients(key string, clientsToDelete []*Client) (isDeleteUser bool) {
m := utils.SliceToMapAny(clients, func(c *Client) (string, struct{}) { // Convert the slice of clients to delete into a map for efficient lookup.
return c.ctx.GetRemoteAddr(), struct{}{} deleteMap := make(map[string]struct{})
}) for _, client := range clientsToDelete {
deleteMap[client.ctx.GetRemoteAddr()] = struct{}{}
}
// Load the current clients associated with the key.
allClients, existed := u.m.Load(key) allClients, existed := u.m.Load(key)
if existed { if !existed {
oldClients := allClients.([]*Client) // If the key doesn't exist, return false.
var a []*Client return false
for _, client := range oldClients { }
if _, ok := m[client.ctx.GetRemoteAddr()]; !ok {
a = append(a, client) // Filter out clients that are in the deleteMap.
} oldClients := allClients.([]*Client)
} var remainingClients []*Client
if len(a) == 0 { for _, client := range oldClients {
u.m.Delete(key) if _, shouldBeDeleted := deleteMap[client.ctx.GetRemoteAddr()]; !shouldBeDeleted {
return true remainingClients = append(remainingClients, client)
} else {
u.m.Store(key, a)
return false
} }
} }
return existed
// Update or delete the key based on the remaining clients.
if len(remainingClients) == 0 {
u.m.Delete(key)
return true
}
u.m.Store(key, remainingClients)
return false
} }
func (u *UserMap) DeleteAll(key string) { func (u *UserMap) DeleteAll(key string) {

@ -184,12 +184,11 @@ func (och *OnlineHistoryRedisConsumerHandler) getPushStorageMsgList(
options2 := msgprocessor.Options(msg.Options) options2 := msgprocessor.Options(msg.Options)
if options2.IsHistory() { if options2.IsHistory() {
return true return true
} else {
// if !(!options2.IsSenderSync() && conversationID == msg.MsgData.SendID) {
// return false
// }
return false
} }
// if !(!options2.IsSenderSync() && conversationID == msg.MsgData.SendID) {
// return false
// }
return false
} }
for _, v := range totalMsgs { for _, v := range totalMsgs {
options := msgprocessor.Options(v.message.Options) options := msgprocessor.Options(v.message.Options)

@ -90,9 +90,8 @@ func (g *Client) Push(ctx context.Context, userIDs []string, title, content stri
for i, v := range s.GetSplitResult() { for i, v := range s.GetSplitResult() {
go func(index int, userIDs []string) { go func(index int, userIDs []string) {
defer wg.Done() defer wg.Done()
if err := g.batchPush(ctx, token, userIDs, pushReq); err != nil { if err = g.batchPush(ctx, token, userIDs, pushReq); err != nil {
log.ZError(ctx, "batchPush failed", err, "index", index, "token", token, "req", pushReq) log.ZError(ctx, "batchPush failed", err, "index", index, "token", token, "req", pushReq)
err = err
} }
}(i, v.Item) }(i, v.Item)
} }

@ -90,9 +90,8 @@ func (r *pushServer) PushMsg(ctx context.Context, pbData *pbpush.PushMsgReq) (re
if err != nil { if err != nil {
if err != errNoOfflinePusher { if err != errNoOfflinePusher {
return nil, err return nil, err
} else {
log.ZWarn(ctx, "offline push failed", err, "msg", pbData.String())
} }
log.ZWarn(ctx, "offline push failed", err, "msg", pbData.String())
} }
return &pbpush.PushMsgResp{}, nil return &pbpush.PushMsgResp{}, nil
} }

@ -26,7 +26,6 @@ func GetContent(msg *sdkws.MsgData) string {
_ = proto.Unmarshal(msg.Content, &tips) _ = proto.Unmarshal(msg.Content, &tips)
content := tips.JsonDetail content := tips.JsonDetail
return content return content
} else {
return string(msg.Content)
} }
return string(msg.Content)
} }

@ -79,9 +79,14 @@ func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq)
CreateTime: time.Now(), CreateTime: time.Now(),
Ex: req.Ex, Ex: req.Ex,
} }
if err := s.blackDatabase.Create(ctx, []*relation.BlackModel{&black}); err != nil { if err := s.blackDatabase.Create(ctx, []*relation.BlackModel{&black}); err != nil {
return nil, err return nil, err
} }
s.notificationSender.BlackAddedNotification(ctx, req)
if err := s.notificationSender.BlackAddedNotification(ctx, req); err != nil {
return nil, err
}
return &pbfriend.AddBlackResp{}, nil return &pbfriend.AddBlackResp{}, nil
} }

@ -114,26 +114,36 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply
if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config); err != nil { if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config); err != nil {
return nil, err return nil, err
} }
if req.ToUserID == req.FromUserID { if req.ToUserID == req.FromUserID {
return nil, errs.ErrCanNotAddYourself.Wrap("req.ToUserID", req.ToUserID) return nil, errs.ErrCanNotAddYourself.Wrap("req.ToUserID", req.ToUserID)
} }
if err = CallbackBeforeAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { if err = CallbackBeforeAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err return nil, err
} }
if _, err := s.userRpcClient.GetUsersInfoMap(ctx, []string{req.ToUserID, req.FromUserID}); err != nil { if _, err := s.userRpcClient.GetUsersInfoMap(ctx, []string{req.ToUserID, req.FromUserID}); err != nil {
return nil, err return nil, err
} }
in1, in2, err := s.friendDatabase.CheckIn(ctx, req.FromUserID, req.ToUserID) in1, in2, err := s.friendDatabase.CheckIn(ctx, req.FromUserID, req.ToUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if in1 && in2 { if in1 && in2 {
return nil, errs.ErrRelationshipAlready.Wrap() return nil, errs.ErrRelationshipAlready.Wrap()
} }
if err = s.friendDatabase.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil { if err = s.friendDatabase.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil {
return nil, err return nil, err
} }
s.notificationSender.FriendApplicationAddNotification(ctx, req)
if err = s.notificationSender.FriendApplicationAddNotification(ctx, req); err != nil {
return nil, err
}
if err = CallbackAfterAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { if err = CallbackAfterAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err return nil, err
} }
@ -197,7 +207,9 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.notificationSender.FriendApplicationAgreedNotification(ctx, req) if err := s.notificationSender.FriendApplicationAgreedNotification(ctx, req); err != nil {
return nil, err
}
return resp, nil return resp, nil
} }
if req.HandleResult == constant.FriendResponseRefuse { if req.HandleResult == constant.FriendResponseRefuse {

@ -33,10 +33,7 @@ func (s *groupServer) GetGroupInfoCache(
return resp, nil return resp, nil
} }
func (s *groupServer) GetGroupMemberCache( func (s *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (resp *pbgroup.GetGroupMemberCacheResp, err error) {
ctx context.Context,
req *pbgroup.GetGroupMemberCacheReq,
) (resp *pbgroup.GetGroupMemberCacheResp, err error) {
members, err := s.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID) members, err := s.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID)
if err != nil { if err != nil {
return nil, err return nil, err

@ -637,6 +637,7 @@ func (s *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG
return resp, nil return resp, nil
} }
// GetGroupApplicationList handles functions that get a list of group requests.
func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.GetGroupApplicationListReq) (*pbgroup.GetGroupApplicationListResp, error) { func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.GetGroupApplicationListReq) (*pbgroup.GetGroupApplicationListResp, error) {
groupIDs, err := s.db.FindUserManagedGroupID(ctx, req.FromUserID) groupIDs, err := s.db.FindUserManagedGroupID(ctx, req.FromUserID)
if err != nil { if err != nil {
@ -951,6 +952,7 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
return nil, errs.Wrap(errs.ErrDismissedAlready) return nil, errs.Wrap(errs.ErrDismissedAlready)
} }
resp := &pbgroup.SetGroupInfoResp{} resp := &pbgroup.SetGroupInfoResp{}
count, err := s.db.FindGroupMemberNum(ctx, group.GroupID) count, err := s.db.FindGroupMemberNum(ctx, group.GroupID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1077,6 +1079,7 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
total, group, err = s.db.SearchGroup(ctx, req.GroupName, req.Pagination) total, group, err = s.db.SearchGroup(ctx, req.GroupName, req.Pagination)
resp.Total = uint32(total) resp.Total = uint32(total)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1084,10 +1087,12 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
groupIDs := utils.Slice(group, func(e *relationtb.GroupModel) string { groupIDs := utils.Slice(group, func(e *relationtb.GroupModel) string {
return e.GroupID return e.GroupID
}) })
ownerMembers, err := s.db.FindGroupsOwner(ctx, groupIDs) ownerMembers, err := s.db.FindGroupsOwner(ctx, groupIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ownerMemberMap := utils.SliceToMap(ownerMembers, func(e *relationtb.GroupMemberModel) string { ownerMemberMap := utils.SliceToMap(ownerMembers, func(e *relationtb.GroupMemberModel) string {
return e.GroupID return e.GroupID
}) })

@ -156,27 +156,27 @@ func callbackMsgModify(ctx context.Context, globalConfig *config.GlobalConfig, m
return nil return nil
} }
func CallbackGroupMsgRead(ctx context.Context, globalConfig *config.GlobalConfig, req *cbapi.CallbackGroupMsgReadReq) error { func CallbackGroupMsgRead(ctx context.Context, globalConfig *config.GlobalConfig, req *cbapi.CallbackGroupMsgReadReq) error {
if !globalConfig.Callback.CallbackGroupMsgRead.Enable || req.ContentType != constant.Text { if !globalConfig.Callback.CallbackGroupMsgRead.Enable {
return nil return nil
} }
req.CallbackCommand = cbapi.CallbackGroupMsgReadCommand req.CallbackCommand = cbapi.CallbackGroupMsgReadCommand
resp := &cbapi.CallbackGroupMsgReadResp{} resp := &cbapi.CallbackGroupMsgReadResp{}
if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackMsgModify); err != nil { if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackGroupMsgRead); err != nil {
return err return err
} }
return nil return nil
} }
func CallbackSingleMsgRead(ctx context.Context, globalConfig *config.GlobalConfig, req *cbapi.CallbackSingleMsgReadReq) error { func CallbackSingleMsgRead(ctx context.Context, globalConfig *config.GlobalConfig, req *cbapi.CallbackSingleMsgReadReq) error {
if !globalConfig.Callback.CallbackSingleMsgRead.Enable || req.ContentType != constant.Text { if !globalConfig.Callback.CallbackSingleMsgRead.Enable {
return nil return nil
} }
req.CallbackCommand = cbapi.CallbackSingleMsgRead req.CallbackCommand = cbapi.CallbackSingleMsgRead
resp := &cbapi.CallbackSingleMsgReadResp{} resp := &cbapi.CallbackSingleMsgReadResp{}
if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackMsgModify); err != nil { if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackSingleMsgRead); err != nil {
return err return err
} }
return nil return nil

@ -137,6 +137,7 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
groupIDs = append(groupIDs, chatLog.GroupID) groupIDs = append(groupIDs, chatLog.GroupID)
} }
} }
// Retrieve sender and receiver information
if len(sendIDs) != 0 { if len(sendIDs) != 0 {
sendInfos, err := m.UserLocalCache.GetUsersInfo(ctx, sendIDs) sendInfos, err := m.UserLocalCache.GetUsersInfo(ctx, sendIDs)
if err != nil { if err != nil {
@ -155,6 +156,8 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
recvMap[recvInfo.UserID] = recvInfo.Nickname recvMap[recvInfo.UserID] = recvInfo.Nickname
} }
} }
// Retrieve group information including member counts
if len(groupIDs) != 0 { if len(groupIDs) != 0 {
groupInfos, err := m.GroupLocalCache.GetGroupInfos(ctx, groupIDs) groupInfos, err := m.GroupLocalCache.GetGroupInfos(ctx, groupIDs)
if err != nil { if err != nil {
@ -162,8 +165,14 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
} }
for _, groupInfo := range groupInfos { for _, groupInfo := range groupInfos {
groupMap[groupInfo.GroupID] = groupInfo groupMap[groupInfo.GroupID] = groupInfo
// Get actual member count
memberIDs, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, groupInfo.GroupID)
if err == nil {
groupInfo.MemberCount = uint32(len(memberIDs)) // Update the member count with actual number
}
} }
} }
// Construct response with updated information
for _, chatLog := range chatLogs { for _, chatLog := range chatLogs {
pbchatLog := &msg.ChatLog{} pbchatLog := &msg.ChatLog{}
utils.CopyStructFields(pbchatLog, chatLog) utils.CopyStructFields(pbchatLog, chatLog)
@ -175,14 +184,14 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
switch chatLog.SessionType { switch chatLog.SessionType {
case constant.SingleChatType, constant.NotificationChatType: case constant.SingleChatType, constant.NotificationChatType:
pbchatLog.RecvNickname = recvMap[chatLog.RecvID] pbchatLog.RecvNickname = recvMap[chatLog.RecvID]
case constant.GroupChatType, constant.SuperGroupChatType: case constant.GroupChatType, constant.SuperGroupChatType:
pbchatLog.SenderFaceURL = groupMap[chatLog.GroupID].FaceURL groupInfo := groupMap[chatLog.GroupID]
pbchatLog.GroupMemberCount = groupMap[chatLog.GroupID].MemberCount pbchatLog.SenderFaceURL = groupInfo.FaceURL
pbchatLog.RecvID = groupMap[chatLog.GroupID].GroupID pbchatLog.GroupMemberCount = groupInfo.MemberCount // Reflects actual member count
pbchatLog.GroupName = groupMap[chatLog.GroupID].GroupName pbchatLog.RecvID = groupInfo.GroupID
pbchatLog.GroupOwner = groupMap[chatLog.GroupID].OwnerUserID pbchatLog.GroupName = groupInfo.GroupName
pbchatLog.GroupType = groupMap[chatLog.GroupID].GroupType pbchatLog.GroupOwner = groupInfo.OwnerUserID
pbchatLog.GroupType = groupInfo.GroupType
} }
resp.ChatLogs = append(resp.ChatLogs, pbchatLog) resp.ChatLogs = append(resp.ChatLogs, pbchatLog)
} }

@ -158,9 +158,6 @@ func (m *msgServer) encapsulateMsgData(msg *sdkws.MsgData) {
case constant.Custom: case constant.Custom:
fallthrough fallthrough
case constant.Quote: case constant.Quote:
utils.SetSwitchFromOptions(msg.Options, constant.IsConversationUpdate, true)
utils.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, true)
utils.SetSwitchFromOptions(msg.Options, constant.IsSenderSync, true)
case constant.Revoke: case constant.Revoke:
utils.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) utils.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false)
utils.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) utils.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false)

@ -60,6 +60,7 @@ func CheckAdmin(ctx context.Context, config *config.GlobalConfig) error {
} }
return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx))) return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx)))
} }
func CheckIMAdmin(ctx context.Context, config *config.GlobalConfig) error { func CheckIMAdmin(ctx context.Context, config *config.GlobalConfig) error {
if utils.IsContain(mcontext.GetOpUserID(ctx), config.IMAdmin.UserID) { if utils.IsContain(mcontext.GetOpUserID(ctx), config.IMAdmin.UserID) {
return nil return nil

@ -16,9 +16,9 @@ package mgo
import ( import (
"context" "context"
"github.com/OpenIMSDK/protocol/constant"
"time" "time"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/tools/errs" "github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/mgoutil" "github.com/OpenIMSDK/tools/mgoutil"
"github.com/OpenIMSDK/tools/pagination" "github.com/OpenIMSDK/tools/pagination"
@ -70,8 +70,12 @@ func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *relation.Gr
} }
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*relation.GroupModel, err error) { func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*relation.GroupModel, err error) {
return mgoutil.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{"group_name": bson.M{"$regex": keyword}, opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
"status": bson.M{"$ne": constant.GroupStatusDismissed}}, pagination)
return mgoutil.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{
"group_name": bson.M{"$regex": keyword},
"status": bson.M{"$ne": constant.GroupStatusDismissed},
}, pagination, opts)
} }
func (g *GroupMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) { func (g *GroupMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {

@ -49,11 +49,7 @@ func NewGroupRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *co
return GroupRpcClient(*NewGroup(discov, config)) return GroupRpcClient(*NewGroup(discov, config))
} }
func (g *GroupRpcClient) GetGroupInfos( func (g *GroupRpcClient) GetGroupInfos(ctx context.Context, groupIDs []string, complete bool) ([]*sdkws.GroupInfo, error) {
ctx context.Context,
groupIDs []string,
complete bool,
) ([]*sdkws.GroupInfo, error) {
resp, err := g.Client.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{ resp, err := g.Client.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{
GroupIDs: groupIDs, GroupIDs: groupIDs,
}) })
@ -184,11 +180,7 @@ func (g *GroupRpcClient) GetGroupInfoCache(ctx context.Context, groupID string)
return resp.GroupInfo, nil return resp.GroupInfo, nil
} }
func (g *GroupRpcClient) GetGroupMemberCache( func (g *GroupRpcClient) GetGroupMemberCache(ctx context.Context, groupID string, groupMemberID string) (*sdkws.GroupMemberFullInfo, error) {
ctx context.Context,
groupID string,
groupMemberID string,
) (*sdkws.GroupMemberFullInfo, error) {
resp, err := g.Client.GetGroupMemberCache(ctx, &group.GetGroupMemberCacheReq{ resp, err := g.Client.GetGroupMemberCache(ctx, &group.GetGroupMemberCacheReq{
GroupID: groupID, GroupID: groupID,
GroupMemberID: groupMemberID, GroupMemberID: groupMemberID,

@ -126,10 +126,7 @@ func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Conte
return f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips) return f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips)
} }
func (f *FriendNotificationSender) FriendApplicationAddNotification( func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) error {
ctx context.Context,
req *pbfriend.ApplyToAddFriendReq,
) error {
tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{ tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID, FromUserID: req.FromUserID,
ToUserID: req.ToUserID, ToUserID: req.ToUserID,

@ -932,7 +932,7 @@ openim::test::set_group_info() {
{ {
"groupInfoForSet": { "groupInfoForSet": {
"groupID": "${1}", "groupID": "${1}",
"groupName": "new-name", "groupName": "new group name",
"notification": "new notification", "notification": "new notification",
"introduction": "new introduction", "introduction": "new introduction",
"faceURL": "www.newfaceURL.com", "faceURL": "www.newfaceURL.com",
@ -1076,6 +1076,7 @@ function openim::test::group() {
local GROUP_ID=$RANDOM local GROUP_ID=$RANDOM
local GROUP_ID2=$RANDOM local GROUP_ID2=$RANDOM
# Assumes that TEST_GROUP_ID, USER_ID, and other necessary IDs are set as environment variables before running this suite. # Assumes that TEST_GROUP_ID, USER_ID, and other necessary IDs are set as environment variables before running this suite.
# 0. Register a friend user. # 0. Register a friend user.
openim::test::user_register "${USER_ID}" "group00" "new_face_url" openim::test::user_register "${USER_ID}" "group00" "new_face_url"

@ -413,7 +413,7 @@ openim::util::check_process_names() {
else else
# If there are PIDs, loop through each one # If there are PIDs, loop through each one
for pid in "${pids[@]}"; do for pid in "${pids[@]}"; do
local command=$(ps -p $pid -o cmd=) local command=$(ps -p $pid -o comm=)
local start_time=$(ps -p $pid -o lstart=) local start_time=$(ps -p $pid -o lstart=)
local port=$(get_port $pid) local port=$(get_port $pid)
@ -489,7 +489,7 @@ openim::util::check_process_names_for_stop() {
else else
# If there are PIDs, loop through each one # If there are PIDs, loop through each one
for pid in "${pids[@]}"; do for pid in "${pids[@]}"; do
local command=$(ps -p $pid -o cmd=) local command=$(ps -p $pid -o comm=)
local start_time=$(ps -p $pid -o lstart=) local start_time=$(ps -p $pid -o lstart=)
local port=$(get_port $pid) local port=$(get_port $pid)

@ -1,46 +0,0 @@
#!/usr/bin/env bash
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script verifies whether codes follow golang convention.
# Usage: `scripts/verify-pkg-names.sh`.
set -o errexit
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
source "${OPENIM_ROOT}/scripts/lib/init.sh"
openim::golang::verify_go_version
openim::golang::verify_go_version
OPENIM_OUTPUT_HOSTBIN_TOOLS="${OPENIM_ROOT}/_output/bin/tools/linux/amd64"
CODESCAN_BINARY="${OPENIM_OUTPUT_HOSTBIN_TOOLS}/codescan"
if [[ ! -f "${CODESCAN_BINARY}" ]]; then
echo "codescan binary not found, building..."
pushd "${OPENIM_ROOT}" >/dev/null
make build BINS="codescan"
popd >/dev/null
fi
if [[ ! -f "${CODESCAN_BINARY}" ]]; then
echo "Failed to build codescan binary."
exit 1
fi
CONFIG_PATH="${OPENIM_ROOT}/tools/codescan/config.yaml"
"${CODESCAN_BINARY}" -config "${CONFIG_PATH}"

@ -1,104 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package checker
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/openimsdk/open-im-server/tools/codescan/config"
)
type CheckResult struct {
FilePath string
Lines []int
}
func checkFileForChineseComments(filePath string) ([]CheckResult, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
var results []CheckResult
scanner := bufio.NewScanner(file)
reg := regexp.MustCompile(`[\p{Han}]+`)
lineNumber := 0
var linesWithChinese []int
for scanner.Scan() {
lineNumber++
if reg.FindString(scanner.Text()) != "" {
linesWithChinese = append(linesWithChinese, lineNumber)
}
}
if len(linesWithChinese) > 0 {
results = append(results, CheckResult{
FilePath: filePath,
Lines: linesWithChinese,
})
}
if err := scanner.Err(); err != nil {
return nil, err
}
return results, nil
}
func WalkDirAndCheckComments(cfg config.Config) error {
var allResults []CheckResult
err := filepath.Walk(cfg.Directory, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
for _, fileType := range cfg.FileTypes {
if filepath.Ext(path) == fileType {
results, err := checkFileForChineseComments(path)
if err != nil {
return err
}
if len(results) > 0 {
allResults = append(allResults, results...)
}
}
}
return nil
})
if err != nil {
return err
}
if len(allResults) > 0 {
var errMsg strings.Builder
errMsg.WriteString("Files containing Chinese comments:\n")
for _, result := range allResults {
errMsg.WriteString(fmt.Sprintf("%s: Lines %v\n", result.FilePath, result.Lines))
}
return fmt.Errorf(errMsg.String())
}
return nil
}

@ -1,34 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"log"
"github.com/openimsdk/open-im-server/tools/codescan/checker"
"github.com/openimsdk/open-im-server/tools/codescan/config"
)
func main() {
cfg, err := config.ParseConfig()
if err != nil {
log.Fatalf("Error parsing config: %v", err)
}
err = checker.WalkDirAndCheckComments(cfg)
if err != nil {
panic(err)
}
}

@ -1,49 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"flag"
"log"
"os"
"gopkg.in/yaml.v2"
)
type Config struct {
Directory string `yaml:"directory"`
FileTypes []string `yaml:"file_types"`
Languages []string `yaml:"languages"`
}
func ParseConfig() (Config, error) {
var configPath string
flag.StringVar(&configPath, "config", "./", "Path to config file")
flag.Parse()
var config Config
if configPath != "" {
configFile, err := os.ReadFile(configPath)
if err != nil {
return Config{}, err
}
if err := yaml.Unmarshal(configFile, &config); err != nil {
return Config{}, err
}
} else {
log.Fatal("Config file must be provided")
}
return config, nil
}

@ -1,3 +0,0 @@
module github.com/openimsdk/open-im-server/tools/codescan
go 1.19
Loading…
Cancel
Save