feat(admin): 添加版本信息显示与兼容性检查功能
- 在布局侧边栏底部添加版本信息区域,显示 API 和后台版本 - 点击版本信息可打开模态框,展示详细的版本信息和兼容性状态 - 当 API 版本与后台版本不一致时提示用户更新以避免兼容性问题 - 移除登录页面中已注释的 GitHub 链接 - 更新 package.json 版本号至 0.0.11 - 优化 API 根路径响应,返回 JSON 格式的版本信息 - 清理重复的导入语句和冗余代码
This commit is contained in:
@@ -114,12 +114,37 @@
|
|||||||
width: 180px;
|
width: 180px;
|
||||||
background-color: var(--bg-sider);
|
background-color: var(--bg-sider);
|
||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sider-footer {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sider-footer:hover {
|
||||||
|
background-color: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sider-footer-line {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
|
|||||||
@@ -114,12 +114,54 @@
|
|||||||
数据管理
|
数据管理
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="layout-sider-footer" @click="openVersionModal">
|
||||||
|
<div class="layout-sider-footer-line">
|
||||||
|
<span>API {{ apiVersion }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="layout-sider-footer-line">Admin {{ adminVersion }}</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div v-if="isMobileSiderOpen" class="layout-sider-mask" @click="closeSider" />
|
<div v-if="isMobileSiderOpen" class="layout-sider-mask" @click="closeSider" />
|
||||||
<main class="layout-content">
|
<main class="layout-content">
|
||||||
<router-view />
|
<router-view />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="versionModalVisible" class="modal-overlay" @click.self="closeVersionModal">
|
||||||
|
<div class="modal">
|
||||||
|
<h3 class="modal-title">版本信息</h3>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="modal-row">
|
||||||
|
<span class="modal-label">API 地址</span>
|
||||||
|
<span class="modal-value">{{ checkedApiBaseUrl || "未配置" }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="modal-row">
|
||||||
|
<span class="modal-label">接口版本</span>
|
||||||
|
<span class="modal-value">
|
||||||
|
{{ apiVersion || (apiVersionError ? "未获取到" : "加载中...") }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="modal-row">
|
||||||
|
<span class="modal-label">后台版本</span>
|
||||||
|
<span class="modal-value">{{ adminVersion }}</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="apiVersion && apiVersion === adminVersion" class="modal-status">
|
||||||
|
当前后台与接口版本一致,可以正常使用。
|
||||||
|
</p>
|
||||||
|
<p v-else-if="apiVersion && apiVersion !== adminVersion" class="modal-status">
|
||||||
|
当前后台与接口版本不一致,推荐将 API 服务更新到与后台版本一致,
|
||||||
|
以避免潜在的兼容性问题。
|
||||||
|
</p>
|
||||||
|
<p v-else-if="apiVersionError" class="modal-status">
|
||||||
|
无法获取接口版本:{{ apiVersionError }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="modal-btn" type="button" @click="closeVersionModal">
|
||||||
|
我知道了
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -128,8 +170,10 @@ import { ref, onMounted, watch, provide, computed } from "vue";
|
|||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { logoutAdmin, fetchDomainList } from "../../api/admin";
|
import { logoutAdmin, fetchDomainList } from "../../api/admin";
|
||||||
import { useTheme } from "../../composables/useTheme";
|
import { useTheme } from "../../composables/useTheme";
|
||||||
|
import packageJson from "../../../../package.json";
|
||||||
|
|
||||||
const DOMAIN_STORAGE_KEY = "cwd_admin_domain_filter";
|
const DOMAIN_STORAGE_KEY = "cwd_admin_domain_filter";
|
||||||
|
const API_BASE_URL_KEY = "cwd_admin_api_base_url";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -137,6 +181,11 @@ const { theme, setTheme } = useTheme();
|
|||||||
|
|
||||||
const isMobileSiderOpen = ref(false);
|
const isMobileSiderOpen = ref(false);
|
||||||
const isActionsOpen = ref(false);
|
const isActionsOpen = ref(false);
|
||||||
|
const adminVersion = ref(packageJson.version || "0.0.0");
|
||||||
|
const apiVersion = ref("");
|
||||||
|
const checkedApiBaseUrl = ref("");
|
||||||
|
const apiVersionError = ref("");
|
||||||
|
const versionModalVisible = ref(false);
|
||||||
|
|
||||||
const themeTitle = computed(() => {
|
const themeTitle = computed(() => {
|
||||||
if (theme.value === "light") return "明亮模式";
|
if (theme.value === "light") return "明亮模式";
|
||||||
@@ -171,10 +220,40 @@ async function loadDomains() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadVersion() {
|
||||||
|
const baseUrl = (localStorage.getItem(API_BASE_URL_KEY) || "").trim();
|
||||||
|
if (!baseUrl) {
|
||||||
|
checkedApiBaseUrl.value = "";
|
||||||
|
apiVersionError.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkedApiBaseUrl.value = baseUrl;
|
||||||
|
apiVersionError.value = "";
|
||||||
|
try {
|
||||||
|
const res = await fetch(baseUrl);
|
||||||
|
const contentType = res.headers.get("content-type") || "";
|
||||||
|
if (!res.ok || !contentType.includes("application/json")) {
|
||||||
|
apiVersionError.value =
|
||||||
|
"当前 API 版本较旧,未提供版本信息接口。推荐后续升级到最新版本以获得完整的版本检测能力(不影响当前使用)。";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
if (data && typeof data.version === "string") {
|
||||||
|
apiVersion.value = data.version;
|
||||||
|
} else {
|
||||||
|
apiVersionError.value =
|
||||||
|
"当前 API 版本较旧,未提供版本信息接口。推荐后续升级到最新版本以获得完整的版本检测能力(不影响当前使用)。";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
apiVersionError.value = (e as Error).message || "获取接口版本失败";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
provide("domainFilter", domainFilter);
|
provide("domainFilter", domainFilter);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadDomains();
|
loadDomains();
|
||||||
|
loadVersion();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(domainFilter, (value) => {
|
watch(domainFilter, (value) => {
|
||||||
@@ -248,8 +327,98 @@ function handleLogoutFromActions() {
|
|||||||
closeActions();
|
closeActions();
|
||||||
handleLogout();
|
handleLogout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openVersionModal() {
|
||||||
|
loadVersion();
|
||||||
|
versionModalVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeVersionModal() {
|
||||||
|
versionModalVisible.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
@import "../../styles/layout.less";
|
@import "../../styles/layout.less";
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background-color: var(--bg-card);
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 420px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 20px 20px 18px;
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-row {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-value {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-status {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn:focus-visible {
|
||||||
|
outline: 2px solid var(--primary-color);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -42,9 +42,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="login-link">
|
|
||||||
<a href="https://github.com/anghunk/cwd" target="_blank">Github</a>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { Hono } from 'hono';
|
|||||||
import { Bindings } from './bindings';
|
import { Bindings } from './bindings';
|
||||||
import { customCors } from './utils/cors';
|
import { customCors } from './utils/cors';
|
||||||
import { adminAuth } from './utils/auth';
|
import { adminAuth } from './utils/auth';
|
||||||
import {
|
import { isValidEmail, loadEmailNotificationSettings, saveEmailNotificationSettings } from './utils/email';
|
||||||
isValidEmail,
|
|
||||||
loadEmailNotificationSettings,
|
|
||||||
saveEmailNotificationSettings
|
|
||||||
} from './utils/email';
|
|
||||||
import { loadFeatureSettings } from './utils/featureSettings';
|
import { loadFeatureSettings } from './utils/featureSettings';
|
||||||
import packageJson from '../../package.json';
|
import packageJson from '../../package.json';
|
||||||
|
|
||||||
@@ -24,12 +20,6 @@ import { exportStats } from './api/admin/exportStats';
|
|||||||
import { importStats } from './api/admin/importStats';
|
import { importStats } from './api/admin/importStats';
|
||||||
import { exportBackup } from './api/admin/exportBackup';
|
import { exportBackup } from './api/admin/exportBackup';
|
||||||
import { importBackup } from './api/admin/importBackup';
|
import { importBackup } from './api/admin/importBackup';
|
||||||
import { exportConfig } from './api/admin/exportConfig';
|
|
||||||
import { importConfig } from './api/admin/importConfig';
|
|
||||||
import { exportStats } from './api/admin/exportStats';
|
|
||||||
import { importStats } from './api/admin/importStats';
|
|
||||||
import { exportBackup } from './api/admin/exportBackup';
|
|
||||||
import { importBackup } from './api/admin/importBackup';
|
|
||||||
import { updateStatus } from './api/admin/updateStatus';
|
import { updateStatus } from './api/admin/updateStatus';
|
||||||
import { updateComment } from './api/admin/updateComment';
|
import { updateComment } from './api/admin/updateComment';
|
||||||
import { getAdminEmail } from './api/admin/getAdminEmail';
|
import { getAdminEmail } from './api/admin/getAdminEmail';
|
||||||
@@ -43,20 +33,12 @@ import { getLikeStatus, likePage } from './api/public/like';
|
|||||||
import { likeComment } from './api/public/likeComment';
|
import { likeComment } from './api/public/likeComment';
|
||||||
import { listLikes } from './api/admin/listLikes';
|
import { listLikes } from './api/admin/listLikes';
|
||||||
import { getLikeStats } from './api/admin/likeStats';
|
import { getLikeStats } from './api/admin/likeStats';
|
||||||
import {
|
import { getFeatureSettings, updateFeatureSettings } from './api/admin/featureSettings';
|
||||||
getFeatureSettings,
|
import { getTelegramSettings, updateTelegramSettings, setupTelegramWebhook, testTelegramMessage } from './api/admin/telegramSettings';
|
||||||
updateFeatureSettings
|
|
||||||
} from './api/admin/featureSettings';
|
|
||||||
import {
|
|
||||||
getTelegramSettings,
|
|
||||||
updateTelegramSettings,
|
|
||||||
setupTelegramWebhook,
|
|
||||||
testTelegramMessage
|
|
||||||
} from './api/admin/telegramSettings';
|
|
||||||
import { telegramWebhook } from './api/telegram/webhook';
|
import { telegramWebhook } from './api/telegram/webhook';
|
||||||
|
|
||||||
const app = new Hono<{ Bindings: Bindings }>();
|
const app = new Hono<{ Bindings: Bindings }>();
|
||||||
const VERSION = `v${packageJson.version}`;
|
const VERSION = `${packageJson.version}`;
|
||||||
|
|
||||||
const COMMENT_ADMIN_EMAIL_KEY = 'comment_admin_email';
|
const COMMENT_ADMIN_EMAIL_KEY = 'comment_admin_email';
|
||||||
const COMMENT_ADMIN_BADGE_KEY = 'comment_admin_badge';
|
const COMMENT_ADMIN_BADGE_KEY = 'comment_admin_badge';
|
||||||
@@ -68,11 +50,8 @@ const COMMENT_REQUIRE_REVIEW_KEY = 'comment_require_review';
|
|||||||
const COMMENT_BLOCKED_IPS_KEY = 'comment_blocked_ips';
|
const COMMENT_BLOCKED_IPS_KEY = 'comment_blocked_ips';
|
||||||
const COMMENT_BLOCKED_EMAILS_KEY = 'comment_blocked_emails';
|
const COMMENT_BLOCKED_EMAILS_KEY = 'comment_blocked_emails';
|
||||||
|
|
||||||
|
|
||||||
async function loadCommentSettings(env: Bindings) {
|
async function loadCommentSettings(env: Bindings) {
|
||||||
await env.CWD_DB.prepare(
|
await env.CWD_DB.prepare('CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)').run();
|
||||||
'CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)'
|
|
||||||
).run();
|
|
||||||
const keys = [
|
const keys = [
|
||||||
COMMENT_ADMIN_EMAIL_KEY,
|
COMMENT_ADMIN_EMAIL_KEY,
|
||||||
COMMENT_ADMIN_BADGE_KEY,
|
COMMENT_ADMIN_BADGE_KEY,
|
||||||
@@ -82,11 +61,9 @@ async function loadCommentSettings(env: Bindings) {
|
|||||||
COMMENT_ADMIN_KEY_HASH_KEY,
|
COMMENT_ADMIN_KEY_HASH_KEY,
|
||||||
COMMENT_REQUIRE_REVIEW_KEY,
|
COMMENT_REQUIRE_REVIEW_KEY,
|
||||||
COMMENT_BLOCKED_IPS_KEY,
|
COMMENT_BLOCKED_IPS_KEY,
|
||||||
COMMENT_BLOCKED_EMAILS_KEY
|
COMMENT_BLOCKED_EMAILS_KEY,
|
||||||
];
|
];
|
||||||
const { results } = await env.CWD_DB.prepare(
|
const { results } = await env.CWD_DB.prepare('SELECT key, value FROM Settings WHERE key IN (?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
||||||
'SELECT key, value FROM Settings WHERE key IN (?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
||||||
)
|
|
||||||
.bind(...keys)
|
.bind(...keys)
|
||||||
.all<{ key: string; value: string }>();
|
.all<{ key: string; value: string }>();
|
||||||
|
|
||||||
@@ -105,18 +82,27 @@ async function loadCommentSettings(env: Bindings) {
|
|||||||
|
|
||||||
const blockedIpsRaw = map.get(COMMENT_BLOCKED_IPS_KEY) ?? '';
|
const blockedIpsRaw = map.get(COMMENT_BLOCKED_IPS_KEY) ?? '';
|
||||||
const blockedIps = blockedIpsRaw
|
const blockedIps = blockedIpsRaw
|
||||||
? blockedIpsRaw.split(',').map((d) => d.trim()).filter(Boolean)
|
? blockedIpsRaw
|
||||||
|
.split(',')
|
||||||
|
.map((d) => d.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const blockedEmailsRaw = map.get(COMMENT_BLOCKED_EMAILS_KEY) ?? '';
|
const blockedEmailsRaw = map.get(COMMENT_BLOCKED_EMAILS_KEY) ?? '';
|
||||||
const blockedEmails = blockedEmailsRaw
|
const blockedEmails = blockedEmailsRaw
|
||||||
? blockedEmailsRaw.split(',').map((d) => d.trim()).filter(Boolean)
|
? blockedEmailsRaw
|
||||||
|
.split(',')
|
||||||
|
.map((d) => d.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// 解析允许的域名列表
|
// 解析允许的域名列表
|
||||||
const allowedDomainsRaw = map.get(COMMENT_ALLOWED_DOMAINS_KEY) ?? '';
|
const allowedDomainsRaw = map.get(COMMENT_ALLOWED_DOMAINS_KEY) ?? '';
|
||||||
const allowedDomains = allowedDomainsRaw
|
const allowedDomains = allowedDomainsRaw
|
||||||
? allowedDomainsRaw.split(',').map((d) => d.trim()).filter(Boolean)
|
? allowedDomainsRaw
|
||||||
|
.split(',')
|
||||||
|
.map((d) => d.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -129,7 +115,7 @@ async function loadCommentSettings(env: Bindings) {
|
|||||||
blockedIps,
|
blockedIps,
|
||||||
blockedEmails,
|
blockedEmails,
|
||||||
adminKey: map.get(COMMENT_ADMIN_KEY_HASH_KEY) ?? null,
|
adminKey: map.get(COMMENT_ADMIN_KEY_HASH_KEY) ?? null,
|
||||||
adminKeySet: !!map.get(COMMENT_ADMIN_KEY_HASH_KEY)
|
adminKeySet: !!map.get(COMMENT_ADMIN_KEY_HASH_KEY),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +133,7 @@ async function saveCommentSettings(
|
|||||||
blockedEmails?: string[];
|
blockedEmails?: string[];
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
await env.CWD_DB.prepare(
|
await env.CWD_DB.prepare('CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)').run();
|
||||||
'CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)'
|
|
||||||
).run();
|
|
||||||
|
|
||||||
let adminKeyValue: string | undefined;
|
let adminKeyValue: string | undefined;
|
||||||
if (settings.adminKey !== undefined) {
|
if (settings.adminKey !== undefined) {
|
||||||
@@ -162,38 +146,28 @@ async function saveCommentSettings(
|
|||||||
{ key: COMMENT_AVATAR_PREFIX_KEY, value: settings.avatarPrefix },
|
{ key: COMMENT_AVATAR_PREFIX_KEY, value: settings.avatarPrefix },
|
||||||
{
|
{
|
||||||
key: COMMENT_ADMIN_ENABLED_KEY,
|
key: COMMENT_ADMIN_ENABLED_KEY,
|
||||||
value:
|
value: typeof settings.adminEnabled === 'boolean' ? (settings.adminEnabled ? '1' : '0') : undefined,
|
||||||
typeof settings.adminEnabled === 'boolean'
|
|
||||||
? settings.adminEnabled
|
|
||||||
? '1'
|
|
||||||
: '0'
|
|
||||||
: undefined
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: COMMENT_ALLOWED_DOMAINS_KEY,
|
key: COMMENT_ALLOWED_DOMAINS_KEY,
|
||||||
value: settings.allowedDomains ? settings.allowedDomains.join(',') : undefined
|
value: settings.allowedDomains ? settings.allowedDomains.join(',') : undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: COMMENT_ADMIN_KEY_HASH_KEY,
|
key: COMMENT_ADMIN_KEY_HASH_KEY,
|
||||||
value: adminKeyValue
|
value: adminKeyValue,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: COMMENT_REQUIRE_REVIEW_KEY,
|
key: COMMENT_REQUIRE_REVIEW_KEY,
|
||||||
value:
|
value: typeof settings.requireReview === 'boolean' ? (settings.requireReview ? '1' : '0') : undefined,
|
||||||
typeof settings.requireReview === 'boolean'
|
|
||||||
? settings.requireReview
|
|
||||||
? '1'
|
|
||||||
: '0'
|
|
||||||
: undefined
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: COMMENT_BLOCKED_IPS_KEY,
|
key: COMMENT_BLOCKED_IPS_KEY,
|
||||||
value: settings.blockedIps ? settings.blockedIps.join(',') : undefined
|
value: settings.blockedIps ? settings.blockedIps.join(',') : undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: COMMENT_BLOCKED_EMAILS_KEY,
|
key: COMMENT_BLOCKED_EMAILS_KEY,
|
||||||
value: settings.blockedEmails ? settings.blockedEmails.join(',') : undefined
|
value: settings.blockedEmails ? settings.blockedEmails.join(',') : undefined,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@@ -201,9 +175,7 @@ async function saveCommentSettings(
|
|||||||
const value = entry.value === null ? '' : entry.value;
|
const value = entry.value === null ? '' : entry.value;
|
||||||
const trimmed = typeof value === 'string' ? value.trim() : value;
|
const trimmed = typeof value === 'string' ? value.trim() : value;
|
||||||
if (trimmed) {
|
if (trimmed) {
|
||||||
await env.CWD_DB.prepare('REPLACE INTO Settings (key, value) VALUES (?, ?)')
|
await env.CWD_DB.prepare('REPLACE INTO Settings (key, value) VALUES (?, ?)').bind(entry.key, trimmed).run();
|
||||||
.bind(entry.key, trimmed)
|
|
||||||
.run();
|
|
||||||
} else {
|
} else {
|
||||||
await env.CWD_DB.prepare('DELETE FROM Settings WHERE key = ?').bind(entry.key).run();
|
await env.CWD_DB.prepare('DELETE FROM Settings WHERE key = ?').bind(entry.key).run();
|
||||||
}
|
}
|
||||||
@@ -217,12 +189,12 @@ app.use('*', async (c, next) => {
|
|||||||
path: c.req.path,
|
path: c.req.path,
|
||||||
url: c.req.url,
|
url: c.req.url,
|
||||||
hasDb: !!c.env.CWD_DB,
|
hasDb: !!c.env.CWD_DB,
|
||||||
hasAuthKv: !!c.env.CWD_AUTH_KV
|
hasAuthKv: !!c.env.CWD_AUTH_KV,
|
||||||
});
|
});
|
||||||
const res = await next();
|
const res = await next();
|
||||||
console.log('Request:end', {
|
console.log('Request:end', {
|
||||||
method: c.req.method,
|
method: c.req.method,
|
||||||
path: c.req.path
|
path: c.req.path,
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
@@ -237,9 +209,8 @@ app.use('/admin/*', async (c, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/', (c) => {
|
app.get('/', (c) => {
|
||||||
return c.html(
|
c.header('Access-Control-Allow-Origin', '*');
|
||||||
`CWD 评论部署成功,当前版本 ${VERSION},<a href="https://cdw.js.org" target="_blank" rel="noreferrer">查看文档</a>`
|
return c.json({ version: VERSION, data: '成功部署 CWD 评论系统 API!文档地址 https://cwd.js.org' });
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/comments', getComments);
|
app.get('/api/comments', getComments);
|
||||||
@@ -254,13 +225,7 @@ app.get('/api/config/comments', async (c) => {
|
|||||||
try {
|
try {
|
||||||
const settings = await loadCommentSettings(c.env);
|
const settings = await loadCommentSettings(c.env);
|
||||||
const featureSettings = await loadFeatureSettings(c.env);
|
const featureSettings = await loadFeatureSettings(c.env);
|
||||||
const {
|
const { adminKey, adminKeySet, blockedIps, blockedEmails, ...publicSettings } = settings as any;
|
||||||
adminKey,
|
|
||||||
adminKeySet,
|
|
||||||
blockedIps,
|
|
||||||
blockedEmails,
|
|
||||||
...publicSettings
|
|
||||||
} = settings as any;
|
|
||||||
|
|
||||||
return c.json({ ...publicSettings, ...featureSettings });
|
return c.json({ ...publicSettings, ...featureSettings });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -303,15 +268,14 @@ app.get('/admin/settings/email-notify', async (c) => {
|
|||||||
app.put('/admin/settings/email-notify', async (c) => {
|
app.put('/admin/settings/email-notify', async (c) => {
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
const body = await c.req.json();
|
||||||
const globalEnabled =
|
const globalEnabled = typeof body.globalEnabled === 'boolean' ? body.globalEnabled : undefined;
|
||||||
typeof body.globalEnabled === 'boolean' ? body.globalEnabled : undefined;
|
|
||||||
const smtp = body.smtp && typeof body.smtp === 'object' ? body.smtp : undefined;
|
const smtp = body.smtp && typeof body.smtp === 'object' ? body.smtp : undefined;
|
||||||
const templates = body.templates && typeof body.templates === 'object' ? body.templates : undefined;
|
const templates = body.templates && typeof body.templates === 'object' ? body.templates : undefined;
|
||||||
|
|
||||||
await saveEmailNotificationSettings(c.env, {
|
await saveEmailNotificationSettings(c.env, {
|
||||||
globalEnabled,
|
globalEnabled,
|
||||||
smtp,
|
smtp,
|
||||||
templates
|
templates,
|
||||||
});
|
});
|
||||||
|
|
||||||
return c.json({ message: '保存成功' });
|
return c.json({ message: '保存成功' });
|
||||||
@@ -351,24 +315,12 @@ app.put('/admin/settings/comments', async (c) => {
|
|||||||
const adminEmail = rawAdminEmail.trim();
|
const adminEmail = rawAdminEmail.trim();
|
||||||
const adminBadge = rawAdminBadge.trim();
|
const adminBadge = rawAdminBadge.trim();
|
||||||
const avatarPrefix = rawAvatarPrefix.trim();
|
const avatarPrefix = rawAvatarPrefix.trim();
|
||||||
const adminEnabled =
|
const adminEnabled = typeof rawAdminEnabled === 'boolean' ? rawAdminEnabled : rawAdminEnabled === '1' || rawAdminEnabled === 1;
|
||||||
typeof rawAdminEnabled === 'boolean'
|
const allowedDomains = rawAllowedDomains.map((d: any) => (typeof d === 'string' ? d.trim() : '')).filter(Boolean);
|
||||||
? rawAdminEnabled
|
|
||||||
: rawAdminEnabled === '1' || rawAdminEnabled === 1;
|
|
||||||
const allowedDomains = rawAllowedDomains
|
|
||||||
.map((d: any) => (typeof d === 'string' ? d.trim() : ''))
|
|
||||||
.filter(Boolean);
|
|
||||||
const adminKey = rawAdminKey; // Can be undefined or empty string
|
const adminKey = rawAdminKey; // Can be undefined or empty string
|
||||||
const requireReview =
|
const requireReview = typeof rawRequireReview === 'boolean' ? rawRequireReview : rawRequireReview === '1' || rawRequireReview === 1;
|
||||||
typeof rawRequireReview === 'boolean'
|
const blockedIps = rawBlockedIps.map((d: any) => (typeof d === 'string' ? d.trim() : '')).filter(Boolean);
|
||||||
? rawRequireReview
|
const blockedEmails = rawBlockedEmails.map((d: any) => (typeof d === 'string' ? d.trim() : '')).filter(Boolean);
|
||||||
: rawRequireReview === '1' || rawRequireReview === 1;
|
|
||||||
const blockedIps = rawBlockedIps
|
|
||||||
.map((d: any) => (typeof d === 'string' ? d.trim() : ''))
|
|
||||||
.filter(Boolean);
|
|
||||||
const blockedEmails = rawBlockedEmails
|
|
||||||
.map((d: any) => (typeof d === 'string' ? d.trim() : ''))
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
if (adminEmail && !isValidEmail(adminEmail)) {
|
if (adminEmail && !isValidEmail(adminEmail)) {
|
||||||
return c.json({ message: '邮箱格式不正确' }, 400);
|
return c.json({ message: '邮箱格式不正确' }, 400);
|
||||||
@@ -383,7 +335,7 @@ app.put('/admin/settings/comments', async (c) => {
|
|||||||
adminKey,
|
adminKey,
|
||||||
requireReview,
|
requireReview,
|
||||||
blockedIps,
|
blockedIps,
|
||||||
blockedEmails
|
blockedEmails,
|
||||||
});
|
});
|
||||||
|
|
||||||
return c.json({ message: '保存成功' });
|
return c.json({ message: '保存成功' });
|
||||||
@@ -402,9 +354,7 @@ app.post('/admin/comments/block-ip', async (c) => {
|
|||||||
return c.json({ message: 'IP 地址不能为空' }, 400);
|
return c.json({ message: 'IP 地址不能为空' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
await c.env.CWD_DB.prepare(
|
await c.env.CWD_DB.prepare('CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)').run();
|
||||||
'CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)'
|
|
||||||
).run();
|
|
||||||
|
|
||||||
const row = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
const row = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
||||||
.bind(COMMENT_BLOCKED_IPS_KEY)
|
.bind(COMMENT_BLOCKED_IPS_KEY)
|
||||||
@@ -412,17 +362,16 @@ app.post('/admin/comments/block-ip', async (c) => {
|
|||||||
|
|
||||||
const existing = row?.value || '';
|
const existing = row?.value || '';
|
||||||
const list = existing
|
const list = existing
|
||||||
? existing.split(',').map((d) => d.trim()).filter(Boolean)
|
? existing
|
||||||
|
.split(',')
|
||||||
|
.map((d) => d.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (!list.includes(ip)) {
|
if (!list.includes(ip)) {
|
||||||
list.push(ip);
|
list.push(ip);
|
||||||
const joined = list.join(',');
|
const joined = list.join(',');
|
||||||
await c.env.CWD_DB.prepare(
|
await c.env.CWD_DB.prepare('REPLACE INTO Settings (key, value) VALUES (?, ?)').bind(COMMENT_BLOCKED_IPS_KEY, joined).run();
|
||||||
'REPLACE INTO Settings (key, value) VALUES (?, ?)'
|
|
||||||
)
|
|
||||||
.bind(COMMENT_BLOCKED_IPS_KEY, joined)
|
|
||||||
.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.json({ message: '已加入 IP 黑名单' });
|
return c.json({ message: '已加入 IP 黑名单' });
|
||||||
@@ -445,9 +394,7 @@ app.post('/admin/comments/block-email', async (c) => {
|
|||||||
return c.json({ message: '邮箱格式不正确' }, 400);
|
return c.json({ message: '邮箱格式不正确' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
await c.env.CWD_DB.prepare(
|
await c.env.CWD_DB.prepare('CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)').run();
|
||||||
'CREATE TABLE IF NOT EXISTS Settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)'
|
|
||||||
).run();
|
|
||||||
|
|
||||||
const row = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
const row = await c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
|
||||||
.bind(COMMENT_BLOCKED_EMAILS_KEY)
|
.bind(COMMENT_BLOCKED_EMAILS_KEY)
|
||||||
@@ -455,17 +402,16 @@ app.post('/admin/comments/block-email', async (c) => {
|
|||||||
|
|
||||||
const existing = row?.value || '';
|
const existing = row?.value || '';
|
||||||
const list = existing
|
const list = existing
|
||||||
? existing.split(',').map((d) => d.trim()).filter(Boolean)
|
? existing
|
||||||
|
.split(',')
|
||||||
|
.map((d) => d.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (!list.includes(email)) {
|
if (!list.includes(email)) {
|
||||||
list.push(email);
|
list.push(email);
|
||||||
const joined = list.join(',');
|
const joined = list.join(',');
|
||||||
await c.env.CWD_DB.prepare(
|
await c.env.CWD_DB.prepare('REPLACE INTO Settings (key, value) VALUES (?, ?)').bind(COMMENT_BLOCKED_EMAILS_KEY, joined).run();
|
||||||
'REPLACE INTO Settings (key, value) VALUES (?, ?)'
|
|
||||||
)
|
|
||||||
.bind(COMMENT_BLOCKED_EMAILS_KEY, joined)
|
|
||||||
.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.json({ message: '已加入邮箱黑名单' });
|
return c.json({ message: '已加入邮箱黑名单' });
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cwd",
|
"name": "cwd",
|
||||||
"version": "0.0.10",
|
"version": "0.0.11",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user