diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index bc7a5009..5b00a918 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -15,13 +15,13 @@ jobs: steps: - name: Check out the repo uses: actions/checkout@v2 - + - name: Log in to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_HUB_ACCOUNT }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - + - name: Build and push main app Docker image uses: docker/build-push-action@v2 with: @@ -38,13 +38,13 @@ jobs: uses: actions/checkout@v2 with: submodules: 'true' - + - name: Log in to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_HUB_ACCOUNT }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - + - name: Build and push website Docker image uses: docker/build-push-action@v2 with: @@ -52,7 +52,7 @@ jobs: file: deploy/Dockerfile.proxy push: true tags: connectai/gitmaya-proxy:latest - + - name: Build and push website Docker image-SaaS uses: docker/build-push-action@v2 with: @@ -60,4 +60,3 @@ jobs: file: deploy/Dockerfile.proxy.saas push: true tags: connectai/gitmaya-proxy:saas - diff --git a/server/model/schema.py b/server/model/schema.py index f55b907e..de422eba 100644 --- a/server/model/schema.py +++ b/server/model/schema.py @@ -208,6 +208,9 @@ class Repo(Base): owner_bind_id = db.Column( ObjID(12), ForeignKey("bind_user.id"), nullable=True, comment="项目所有者" ) + chat_group_id = db.Column( + ObjID(12), ForeignKey("chat_group_v1.id"), nullable=True, comment="项目群ID" + ) repo_id = db.Column(db.String(128), nullable=True, comment="repo_id") name = db.Column(db.String(128), nullable=True, comment="名称") description = db.Column(db.String(1024), nullable=True, comment="描述") @@ -262,10 +265,7 @@ class IMApplication(Base): class ChatGroup(Base): - __tablename__ = "chat_group" - repo_id = db.Column( - ObjID(12), ForeignKey("repo.id"), nullable=True, comment="属于哪一个项目" - ) + __tablename__ = "chat_group_v1" im_application_id = db.Column( ObjID(12), ForeignKey("im_application.id"), nullable=True, comment="哪一个项目创建的" ) @@ -351,6 +351,63 @@ def create(): if "exist" in str(e): db.create_all() + try: + db.session.query(ChatGroup).first() + except Exception as e: + if "exist" in str(e): + db.create_all() + db.session.execute( + text( + "alter table repo add chat_group_id binary(12) after owner_bind_id" + ) + ) + + class ChatGroupOld(Base): + __tablename__ = "chat_group" + repo_id = db.Column( + ObjID(12), ForeignKey("repo.id"), nullable=True, comment="属于哪一个项目" + ) + + im_application_id = db.Column( + ObjID(12), + ForeignKey("im_application.id"), + nullable=True, + comment="哪一个项目创建的", + ) + chat_id = db.Column(db.String(128), nullable=True, comment="chat_id") + name = db.Column(db.String(128), nullable=True, comment="群名称") + description = db.Column(db.String(256), nullable=True, comment="群描述") + extra = db.Column( + JSONStr(2048), + nullable=True, + server_default=text("'{}'"), + comment="其他字段", + ) + + for group in db.session.query(ChatGroupOld).all(): + if ( + not db.session.query(ChatGroup) + .filter(ChatGroup.id == group.id) + .limit(1) + .scalar() + ): + db.session.add( + ChatGroup( + id=group.id, + im_application_id=group.im_application_id, + chat_id=group.chat_id, + name=group.name, + description=group.description, + extra=group.extra, + created=group.created, + modified=group.modified, + ) + ) + db.session.query(Repo).filter(Repo.id == group.repo_id).update( + dict(chat_group_id=group.id) + ) + db.session.commit() + # add command function to cli commands app.cli.add_command(create) diff --git a/server/model/team.py b/server/model/team.py index 34c5fae1..0f2660e7 100644 --- a/server/model/team.py +++ b/server/model/team.py @@ -47,7 +47,7 @@ class RepoWithUsers(Repo): group = relationship( ChatGroup, primaryjoin=and_( - ChatGroup.repo_id == Repo.id, + ChatGroup.id == Repo.chat_group_id, ChatGroup.status == 0, ), viewonly=True, @@ -202,6 +202,9 @@ def get_team_repo(team_id, user_id, page=1, size=20): CodeApplication.status == 0, RepoWithUsers.status == 0, ) + ).order_by( + Repo.chat_group_id.desc(), # 已经关联的在前面 + Repo.created.desc(), # 创建时间倒序 ) total = query.count() if total == 0: @@ -463,7 +466,7 @@ def create_repo_chat_group_by_repo_id(user_id, team_id, repo_id, chat_name=None) chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == repo.id, + ChatGroup.id == repo.chat_group_id, ChatGroup.status == 0, ) .first() @@ -539,7 +542,6 @@ def create_repo_chat_group_by_repo_id(user_id, team_id, repo_id, chat_name=None) chat_group_id = ObjID.new_id() chat_group = ChatGroup( id=chat_group_id, - repo_id=repo.id, im_application_id=application.id, chat_id=chat_id, name=name, @@ -547,6 +549,10 @@ def create_repo_chat_group_by_repo_id(user_id, team_id, repo_id, chat_name=None) extra=result, ) db.session.add(chat_group) + # 更改连表规则,创建新的群之后,需要更新repo.chat_group_id + db.session.query(Repo).filter( + Repo.id == repo.id, + ).update(dict(chat_group_id=chat_group_id)) db.session.commit() # send card message, and pin repo card tasks.send_repo_to_chat_group.delay(repo.id, app_id, chat_id) diff --git a/server/tasks/github/push.py b/server/tasks/github/push.py index cfc1c098..7a65a813 100644 --- a/server/tasks/github/push.py +++ b/server/tasks/github/push.py @@ -36,7 +36,7 @@ def on_push(data: dict | None) -> list: # 发送 Commit Log 信息 chat_group = ( - db.session.query(ChatGroup).filter(ChatGroup.repo_id == repo.id).first() + db.session.query(ChatGroup).filter(ChatGroup.id == repo.chat_group_id).first() ) if not chat_group: app.logger.info(f"ChatGroup not found: {repo.name}") diff --git a/server/tasks/lark/chat.py b/server/tasks/lark/chat.py index ca3d1177..b3ed0910 100644 --- a/server/tasks/lark/chat.py +++ b/server/tasks/lark/chat.py @@ -23,6 +23,7 @@ from .base import ( get_bot_by_application_id, get_chat_group_by_chat_id, + get_git_object_by_message_id, get_repo_name_by_repo_id, with_authenticated_github, ) @@ -61,7 +62,7 @@ def send_chat_manual(app_id, message_id, content, data, *args, **kwargs): repo = ( db.session.query(Repo) .filter( - Repo.id == chat_group.repo_id, + Repo.chat_group_id == chat_group.id, Repo.status == 0, ) .first() @@ -187,19 +188,44 @@ def create_issue( return send_chat_failed_tip( "找不到项目群", app_id, message_id, content, data, *args, **kwargs ) - repo = ( - db.session.query(Repo) - .filter( - Repo.id == chat_group.repo_id, - Repo.status == 0, + repos = [] + try: + # 如果是在话题内运行命令(repo/issue/pull_request)尝试找到对应的repo + if len(repos) == 0: + root_id = data["event"]["message"].get("root_id", "") + if root_id: + repo, issue, pr = tasks.get_git_object_by_message_id(root_id) + if repo: + repos = [repo] + elif issue or pr: + repo_id = issue.repo_id if issue else pr.repo_id + repo = db.session.query(Repo).filter(Repo.id == repo_id).first() + if repo: + repos = [repo] + except Exception as e: + logging.error(e) + + if len(repos) == 0: + repos = ( + db.session.query(Repo) + .filter( + Repo.chat_group_id == chat_group.id, + Repo.status == 0, + ) + .all() ) - .first() - ) - if not repo: + if len(repos) > 1: + return send_chat_failed_tip( + "当前群有多个项目,无法唯一确定仓库", app_id, message_id, content, data, *args, **kwargs + ) + + if len(repos) == 0: return send_chat_failed_tip( "找不到项目", app_id, message_id, content, data, *args, **kwargs ) + repo = repos[0] # 能找到唯一的仓库才执行 + code_application = ( db.session.query(CodeApplication) .filter( diff --git a/server/tasks/lark/issue.py b/server/tasks/lark/issue.py index 76b7293c..584131ae 100644 --- a/server/tasks/lark/issue.py +++ b/server/tasks/lark/issue.py @@ -249,14 +249,16 @@ def send_issue_card(issue_id): """ issue = db.session.query(Issue).filter(Issue.id == issue_id).first() if issue: + repo = db.session.query(Repo).filter(Repo.id == issue.repo_id).first() + if not repo: + return False chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == issue.repo_id, + ChatGroup.id == repo.chat_group_id, ) .first() ) - repo = db.session.query(Repo).filter(Repo.id == issue.repo_id).first() if chat_group and repo: bot, application = get_bot_by_application_id(chat_group.im_application_id) team = db.session.query(Team).filter(Team.id == application.team_id).first() @@ -303,10 +305,13 @@ def send_issue_comment(issue_id, comment, user_name: str): """ issue = db.session.query(Issue).filter(Issue.id == issue_id).first() if issue: + repo = db.session.query(Repo).filter(Repo.id == issue.repo_id).first() + if not repo: + return False chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == issue.repo_id, + ChatGroup.id == repo.chat_group_id, ) .first() ) @@ -355,14 +360,16 @@ def update_issue_card(issue_id: str): issue = db.session.query(Issue).filter(Issue.id == issue_id).first() if issue: + repo = db.session.query(Repo).filter(Repo.id == issue.repo_id).first() + if not repo: + return False chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == issue.repo_id, + ChatGroup.id == repo.chat_group_id, ) .first() ) - repo = db.session.query(Repo).filter(Repo.id == issue.repo_id).first() if chat_group and repo: bot, application = get_bot_by_application_id(chat_group.im_application_id) diff --git a/server/tasks/lark/manage.py b/server/tasks/lark/manage.py index 138bbeb9..06dc7bbe 100644 --- a/server/tasks/lark/manage.py +++ b/server/tasks/lark/manage.py @@ -17,6 +17,7 @@ TeamMember, db, ) +from sqlalchemy import or_ from sqlalchemy.orm import aliased from utils.lark.chat_manual import ChatManual from utils.lark.manage_fail import ManageFaild @@ -81,7 +82,7 @@ def send_welcome_message(app_id, event_id, event, message, *args, **kwargs): .order_by( Repo.modified.desc(), ) - .limit(20) + # .limit(20) # 这里先不限制长度,看看飞书那边展示情况 .all() ) message = ManageManual( @@ -121,7 +122,7 @@ def send_manage_manual(app_id, message_id, *args, **kwargs): .order_by( Repo.modified.desc(), ) - .limit(20) + # .limit(20) # 这里先不限制长度,看看飞书那边展示情况 .all() ) message = ManageManual( @@ -298,7 +299,7 @@ def create_chat_group_for_repo( chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == repo.id, + ChatGroup.id == repo.chat_group_id, ChatGroup.status == 0, ) .first() @@ -315,19 +316,7 @@ def create_chat_group_for_repo( "不允许重复创建项目群", app_id, message_id, *args, bot=bot, **kwargs ) - # 持有相同uuid的请求10小时内只可成功创建1个群聊 - chat_group_url = f"{bot.host}/open-apis/im/v1/chats?uuid={repo.id}" - # TODO 这里是一个可以配置的模板 - name = chat_name or f"{repo.name} 项目群" - description = f"{repo.description}" - # TODO 当前先使用发消息的人,后面查找这个项目的所有者... - try: - # parser.parse_args(text, bot.app_id, message_id, content, *args, **kwargs) - owner_id = args[1]["event"]["sender"]["sender_id"]["open_id"] - except Exception as e: - logging.error(e) - # card event - owner_id = args[0]["open_id"] + # 先查询当前项目成员列表 CodeUser = aliased(BindUser) IMUser = aliased(BindUser) # user_id_list 使用这个项目绑定的人的列表,同时属于当前repo @@ -348,6 +337,76 @@ def create_chat_group_for_repo( RepoUser.repo_id == repo.id, ) ] + # 把user_id_list中的每个user_id查User表,获取每个人的名字 + user_name_list = [ + name + for name, in db.session.query(IMUser.name).filter( + IMUser.openid.in_(user_id_list), + ) + ] + try: + chat_id == args[1]["event"]["message"]["chat_id"] + except Exception as e: + chat_id = "" + + # 如果有已经存在的项目群,尝试直接绑定这个群,将当前项目人员拉进群 + exists_chat_group = ( + db.session.query(ChatGroup) + .filter( + ChatGroup.im_application_id == application.id, + or_( + ChatGroup.name == chat_name if chat_name else False, + # 现在支持在群聊里面使用match,尝试从chat_id获取名字 + ChatGroup.chat_id == chat_id, + ), + ) + .first() + ) + if exists_chat_group: + db.session.query(Repo).filter(Repo.id == repo.id).update( + dict(chat_group_id=exists_chat_group.id) + ) + db.session.commit() + chat_id = exists_chat_group.chat_id + chat_group_members_url = f"{bot.host}/open-apis/im/v1/chats/{chat_id}/members" + result = bot.post( + chat_group_members_url, + json={"id_list": user_id_list}, + ).json() + logging.debug("add members %r to chat_id %r", user_id_list, chat_id) + invite_message = ( + f"2. 成功拉取「 {'、'.join(user_name_list)} 」进入「{exists_chat_group.name}」群" + if len(user_name_list) > 0 + else "2. 未获取相关绑定成员, 请检查成员是否绑定" + ) + + content = "\n".join( + [ + f"1. 成功绑定名为「{chat_name}」的项目群", + invite_message, + ] + ) + # 这里可以再触发一个异步任务给群发卡片,不过为了保存结果,就同步调用 + result = send_repo_to_chat_group(repo.id, app_id, chat_id) + [ + send_manage_success_message( + content, app_id, message_id, *args, bot=bot, **kwargs + ) + ] + return result + + # 持有相同uuid的请求10小时内只可成功创建1个群聊 + chat_group_url = f"{bot.host}/open-apis/im/v1/chats?uuid={repo.id}" + # TODO 这里是一个可以配置的模板 + name = chat_name or f"{repo.name} 项目群" + description = f"{repo.description}" + # TODO 当前先使用发消息的人,后面查找这个项目的所有者... + try: + # parser.parse_args(text, bot.app_id, message_id, content, *args, **kwargs) + owner_id = args[1]["event"]["sender"]["sender_id"]["open_id"] + except Exception as e: + logging.error(e) + # card event + owner_id = args[0]["open_id"] if owner_id not in user_id_list: user_id_list += [owner_id] @@ -373,7 +432,6 @@ def create_chat_group_for_repo( chat_group_id = ObjID.new_id() chat_group = ChatGroup( id=chat_group_id, - repo_id=repo.id, im_application_id=application.id, chat_id=chat_id, name=name, @@ -381,20 +439,16 @@ def create_chat_group_for_repo( extra=result, ) db.session.add(chat_group) + # 创建群组之后,更新repo.chat_group_id + db.session.query(Repo).filter(Repo.id == repo.id).update( + dict(chat_group_id=chat_group_id) + ) db.session.commit() """ 创建项目群之后,需要发两条消息: 1. 给操作的用户发成功的消息 2. 给群发送repo 卡片消息,并pin """ - - # 把user_id_list中的每个user_id查User表,获取每个人的名字 - user_name_list = [ - name - for name, in db.session.query(IMUser.name).filter( - IMUser.openid.in_(user_id_list), - ) - ] invite_message = ( f"2. 成功拉取「 {'、'.join(user_name_list)} 」进入「{name}」群" if len(user_name_list) > 0 diff --git a/server/tasks/lark/pull_request.py b/server/tasks/lark/pull_request.py index cb886804..a560dcf2 100644 --- a/server/tasks/lark/pull_request.py +++ b/server/tasks/lark/pull_request.py @@ -281,14 +281,14 @@ def send_pull_request_card(pull_request_id: str): """ pr = db.session.query(PullRequest).filter(PullRequest.id == pull_request_id).first() if pr: + repo = db.session.query(Repo).filter(Repo.id == pr.repo_id).first() chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == pr.repo_id, + ChatGroup.id == repo.chat_group_id, ) .first() ) - repo = db.session.query(Repo).filter(Repo.id == pr.repo_id).first() if chat_group and repo: bot, application = get_bot_by_application_id(chat_group.im_application_id) team = db.session.query(Team).filter(Team.id == application.team_id).first() @@ -338,10 +338,13 @@ def send_pull_request_comment(pull_request_id, comment, user_name: str): """ pr = db.session.query(PullRequest).filter(PullRequest.id == pull_request_id).first() if pr: + repo = db.session.query(Repo).filter(Repo.id == pr.repo_id).first() + if not repo: + return False chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == pr.repo_id, + ChatGroup.id == repo.chat_group_id, ) .first() ) @@ -367,14 +370,16 @@ def update_pull_request_card(pr_id: str) -> bool | dict: pr = db.session.query(PullRequest).filter(PullRequest.id == pr_id).first() if pr: + repo = db.session.query(Repo).filter(Repo.id == pr.repo_id).first() + if not repo: + return False chat_group = ( db.session.query(ChatGroup) .filter( - ChatGroup.repo_id == pr.repo_id, + ChatGroup.id == repo.chat_group_id, ) .first() ) - repo = db.session.query(Repo).filter(Repo.id == pr.repo_id).first() if chat_group and repo: bot, application = get_bot_by_application_id(chat_group.im_application_id) team = db.session.query(Team).filter(Team.id == application.team_id).first()