This commit is contained in:
freedakgmail
2025-11-23 03:07:39 +08:00
parent 626d7dddde
commit e92884ae53
679 changed files with 208175 additions and 1183474 deletions
+61
View File
@@ -0,0 +1,61 @@
import type { NextAuthOptions } from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import { prisma } from "./prisma"
import bcrypt from "bcryptjs"
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error("请输入邮箱和密码")
}
const user = await prisma.user.findUnique({
where: { email: credentials.email }
})
if (!user || !user.password) {
throw new Error("用户不存在")
}
const isValid = await bcrypt.compare(credentials.password, user.password)
if (!isValid) {
throw new Error("密码错误")
}
return {
id: user.id,
email: user.email,
name: user.name,
}
}
})
],
session: {
strategy: "jwt"
},
pages: {
signIn: "/auth/signin",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string
}
return session
}
}
}
+125
View File
@@ -0,0 +1,125 @@
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
}
}
+49
View File
@@ -0,0 +1,49 @@
import { prisma } from "./prisma"
import { Role } from "@prisma/client"
export async function checkPermission(
userId: string,
treeId: string,
requiredRole: Role = Role.VIEWER
) {
// 检查是否是所有者
const tree = await prisma.familyTree.findFirst({
where: {
id: treeId,
ownerId: userId
}
})
if (tree) {
return { hasPermission: true, role: Role.OWNER }
}
// 检查协作者权限
const collaborator = await prisma.treeCollaborator.findFirst({
where: {
treeId,
userId
}
})
if (!collaborator) {
return { hasPermission: false, role: null }
}
const roleHierarchy = {
[Role.OWNER]: 3,
[Role.EDITOR]: 2,
[Role.VIEWER]: 1
}
const hasPermission = roleHierarchy[collaborator.role] >= roleHierarchy[requiredRole]
return { hasPermission, role: collaborator.role }
}
export async function requireAuth(userId: string | undefined) {
if (!userId) {
throw new Error("未授权")
}
return userId
}
+9
View File
@@ -0,0 +1,9 @@
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
+10 -5
View File
@@ -4,7 +4,7 @@ export function registerServiceWorker() {
return
}
window.addEventListener("load", () => {
const register = () => {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
@@ -17,9 +17,7 @@ export function registerServiceWorker() {
newWorker.addEventListener("statechange", () => {
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
// 新的 Service Worker 已安装,提示用户刷新
if (confirm("发现新版本,是否立即更新?")) {
window.location.reload()
}
console.log("发现新版本,可以刷新页面")
}
})
}
@@ -28,7 +26,14 @@ export function registerServiceWorker() {
.catch((error) => {
console.log("Service Worker 注册失败:", error)
})
})
}
// 如果页面已经加载完成,直接注册;否则等待 load 事件
if (document.readyState === "complete") {
register()
} else {
window.addEventListener("load", register)
}
}
export function unregisterServiceWorker() {