0.0.1.0
This commit is contained in:
+61
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user