mirror of https://github.com/certd/certd
feat: 邮件通知
parent
64afebecd4
commit
937e3fac19
|
@ -2,9 +2,9 @@
|
|||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.0.6",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/pipeline.mjs",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
"main": "./src",
|
||||
"module": "./src",
|
||||
"types": "./src",
|
||||
"publishConfig": {
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.mjs",
|
||||
|
@ -19,6 +19,7 @@
|
|||
"dependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"nodemailer": "^6.9.3",
|
||||
"qs": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ConcurrencyStrategy, NotificationType, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts";
|
||||
import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts";
|
||||
import _ from "lodash";
|
||||
import { RunHistory, RunnableCollection } from "./run-history";
|
||||
import { AbstractTaskPlugin, PluginDefine, pluginRegistry } from "../plugin";
|
||||
|
@ -216,13 +216,13 @@ export class Executor {
|
|||
let subject = "";
|
||||
let content = "";
|
||||
if (when === "start") {
|
||||
subject = `【CertD】${this.pipeline.title} 开始执行,buildId:${this.runtime.id}`;
|
||||
content = `【CertD】${this.pipeline.title} 开始执行,buildId:${this.runtime.id}`;
|
||||
subject = `【CertD】开始执行,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
||||
content = subject;
|
||||
} else if (when === "success") {
|
||||
subject = `【CertD】${this.pipeline.title} 执行成功,buildId:${this.runtime.id}`;
|
||||
content = `【CertD】${this.pipeline.title} 执行成功,buildId:${this.runtime.id}`;
|
||||
subject = `【CertD】执行成功,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
||||
content = subject;
|
||||
} else if (when === "error") {
|
||||
subject = `【CertD】${this.pipeline.title} 执行失败,buildId:${this.runtime.id}`;
|
||||
subject = `【CertD】执行失败,${this.pipeline.title}, buildId:${this.runtime.id}`;
|
||||
content = `<pre>${error.message}</pre>`;
|
||||
} else {
|
||||
return;
|
||||
|
@ -234,6 +234,7 @@ export class Executor {
|
|||
}
|
||||
if (notification.type === "email") {
|
||||
this.options.emailService?.send({
|
||||
userId: this.pipeline.userId,
|
||||
subject,
|
||||
content,
|
||||
receivers: notification.options.receivers,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export type EmailSend = {
|
||||
userId: number;
|
||||
subject: string;
|
||||
content: string;
|
||||
receivers: string[];
|
||||
};
|
||||
|
||||
export interface IEmailService {
|
||||
send(email: EmailSend): Promise<void>;
|
||||
}
|
|
@ -1,9 +1 @@
|
|||
export type EmailSend = {
|
||||
subject: string;
|
||||
content: string;
|
||||
receivers: string[];
|
||||
};
|
||||
|
||||
export interface IEmailService {
|
||||
send(email: EmailSend): Promise<void>;
|
||||
}
|
||||
export * from "./email";
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
VITE_APP_API=/api
|
||||
#登录与权限关闭
|
||||
VITE_APP_PM_ENABLED=true
|
||||
VITE_APP_TITLE=Certd
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>antdv-fast-crud</title>
|
||||
<title>Certd-让你的证书永不过期</title>
|
||||
<link rel="stylesheet" type="text/css" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -106,8 +106,8 @@ function createService() {
|
|||
* @description 创建请求方法
|
||||
* @param {Object} service axios 实例
|
||||
*/
|
||||
function createRequestFunction(service) {
|
||||
return function (config) {
|
||||
function createRequestFunction(service: any) {
|
||||
return function (config: any) {
|
||||
const configDefault = {
|
||||
headers: {
|
||||
"Content-Type": get(config, "headers.Content-Type", "application/json")
|
||||
|
|
|
@ -48,7 +48,10 @@ export function responseError(data = {}, msg = "请求失败", code = 500) {
|
|||
* @description 记录和显示错误
|
||||
* @param {Error} error 错误对象
|
||||
*/
|
||||
export function errorLog(error) {
|
||||
export function errorLog(error: any) {
|
||||
if (error?.response?.data?.message) {
|
||||
error.message = error?.response?.data?.message;
|
||||
}
|
||||
// 打印到控制台
|
||||
console.error(error);
|
||||
// 显示提示
|
||||
|
@ -59,8 +62,6 @@ export function errorLog(error) {
|
|||
* @description 创建一个错误
|
||||
* @param {String} msg 错误信息
|
||||
*/
|
||||
export function errorCreate(msg) {
|
||||
const error = new Error(msg);
|
||||
errorLog(error);
|
||||
throw error;
|
||||
export function errorCreate(msg: string) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,28 @@ export const certdResources = [
|
|||
meta: {
|
||||
icon: "ion:disc-outline"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "设置",
|
||||
name: "certdSettings",
|
||||
path: "/certd/settings",
|
||||
redirect: "/certd/settings/email",
|
||||
meta: {
|
||||
icon: "ion:settings-outline",
|
||||
auth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "邮箱设置",
|
||||
name: "email",
|
||||
path: "/certd/settings/email",
|
||||
component: "/certd/settings/email-setting.vue",
|
||||
meta: {
|
||||
icon: "ion:mail-outline",
|
||||
auth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
// @ts-ignore
|
||||
import _ from "lodash";
|
||||
export function getEnvValue(key) {
|
||||
export function getEnvValue(key: string) {
|
||||
// @ts-ignore
|
||||
return import.meta.env["VITE_APP_" + key];
|
||||
}
|
||||
|
||||
export class EnvConfig {
|
||||
API;
|
||||
MODE;
|
||||
STORAGE;
|
||||
TITLE;
|
||||
PM_ENABLED;
|
||||
API: string;
|
||||
MODE: string;
|
||||
STORAGE: string;
|
||||
TITLE: string;
|
||||
PM_ENABLED: string;
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
@ -19,6 +20,7 @@ export class EnvConfig {
|
|||
_.forEach(import.meta.env, (value, key) => {
|
||||
if (key.startsWith("VITE_APP")) {
|
||||
key = key.replace("VITE_APP_", "");
|
||||
//@ts-ignore
|
||||
this[key] = value;
|
||||
}
|
||||
});
|
||||
|
@ -26,7 +28,8 @@ export class EnvConfig {
|
|||
this.MODE = import.meta.env.MODE;
|
||||
}
|
||||
|
||||
get(key, defaultValue) {
|
||||
get(key: string, defaultValue: string) {
|
||||
//@ts-ignore
|
||||
return this[key] ?? defaultValue;
|
||||
}
|
||||
isDev() {
|
||||
|
|
|
@ -2,9 +2,9 @@ import { env } from "./util.env";
|
|||
export const site = {
|
||||
/**
|
||||
* @description 更新标题
|
||||
* @param {String} title 标题
|
||||
* @param titleText
|
||||
*/
|
||||
title: function (titleText) {
|
||||
title: function (titleText: string) {
|
||||
const processTitle = env.TITLE || "FsAdmin";
|
||||
window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ""}`;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { RunHistory } from "/@/views/certd/pipeline/pipeline/type";
|
|||
|
||||
const apiPrefix = "/pi/history";
|
||||
|
||||
export async function GetList(query) {
|
||||
export async function GetList(query: any) {
|
||||
const list = await request({
|
||||
url: apiPrefix + "/list",
|
||||
method: "post",
|
||||
|
@ -18,7 +18,7 @@ export async function GetList(query) {
|
|||
return list;
|
||||
}
|
||||
|
||||
export async function GetDetail(query): Promise<RunHistory> {
|
||||
export async function GetDetail(query: any): Promise<RunHistory> {
|
||||
const detail = await request({
|
||||
url: apiPrefix + "/detail",
|
||||
method: "post",
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<a-drawer v-model:visible="notificationDrawerVisible" placement="right" :closable="true" width="600px" class="pi-notification-form" :after-visible-change="notificationDrawerOnAfterVisibleChange">
|
||||
<template #title>
|
||||
编辑触发器
|
||||
<a-button v-if="mode === 'edit'" @click="notificationDelete()">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-if="currentNotification">
|
||||
<pi-container>
|
||||
<a-form ref="notificationFormRef" class="notification-form" :model="currentNotification" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<fs-form-item
|
||||
v-model="currentNotification.type"
|
||||
:item="{
|
||||
title: '类型',
|
||||
key: 'type',
|
||||
value: 'email',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
options: [{ value: 'email', label: '邮件' }]
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
}"
|
||||
/>
|
||||
<fs-form-item
|
||||
v-model="currentNotification.when"
|
||||
:item="{
|
||||
title: '触发时机',
|
||||
key: 'type',
|
||||
value: ['error'],
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
mode: 'multiple',
|
||||
options: [
|
||||
{ value: 'start', label: '开始时' },
|
||||
{ value: 'success', label: '成功时' },
|
||||
{ value: 'error', label: '错误时' }
|
||||
]
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
}"
|
||||
/>
|
||||
<pi-notification-form-email ref="optionsRef" v-model:options="currentNotification.options"></pi-notification-form-email>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="notificationSave"> 确定 </a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</pi-container>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { ref } from "vue";
|
||||
import _ from "lodash";
|
||||
import { nanoid } from "nanoid";
|
||||
import PiNotificationFormEmail from "./pi-notification-form-email.vue";
|
||||
|
||||
export default {
|
||||
name: "PiNotificationForm",
|
||||
components: { PiNotificationFormEmail },
|
||||
props: {
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ["update"],
|
||||
setup(props: any, context: any) {
|
||||
/**
|
||||
* notification drawer
|
||||
* @returns
|
||||
*/
|
||||
function useNotificationForm() {
|
||||
const mode = ref("add");
|
||||
const callback = ref();
|
||||
const currentNotification = ref({ type: undefined, when: [], options: {} });
|
||||
const currentPlugin = ref({});
|
||||
const notificationFormRef = ref(null);
|
||||
const notificationDrawerVisible = ref(false);
|
||||
const optionsRef = ref();
|
||||
const rules = ref({
|
||||
type: [
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: "请选择类型"
|
||||
}
|
||||
],
|
||||
when: [
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: "请选择通知时机"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const notificationDrawerShow = () => {
|
||||
notificationDrawerVisible.value = true;
|
||||
};
|
||||
const notificationDrawerClose = () => {
|
||||
notificationDrawerVisible.value = false;
|
||||
};
|
||||
|
||||
const notificationDrawerOnAfterVisibleChange = (val: any) => {
|
||||
console.log("notificationDrawerOnAfterVisibleChange", val);
|
||||
};
|
||||
|
||||
const notificationOpen = (notification: any, emit: any) => {
|
||||
callback.value = emit;
|
||||
currentNotification.value = _.cloneDeep(notification);
|
||||
console.log("currentNotificationOpen", currentNotification.value);
|
||||
notificationDrawerShow();
|
||||
};
|
||||
|
||||
const notificationAdd = (emit: any) => {
|
||||
mode.value = "add";
|
||||
const notification = { id: nanoid(), type: "email", when: ["error"] };
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationEdit = (notification: any, emit: any) => {
|
||||
mode.value = "edit";
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationView = (notification: any, emit: any) => {
|
||||
mode.value = "view";
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationSave = async (e: any) => {
|
||||
currentNotification.value.options = await optionsRef.value.getValue();
|
||||
console.log("currentNotificationSave", currentNotification.value);
|
||||
try {
|
||||
await notificationFormRef.value.validate();
|
||||
} catch (e) {
|
||||
console.error("表单验证失败:", e);
|
||||
return;
|
||||
}
|
||||
|
||||
callback.value("save", currentNotification.value);
|
||||
notificationDrawerClose();
|
||||
};
|
||||
|
||||
const notificationDelete = () => {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要删除此触发器吗?`,
|
||||
async onOk() {
|
||||
callback.value("delete");
|
||||
notificationDrawerClose();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const blankFn = () => {
|
||||
return {};
|
||||
};
|
||||
return {
|
||||
notificationFormRef,
|
||||
mode,
|
||||
notificationAdd,
|
||||
notificationEdit,
|
||||
notificationView,
|
||||
notificationDrawerShow,
|
||||
notificationDrawerVisible,
|
||||
notificationDrawerOnAfterVisibleChange,
|
||||
currentNotification,
|
||||
currentPlugin,
|
||||
notificationSave,
|
||||
notificationDelete,
|
||||
rules,
|
||||
blankFn,
|
||||
optionsRef
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...useNotificationForm(),
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 16 }
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.pi-notification-form {
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div>
|
||||
<fs-form-item
|
||||
v-model="optionsFormState.receivers"
|
||||
:item="{
|
||||
title: '收件邮箱',
|
||||
key: 'type',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
mode: 'tags'
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Ref, ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
|
||||
const optionsFormState: Ref<any> = ref({});
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.options;
|
||||
},
|
||||
() => {
|
||||
optionsFormState.value = {
|
||||
...props.options
|
||||
};
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["change"]);
|
||||
function doEmit() {
|
||||
emit("change", { ...optionsFormState.value });
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
return { ...optionsFormState.value };
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
doEmit,
|
||||
getValue
|
||||
});
|
||||
</script>
|
|
@ -2,7 +2,6 @@
|
|||
<fs-page v-if="pipeline" class="page-pipeline-edit">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
<fs-button icon="ion:left" @click="goBack" />
|
||||
<pi-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></pi-editable>
|
||||
</div>
|
||||
<div class="more">
|
||||
|
@ -63,7 +62,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="(stage, index) of pipeline.stages" :key="stage.id" class="stage" :class="{ 'last-stage': !editMode && index === pipeline.stages.length - 1 }">
|
||||
<div v-for="(stage, index) of pipeline.stages" :key="stage.id" class="stage" :class="{ 'last-stage': isLastStage(index) }">
|
||||
<div class="title">
|
||||
<pi-editable v-model="stage.title" :disabled="!editMode"></pi-editable>
|
||||
</div>
|
||||
|
@ -118,6 +117,48 @@
|
|||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item, ii) of pipeline.notifications" :key="ii" class="task-container">
|
||||
<div class="line">
|
||||
<div class="flow-line"></div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="notificationEdit(item, ii as number)">
|
||||
<fs-icon icon="ion:notifications"></fs-icon>
|
||||
【通知】 {{ item.type }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-container">
|
||||
<div class="line">
|
||||
<div class="flow-line"></div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" type="dashed" @click="notificationAdd()">
|
||||
<fs-icon icon="ion:add-circle-outline"></fs-icon>
|
||||
|
||||
添加通知
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="stage last-stage">
|
||||
<div class="title">
|
||||
<pi-editable model-value="结束" :disabled="true" />
|
||||
</div>
|
||||
<div class="tasks">
|
||||
<div v-for="(item, index) of pipeline.notifications" :key="index" class="task-container" :class="{ 'first-task': index == 0 }">
|
||||
<div class="line">
|
||||
<div class="flow-line"></div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="notificationEdit(item, index)">
|
||||
<fs-icon icon="ion:notifications"></fs-icon>
|
||||
|
||||
【通知】 {{ item.type }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -140,6 +181,7 @@
|
|||
<pi-task-form ref="taskFormRef" :edit-mode="editMode"></pi-task-form>
|
||||
<pi-trigger-form ref="triggerFormRef" :edit-mode="editMode"></pi-trigger-form>
|
||||
<pi-task-view ref="taskViewRef"></pi-task-view>
|
||||
<PiNotificationForm ref="notificationFormRef" :edit-mode="editMode"></PiNotificationForm>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
@ -148,9 +190,10 @@ import { defineComponent, ref, provide, Ref, watch } from "vue";
|
|||
import { useRouter } from "vue-router";
|
||||
import PiTaskForm from "./component/task-form/index.vue";
|
||||
import PiTriggerForm from "./component/trigger-form/index.vue";
|
||||
import PiNotificationForm from "./component/notification-form/index.vue";
|
||||
import PiTaskView from "./component/task-view/index.vue";
|
||||
import PiStatusShow from "./component/status-show.vue";
|
||||
import _ from "lodash-es";
|
||||
import _ from "lodash";
|
||||
import { message, Modal, notification } from "ant-design-vue";
|
||||
import { pluginManager } from "/@/views/certd/pipeline/pipeline/plugin";
|
||||
import { nanoid } from "nanoid";
|
||||
|
@ -159,7 +202,7 @@ import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/hi
|
|||
export default defineComponent({
|
||||
name: "PipelineEdit",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow },
|
||||
components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm },
|
||||
props: {
|
||||
pipelineId: {
|
||||
type: [Number, String],
|
||||
|
@ -269,7 +312,7 @@ export default defineComponent({
|
|||
return;
|
||||
}
|
||||
const detail: PipelineDetail = await props.options.getPipelineDetail({ pipelineId: value });
|
||||
currentPipeline.value = _.merge({ title: "新管道流程", stages: [], triggers: [] }, detail.pipeline);
|
||||
currentPipeline.value = _.merge({ title: "新管道流程", stages: [], triggers: [], notifications: [] }, detail.pipeline);
|
||||
pipeline.value = currentPipeline.value;
|
||||
await loadHistoryList(true);
|
||||
},
|
||||
|
@ -369,8 +412,13 @@ export default defineComponent({
|
|||
pipeline.value.stages.splice(stageIndex, 0, stage);
|
||||
});
|
||||
};
|
||||
|
||||
function isLastStage(index: number) {
|
||||
return !props.editMode && index === pipeline.value.stages.length - 1 && pipeline.value.notifications?.length < 1;
|
||||
}
|
||||
return {
|
||||
stageAdd
|
||||
stageAdd,
|
||||
isLastStage
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -406,6 +454,41 @@ export default defineComponent({
|
|||
};
|
||||
}
|
||||
|
||||
function useNotification() {
|
||||
const notificationFormRef = ref();
|
||||
const notificationAdd = () => {
|
||||
notificationFormRef.value.notificationAdd((type: string, value: any) => {
|
||||
if (type === "save") {
|
||||
if (pipeline.value.notifications == null) {
|
||||
pipeline.value.notifications = [];
|
||||
}
|
||||
pipeline.value.notifications.push(value);
|
||||
}
|
||||
});
|
||||
};
|
||||
const notificationEdit = (notification: any, index: any) => {
|
||||
if (notificationFormRef.value == null) {
|
||||
return;
|
||||
}
|
||||
if (props.editMode) {
|
||||
notificationFormRef.value.notificationEdit(notification, (type: string, value: any) => {
|
||||
if (type === "delete") {
|
||||
pipeline.value.notifications.splice(index, 1);
|
||||
} else if (type === "save") {
|
||||
pipeline.value.notifications[index] = value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
notificationFormRef.value.notificationView(notification, (type: string, value: any) => {});
|
||||
}
|
||||
};
|
||||
return {
|
||||
notificationAdd,
|
||||
notificationEdit,
|
||||
notificationFormRef
|
||||
};
|
||||
}
|
||||
|
||||
function useActions() {
|
||||
const saveLoading = ref();
|
||||
const run = async () => {
|
||||
|
@ -484,6 +567,7 @@ export default defineComponent({
|
|||
historyCancel
|
||||
};
|
||||
}
|
||||
|
||||
const useTaskRet = useTask();
|
||||
const useStageRet = useStage(useTaskRet);
|
||||
|
||||
|
@ -495,7 +579,8 @@ export default defineComponent({
|
|||
...useStageRet,
|
||||
...useTrigger(),
|
||||
...useActions(),
|
||||
...useHistory()
|
||||
...useHistory(),
|
||||
...useNotification()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,9 +8,9 @@ export class PluginManager {
|
|||
* 初始化plugins
|
||||
* @param plugins
|
||||
*/
|
||||
init(plugins) {
|
||||
init(plugins: any) {
|
||||
const list = plugins;
|
||||
const map = {};
|
||||
const map: any = {};
|
||||
for (const plugin of list) {
|
||||
map[plugin.key] = plugin;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export class PluginManager {
|
|||
return this.map[name];
|
||||
}
|
||||
|
||||
getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }) {
|
||||
getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) {
|
||||
const steps = this.collectionPreStepOutputs({
|
||||
pipeline,
|
||||
currentStageIndex,
|
||||
|
@ -42,7 +42,7 @@ export class PluginManager {
|
|||
return options;
|
||||
}
|
||||
|
||||
collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }) {
|
||||
collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) {
|
||||
const steps: any[] = [];
|
||||
// 开始放step
|
||||
for (let i = 0; i < currentStageIndex; i++) {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { request } from "/@/api/service";
|
||||
const apiPrefix = "/basic/email";
|
||||
|
||||
export async function TestSend(receiver: string) {
|
||||
await request({
|
||||
url: apiPrefix + "/test",
|
||||
method: "post",
|
||||
data: {
|
||||
receiver
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { request } from "/@/api/service";
|
||||
const apiPrefix = "/sys/settings";
|
||||
|
||||
export const SettingKeys = {
|
||||
Email: "email"
|
||||
};
|
||||
export async function SettingsGet(key: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/get",
|
||||
method: "post",
|
||||
params: {
|
||||
key
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function SettingsSave(key: string, setting: any) {
|
||||
await request({
|
||||
url: apiPrefix + "/save",
|
||||
method: "post",
|
||||
data: {
|
||||
key,
|
||||
setting: JSON.stringify(setting)
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<fs-page class="page-setting-email">
|
||||
<template #header>
|
||||
<div class="title">邮件设置</div>
|
||||
</template>
|
||||
<div class="email-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<a-form-item label="STMP域名" name="host" :rules="[{ required: true, message: '请输入smtp域名或ip' }]">
|
||||
<a-input v-model:value="formState.host" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="STMP端口" name="port" :rules="[{ required: true, message: '请输入smtp端口号' }]">
|
||||
<a-input v-model:value="formState.port" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="用户名" :name="['auth', 'user']" :rules="[{ required: true, message: '请输入用户名' }]">
|
||||
<a-input v-model:value="formState.auth.user" />
|
||||
</a-form-item>
|
||||
<a-form-item label="密码" :name="['auth', 'pass']" :rules="[{ required: true, message: '请输入密码' }]">
|
||||
<a-input-password v-model:value="formState.auth.pass" />
|
||||
</a-form-item>
|
||||
<a-form-item label="发件邮箱" name="sender" :rules="[{ required: true, message: '请输入发件邮箱' }]">
|
||||
<a-input v-model:value="formState.sender" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否ssl" name="secure">
|
||||
<a-switch v-model:checked="formState.secure" />
|
||||
</a-form-item>
|
||||
<a-form-item label="忽略证书校验" name="tls.rejectUnauthorized">
|
||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">保存</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div>
|
||||
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
|
||||
<a-form-item label="测试收件邮箱" name="receiver" :rules="[{ required: true, message: '请输入测试收件邮箱' }]">
|
||||
<a-input v-model:value="testFormState.receiver" />
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">测试</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from "vue";
|
||||
import * as api from "./api";
|
||||
import * as emailApi from "./api.email";
|
||||
|
||||
import { SettingKeys } from "./api";
|
||||
import { notification } from "ant-design-vue";
|
||||
|
||||
interface FormState {
|
||||
host: string;
|
||||
port: number;
|
||||
auth: {
|
||||
user: string;
|
||||
pass: string;
|
||||
};
|
||||
secure: boolean; // use TLS
|
||||
tls: {
|
||||
// do not fail on invalid certs
|
||||
rejectUnauthorized?: boolean;
|
||||
};
|
||||
sender: string;
|
||||
}
|
||||
|
||||
const formState = reactive<Partial<FormState>>({
|
||||
auth: {
|
||||
user: "",
|
||||
pass: ""
|
||||
},
|
||||
tls: {}
|
||||
});
|
||||
|
||||
async function load() {
|
||||
const data: any = await api.SettingsGet(SettingKeys.Email);
|
||||
const setting = JSON.parse(data.setting);
|
||||
Object.assign(formState, setting);
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
const onFinish = async (form: any) => {
|
||||
console.log("Success:", form);
|
||||
await api.SettingsSave(SettingKeys.Email, form);
|
||||
notification.success({
|
||||
message: "保存成功"
|
||||
});
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
// console.log("Failed:", errorInfo);
|
||||
};
|
||||
|
||||
interface TestFormState {
|
||||
receiver: string;
|
||||
loading: boolean;
|
||||
}
|
||||
const testFormState = reactive<TestFormState>({
|
||||
receiver: "",
|
||||
loading: false
|
||||
});
|
||||
async function onTestSend() {
|
||||
testFormState.loading = true;
|
||||
try {
|
||||
await emailApi.TestSend(testFormState.receiver);
|
||||
notification.success({
|
||||
message: "发送成功"
|
||||
});
|
||||
} finally {
|
||||
testFormState.loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-setting-email {
|
||||
.email-form {
|
||||
width: 500px;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE "sys_settings" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"user_id" integer NOT NULL,
|
||||
"key" varchar(100) NOT NULL,
|
||||
"title" varchar(100) NOT NULL,
|
||||
"setting" varchar(1024),
|
||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||
);
|
|
@ -9,7 +9,7 @@
|
|||
"online:preview": "NODE_ENV=preview node ./bootstrap.js",
|
||||
"dev": "cross-env NODE_ENV=local midway-bin dev --ts --watchFile='../../core/pipeline/src,../../plugins/'",
|
||||
"dev:preview": "cross-env NODE_ENV=preview midway-bin dev --ts",
|
||||
"dev:syncdb": "cross-env NODE_ENV=syncdb midway-bin dev --ts --watchFile='../../core/pipeline/src'",
|
||||
"db": "cross-env NODE_ENV=syncdb midway-bin dev --ts",
|
||||
"test": "midway-bin test --ts",
|
||||
"cov": "midway-bin cov --ts",
|
||||
"lint": "mwts check",
|
||||
|
@ -54,6 +54,7 @@
|
|||
"md5": "^2.3.0",
|
||||
"midway-flyway-js": "^3.0.0",
|
||||
"node-cron": "^3.0.2",
|
||||
"nodemailer": "^6.9.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sqlite3": "^5.1.4",
|
||||
"svg-captcha": "^1.4.0",
|
||||
|
@ -67,6 +68,7 @@
|
|||
"@types/jest": "^26.0.24",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/node": "^14.18.35",
|
||||
"@types/nodemailer": "^6.4.8",
|
||||
"cross-env": "^6.0.3",
|
||||
"jest": "^26.6.3",
|
||||
"mwts": "^1.3.0",
|
||||
|
|
|
@ -10,7 +10,7 @@ export abstract class BaseController {
|
|||
* 成功返回
|
||||
* @param data 返回数据
|
||||
*/
|
||||
ok(data) {
|
||||
ok(data: any) {
|
||||
const res = {
|
||||
...Constants.res.success,
|
||||
data: undefined,
|
||||
|
@ -22,12 +22,21 @@ export abstract class BaseController {
|
|||
}
|
||||
/**
|
||||
* 失败返回
|
||||
* @param message
|
||||
* @param msg
|
||||
* @param code
|
||||
*/
|
||||
fail(msg, code) {
|
||||
fail(msg: string, code: any) {
|
||||
return {
|
||||
code: code ? code : Constants.res.error.code,
|
||||
msg: msg ? msg : Constants.res.error.code,
|
||||
};
|
||||
}
|
||||
|
||||
getUserId() {
|
||||
const userId = this.ctx.user?.id;
|
||||
if (userId == null) {
|
||||
throw new Error('Token已过期');
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,14 +187,19 @@ export abstract class BaseService<T> {
|
|||
return await qb.getMany();
|
||||
}
|
||||
|
||||
async checkUserId(id = 0, userId, userKey = 'userId') {
|
||||
async checkUserId(
|
||||
id: any = 0,
|
||||
userId,
|
||||
userKey = 'userId',
|
||||
queryIdKey = 'id'
|
||||
) {
|
||||
// @ts-ignore
|
||||
const res = await this.getRepository().findOne({
|
||||
// @ts-ignore
|
||||
select: { [userKey]: true },
|
||||
// @ts-ignore
|
||||
where: {
|
||||
// @ts-ignore
|
||||
id,
|
||||
[queryIdKey]: id,
|
||||
},
|
||||
});
|
||||
// @ts-ignore
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import { Provide } from '@midwayjs/decorator';
|
||||
import {
|
||||
IWebMiddleware,
|
||||
IMidwayKoaContext,
|
||||
NextFunction,
|
||||
} from '@midwayjs/koa';
|
||||
import { IWebMiddleware, IMidwayKoaContext, NextFunction } from '@midwayjs/koa';
|
||||
import { logger } from '../utils/logger';
|
||||
import { Result } from '../basic/result';
|
||||
|
||||
|
@ -20,7 +16,10 @@ export class GlobalExceptionMiddleware implements IWebMiddleware {
|
|||
} catch (err) {
|
||||
logger.error('请求异常:', url, Date.now() - startTime + 'ms', err);
|
||||
ctx.status = 200;
|
||||
ctx.body = Result.error(err.code != null ? err.code : 1, err.message);
|
||||
if (err.code == null || typeof err.code !== 'number') {
|
||||
err.code = 1;
|
||||
}
|
||||
ctx.body = Result.error(err.code, err.message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Rule,RuleType } from '@midwayjs/validate';
|
||||
import { Rule, RuleType } from '@midwayjs/validate';
|
||||
import { ALL, Inject } from '@midwayjs/decorator';
|
||||
import { Body } from '@midwayjs/decorator';
|
||||
import { Controller, Post, Provide } from '@midwayjs/decorator';
|
||||
import { BaseController } from '../../../basic/base-controller';
|
||||
import { CodeService } from '../service/code-service';
|
||||
import { EmailService } from '../service/email-service';
|
||||
export class SmsCodeReq {
|
||||
@Rule(RuleType.number().required())
|
||||
phoneCode: number;
|
||||
|
@ -18,22 +19,17 @@ export class SmsCodeReq {
|
|||
imgCode: string;
|
||||
}
|
||||
|
||||
// const enumsMap = {};
|
||||
// glob('src/modules/**/enums/*.ts', {}, (err, matches) => {
|
||||
// console.log('matched', matches);
|
||||
// for (const filePath of matches) {
|
||||
// const module = require('/' + filePath);
|
||||
// console.log('modules', module);
|
||||
// }
|
||||
// });
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/basic')
|
||||
@Controller('/api/basic/code')
|
||||
export class BasicController extends BaseController {
|
||||
@Inject()
|
||||
codeService: CodeService;
|
||||
|
||||
@Inject()
|
||||
emailService: EmailService;
|
||||
|
||||
@Post('/sendSmsCode')
|
||||
public sendSmsCode(
|
||||
@Body(ALL)
|
||||
|
@ -53,4 +49,3 @@ export class BasicController extends BaseController {
|
|||
return this.ok(captcha.data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { Body, Controller, Inject, Post, Provide } from '@midwayjs/decorator';
|
||||
import { BaseController } from '../../../basic/base-controller';
|
||||
import { EmailService } from '../service/email-service';
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/basic/email')
|
||||
export class EmailController extends BaseController {
|
||||
@Inject()
|
||||
emailService: EmailService;
|
||||
|
||||
@Post('/test')
|
||||
public async test(
|
||||
@Body('receiver')
|
||||
receiver
|
||||
) {
|
||||
const userId = super.getUserId();
|
||||
await this.emailService.test(userId, receiver);
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/decorator';
|
||||
import type { EmailSend } from '@certd/pipeline';
|
||||
import { IEmailService } from '@certd/pipeline';
|
||||
import nodemailer from 'nodemailer';
|
||||
import { SettingsService } from '../../system/service/settings-service';
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||
|
||||
export type EmailConfig = {
|
||||
host: string;
|
||||
port: number;
|
||||
auth: {
|
||||
user: string;
|
||||
pass: string;
|
||||
};
|
||||
secure: boolean; // use TLS
|
||||
tls: {
|
||||
// do not fail on invalid certs
|
||||
rejectUnauthorized: boolean;
|
||||
};
|
||||
sender: string;
|
||||
} & SMTPConnection.Options;
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class EmailService implements IEmailService {
|
||||
@Inject()
|
||||
settingsService: SettingsService;
|
||||
|
||||
/**
|
||||
*/
|
||||
async send(email: EmailSend) {
|
||||
console.log('sendEmail', email);
|
||||
|
||||
const emailConfigEntity = await this.settingsService.getByKey(
|
||||
'email',
|
||||
email.userId
|
||||
);
|
||||
if (emailConfigEntity == null || !emailConfigEntity.setting) {
|
||||
throw new Error('email settings 未设置');
|
||||
}
|
||||
const emailConfig = JSON.parse(emailConfigEntity.setting) as EmailConfig;
|
||||
const transporter = nodemailer.createTransport(emailConfig);
|
||||
const mailOptions = {
|
||||
from: emailConfig.sender,
|
||||
to: email.receivers.join(', '), // list of receivers
|
||||
subject: email.subject,
|
||||
text: email.content,
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('sendEmail success', email);
|
||||
}
|
||||
|
||||
async test(userId: number, receiver: string) {
|
||||
await this.send({
|
||||
userId,
|
||||
receivers: [receiver],
|
||||
subject: '测试邮件,from certd',
|
||||
content: '测试邮件,from certd',
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/decorator";
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/decorator';
|
||||
import { PipelineService } from '../service/pipeline-service';
|
||||
import { logger } from '../../../utils/logger';
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { HistoryEntity } from '../entity/history';
|
|||
import { HistoryLogEntity } from '../entity/history-log';
|
||||
import { HistoryLogService } from './history-log-service';
|
||||
import { logger } from '../../../utils/logger';
|
||||
import { EmailService } from '../../basic/service/email-service';
|
||||
|
||||
/**
|
||||
* 证书申请
|
||||
|
@ -23,7 +24,8 @@ import { logger } from '../../../utils/logger';
|
|||
export class PipelineService extends BaseService<PipelineEntity> {
|
||||
@InjectEntityModel(PipelineEntity)
|
||||
repository: Repository<PipelineEntity>;
|
||||
|
||||
@Inject()
|
||||
emailService: EmailService;
|
||||
@Inject()
|
||||
accessService: AccessService;
|
||||
@Inject()
|
||||
|
@ -191,6 +193,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
onChanged,
|
||||
accessService: this.accessService,
|
||||
storage: new DbStorage(userId, this.storageService),
|
||||
emailService: this.emailService,
|
||||
});
|
||||
try {
|
||||
await executor.init();
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/decorator";
|
||||
import { CrudController } from "../../../basic/crud-controller";
|
||||
import { SettingsService } from "../service/settings-service";
|
||||
import {
|
||||
ALL,
|
||||
Body,
|
||||
Controller,
|
||||
Inject,
|
||||
Post,
|
||||
Provide,
|
||||
Query,
|
||||
} from '@midwayjs/decorator';
|
||||
import { CrudController } from '../../../basic/crud-controller';
|
||||
import { SettingsService } from '../service/settings-service';
|
||||
import { SettingsEntity } from '../entity/settings';
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -50,4 +59,18 @@ export class SettingsController extends CrudController<SettingsService> {
|
|||
return super.delete(id);
|
||||
}
|
||||
|
||||
@Post('/save')
|
||||
async save(@Body(ALL) bean: SettingsEntity) {
|
||||
await this.service.checkUserId(bean.key, this.ctx.user.id, 'userId', 'key');
|
||||
bean.userId = this.ctx.user.id;
|
||||
await this.service.save(bean);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post('/get')
|
||||
async get(@Query('key') key: string) {
|
||||
await this.service.checkUserId(key, this.ctx.user.id, 'userId', 'key');
|
||||
const entity = await this.service.getByKey(key, this.ctx.user.id);
|
||||
return this.ok(entity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ export class SettingsEntity {
|
|||
id: number;
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
userId: number;
|
||||
@Column({ comment: 'key', length: 100 })
|
||||
key: string;
|
||||
@Column({ comment: '名称', length: 100 })
|
||||
name: string;
|
||||
title: string;
|
||||
@Column({ name: 'setting', comment: '设置', length: 1024, nullable: true })
|
||||
setting: string;
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { BaseService } from "../../../basic/base-service";
|
||||
import { SettingsEntity } from "../entity/settings";
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/decorator';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService } from '../../../basic/base-service';
|
||||
import { SettingsEntity } from '../entity/settings';
|
||||
|
||||
/**
|
||||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class SettingsService
|
||||
extends BaseService<SettingsEntity>
|
||||
{
|
||||
export class SettingsService extends BaseService<SettingsEntity> {
|
||||
@InjectEntityModel(SettingsEntity)
|
||||
repository: Repository<SettingsEntity>;
|
||||
|
||||
|
@ -19,8 +17,11 @@ export class SettingsService
|
|||
return this.repository;
|
||||
}
|
||||
|
||||
async getById(id: any): Promise<any> {
|
||||
async getById(id: any): Promise<SettingsEntity | null> {
|
||||
const entity = await this.info(id);
|
||||
if (!entity) {
|
||||
return null;
|
||||
}
|
||||
// const access = accessRegistry.get(entity.type);
|
||||
const setting = JSON.parse(entity.setting);
|
||||
return {
|
||||
|
@ -29,5 +30,38 @@ export class SettingsService
|
|||
};
|
||||
}
|
||||
|
||||
async getByKey(key: string, userId: number): Promise<SettingsEntity | null> {
|
||||
if (!key || !userId) {
|
||||
return null;
|
||||
}
|
||||
return await this.repository.findOne({
|
||||
where: {
|
||||
key,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getSettingByKey(key: string, userId: number): Promise<any | null> {
|
||||
const entity = await this.getByKey(key, userId);
|
||||
if (!entity) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(entity.setting);
|
||||
}
|
||||
|
||||
async save(bean: SettingsEntity) {
|
||||
const entity = await this.repository.findOne({
|
||||
where: {
|
||||
key: bean.key,
|
||||
},
|
||||
});
|
||||
if (entity) {
|
||||
entity.setting = bean.setting;
|
||||
await this.repository.save(entity);
|
||||
} else {
|
||||
bean.title = bean.key;
|
||||
await this.repository.save(bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue