open-prompt / src /app /api /profile /route.ts
GitHub Action
Automated sync to Hugging Face
bcce530
import { NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { getAuthUser } from '@/lib/auth'
import { updateProfileSchema, syncProfileSchema, parseBody } from '@/lib/validations'
export const dynamic = 'force-dynamic'
// GET - Fetch user profile
// Only the profile owner can see private fields (email, notification prefs)
export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url)
const userId = searchParams.get('userId')
const username = searchParams.get('username')
if (!userId && !username) {
return NextResponse.json(
{ error: 'userId or username required' },
{ status: 400 }
)
}
// Check if the viewer is the profile owner
const authUser = await getAuthUser()
const isOwner = authUser && (
(userId && authUser.id === userId) ||
false // username check happens after fetch
)
const user = await prisma.user.findFirst({
where: userId ? { id: userId } : { username },
select: {
id: true,
name: true,
username: true,
image: true,
bio: true,
rank: true,
totalRuns: true,
totalStars: true,
totalRemixes: true,
socialLinks: true,
createdAt: true,
// Sensitive fields — only for owner
email: true,
notifyEmail: true,
notifyStars: true,
notifyRemixes: true,
_count: {
select: {
prompts: true,
collections: true,
},
},
},
})
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
)
}
// Strip sensitive fields if not the owner
const isRealOwner = isOwner || (authUser && authUser.id === user.id)
if (!isRealOwner) {
return NextResponse.json({
id: user.id,
name: user.name,
username: user.username,
image: user.image,
bio: user.bio,
rank: user.rank,
totalRuns: user.totalRuns,
totalStars: user.totalStars,
totalRemixes: user.totalRemixes,
socialLinks: user.socialLinks,
createdAt: user.createdAt,
_count: user._count,
})
}
return NextResponse.json(user)
} catch (error) {
console.error('Get profile error:', error)
return NextResponse.json(
{ error: 'Failed to fetch profile' },
{ status: 500 }
)
}
}
// PUT - Update user profile — requires authentication (uses server-side auth, not client userId)
export async function PUT(req: Request) {
try {
const authUser = await getAuthUser()
if (!authUser) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const body = await req.json()
const parsed = parseBody(updateProfileSchema, body)
if (!parsed.success) return parsed.response
const {
name,
username,
bio,
socialLinks,
image,
notifyEmail,
notifyStars,
notifyRemixes
} = parsed.data
// Check if username is taken (if changing username)
if (username) {
const existingUser = await prisma.user.findFirst({
where: {
username,
NOT: { id: authUser.id },
},
})
if (existingUser) {
return NextResponse.json(
{ error: 'Username is already taken' },
{ status: 409 }
)
}
}
// Upsert — uses authUser.id from server session, NOT from client body
const updatedUser = await prisma.user.upsert({
where: { id: authUser.id },
create: {
id: authUser.id,
email: authUser.email || `${authUser.id}@stackauth.local`,
name: name || null,
username: username || null,
bio: bio || null,
image: image || null,
notifyEmail: notifyEmail ?? true,
notifyStars: notifyStars ?? true,
notifyRemixes: notifyRemixes ?? true,
},
update: {
...(name !== undefined && { name }),
...(username !== undefined && { username }),
...(bio !== undefined && { bio }),
...(socialLinks !== undefined && { socialLinks }),
...(image !== undefined && { image }),
...(notifyEmail !== undefined && { notifyEmail }),
...(notifyStars !== undefined && { notifyStars }),
...(notifyRemixes !== undefined && { notifyRemixes }),
},
select: {
id: true,
name: true,
username: true,
image: true,
bio: true,
socialLinks: true,
notifyEmail: true,
notifyStars: true,
notifyRemixes: true,
},
})
return NextResponse.json(updatedUser)
} catch (error) {
console.error('Update profile error:', error)
return NextResponse.json(
{ error: 'Failed to update profile' },
{ status: 500 }
)
}
}
// POST - Create or sync user from Stack Auth
export async function POST(req: Request) {
try {
const body = await req.json()
const parsed = parseBody(syncProfileSchema, body)
if (!parsed.success) return parsed.response
const { id, email, name, image } = parsed.data
// Upsert user - create if doesn't exist, update if does
const user = await prisma.user.upsert({
where: { id },
create: {
id,
email,
name: name || null,
image: image || null,
username: email.split('@')[0], // Default username from email
},
update: {
email,
name: name || null,
image: image || null,
},
})
return NextResponse.json(user)
} catch (error) {
console.error('Sync user error:', error)
return NextResponse.json(
{ error: 'Failed to sync user' },
{ status: 500 }
)
}
}