feat: 添加Gitlab项目管理功能,支持创建、更新和通过上下文获取项目
This commit is contained in:
parent
f55ca65c30
commit
4dd5d8a36c
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -3,6 +3,7 @@
|
||||
"bunx",
|
||||
"CEINTL",
|
||||
"Chakroun",
|
||||
"CICD",
|
||||
"commitlint",
|
||||
"dbaeumer",
|
||||
"deepseek",
|
||||
|
@ -14,6 +14,11 @@ const functionMap = {
|
||||
xAuthor: "zhaoyingbo",
|
||||
xIcon: "🍪",
|
||||
},
|
||||
gitlabAgent: {
|
||||
xName: "小煎蛋 Gitlab Agent",
|
||||
xAuthor: "zhaoyingbo",
|
||||
xIcon: "🐙",
|
||||
},
|
||||
}
|
||||
|
||||
export default functionMap
|
||||
|
7
controller/gitlabEvent/index.ts
Normal file
7
controller/gitlabEvent/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import register from "./register"
|
||||
|
||||
const gitlabEvent = {
|
||||
register,
|
||||
}
|
||||
|
||||
export default gitlabEvent
|
144
controller/gitlabEvent/register.ts
Normal file
144
controller/gitlabEvent/register.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import db from "../../db"
|
||||
import { GitlabProjectModel } from "../../db/gitlabProject/index."
|
||||
import { Context } from "../../types"
|
||||
|
||||
/**
|
||||
* 刷新项目的Gitlab Event
|
||||
* @param param0
|
||||
* @param project
|
||||
* @returns
|
||||
*/
|
||||
const refreshProjectHooks = async (
|
||||
{ gitlabService }: Context,
|
||||
project: GitlabProjectModel
|
||||
) => {
|
||||
gitlabService.setProjectId(project.projectId)
|
||||
const eventList = await gitlabService.hook.getList()
|
||||
const HOOK_URL =
|
||||
Bun.env.NODE_ENV === "production"
|
||||
? "https://lark-egg.ai.xiaomi.com/gitlab"
|
||||
: "https://lark-egg-preview.ai.xiaomi.com/gitlab"
|
||||
// 找到目标Event
|
||||
const targetEvent = eventList.find((event) => event.url === HOOK_URL)
|
||||
|
||||
// 清除Event
|
||||
if (!project.openCICDNotify && !project.openMRSummary) {
|
||||
if (!targetEvent) return
|
||||
await gitlabService.hook.delete(targetEvent.id)
|
||||
return
|
||||
}
|
||||
|
||||
// 添加Event
|
||||
if (targetEvent) return
|
||||
await gitlabService.hook.add({
|
||||
url: HOOK_URL,
|
||||
job_events: true,
|
||||
merge_requests_events: true,
|
||||
})
|
||||
}
|
||||
|
||||
const openFunc = async (
|
||||
ctx: Context,
|
||||
projectId: number,
|
||||
func: "openCICDNotify" | "openMRSummary"
|
||||
) => {
|
||||
const { logger, larkBody, larkService, larkCard } = ctx
|
||||
const cardGender = larkCard.child("gitlabAgent")
|
||||
if (!projectId) {
|
||||
logger.error(`项目ID格式错误,项目ID:${projectId}`)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genErrorCard("项目ID格式错误,请检查项目ID是否正确")
|
||||
)
|
||||
return
|
||||
}
|
||||
const project = await db.gitlabProject.getAndCreate(
|
||||
projectId,
|
||||
ctx.gitlabService
|
||||
)
|
||||
if (!project) {
|
||||
logger.error(`项目不存在,项目ID:${projectId}`)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genErrorCard("该项目不存在,请检查项目ID是否正确")
|
||||
)
|
||||
return
|
||||
}
|
||||
const newProj = await db.gitlabProject.update(project.id, {
|
||||
[func]: true,
|
||||
})
|
||||
if (!newProj) {
|
||||
logger.error(`更新项目失败,项目ID:${projectId}`)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genErrorCard("操作失败,请稍后再试")
|
||||
)
|
||||
return
|
||||
}
|
||||
await refreshProjectHooks(ctx, newProj)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genSuccessCard(
|
||||
`已开启${func === "openCICDNotify" ? "CI/CD成功提醒" : "MR自动总结"}`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const closeFunc = async (
|
||||
ctx: Context,
|
||||
projectId: number,
|
||||
func: "openCICDNotify" | "openMRSummary"
|
||||
) => {
|
||||
const { logger, larkBody, larkService, larkCard } = ctx
|
||||
const cardGender = larkCard.child("gitlabAgent")
|
||||
if (!projectId) {
|
||||
logger.error(`项目ID格式错误,项目ID:${projectId}`)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genErrorCard("项目ID格式错误,请检查项目ID是否正确")
|
||||
)
|
||||
return
|
||||
}
|
||||
const project = await db.gitlabProject.getByProjectId(projectId)
|
||||
if (!project) {
|
||||
logger.error(`项目不存在,项目ID:${projectId}`)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genSuccessCard(
|
||||
`已关闭${func === "openCICDNotify" ? "CI/CD成功提醒" : "MR自动总结"}`
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
const newProj = await db.gitlabProject.update(project.id, {
|
||||
[func]: false,
|
||||
})
|
||||
if (!newProj) {
|
||||
logger.error(`更新项目失败,项目ID:${projectId}`)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genErrorCard("操作失败,请稍后再试")
|
||||
)
|
||||
return
|
||||
}
|
||||
await refreshProjectHooks(ctx, newProj)
|
||||
await larkService.message.replyCard(
|
||||
larkBody.messageId,
|
||||
cardGender.genSuccessCard(
|
||||
`已关闭${func === "openCICDNotify" ? "CI/CD成功提醒" : "MR自动总结"}`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const register = {
|
||||
openCICDNotify: async (ctx: Context, projectId: number) =>
|
||||
openFunc(ctx, projectId, "openCICDNotify"),
|
||||
closeCICDNotify: async (ctx: Context, projectId: number) =>
|
||||
closeFunc(ctx, projectId, "openCICDNotify"),
|
||||
openMRSummary: async (ctx: Context, projectId: number) =>
|
||||
openFunc(ctx, projectId, "openMRSummary"),
|
||||
closeMRSummary: async (ctx: Context, projectId: number) =>
|
||||
closeFunc(ctx, projectId, "openMRSummary"),
|
||||
}
|
||||
|
||||
export default register
|
97
db/gitlabProject/index..tsx
Normal file
97
db/gitlabProject/index..tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import { Gitlab, GitlabService } from "@egg/net-tool"
|
||||
import { RecordModel } from "pocketbase"
|
||||
|
||||
import { Context } from "../../types"
|
||||
import { managePbError } from "../../utils/pbTools"
|
||||
import pbClient from "../pbClient"
|
||||
|
||||
const DB_NAME = "gitlabProject"
|
||||
|
||||
// Gitlab项目接口定义
|
||||
export interface GitlabProject {
|
||||
projectId: number
|
||||
name: string
|
||||
desc: string
|
||||
pathWithNamespace: string
|
||||
webUrl: string
|
||||
openCICDNotify: boolean
|
||||
openMRSummary: boolean
|
||||
}
|
||||
|
||||
// Gitlab项目模型类型
|
||||
export type GitlabProjectModel = GitlabProject & RecordModel
|
||||
|
||||
/**
|
||||
* 创建Gitlab项目
|
||||
* @param {GitlabProject} project - Gitlab项目信息
|
||||
* @returns {Promise<GitlabProjectModel>} - 创建的Gitlab项目模型
|
||||
*/
|
||||
const create = async (project: GitlabProject) =>
|
||||
managePbError<GitlabProjectModel>(() =>
|
||||
pbClient.collection(DB_NAME).create(project)
|
||||
)
|
||||
|
||||
/**
|
||||
* 通过项目ID获取Gitlab项目
|
||||
* @param {number} projectId - 项目ID
|
||||
* @returns {Promise<GitlabProjectModel | null>} - Gitlab项目模型或null
|
||||
*/
|
||||
const getByProjectId = async (projectId: number) =>
|
||||
managePbError<GitlabProjectModel>(() =>
|
||||
pbClient.collection(DB_NAME).getFirstListItem(`projectId = "${projectId}"`)
|
||||
)
|
||||
|
||||
/**
|
||||
* 通过项目ID获取Gitlab项目,如果不存在则创建
|
||||
* @param {number} projectId - 项目ID
|
||||
* @param {GitlabService} gitlabService - Gitlab服务
|
||||
* @returns {Promise<GitlabProjectModel | null>} - Gitlab项目模型或null
|
||||
*/
|
||||
const getAndCreate = async (
|
||||
projectId: number,
|
||||
gitlabService: GitlabService
|
||||
) => {
|
||||
const project = await getByProjectId(projectId)
|
||||
if (project) return project
|
||||
gitlabService.setProjectId(projectId)
|
||||
const projectInfo = await gitlabService.project.getDetail()
|
||||
if (!projectInfo) return null
|
||||
const { name, description, path_with_namespace, web_url } = projectInfo
|
||||
const newProject = {
|
||||
projectId,
|
||||
name,
|
||||
desc: description,
|
||||
pathWithNamespace: path_with_namespace,
|
||||
webUrl: web_url,
|
||||
openCICDNotify: false,
|
||||
openMRSummary: false,
|
||||
}
|
||||
return await create(newProject)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过上下文获取Gitlab项目
|
||||
* @param {Context} context - 上下文对象
|
||||
* @returns {Promise<GitlabProjectModel | null>} - Gitlab项目模型或null
|
||||
*/
|
||||
const getByCtx = async ({ body: rawBody, gitlabService }: Context) => {
|
||||
const body = rawBody as Gitlab.PipelineEvent | Gitlab.MergeRequestEvent
|
||||
const projectId = body.project.id
|
||||
if (!projectId) return null
|
||||
return await getAndCreate(projectId, gitlabService)
|
||||
}
|
||||
|
||||
const update = async (id: string, project: Partial<GitlabProjectModel>) =>
|
||||
managePbError<GitlabProjectModel>(() =>
|
||||
pbClient.collection(DB_NAME).update(id, project)
|
||||
)
|
||||
|
||||
const gitlabProject = {
|
||||
create,
|
||||
update,
|
||||
getByProjectId,
|
||||
getByCtx,
|
||||
getAndCreate,
|
||||
}
|
||||
|
||||
export default gitlabProject
|
@ -3,6 +3,8 @@ import { RecordModel } from "pocketbase"
|
||||
import { managePbError } from "../../utils/pbTools"
|
||||
import pbClient from "../pbClient"
|
||||
|
||||
const DB_NAME = "groupSummaryLog"
|
||||
|
||||
export interface GroupSummaryLog {
|
||||
subscription: string
|
||||
content: string
|
||||
@ -18,7 +20,7 @@ export type GroupSummaryLogModel = GroupSummaryLog & RecordModel
|
||||
*/
|
||||
const create = async (log: GroupSummaryLog) =>
|
||||
managePbError<GroupSummaryLogModel>(() =>
|
||||
pbClient.collection("groupSummaryLog").create(log)
|
||||
pbClient.collection(DB_NAME).create(log)
|
||||
)
|
||||
|
||||
const grpSumLog = {
|
||||
|
@ -4,6 +4,8 @@ import { AppInfoModel } from "../../constant/config"
|
||||
import { managePbError } from "../../utils/pbTools"
|
||||
import pbClient from "../pbClient"
|
||||
|
||||
const DB_NAME = "groupSummarySubscription"
|
||||
|
||||
export interface GroupSummarySubscription {
|
||||
app: string
|
||||
initiator: string
|
||||
@ -23,7 +25,7 @@ export interface GrpSumSubWithApp extends GroupSummarySubscriptionModel {
|
||||
|
||||
const create = async (subscription: GroupSummarySubscription) =>
|
||||
managePbError<GroupSummarySubscriptionModel>(() =>
|
||||
pbClient.collection("groupSummarySubscription").create(subscription)
|
||||
pbClient.collection(DB_NAME).create(subscription)
|
||||
)
|
||||
|
||||
const update = async (
|
||||
@ -31,12 +33,12 @@ const update = async (
|
||||
subscription: Partial<GroupSummarySubscription>
|
||||
) =>
|
||||
managePbError<GroupSummarySubscriptionModel>(() =>
|
||||
pbClient.collection("groupSummarySubscription").update(id, subscription)
|
||||
pbClient.collection(DB_NAME).update(id, subscription)
|
||||
)
|
||||
|
||||
const getAll = async (filter: string = "") =>
|
||||
managePbError<GrpSumSubWithApp[]>(() =>
|
||||
pbClient.collection("groupSummarySubscription").getFullList({
|
||||
pbClient.collection(DB_NAME).getFullList({
|
||||
filter,
|
||||
expand: "app",
|
||||
})
|
||||
@ -44,9 +46,7 @@ const getAll = async (filter: string = "") =>
|
||||
|
||||
const getByFilter = async (filter: string) =>
|
||||
managePbError<GrpSumSubWithApp>(() =>
|
||||
pbClient
|
||||
.collection("groupSummarySubscription")
|
||||
.getFirstListItem(filter, { expand: "app" })
|
||||
pbClient.collection(DB_NAME).getFirstListItem(filter, { expand: "app" })
|
||||
)
|
||||
|
||||
const grpSumSub = {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import apiKey from "./apiKey"
|
||||
import gitlabProject from "./gitlabProject/index."
|
||||
import grpSumLog from "./grpSumLog"
|
||||
import grpSumSub from "./grpSumSub"
|
||||
import log from "./log"
|
||||
@ -12,6 +13,7 @@ const db = {
|
||||
user,
|
||||
grpSumLog,
|
||||
grpSumSub,
|
||||
gitlabProject,
|
||||
}
|
||||
|
||||
export default db
|
||||
|
@ -71,8 +71,7 @@ const getByCtx = async ({ larkBody, larkService }: Context) => {
|
||||
password: email,
|
||||
}
|
||||
// 创建新用户
|
||||
const finalUser = await create(newUser)
|
||||
return finalUser
|
||||
return await create(newUser)
|
||||
}
|
||||
|
||||
// 用户对象
|
||||
|
10
package.json
10
package.json
@ -22,7 +22,7 @@
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"bun-types": "^1.1.38",
|
||||
"bun-types": "^1.1.40",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
@ -39,14 +39,14 @@
|
||||
"@egg/hooks": "^1.2.0",
|
||||
"@egg/lark-msg-tool": "^1.21.0",
|
||||
"@egg/logger": "^1.6.0",
|
||||
"@egg/net-tool": "^1.19.0",
|
||||
"@egg/net-tool": "^1.21.0",
|
||||
"@egg/path-tool": "^1.4.1",
|
||||
"@langchain/core": "^0.3.24",
|
||||
"@langchain/openai": "^0.3.14",
|
||||
"@langchain/core": "^0.3.26",
|
||||
"@langchain/openai": "^0.3.16",
|
||||
"joi": "^17.13.3",
|
||||
"langfuse-langchain": "^3.32.0",
|
||||
"node-schedule": "^2.1.1",
|
||||
"p-limit": "^6.1.0",
|
||||
"p-limit": "^6.2.0",
|
||||
"pocketbase": "^0.23.0",
|
||||
"uuid": "^10.0.0"
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import tempMap from "../../constant/template"
|
||||
import gitlabEvent from "../../controller/gitlabEvent"
|
||||
import groupAgent from "../../controller/groupAgent"
|
||||
import createKVTemp from "../../controller/sheet/createKVTemp"
|
||||
import { Context } from "../../types"
|
||||
@ -137,6 +138,46 @@ const manageCMDMsg = async (ctx: Context) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 开启CICD成功提醒
|
||||
if (msgText.startsWith("/ci")) {
|
||||
logger.info(`bot command is /ci, chatId: ${chatId}`)
|
||||
await gitlabEvent.register.openCICDNotify(
|
||||
ctx,
|
||||
Number(msgText.replace("/ci ", ""))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 关闭CICD成功提醒
|
||||
if (msgText === "/ci off") {
|
||||
logger.info(`bot command is /notify ci off, chatId: ${chatId}`)
|
||||
await gitlabEvent.register.closeCICDNotify(
|
||||
ctx,
|
||||
Number(msgText.replace("/ci off ", ""))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 开启MR自动总结
|
||||
if (msgText === "/mr") {
|
||||
logger.info(`bot command is /mr, chatId: ${chatId}`)
|
||||
await gitlabEvent.register.openMRSummary(
|
||||
ctx,
|
||||
Number(msgText.replace("/mr ", ""))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 关闭MR自动总结
|
||||
if (msgText === "/mr off") {
|
||||
logger.info(`bot command is /mr off, chatId: ${chatId}`)
|
||||
await gitlabEvent.register.closeMRSummary(
|
||||
ctx,
|
||||
Number(msgText.replace("/mr off ", ""))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 仅限群组功能
|
||||
if (isInGroup) {
|
||||
// 注册群组日报
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { LarkBody, LarkCard } from "@egg/lark-msg-tool"
|
||||
import { LarkService, NetTool } from "@egg/net-tool"
|
||||
import { GitlabService, LarkService, NetTool } from "@egg/net-tool"
|
||||
import { PathCheckTool } from "@egg/path-tool"
|
||||
import { Logger } from "winston"
|
||||
|
||||
@ -20,6 +20,7 @@ export interface Context {
|
||||
larkBody: LarkBody
|
||||
larkCard: LarkCard<typeof cardMap, typeof tempMap, typeof functionMap>
|
||||
attachService: AttachService
|
||||
gitlabService: GitlabService
|
||||
path: PathCheckTool
|
||||
searchParams: URLSearchParams
|
||||
app: "michat" | "egg" | string
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { LarkBody, LarkCard } from "@egg/lark-msg-tool"
|
||||
import loggerIns from "@egg/logger"
|
||||
import { NetTool } from "@egg/net-tool"
|
||||
import { GitlabService, NetTool } from "@egg/net-tool"
|
||||
import { PathCheckTool } from "@egg/path-tool"
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
import cardMap from "../constant/card"
|
||||
import { APP_MAP } from "../constant/config"
|
||||
import { APP_CONFIG, APP_MAP } from "../constant/config"
|
||||
import functionMap from "../constant/function"
|
||||
import tempMap from "../constant/template"
|
||||
import { AttachService } from "../services"
|
||||
@ -48,6 +48,11 @@ const genContext = async (req: Request) => {
|
||||
const logger = loggerIns.child({ requestId })
|
||||
const genResp = new NetTool({ requestId })
|
||||
const larkService = genLarkService("egg", requestId)
|
||||
const gitlabService = new GitlabService({
|
||||
baseUrl: APP_CONFIG.GITLAB_BASE_URL,
|
||||
authKey: APP_CONFIG.GITLAB_AUTH_KEY,
|
||||
requestId,
|
||||
})
|
||||
const attachService = new AttachService({ requestId })
|
||||
const path = new PathCheckTool(req.url)
|
||||
const larkCard = new LarkCard(
|
||||
@ -71,6 +76,7 @@ const genContext = async (req: Request) => {
|
||||
larkBody,
|
||||
larkCard,
|
||||
attachService,
|
||||
gitlabService,
|
||||
searchParams,
|
||||
app,
|
||||
appInfo,
|
||||
|
Loading…
x
Reference in New Issue
Block a user