0.0.1.2
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
import nodemailer from 'nodemailer'
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT || '465'),
|
||||
secure: true, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
})
|
||||
|
||||
export async function sendInvitationEmail(
|
||||
to: string,
|
||||
inviterName: string,
|
||||
treeName: string,
|
||||
role: string,
|
||||
password?: string
|
||||
) {
|
||||
const loginUrl = `${process.env.NEXTAUTH_URL}/auth/signin`
|
||||
const roleText = role === 'EDITOR' ? '编辑者' : '查看者'
|
||||
|
||||
let htmlContent = `
|
||||
<div style="font-family: sans-serif; padding: 20px;">
|
||||
<h2>家族树协作邀请</h2>
|
||||
<p>您好,</p>
|
||||
<p><strong>${inviterName}</strong> 邀请您作为 <strong>${roleText}</strong> 加入家族树 <strong>"${treeName}"</strong>。</p>
|
||||
`
|
||||
|
||||
if (password) {
|
||||
htmlContent += `
|
||||
<p>您的初始登录密码为:<br>
|
||||
<strong style="font-size: 18px; background: #eee; padding: 5px 10px; border-radius: 4px;">${password}</strong>
|
||||
</p>
|
||||
<p>请使用您的邮箱 (${to}) 和上述密码登录。</p>
|
||||
`
|
||||
} else {
|
||||
htmlContent += `
|
||||
<p>您可以使用现有的账号登录查看。</p>
|
||||
`
|
||||
}
|
||||
|
||||
htmlContent += `
|
||||
<p>
|
||||
<a href="${loginUrl}" style="display: inline-block; background: #2563eb; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">
|
||||
立即登录
|
||||
</a>
|
||||
</p>
|
||||
<p style="color: #666; font-size: 12px; margin-top: 30px;">此邮件由中华家谱系统自动发送,请勿回复。</p>
|
||||
</div>
|
||||
`
|
||||
|
||||
await transporter.sendMail({
|
||||
from: process.env.SMTP_FROM,
|
||||
to,
|
||||
subject: `[中华家谱] ${inviterName} 邀请您协作`,
|
||||
html: htmlContent,
|
||||
})
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { db } from "./db"
|
||||
import { prisma } from "./prisma"
|
||||
import type { FamilyMember } from "@/types/family"
|
||||
|
||||
/**
|
||||
* 从 IndexedDB 迁移数据到 PostgreSQL
|
||||
* @param treeId 目标家族树ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
export async function migrateFromIndexedDB(treeId: string, userId: string) {
|
||||
try {
|
||||
console.log("🚀 开始数据迁移...")
|
||||
|
||||
// 1. 从 IndexedDB 读取所有成员
|
||||
const members = await db.members.toArray()
|
||||
console.log(`📊 找到 ${members.length} 个成员`)
|
||||
|
||||
if (members.length === 0) {
|
||||
console.log("⚠️ 没有数据需要迁移")
|
||||
return { success: true, count: 0 }
|
||||
}
|
||||
|
||||
// 2. 获取 rootId
|
||||
const rootIdSetting = await db.settings.get("rootId")
|
||||
const rootId = rootIdSetting?.value
|
||||
|
||||
// 3. 批量创建成员
|
||||
let successCount = 0
|
||||
let errorCount = 0
|
||||
|
||||
for (const member of members) {
|
||||
try {
|
||||
await prisma.familyMember.create({
|
||||
data: {
|
||||
id: member.id,
|
||||
treeId,
|
||||
surname: member.surname,
|
||||
givenName: member.givenName,
|
||||
fullName: member.fullName,
|
||||
gender: member.gender.toUpperCase() as "MALE" | "FEMALE",
|
||||
generation: member.generation,
|
||||
generationName: member.generationName,
|
||||
courtesyName: member.courtesyName,
|
||||
artName: member.artName,
|
||||
posthumousName: member.posthumousName,
|
||||
rank: member.rank,
|
||||
birthDate: member.birthDate,
|
||||
deathDate: member.deathDate,
|
||||
isLunarDate: member.isLunarDate || false,
|
||||
ancestralHome: member.ancestralHome,
|
||||
birthPlace: member.birthPlace,
|
||||
burialPlace: member.burialPlace,
|
||||
phone: member.phone,
|
||||
telephone: member.telephone,
|
||||
email: member.email,
|
||||
address: member.address,
|
||||
fatherId: member.fatherId,
|
||||
motherId: member.motherId,
|
||||
spouseIds: member.spouseIds || [],
|
||||
childrenIds: member.childrenIds || [],
|
||||
spouseFatherName: member.spouseFatherName,
|
||||
spouseMotherName: member.spouseMotherName,
|
||||
bio: member.bio,
|
||||
tags: member.tags || [],
|
||||
avatarUrl: member.avatarUrl,
|
||||
avatarImageId: member.avatarImageId,
|
||||
photoIds: member.photoIds || [],
|
||||
}
|
||||
})
|
||||
successCount++
|
||||
console.log(`✅ 迁移成员: ${member.fullName}`)
|
||||
} catch (error) {
|
||||
errorCount++
|
||||
console.error(`❌ 迁移失败: ${member.fullName}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 更新家族树的 rootMemberId
|
||||
if (rootId) {
|
||||
await prisma.familyTree.update({
|
||||
where: { id: treeId },
|
||||
data: { rootMemberId: rootId }
|
||||
})
|
||||
}
|
||||
|
||||
// 5. 记录迁移日志
|
||||
await prisma.activityLog.create({
|
||||
data: {
|
||||
treeId,
|
||||
userId,
|
||||
action: "IMPORT",
|
||||
entityType: "MEMBER",
|
||||
entityName: `迁移 ${successCount} 个成员`,
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`✅ 迁移完成!成功: ${successCount}, 失败: ${errorCount}`)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
count: successCount,
|
||||
errors: errorCount,
|
||||
total: members.length
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 迁移失败:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空 IndexedDB 数据(迁移后可选)
|
||||
*/
|
||||
export async function clearIndexedDB() {
|
||||
try {
|
||||
await db.members.clear()
|
||||
await db.settings.clear()
|
||||
await db.activityLogs.clear()
|
||||
console.log("✅ IndexedDB 数据已清空")
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error("❌ 清空失败:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user