mirror of
https://github.com/Tony0410/nextstep.git
synced 2026-05-24 13:21:39 +08:00
- Fix argon2 native module build in Docker (add build-essential, python3) - Switch Docker base image from Alpine to Debian-slim for OpenSSL compatibility - Fix session cookies for HTTP access (COOKIE_SECURE env var) - Fix TypeScript type errors in sync routes and middleware - Fix CSS circular dependency in globals.css - Fix Map iteration in rate-limit cleanup - Add createdAt field to LocalNote interface - Configure Tailscale Funnel on port 10000 - Update NEXT_PUBLIC_APP_URL for public funnel access - Add initial Prisma migration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
176 lines
4.0 KiB
TypeScript
176 lines
4.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { prisma } from '@/lib/db/prisma'
|
|
import { getSession } from '@/lib/auth'
|
|
import { withRateLimit } from '@/lib/auth/middleware'
|
|
|
|
// GET /api/invite/[token] - Get invite details (public)
|
|
async function getHandler(
|
|
req: NextRequest,
|
|
{ params }: { params: Promise<{ token: string }> }
|
|
) {
|
|
try {
|
|
const { token } = await params
|
|
|
|
const invite = await prisma.inviteToken.findUnique({
|
|
where: { token },
|
|
include: {
|
|
workspace: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if (!invite) {
|
|
return NextResponse.json(
|
|
{ error: 'Invite not found' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
if (invite.usedAt) {
|
|
return NextResponse.json(
|
|
{ error: 'This invite has already been used' },
|
|
{ status: 410 }
|
|
)
|
|
}
|
|
|
|
if (invite.expiresAt < new Date()) {
|
|
return NextResponse.json(
|
|
{ error: 'This invite has expired' },
|
|
{ status: 410 }
|
|
)
|
|
}
|
|
|
|
return NextResponse.json({
|
|
invite: {
|
|
workspaceName: invite.workspace.name,
|
|
role: invite.role,
|
|
expiresAt: invite.expiresAt,
|
|
},
|
|
})
|
|
} catch (error) {
|
|
console.error('Get invite error:', error)
|
|
return NextResponse.json(
|
|
{ error: 'Failed to get invite' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
// POST /api/invite/[token] - Accept invite (requires auth)
|
|
async function postHandler(
|
|
req: NextRequest,
|
|
{ params }: { params: Promise<{ token: string }> }
|
|
) {
|
|
try {
|
|
const session = await getSession()
|
|
|
|
if (!session) {
|
|
return NextResponse.json(
|
|
{ error: 'You must be logged in to accept an invite' },
|
|
{ status: 401 }
|
|
)
|
|
}
|
|
|
|
const { token } = await params
|
|
|
|
const invite = await prisma.inviteToken.findUnique({
|
|
where: { token },
|
|
include: {
|
|
workspace: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if (!invite) {
|
|
return NextResponse.json(
|
|
{ error: 'Invite not found' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
if (invite.usedAt) {
|
|
return NextResponse.json(
|
|
{ error: 'This invite has already been used' },
|
|
{ status: 410 }
|
|
)
|
|
}
|
|
|
|
if (invite.expiresAt < new Date()) {
|
|
return NextResponse.json(
|
|
{ error: 'This invite has expired' },
|
|
{ status: 410 }
|
|
)
|
|
}
|
|
|
|
// Check if already a member
|
|
const existingMember = await prisma.workspaceMember.findUnique({
|
|
where: {
|
|
workspaceId_userId: {
|
|
workspaceId: invite.workspaceId,
|
|
userId: session.user.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
if (existingMember) {
|
|
return NextResponse.json(
|
|
{ error: 'You are already a member of this workspace' },
|
|
{ status: 409 }
|
|
)
|
|
}
|
|
|
|
// Accept invite in a transaction
|
|
const [member] = await prisma.$transaction([
|
|
prisma.workspaceMember.create({
|
|
data: {
|
|
workspaceId: invite.workspaceId,
|
|
userId: session.user.id,
|
|
role: invite.role,
|
|
},
|
|
}),
|
|
prisma.inviteToken.update({
|
|
where: { id: invite.id },
|
|
data: {
|
|
usedAt: new Date(),
|
|
usedById: session.user.id,
|
|
},
|
|
}),
|
|
prisma.auditLog.create({
|
|
data: {
|
|
workspaceId: invite.workspaceId,
|
|
userId: session.user.id,
|
|
action: 'JOIN',
|
|
entityType: 'WORKSPACE',
|
|
entityId: invite.workspaceId,
|
|
details: { role: invite.role, inviteToken: token },
|
|
},
|
|
}),
|
|
])
|
|
|
|
return NextResponse.json({
|
|
workspace: {
|
|
id: invite.workspace.id,
|
|
name: invite.workspace.name,
|
|
role: member.role,
|
|
},
|
|
})
|
|
} catch (error) {
|
|
console.error('Accept invite error:', error)
|
|
return NextResponse.json(
|
|
{ error: 'Failed to accept invite' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
export const GET = getHandler
|
|
export const POST = postHandler
|