From c96367f157eb221952fb7f8358afa48858270b04 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 7 Aug 2023 16:27:49 +0800 Subject: [PATCH] feat: add github repo group ensure action which will auto create group and subscribe activity --- client/web/src/plugin/common/index.ts | 1 + client/web/tailchat.d.ts | 10 +- pnpm-lock.yaml | 3 + server/packages/sdk/src/services/base.ts | 17 +++- server/packages/sdk/src/structs/group.ts | 2 + .../plugins/com.msgbyte.github/.ministarrc.js | 2 +- .../plugins/com.msgbyte.github/models/repo.ts | 34 +++++++ .../plugins/com.msgbyte.github/package.json | 1 + .../services/repo.service.dev.ts | 99 +++++++++++++++++++ .../src/components/GithubRepoInfo.tsx | 23 +++++ .../plugins/com.msgbyte.github/src/index.tsx | 22 ++++- server/services/core/group/group.service.ts | 6 +- 12 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 server/plugins/com.msgbyte.github/models/repo.ts create mode 100644 server/plugins/com.msgbyte.github/services/repo.service.dev.ts create mode 100644 server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/components/GithubRepoInfo.tsx diff --git a/client/web/src/plugin/common/index.ts b/client/web/src/plugin/common/index.ts index 3164d14a..a95c5da2 100644 --- a/client/web/src/plugin/common/index.ts +++ b/client/web/src/plugin/common/index.ts @@ -72,6 +72,7 @@ export { useWatch, parseUrlStr, useUpdateRef, + isDevelopment, } from 'tailchat-shared'; export { navigate } from '@/components/AppRouterApi'; diff --git a/client/web/tailchat.d.ts b/client/web/tailchat.d.ts index bb22b1b6..a0332ea6 100644 --- a/client/web/tailchat.d.ts +++ b/client/web/tailchat.d.ts @@ -186,7 +186,11 @@ declare module '@capital/common' { export const useWatch: any; - export const parseUrlStr: any; + export const parseUrlStr: (originUrl: string) => string; + + export const useUpdateRef: (state: T) => React.MutableRefObject; + + export const isDevelopment: boolean; export const navigate: any; @@ -472,6 +476,8 @@ declare module '@capital/component' { export const useChatInputActionContext: any; + export const GroupPanelContainer: any; + export const GroupExtraDataPanel: any; export const Image: any; @@ -599,4 +605,6 @@ declare module '@capital/component' { export const NoData: any; export const NotFound: any; + + export const withKeepAliveOverlay: any; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9cfe453..fe106631 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1890,6 +1890,9 @@ importers: specifier: ^5.4.0 version: 5.8.0 devDependencies: + '@types/react-router': + specifier: ^5.1.20 + version: 5.1.20 less: specifier: ^4.1.2 version: 4.1.3 diff --git a/server/packages/sdk/src/services/base.ts b/server/packages/sdk/src/services/base.ts index 138e4746..d4b637e5 100644 --- a/server/packages/sdk/src/services/base.ts +++ b/server/packages/sdk/src/services/base.ts @@ -18,7 +18,7 @@ import type { TFunction } from 'i18next'; import { t } from './lib/i18n'; import type { ValidationRuleObject } from 'fastest-validator'; import type { BuiltinEventMap } from '../structs/events'; -import { CONFIG_GATEWAY_AFTER_HOOK } from '../const'; +import { CONFIG_GATEWAY_AFTER_HOOK, SYSTEM_USERID } from '../const'; import _ from 'lodash'; import { decodeNoConflictServiceNameKey, @@ -451,6 +451,21 @@ export abstract class TcService extends Service { return this.actions[actionName](params, opts); } + protected systemCall( + ctx: PureContext, + actionName: string, + params?: {}, + opts?: CallingOptions + ): Promise { + return ctx.call(actionName, params, { + ...opts, + meta: { + userId: SYSTEM_USERID, + ...(opts?.meta ?? {}), + }, + }); + } + private buildLoggerWithPrefix(_originLogger: LoggerInstance) { const prefix = `[${this.serviceName}]`; const originLogger = _originLogger; diff --git a/server/packages/sdk/src/structs/group.ts b/server/packages/sdk/src/structs/group.ts index c012df87..1b966822 100644 --- a/server/packages/sdk/src/structs/group.ts +++ b/server/packages/sdk/src/structs/group.ts @@ -41,6 +41,8 @@ export interface GroupRoleStruct { } export interface GroupStruct { + _id: string; + name: string; avatar?: string; diff --git a/server/plugins/com.msgbyte.github/.ministarrc.js b/server/plugins/com.msgbyte.github/.ministarrc.js index ab39d260..f47ca317 100644 --- a/server/plugins/com.msgbyte.github/.ministarrc.js +++ b/server/plugins/com.msgbyte.github/.ministarrc.js @@ -1,7 +1,7 @@ const path = require('path'); module.exports = { - externalDeps: ['react'], + externalDeps: ['react', 'react-router'], pluginRoot: path.resolve(__dirname, './web'), outDir: path.resolve(__dirname, '../../public'), }; diff --git a/server/plugins/com.msgbyte.github/models/repo.ts b/server/plugins/com.msgbyte.github/models/repo.ts new file mode 100644 index 00000000..b654e46c --- /dev/null +++ b/server/plugins/com.msgbyte.github/models/repo.ts @@ -0,0 +1,34 @@ +import { + getModelForClass, + prop, + DocumentType, + modelOptions, +} from '@typegoose/typegoose'; +import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'; +import type { Types } from 'mongoose'; + +@modelOptions({ + options: { + customName: 'p_githubRepo', + }, +}) +export class Repo extends TimeStamps implements Base { + _id: Types.ObjectId; + id: string; + + @prop({ + unique: true, + }) + repoName: string; // 完整地址 + + @prop() + groupId: string; +} + +export type RepoDocument = DocumentType; + +const model = getModelForClass(Repo); + +export type RepoModel = typeof model; + +export default model; diff --git a/server/plugins/com.msgbyte.github/package.json b/server/plugins/com.msgbyte.github/package.json index 4a9a3c21..d0a32aa7 100644 --- a/server/plugins/com.msgbyte.github/package.json +++ b/server/plugins/com.msgbyte.github/package.json @@ -13,6 +13,7 @@ "@octokit/webhooks-types": "^5.4.0" }, "devDependencies": { + "@types/react-router": "^5.1.20", "less": "^4.1.2", "mini-star": "^1.2.8", "rollup-plugin-less": "^1.1.3" diff --git a/server/plugins/com.msgbyte.github/services/repo.service.dev.ts b/server/plugins/com.msgbyte.github/services/repo.service.dev.ts new file mode 100644 index 00000000..dbe75382 --- /dev/null +++ b/server/plugins/com.msgbyte.github/services/repo.service.dev.ts @@ -0,0 +1,99 @@ +import type { GroupStruct } from 'tailchat-server-sdk'; +import { GroupPanelType, TcContext } from 'tailchat-server-sdk'; +import { TcService, TcDbService } from 'tailchat-server-sdk'; +import type { RepoDocument, RepoModel } from '../models/repo'; + +const ACTIVITY_PANEL_NAME = 'Activity'; + +const defaultGroupPanel = [ + { + id: '00', + name: 'Default', + type: GroupPanelType.GROUP, + }, + { + id: '01', + name: 'Lobby', + parentId: '00', + type: GroupPanelType.TEXT, + }, + { + id: '02', + name: ACTIVITY_PANEL_NAME, + parentId: '00', + type: GroupPanelType.TEXT, + }, + { + id: '10', + name: 'Project', + type: GroupPanelType.GROUP, + }, +]; + +/** + * Github项目管理服务 + */ + +interface GithubRepoService + extends TcService, + TcDbService {} +class GithubRepoService extends TcService { + get serviceName() { + return 'plugin:com.msgbyte.github.repo'; + } + + onInit() { + this.registerLocalDb(require('../models/repo').default); + + this.registerAction('ensure', this.ensure, { + params: { + repoName: 'string', + }, + }); + } + + async ensure(ctx: TcContext<{ repoName: string }>): Promise<{ + repoName: string; + groupId: string; + }> { + const { repoName } = ctx.params; + + let doc = await this.adapter.model.findOne({ + repoName, + }); + + if (!doc) { + // 不存在 + const group = await this.systemCall( + ctx, + 'group.createGroup', + { + name: repoName, + panels: defaultGroupPanel, + } + ); + const groupId = String(group._id); + const activityPanel = group.panels.find( + (item) => item.name === ACTIVITY_PANEL_NAME + ); + if (activityPanel) { + await this.systemCall(ctx, 'plugin:com.msgbyte.github.subscribe.add', { + groupId, + textPanelId: activityPanel.id, + repoName, + }); + } + + doc = await this.adapter.model.create({ + repoName, + groupId, + }); + } + + const json = await this.transformDocuments(ctx, {}, doc); + + return json; + } +} + +export default GithubRepoService; diff --git a/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/components/GithubRepoInfo.tsx b/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/components/GithubRepoInfo.tsx new file mode 100644 index 00000000..2778b829 --- /dev/null +++ b/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/components/GithubRepoInfo.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { useParams } from 'react-router'; + +interface GithubRepoName { + owner: string; + repo: string; +} + +export const GithubRepoInfo: React.FC = React.memo((props) => { + return
GithubRepoInfo {JSON.stringify(props)}
; +}); +GithubRepoInfo.displayName = 'GithubRepoInfo'; + +export const GithubRepoInfoRoute: React.FC = React.memo(() => { + const params = useParams(); + + return ( +
+ +
+ ); +}); +GithubRepoInfoRoute.displayName = 'GithubRepoInfoRoute'; diff --git a/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/index.tsx b/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/index.tsx index a774cc34..c563f4ad 100644 --- a/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/index.tsx +++ b/server/plugins/com.msgbyte.github/web/plugins/com.msgbyte.github/src/index.tsx @@ -1,26 +1,40 @@ import { regCustomPanel, - Loadable, regInspectService, regPluginPermission, + regPluginRootRoute, + isDevelopment, } from '@capital/common'; +import { Loadable } from '@capital/component'; import { Translate } from './translate'; +const PLUGIN_ID = 'com.msgbyte.github'; + regCustomPanel({ position: 'groupdetail', - name: 'com.msgbyte.github/groupSubscribe', + name: `${PLUGIN_ID}/groupSubscribe`, label: Translate.groupSubscribe, render: Loadable(() => import('./GroupSubscribePanel')), }); regInspectService({ - name: 'plugin:com.msgbyte.github.subscribe', + name: `plugin:${PLUGIN_ID}.subscribe`, label: Translate.githubService, }); regPluginPermission({ - key: 'plugin.com.msgbyte.github.subscribe.manage', + key: `plugin.${PLUGIN_ID}.subscribe.manage`, title: Translate.permissionTitle, desc: Translate.permissionDesc, default: false, }); + +if (isDevelopment) { + regPluginRootRoute({ + name: `plugin:${PLUGIN_ID}/route`, + path: '/github/:owner/:repo', + component: Loadable(() => + import('./components/GithubRepoInfo').then((m) => m.GithubRepoInfoRoute) + ), + }); +} diff --git a/server/services/core/group/group.service.ts b/server/services/core/group/group.service.ts index 46a8a369..40799178 100644 --- a/server/services/core/group/group.service.ts +++ b/server/services/core/group/group.service.ts @@ -21,6 +21,7 @@ import { GroupPanelType, PanelFeature, config, + SYSTEM_USERID, } from 'tailchat-server-sdk'; import moment from 'moment'; @@ -291,7 +292,10 @@ class GroupService extends TcService { const userId = ctx.meta.userId; const t = ctx.meta.t; - if (config.feature.disableCreateGroup === true) { + if ( + config.feature.disableCreateGroup === true && + userId !== SYSTEM_USERID + ) { // 环境变量禁止创建群组 throw new NoPermissionError(t('创建群组功能已被管理员禁用')); }