feat(admin): 添加版本信息显示与兼容性检查功能

- 在布局侧边栏底部添加版本信息区域,显示 API 和后台版本
- 点击版本信息可打开模态框,展示详细的版本信息和兼容性状态
- 当 API 版本与后台版本不一致时提示用户更新以避免兼容性问题
- 移除登录页面中已注释的 GitHub 链接
- 更新 package.json 版本号至 0.0.11
- 优化 API 根路径响应,返回 JSON 格式的版本信息
- 清理重复的导入语句和冗余代码
This commit is contained in:
anghunk
2026-01-31 10:32:57 +08:00
parent f42a0b7b0a
commit 1a7a9db88f
5 changed files with 250 additions and 113 deletions

View File

@@ -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 {
@@ -275,4 +300,4 @@
.layout-actions-item-danger:hover { .layout-actions-item-danger:hover {
background-color: rgba(209, 36, 47, 0.1); background-color: rgba(209, 36, 47, 0.1);
} }
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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: '已加入邮箱黑名单' });

View File

@@ -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",