Files
sim/apps/sim/app/api/creators/route.ts
Waleed d707d18ee6 fix(build): update dockerfile to contain testing package deps (#2591)
* fix(build): update dockerfile to contain testing package deps

* added logger package
2025-12-26 12:20:38 -08:00

195 lines
6.5 KiB
TypeScript

import { db } from '@sim/db'
import { member, templateCreators } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import type { CreatorProfileDetails } from '@/app/_types/creator-profile'
const logger = createLogger('CreatorProfilesAPI')
const CreatorProfileDetailsSchema = z.object({
about: z.string().max(2000, 'Max 2000 characters').optional(),
xUrl: z.string().url().optional().or(z.literal('')),
linkedinUrl: z.string().url().optional().or(z.literal('')),
websiteUrl: z.string().url().optional().or(z.literal('')),
contactEmail: z.string().email().optional().or(z.literal('')),
})
const CreateCreatorProfileSchema = z.object({
referenceType: z.enum(['user', 'organization']),
referenceId: z.string().min(1, 'Reference ID is required'),
name: z.string().min(1, 'Name is required').max(100, 'Max 100 characters'),
profileImageUrl: z.string().min(1, 'Profile image is required'),
details: CreatorProfileDetailsSchema.optional(),
})
// GET /api/creators - Get creator profiles for current user
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
try {
const session = await getSession()
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthorized access attempt`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Get user's organizations where they're admin or owner
const userOrgs = await db
.select({ organizationId: member.organizationId })
.from(member)
.where(
and(
eq(member.userId, session.user.id),
or(eq(member.role, 'owner'), eq(member.role, 'admin'))
)
)
const orgIds = userOrgs.map((m) => m.organizationId)
// Get creator profiles for user and their organizations
const profiles = await db
.select()
.from(templateCreators)
.where(
or(
and(
eq(templateCreators.referenceType, 'user'),
eq(templateCreators.referenceId, session.user.id)
),
...orgIds.map((orgId) =>
and(
eq(templateCreators.referenceType, 'organization'),
eq(templateCreators.referenceId, orgId)
)
)
)
)
logger.info(`[${requestId}] Retrieved ${profiles.length} creator profiles`)
return NextResponse.json({ profiles })
} catch (error: any) {
logger.error(`[${requestId}] Error fetching creator profiles`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
// POST /api/creators - Create a new creator profile
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const session = await getSession()
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthorized creation attempt`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const data = CreateCreatorProfileSchema.parse(body)
logger.debug(`[${requestId}] Creating creator profile:`, {
referenceType: data.referenceType,
referenceId: data.referenceId,
})
// Validate permissions
if (data.referenceType === 'user') {
if (data.referenceId !== session.user.id) {
logger.warn(`[${requestId}] User tried to create profile for another user`)
return NextResponse.json(
{ error: 'Cannot create profile for another user' },
{ status: 403 }
)
}
} else if (data.referenceType === 'organization') {
// Check if user is admin/owner of the organization
const membership = await db
.select()
.from(member)
.where(
and(
eq(member.userId, session.user.id),
eq(member.organizationId, data.referenceId),
or(eq(member.role, 'owner'), eq(member.role, 'admin'))
)
)
.limit(1)
if (membership.length === 0) {
logger.warn(`[${requestId}] User not authorized for organization: ${data.referenceId}`)
return NextResponse.json(
{ error: 'You must be an admin or owner to create an organization profile' },
{ status: 403 }
)
}
}
// Check if profile already exists
const existing = await db
.select()
.from(templateCreators)
.where(
and(
eq(templateCreators.referenceType, data.referenceType),
eq(templateCreators.referenceId, data.referenceId)
)
)
.limit(1)
if (existing.length > 0) {
logger.warn(
`[${requestId}] Profile already exists for ${data.referenceType}:${data.referenceId}`
)
return NextResponse.json({ error: 'Creator profile already exists' }, { status: 409 })
}
// Create the profile
const profileId = uuidv4()
const now = new Date()
const details: CreatorProfileDetails = {}
if (data.details?.about) details.about = data.details.about
if (data.details?.xUrl) details.xUrl = data.details.xUrl
if (data.details?.linkedinUrl) details.linkedinUrl = data.details.linkedinUrl
if (data.details?.websiteUrl) details.websiteUrl = data.details.websiteUrl
if (data.details?.contactEmail) details.contactEmail = data.details.contactEmail
const newProfile = {
id: profileId,
referenceType: data.referenceType,
referenceId: data.referenceId,
name: data.name,
profileImageUrl: data.profileImageUrl || null,
details: Object.keys(details).length > 0 ? details : null,
createdBy: session.user.id,
createdAt: now,
updatedAt: now,
}
await db.insert(templateCreators).values(newProfile)
logger.info(`[${requestId}] Successfully created creator profile: ${profileId}`)
return NextResponse.json({ data: newProfile }, { status: 201 })
} catch (error: any) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid profile data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid profile data', details: error.errors },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error creating creator profile`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}