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-19 12:26:38 +00:00
commit 26dbcac950
17 changed files with 288 additions and 3 deletions

View File

@ -1083,10 +1083,18 @@ recommendedInstancesDescription: "Recommended servers separated by line breaks t
caption: "Auto description"
splash: "Splash Screen"
updateAvailable: "There might be an update available!"
securityUpdateAvailable: "There is a critical security update available! Please update as soon as possible!"
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"
showAdminUpdates: "Indicate a new Firefish version is avaliable (admin only)"
showAdminSecurityUpdates: "Indicate a new Firefish security version is avaliable (admin only)"
replayTutorial: "Replay tutorial"
migration: "Migration"
moveTo: "Move current account to new account"

View File

@ -1857,6 +1857,7 @@ antennaInstancesDescription: 列出服务器主机名,一行一个
pushNotification: 推送通知
subscribePushNotification: 启用推送通知
showAdminUpdates: 提示新的 Firefish 版本可用(仅对于管理员)
showAdminSecurityUpdates: "提示新的 Firefish 安全更新可用(仅对于管理员)"
searchPlaceholder: 搜索 Firefish
addInstance: 添加服务器
jumpToPrevious: 跳转到上一个
@ -1872,6 +1873,11 @@ showAds: 显示社区横幅
enterSendsMessage: 按回车键发送信息(关闭则是 Ctrl + Return 发送)
recommendedInstances: 推荐服务器
updateAvailable: 可能有可用更新!
securityUpdateAvailable: "有重要的安全更新可用! 请尽快更新!"
updateEmailTips: "更新提醒邮件"
updateEmailTipsInfo: "在 Firefish 发布新版本时接收更新提醒邮件。需要您正确设置发送邮件功能和管理员邮箱才会生效。"
updateEmailTipsSecurityOnly: "只接收安全版本更新提醒"
updateEmailTipsSecurityOnlyInfo: "Firefish 采用滚动更新模式因此新版本可能会频繁发布。此选项用于只在安全更新发布时接收更新提醒邮件。"
swipeOnMobile: 允许在页面之间滑动
swipeOnDesktop: 允许在桌面端以移动设备方式滑动
logoImageUrl: Logo 图像 URL

View File

@ -50,5 +50,8 @@
"execa": "9.1.0",
"pnpm": "9.1.1",
"typescript": "5.4.5"
},
"firefish": {
"latestSecurityUpdate": "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,102 @@
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.firefish.latestSecurityUpdate
: 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.firefish.latestSecurityUpdate === 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: false,
},
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

@ -47,6 +47,15 @@
>{{ i18n.ts.check }}</a
></MkInfo
>
<MkInfo v-if="securityUpdateAvailable" warn class="info"
>{{ i18n.ts.securityUpdateAvailable }}
<a
href="https://firefish.dev/firefish/firefish/-/blob/main/docs/changelog.md"
target="_bank"
class="_link"
>{{ i18n.ts.check }}</a
></MkInfo
>
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
</div>

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(() => []);

View File

@ -324,6 +324,12 @@
class="_formBlock"
>{{ i18n.ts.showAdminUpdates }}</FormSwitch
>
<FormSwitch
v-if="me?.isAdmin"
v-model="showAdminSecruityUpdates"
class="_formBlock"
>{{ i18n.ts.showAdminSecruityUpdates }}</FormSwitch
>
<FormSelect v-model="instanceTicker" class="_formBlock">
<template #label>{{ i18n.ts.instanceTicker }}</template>
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
@ -514,6 +520,9 @@ const swipeOnMobile = computed(defaultStore.makeGetterSetter("swipeOnMobile"));
const showAdminUpdates = computed(
defaultStore.makeGetterSetter("showAdminUpdates"),
);
const showAdminSecruityUpdates = computed(
defaultStore.makeGetterSetter("showAdminSecruityUpdates"),
);
const showPreviewByDefault = computed(
defaultStore.makeGetterSetter("showPreviewByDefault"),
);
@ -636,6 +645,7 @@ watch(
swipeOnDesktop,
seperateRenoteQuote,
showAdminUpdates,
showAdminSecurityUpdates,
advancedMfm,
autoplayMfm,
expandOnNoteClick,

View File

@ -114,6 +114,7 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
"swipeOnMobile",
"swipeOnDesktop",
"showAdminUpdates",
"showAdminSecurityUpdates",
"enableCustomKaTeXMacro",
"enableEmojiReactions",
"showEmojisInReactionNotifications",

View File

@ -356,6 +356,10 @@ export const defaultStore = markRaw(
where: "account",
default: true,
},
showAdminSecurityUpdates: {
where: "account",
default: true,
},
woozyMode: {
where: "device",
default: false,

View File

@ -85,7 +85,8 @@
noMaintainerInformation ||
noBotProtection ||
noEmailServer ||
updateAvailable
updateAvailable ||
securityUpdateAvailable
"
class="indicator"
></span
@ -198,6 +199,7 @@ const noBotProtection =
const noEmailServer = !instance.enableEmail;
const thereIsUnresolvedAbuseReport = ref(false);
const updateAvailable = ref(false);
const securityUpdateAvailable = ref(false);
if (isAdmin) {
os.api("admin/abuse-user-reports", {
@ -208,9 +210,16 @@ if (isAdmin) {
});
}
if (defaultStore.state.showAdminUpdates) {
if (
defaultStore.state.showAdminUpdates ||
defaultStore.state.showAdminSecurityUpdates
) {
os.api("latest-version").then((res) => {
updateAvailable.value = version < res?.latest_version;
securityUpdateAvailable.value = version < res?.latest_security_update;
updateAvailable.value =
version >= res?.latest_security_update &&
defaultStore.state.showAdminUpdates &&
version < res?.latest_version;
});
}