Merge branch 'feat/update_email_tips' into 'develop'

feat: update email tips


See merge request firefish/firefish!10716
This commit is contained in:
laozhoubuluo 2024-05-08 02:08:46 +00:00
commit 590e44345b
12 changed files with 251 additions and 0 deletions

View File

@ -1083,6 +1083,12 @@ recommendedInstancesDescription: "Recommended servers separated by line breaks t
caption: "Auto description"
splash: "Splash Screen"
updateAvailable: "There might be an update available!"
updateEmailTips: "Update Email Tips"
updateEmailTipsInfo: "To receive email tips when Firefish releases a new version. You need to
correctly set up email sending and maintainer email for it to take effect."
updateEmailTipsSecurityOnly: "Only receive security update tips"
updateEmailTipsSecurityOnlyInfo: "Firefish using rolling update and new versions may be
released frequently. This option is used to only receive email tips for security version update."
swipeOnMobile: "Allow swiping between pages"
swipeOnDesktop: "Allow mobile-style swiping on desktop"
logoImageUrl: "Logo image URL"

View File

@ -1872,6 +1872,10 @@ showAds: 显示社区横幅
enterSendsMessage: 按回车键发送信息(关闭则是 Ctrl + Return 发送)
recommendedInstances: 推荐服务器
updateAvailable: 可能有可用更新!
updateEmailTips: 更新提醒邮件
updateEmailTipsInfo: 在 Firefish 发布新版本时接收更新提醒邮件。需要您正确设置发送邮件功能和管理员邮箱才会生效。
updateEmailTipsSecurityOnly: 只接收安全版本更新提醒
updateEmailTipsSecurityOnlyInfo: Firefish 采用滚动更新模式因此新版本可能会频繁发布。此选项用于只在安全更新发布时接收更新提醒邮件。
swipeOnMobile: 允许在页面之间滑动
swipeOnDesktop: 允许在桌面端以移动设备方式滑动
logoImageUrl: Logo 图像 URL

View File

@ -50,5 +50,8 @@
"execa": "8.0.1",
"pnpm": "8.15.7",
"typescript": "5.4.5"
},
"firefishCustomFields": {
"lastSecurityUpdate": "20240330"
}
}

View File

@ -128,6 +128,12 @@ pub struct Model {
pub secure_mode: Option<bool>,
#[sea_orm(column_name = "privateMode")]
pub private_mode: Option<bool>,
#[sea_orm(column_name = "updateEmailTips")]
pub update_email_tips: Option<bool>,
#[sea_orm(column_name = "updateEmailTipsSecurityOnly")]
pub update_email_tips_security_only: Option<bool>,
#[sea_orm(column_name = "updateTipsVersion")]
pub update_tips_version: Option<String>,
#[sea_orm(column_name = "deeplAuthKey")]
pub deepl_auth_key: Option<String>,
#[sea_orm(column_name = "deeplIsPro")]

View File

@ -0,0 +1,25 @@
import type { MigrationInterface, QueryRunner } from "typeorm";
export class updateEmailTips1711616400000 implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "meta" ADD "updateEmailTips" bool default true`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD "updateEmailTipsSecurityOnly" bool default true`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD "updateTipsVersion" character varying(512)`,
);
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "updateEmailTips"`);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "updateEmailTipsSecurityOnly"`,
);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "updateTipsVersion"`,
);
}
}

View File

@ -152,6 +152,22 @@ export class Meta {
})
public allowedHosts: string[];
@Column("boolean", {
default: true,
})
public updateEmailTips: boolean;
@Column("boolean", {
default: true,
})
public updateEmailTipsSecurityOnly: boolean;
@Column("varchar", {
length: 512,
nullable: true,
})
public updateTipsVersion: string | null;
@Column("varchar", {
length: 512,
array: true,

View File

@ -559,6 +559,16 @@ export default function () {
},
);
systemQueue.add(
"updateEmailTips",
{},
{
repeat: { cron: "0 0 * * 0" },
removeOnComplete: true,
removeOnFail: true,
},
);
processSystemQueue(systemQueue);
}

View File

@ -4,6 +4,7 @@ import { checkExpiredMutings } from "./check-expired-mutings.js";
import { clean } from "./clean.js";
import { setLocalEmojiSizes } from "./local-emoji-size.js";
import { verifyLinks } from "./verify-links.js";
import { updateEmailTips } from "./update-email-tips.js";
const jobs = {
cleanCharts,
@ -11,6 +12,7 @@ const jobs = {
clean,
setLocalEmojiSizes,
verifyLinks,
updateEmailTips,
} as Record<
string,
| Bull.ProcessCallbackFunction<Record<string, unknown>>

View File

@ -0,0 +1,105 @@
import type Bull from "bull";
import { Meta } from "@/models/entities/meta.js";
import fetch from "node-fetch";
import { queueLogger } from "../../logger.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { db } from "@/db/postgre.js";
import { sendEmail } from "@/services/send-email.js";
const logger = queueLogger.createSubLogger("update-email-tips");
export async function updateEmailTips(
job: Bull.Job<Record<string, unknown>>,
done: any,
): Promise<void> {
logger.info("Checking firefish Update...");
const instance = await fetchMeta(true);
if (!instance.updateEmailTips) {
logger.info("Exit due to not enable update email tips.");
} else if (!instance.enableEmail) {
logger.info("Exit due to not enable email.");
} else if (
instance.maintainerEmail === null ||
typeof instance.maintainerEmail !== "string"
) {
logger.info("Exit due to not vaild maintainer email.");
} else {
const url =
"https://firefish.dev/firefish/firefish/-/raw/main/package.json";
const res = await fetch(url).catch((e) => {
logger.info("Exit due to network error.");
});
if (res !== null) {
const packageData = await res.json();
const version = instance.updateEmailTipsSecurityOnly
? packageData.firefishCustomFields.lastSecurityUpdate
: packageData.version;
if (instance.updateTipsVersion === null) {
await db.transaction(async (transactionalEntityManager) => {
const metas = await transactionalEntityManager.find(Meta, {
order: {
id: "DESC",
},
});
await transactionalEntityManager.update(Meta, metas[0].id, {
updateTipsVersion: version,
});
});
logger.info("Exit due to first time update.");
} else if (instance.updateTipsVersion < version) {
if (
packageData.firefishCustomFields.lastSecurityUpdate ===
packageData.version
) {
logger.info(
`Found security update version ${version}, last check version is ${instance.updateTipsVersion}`,
);
await sendEmail(
instance.maintainerEmail,
"Security Update Tips",
`Firefish has released a new security update version ${version}, please update as soon as possible to ensure the security of your site.<br>The changelog can be viewed at the following url: <a href="https://firefish.dev/firefish/firefish/-/blob/develop/docs/changelog.md">https://firefish.dev/firefish/firefish/-/blob/develop/docs/changelog.md</a>`,
`Firefish has released a new security update version ${version}, please update as soon as possible to ensure the security of your site.\nThe changelog can be viewed at the following url: https://firefish.dev/firefish/firefish/-/blob/develop/docs/changelog.md`,
);
} else {
logger.info(
`Found version ${version}, last check version is ${instance.updateTipsVersion}`,
);
await sendEmail(
instance.maintainerEmail,
"Update Tips",
`Firefish has released a new version ${version}.<br>The changelog can be viewed at the following url: <a href="https://firefish.dev/firefish/firefish/-/blob/develop/docs/changelog.md">https://firefish.dev/firefish/firefish/-/blob/develop/docs/changelog.md</a>`,
`Firefish has released a new version ${version}.\nThe changelog can be viewed at the following url: https://firefish.dev/firefish/firefish/-/blob/develop/docs/changelog.md`,
);
}
await db.transaction(async (transactionalEntityManager) => {
const metas = await transactionalEntityManager.find(Meta, {
order: {
id: "DESC",
},
});
await transactionalEntityManager.update(Meta, metas[0].id, {
updateTipsVersion: version,
});
});
logger.info("Email send.");
} else {
logger.info("No new update.");
}
logger.succ("Checking firefish update successfully.");
}
}
done();
}

View File

@ -288,6 +288,18 @@ export const meta = {
optional: true,
nullable: true,
},
updateEmailTips: {
type: "boolean",
optional: true,
nullable: false,
default: true,
},
updateEmailTipsSecurityOnly: {
type: "boolean",
optional: true,
nullable: false,
default: true,
},
recaptchaSecretKey: {
type: "string",
optional: true,
@ -528,6 +540,8 @@ export default define(meta, paramDef, async () => {
allowedHosts: instance.allowedHosts,
privateMode: instance.privateMode,
secureMode: instance.secureMode,
updateEmailTips: instance.updateEmailTips,
updateEmailTipsSecurityOnly: instance.updateEmailTipsSecurityOnly,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
proxyAccountId: instance.proxyAccountId,

View File

@ -77,6 +77,8 @@ export const paramDef = {
},
secureMode: { type: "boolean", nullable: true },
privateMode: { type: "boolean", nullable: true },
updateEmailTips: { type: "boolean", nullable: true },
updateEmailTipsSecurityOnly: { type: "boolean", nullable: true },
themeColor: {
type: "string",
nullable: true,
@ -280,6 +282,14 @@ export default define(meta, paramDef, async (ps, me) => {
set.secureMode = ps.secureMode;
}
if (typeof ps.updateEmailTips === "boolean") {
set.updateEmailTips = ps.updateEmailTips;
}
if (typeof ps.updateEmailTipsSecurityOnly === "boolean") {
set.updateEmailTipsSecurityOnly = ps.updateEmailTipsSecurityOnly;
}
if (ps.mascotImageUrl !== undefined) {
set.mascotImageUrl = ps.mascotImageUrl;
}

View File

@ -134,6 +134,41 @@
>
</div>
</FormFolder>
<FormFolder class="_formBlock">
<template #label>{{
i18n.ts.updateEmailTips
}}</template>
<div class="_formRoot">
<FormSwitch v-model="updateEmailTips">
<template #label>{{
i18n.ts.updateEmailTips
}}</template>
<template #caption>{{
i18n.ts.updateEmailTipsInfo
}}</template>
</FormSwitch>
<FormSwitch
v-if="updateEmailTips"
v-model="updateEmailTipsSecurityOnly"
>
<template #label>{{
i18n.ts.updateEmailTipsSecurityOnly
}}</template>
<template #caption>{{
i18n.ts.updateEmailTipsSecurityOnlyInfo
}}</template>
</FormSwitch>
<FormButton
primary
class="_formBlock"
@click="saveUpdateEmailTips"
><i :class="icon('ph-floppy-disk-back')"></i>
{{ i18n.ts.save }}</FormButton
>
</div>
</FormFolder>
</div>
</FormSuspense>
</MkSpacer>
@ -166,6 +201,9 @@ const secureMode = ref(false);
const privateMode = ref(false);
const allowedHosts = ref("");
const updateEmailTips = ref(false);
const updateEmailTipsSecurityOnly = ref(false);
async function init() {
const meta = await os.api("admin/meta");
summalyProxy.value = meta.summalyProxy;
@ -177,6 +215,9 @@ async function init() {
secureMode.value = meta.secureMode;
privateMode.value = meta.privateMode;
allowedHosts.value = meta.allowedHosts.join("\n");
updateEmailTips.value = meta.updateEmailTips;
updateEmailTipsSecurityOnly.value = meta.updateEmailTipsSecurityOnly;
}
function save() {
@ -199,6 +240,15 @@ function saveInstance() {
});
}
function saveUpdateEmailTips() {
os.apiWithDialog("admin/update-meta", {
updateEmailTips: updateEmailTips.value,
updateEmailTipsSecurityOnly: updateEmailTipsSecurityOnly.value,
}).then(() => {
fetchInstance();
});
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);