Spaces:
Configuration error
Configuration error
| 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 } | |
| ) | |
| } | |
| } | |