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
+6
View File
@@ -0,0 +1,6 @@
import NextAuth from "next-auth"
import { authOptions } from "@/lib/auth"
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
+65
View File
@@ -0,0 +1,65 @@
import { NextRequest, NextResponse } from "next/server"
import { prisma } from "@/lib/prisma"
import bcrypt from "bcryptjs"
import { z } from "zod"
const registerSchema = z.object({
email: z.string().email("请输入有效的邮箱地址"),
password: z.string().min(6, "密码至少6个字符"),
name: z.string().optional(),
})
export async function POST(req: NextRequest) {
try {
const body = await req.json()
const { email, password, name } = registerSchema.parse(body)
// 检查用户是否已存在
const existingUser = await prisma.user.findUnique({
where: { email }
})
if (existingUser) {
return NextResponse.json(
{ error: "该邮箱已被注册" },
{ status: 400 }
)
}
// 加密密码
const hashedPassword = await bcrypt.hash(password, 10)
// 创建用户
const user = await prisma.user.create({
data: {
email,
password: hashedPassword,
name,
},
select: {
id: true,
email: true,
name: true,
createdAt: true,
}
})
return NextResponse.json({
message: "注册成功",
user
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0].message },
{ status: 400 }
)
}
console.error("注册失败:", error)
return NextResponse.json(
{ error: "注册失败,请稍后重试" },
{ status: 500 }
)
}
}
@@ -0,0 +1,52 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import { checkPermission } from "@/lib/permissions"
// 获取家族树的活动日志
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
const { searchParams } = new URL(req.url)
const limit = parseInt(searchParams.get('limit') || '50')
// 检查权限
const { hasPermission } = await checkPermission(session.user.id, treeId)
if (!hasPermission) {
return NextResponse.json({ error: "无权访问此家族树" }, { status: 403 })
}
const logs = await prisma.activityLog.findMany({
where: { treeId },
orderBy: { timestamp: 'desc' },
take: limit,
include: {
user: {
select: {
id: true,
name: true,
email: true,
}
}
}
})
return NextResponse.json({ logs })
} catch (error) {
console.error("获取活动日志失败:", error)
return NextResponse.json(
{ error: "获取活动日志失败" },
{ status: 500 }
)
}
}
+78
View File
@@ -0,0 +1,78 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import { checkPermission } from "@/lib/permissions"
import { Role } from "@prisma/client"
// 导入家族数据
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
// 检查权限(需要 OWNER 权限)
const { hasPermission } = await checkPermission(session.user.id, treeId, Role.OWNER)
if (!hasPermission) {
return NextResponse.json({ error: "无权限导入数据" }, { status: 403 })
}
const body = await req.json()
const { members, rootId } = body
// 删除现有成员
await prisma.familyMember.deleteMany({
where: { treeId }
})
// 批量创建新成员
const memberArray = Object.values(members)
await prisma.familyMember.createMany({
data: memberArray.map((member: any) => ({
...member,
treeId,
}))
})
// 更新家族树的 rootMemberId
if (rootId) {
await prisma.familyTree.update({
where: { id: treeId },
data: { rootMemberId: rootId }
})
}
// 记录操作日志
await prisma.activityLog.create({
data: {
treeId,
userId: session.user.id,
action: 'IMPORT',
entityType: 'SETTINGS',
entityName: '导入家族数据',
changes: {
memberCount: memberArray.length
},
}
})
return NextResponse.json({
success: true,
memberCount: memberArray.length
})
} catch (error) {
console.error("导入数据失败:", error)
return NextResponse.json(
{ error: "导入数据失败" },
{ status: 500 }
)
}
}
@@ -0,0 +1,170 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import { checkPermission } from "@/lib/permissions"
import { Role } from "@prisma/client"
// 获取单个成员
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string; memberId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId, memberId } = await params
// 检查权限
const { hasPermission } = await checkPermission(session.user.id, treeId)
if (!hasPermission) {
return NextResponse.json({ error: "无权访问此家族树" }, { status: 403 })
}
const member = await prisma.familyMember.findFirst({
where: {
id: memberId,
treeId,
}
})
if (!member) {
return NextResponse.json({ error: "成员不存在" }, { status: 404 })
}
return NextResponse.json({ member })
} catch (error) {
console.error("获取成员失败:", error)
return NextResponse.json(
{ error: "获取成员失败" },
{ status: 500 }
)
}
}
// 更新成员
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string; memberId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId, memberId } = await params
// 检查权限(需要 EDITOR 权限)
const { hasPermission } = await checkPermission(session.user.id, treeId, Role.EDITOR)
if (!hasPermission) {
return NextResponse.json({ error: "无权限修改成员" }, { status: 403 })
}
const body = await req.json()
// 检查成员是否存在
const existingMember = await prisma.familyMember.findFirst({
where: {
id: memberId,
treeId,
}
})
if (!existingMember) {
return NextResponse.json({ error: "成员不存在" }, { status: 404 })
}
const member = await prisma.familyMember.update({
where: { id: memberId },
data: {
...body,
updatedAt: new Date(),
}
})
// 记录操作日志
await prisma.activityLog.create({
data: {
treeId,
userId: session.user.id,
action: 'UPDATE',
entityType: 'MEMBER',
entityId: member.id,
entityName: member.fullName,
changes: body,
}
})
return NextResponse.json({ member })
} catch (error) {
console.error("更新成员失败:", error)
return NextResponse.json(
{ error: "更新成员失败" },
{ status: 500 }
)
}
}
// 删除成员
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string; memberId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId, memberId } = await params
// 检查权限(需要 EDITOR 权限)
const { hasPermission } = await checkPermission(session.user.id, treeId, Role.EDITOR)
if (!hasPermission) {
return NextResponse.json({ error: "无权限删除成员" }, { status: 403 })
}
// 检查成员是否存在
const existingMember = await prisma.familyMember.findFirst({
where: {
id: memberId,
treeId,
}
})
if (!existingMember) {
return NextResponse.json({ error: "成员不存在" }, { status: 404 })
}
await prisma.familyMember.delete({
where: { id: memberId }
})
// 记录操作日志
await prisma.activityLog.create({
data: {
treeId,
userId: session.user.id,
action: 'DELETE',
entityType: 'MEMBER',
entityId: memberId,
entityName: existingMember.fullName,
}
})
return NextResponse.json({ success: true })
} catch (error) {
console.error("删除成员失败:", error)
return NextResponse.json(
{ error: "删除成员失败" },
{ status: 500 }
)
}
}
+138
View File
@@ -0,0 +1,138 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import { checkPermission } from "@/lib/permissions"
import { Role, Gender } from "@prisma/client"
import { z } from "zod"
const createMemberSchema = z.object({
surname: z.string().min(1),
givenName: z.string().min(1),
fullName: z.string().min(1),
gender: z.nativeEnum(Gender),
generation: z.number().int().positive(),
birthDate: z.string().optional(),
deathDate: z.string().optional(),
fatherId: z.string().optional(),
motherId: z.string().optional(),
spouseIds: z.array(z.string()).optional(),
// ... 其他字段
})
// 获取家族树的所有成员
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
// 检查权限
const { hasPermission } = await checkPermission(session.user.id, treeId)
if (!hasPermission) {
return NextResponse.json({ error: "无权访问此家族树" }, { status: 403 })
}
// 获取家族树信息
const tree = await prisma.familyTree.findUnique({
where: { id: treeId },
select: { rootMemberId: true }
})
const members = await prisma.familyMember.findMany({
where: { treeId },
orderBy: [
{ generation: 'asc' },
{ createdAt: 'asc' }
]
})
// 如果没有设置 rootMemberId,尝试找到第一代成员作为根
let rootId = tree?.rootMemberId
if (!rootId && members.length > 0) {
const firstGenMember = members.find((m: any) => m.generation === 1)
if (firstGenMember) {
rootId = firstGenMember.id
// 更新家族树的 rootMemberId
await prisma.familyTree.update({
where: { id: treeId },
data: { rootMemberId: firstGenMember.id }
})
}
}
return NextResponse.json({ members, rootId })
} catch (error) {
console.error("获取成员失败:", error)
return NextResponse.json(
{ error: "获取成员失败" },
{ status: 500 }
)
}
}
// 创建新成员
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
// 检查权限(需要 EDITOR 权限)
const { hasPermission } = await checkPermission(session.user.id, treeId, Role.EDITOR)
if (!hasPermission) {
return NextResponse.json({ error: "无权限添加成员" }, { status: 403 })
}
const body = await req.json()
const data = createMemberSchema.parse(body)
const member = await prisma.familyMember.create({
data: {
...data,
treeId,
}
})
// 记录操作日志
await prisma.activityLog.create({
data: {
treeId,
userId: session.user.id,
action: 'CREATE',
entityType: 'MEMBER',
entityId: member.id,
entityName: member.fullName,
}
})
return NextResponse.json({ member }, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0].message },
{ status: 400 }
)
}
console.error("创建成员失败:", error)
return NextResponse.json(
{ error: "创建成员失败" },
{ status: 500 }
)
}
}
+163
View File
@@ -0,0 +1,163 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
// 获取单个家族树信息
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
console.log('API /trees/[treeId] - Requested treeId:', treeId)
const tree = await prisma.familyTree.findFirst({
where: {
id: treeId,
OR: [
{ ownerId: session.user.id },
{
collaborators: {
some: {
userId: session.user.id
}
}
}
]
},
include: {
owner: {
select: {
id: true,
name: true,
email: true,
}
},
_count: {
select: {
members: true,
collaborators: true,
}
}
}
})
if (!tree) {
console.log('API /trees/[treeId] - Tree not found for id:', treeId)
return NextResponse.json({ error: "家族树不存在或无权访问" }, { status: 404 })
}
console.log('API /trees/[treeId] - Returning tree:', tree.name, tree.id)
return NextResponse.json({ tree })
} catch (error) {
console.error("获取家族树失败:", error)
return NextResponse.json(
{ error: "获取家族树失败" },
{ status: 500 }
)
}
}
// 更新家族树信息
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
const body = await req.json()
const { name, description } = body
// 检查权限
const tree = await prisma.familyTree.findFirst({
where: {
id: treeId,
ownerId: session.user.id
}
})
if (!tree) {
return NextResponse.json({ error: "家族树不存在或无权修改" }, { status: 404 })
}
// 更新家族树
const updatedTree = await prisma.familyTree.update({
where: { id: treeId },
data: {
name: name || tree.name,
description: description !== undefined ? description : tree.description,
},
include: {
owner: {
select: {
id: true,
name: true,
email: true,
}
}
}
})
return NextResponse.json({ tree: updatedTree })
} catch (error) {
console.error("更新家族树失败:", error)
return NextResponse.json(
{ error: "更新家族树失败" },
{ status: 500 }
)
}
}
// 删除家族树
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ treeId: string }> }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const { treeId } = await params
// 检查权限
const tree = await prisma.familyTree.findFirst({
where: {
id: treeId,
ownerId: session.user.id
}
})
if (!tree) {
return NextResponse.json({ error: "家族树不存在或无权删除" }, { status: 404 })
}
// 删除家族树(会级联删除所有成员和相关数据)
await prisma.familyTree.delete({
where: { id: treeId }
})
return NextResponse.json({ success: true })
} catch (error) {
console.error("删除家族树失败:", error)
return NextResponse.json(
{ error: "删除家族树失败" },
{ status: 500 }
)
}
}
+108
View File
@@ -0,0 +1,108 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import { z } from "zod"
const createTreeSchema = z.object({
name: z.string().min(1, "家族树名称不能为空"),
description: z.string().optional(),
})
// 获取用户的所有家族树
export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const trees = await prisma.familyTree.findMany({
where: {
OR: [
{ ownerId: session.user.id },
{
collaborators: {
some: {
userId: session.user.id
}
}
}
]
},
include: {
owner: {
select: {
id: true,
name: true,
email: true,
}
},
_count: {
select: {
members: true,
collaborators: true,
}
}
},
orderBy: {
updatedAt: 'desc'
}
})
return NextResponse.json({ trees })
} catch (error) {
console.error("获取家族树失败:", error)
return NextResponse.json(
{ error: "获取家族树失败" },
{ status: 500 }
)
}
}
// 创建新家族树
export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const body = await req.json()
const { name, description } = createTreeSchema.parse(body)
const tree = await prisma.familyTree.create({
data: {
name,
description,
ownerId: session.user.id,
},
include: {
owner: {
select: {
id: true,
name: true,
email: true,
}
}
}
})
return NextResponse.json({ tree }, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0].message },
{ status: 400 }
)
}
console.error("创建家族树失败:", error)
return NextResponse.json(
{ error: "创建家族树失败" },
{ status: 500 }
)
}
}
+68
View File
@@ -0,0 +1,68 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import bcrypt from "bcryptjs"
import { z } from "zod"
const changePasswordSchema = z.object({
currentPassword: z.string().min(1, "请输入当前密码"),
newPassword: z.string().min(6, "新密码至少需要6个字符"),
confirmPassword: z.string().min(1, "请确认新密码"),
}).refine((data) => data.newPassword === data.confirmPassword, {
message: "两次输入的密码不一致",
path: ["confirmPassword"],
})
export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
const body = await req.json()
const { currentPassword, newPassword } = changePasswordSchema.parse(body)
// 获取用户当前密码
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { password: true }
})
if (!user || !user.password) {
return NextResponse.json({ error: "用户不存在" }, { status: 404 })
}
// 验证当前密码
const isValid = await bcrypt.compare(currentPassword, user.password)
if (!isValid) {
return NextResponse.json({ error: "当前密码错误" }, { status: 400 })
}
// 加密新密码
const hashedPassword = await bcrypt.hash(newPassword, 10)
// 更新密码
await prisma.user.update({
where: { id: session.user.id },
data: { password: hashedPassword }
})
return NextResponse.json({ message: "密码修改成功" })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0].message },
{ status: 400 }
)
}
console.error("修改密码失败:", error)
return NextResponse.json(
{ error: "修改密码失败,请稍后重试" },
{ status: 500 }
)
}
}
+66
View File
@@ -0,0 +1,66 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: "未授权" }, { status: 401 })
}
// 获取用户信息
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: {
id: true,
email: true,
name: true,
createdAt: true,
updatedAt: true,
}
})
if (!user) {
return NextResponse.json({ error: "用户不存在" }, { status: 404 })
}
// 获取用户拥有的家族树数量
const ownedTreesCount = await prisma.familyTree.count({
where: { ownerId: session.user.id }
})
// 获取用户作为协作者的家族树
const collaborations = await prisma.treeCollaborator.findMany({
where: { userId: session.user.id },
include: {
tree: {
select: {
id: true,
name: true,
}
}
}
})
return NextResponse.json({
user: {
...user,
ownedTreesCount,
collaborations: collaborations.map((c) => ({
treeId: c.tree.id,
treeName: c.tree.name,
role: c.role,
}))
}
})
} catch (error) {
console.error("获取用户信息失败:", error)
return NextResponse.json(
{ error: "获取用户信息失败" },
{ status: 500 }
)
}
}