mirror of
https://github.com/RhenCloud/Cloud-Home.git
synced 2026-01-22 17:39:07 +08:00
fix: 修复邮件发送和 Wakatime 数据获取功能,更新 Vercel 配置
This commit is contained in:
103
server/api/send-mail.ts
Normal file
103
server/api/send-mail.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { defineEventHandler, createError, readBody } from "h3";
|
||||
import nodemailer from "nodemailer";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import { useRuntimeConfig } from "#imports";
|
||||
|
||||
type MailConfig = {
|
||||
smtpHost?: string;
|
||||
smtpPort?: number | string;
|
||||
smtpUser?: string;
|
||||
smtpPass?: string;
|
||||
senderEmail?: string;
|
||||
adminEmail?: string;
|
||||
smtpSecure?: boolean;
|
||||
};
|
||||
|
||||
type SendMailPayload = {
|
||||
name?: string;
|
||||
url?: string;
|
||||
desc?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
const ensureValue = (value?: string, fallback = "未填写") => (value?.trim() ? value.trim() : fallback);
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const method = event.node.req.method;
|
||||
if (method === "OPTIONS") {
|
||||
event.node.res.statusCode = 200;
|
||||
return { status: "ok" };
|
||||
}
|
||||
|
||||
if (method !== "POST") {
|
||||
throw createError({ statusCode: 405, statusMessage: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const payload = (await readBody<SendMailPayload>(event)) || {};
|
||||
const { name, url, desc, email, avatar, message } = payload;
|
||||
|
||||
if (!name?.trim() || !url?.trim() || !email?.trim()) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing required fields: name, url, and email",
|
||||
});
|
||||
}
|
||||
|
||||
const config = useRuntimeConfig() as MailConfig;
|
||||
const { smtpHost, smtpPort: configSmtpPort, smtpUser, smtpPass, senderEmail, adminEmail, smtpSecure } = config;
|
||||
|
||||
const smtpPort = Number(configSmtpPort ?? 465);
|
||||
if (!smtpHost || !smtpUser || !smtpPass || !senderEmail || !adminEmail) {
|
||||
throw createError({ statusCode: 500, statusMessage: "SMTP server is not fully configured" });
|
||||
}
|
||||
|
||||
const secure = typeof smtpSecure === "boolean" ? smtpSecure : smtpPort === 465;
|
||||
const smtpOptions: SMTPTransport.Options = {
|
||||
host: smtpHost,
|
||||
port: smtpPort,
|
||||
secure,
|
||||
auth: {
|
||||
user: smtpUser,
|
||||
pass: smtpPass,
|
||||
},
|
||||
};
|
||||
|
||||
const transporter = nodemailer.createTransport(smtpOptions);
|
||||
const friendEntry = `{
|
||||
name: "${ensureValue(name).replace(/"/g, '\\"')}",
|
||||
url: "${ensureValue(url).replace(/"/g, '\\"')}",
|
||||
desc: "${ensureValue(desc).replace(/"/g, '\\"')}",
|
||||
avatar: "${ensureValue(avatar).replace(/"/g, '\\"')}",
|
||||
},`;
|
||||
|
||||
const htmlMessage = `
|
||||
<p>一个新的友链申请已提交,以下是可直接复制到项目中的配置:</p>
|
||||
<pre style="background: #f5f5f5; padding: 12px; border-radius: 4px; overflow: auto;">
|
||||
<code>${friendEntry}</code>
|
||||
</pre>
|
||||
<hr style="margin: 20px 0;" />
|
||||
<p><strong>申请者信息:</strong></p>
|
||||
<p><strong>名称:</strong>${ensureValue(name)}</p>
|
||||
<p><strong>邮箱:</strong>${ensureValue(email)}</p>
|
||||
<p><strong>站点:</strong><a href="${ensureValue(url)}">${ensureValue(url)}</a></p>
|
||||
<p><strong>描述:</strong>${ensureValue(desc)}</p>
|
||||
<p><strong>头像:</strong>${ensureValue(avatar)}</p>
|
||||
<p><strong>想说的话:</strong>${ensureValue(message)}</p>
|
||||
<p><strong>时间:</strong>${new Date().toISOString()}</p>
|
||||
`;
|
||||
|
||||
const info = await transporter.sendMail({
|
||||
from: senderEmail,
|
||||
to: adminEmail,
|
||||
replyTo: email,
|
||||
subject: `友链申请 / 联系表单 · ${ensureValue(name)}`,
|
||||
html: htmlMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
message: "Mail sent",
|
||||
id: info.messageId,
|
||||
};
|
||||
});
|
||||
56
server/api/wakatime.ts
Normal file
56
server/api/wakatime.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { defineEventHandler, getQuery, createError } from "h3";
|
||||
import { useRuntimeConfig } from "#imports";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const res = event.node.res;
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
if (event.node.req.method === "OPTIONS") {
|
||||
res.statusCode = 200;
|
||||
return "ok";
|
||||
}
|
||||
|
||||
if (event.node.req.method !== "GET") {
|
||||
throw createError({ statusCode: 405, statusMessage: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const apiKey = config.wakatimeApiKey;
|
||||
if (typeof apiKey !== "string") {
|
||||
throw createError({ statusCode: 500, statusMessage: "Invalid WakaTime API Key configuration" });
|
||||
}
|
||||
|
||||
const query = getQuery(event);
|
||||
const apiUrl = (query.apiUrl as string) || config.wakatimeApiUrl;
|
||||
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(apiKey).toString("base64")}`,
|
||||
};
|
||||
|
||||
try {
|
||||
const [weeklyStatsResponse, allTimeStatsResponse, statusResponse] = await Promise.all([
|
||||
fetch(`${apiUrl}/users/current/stats/last_7_days`, { headers }),
|
||||
fetch(`${apiUrl}/users/current/stats/all_time`, { headers }),
|
||||
fetch(`${apiUrl}/users/current/status`, { headers }),
|
||||
]);
|
||||
|
||||
if (!weeklyStatsResponse.ok) {
|
||||
throw new Error(`Wakatime API error: ${weeklyStatsResponse.status}`);
|
||||
}
|
||||
|
||||
const weeklyStatsData = await weeklyStatsResponse.json();
|
||||
const allTimeStatsData = allTimeStatsResponse.ok ? await allTimeStatsResponse.json() : null;
|
||||
const statusData = statusResponse.ok ? await statusResponse.json() : null;
|
||||
|
||||
return {
|
||||
weekly: weeklyStatsData.data,
|
||||
allTime: allTimeStatsData ? allTimeStatsData.data : null,
|
||||
status: statusData,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Wakatime API error:", error);
|
||||
throw createError({ statusCode: 500, statusMessage: "Failed to fetch Wakatime data" });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user