Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(creators): add verification for creators (#2135)
  • Loading branch information
waleedlatif1 authored Nov 30, 2025
commit a8f87f7e3a104d4745616d6de50787ec5785bfaa
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async function hasPermission(userId: string, profile: any): Promise<boolean> {
return false
}

// GET /api/creator-profiles/[id] - Get a specific creator profile
// GET /api/creators/[id] - Get a specific creator profile
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
Expand All @@ -70,7 +70,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
}
}

// PUT /api/creator-profiles/[id] - Update a creator profile
// PUT /api/creators/[id] - Update a creator profile
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
Expand Down Expand Up @@ -135,7 +135,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
}
}

// DELETE /api/creator-profiles/[id] - Delete a creator profile
// DELETE /api/creators/[id] - Delete a creator profile
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
Expand Down
114 changes: 114 additions & 0 deletions apps/sim/app/api/creators/[id]/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { db } from '@sim/db'
import { templateCreators, user } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'

const logger = createLogger('CreatorVerificationAPI')

export const revalidate = 0

// POST /api/creators/[id]/verify - Verify a creator (super users only)
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params

try {
const session = await getSession()
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthorized verification attempt for creator: ${id}`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

// Check if user is a super user
const currentUser = await db.select().from(user).where(eq(user.id, session.user.id)).limit(1)

if (!currentUser[0]?.isSuperUser) {
logger.warn(`[${requestId}] Non-super user attempted to verify creator: ${id}`)
return NextResponse.json({ error: 'Only super users can verify creators' }, { status: 403 })
}

// Check if creator exists
const existingCreator = await db
.select()
.from(templateCreators)
.where(eq(templateCreators.id, id))
.limit(1)

if (existingCreator.length === 0) {
logger.warn(`[${requestId}] Creator not found for verification: ${id}`)
return NextResponse.json({ error: 'Creator not found' }, { status: 404 })
}

// Update creator verified status to true
await db
.update(templateCreators)
.set({ verified: true, updatedAt: new Date() })
.where(eq(templateCreators.id, id))

logger.info(`[${requestId}] Creator verified: ${id} by super user: ${session.user.id}`)

return NextResponse.json({
message: 'Creator verified successfully',
creatorId: id,
})
} catch (error) {
logger.error(`[${requestId}] Error verifying creator ${id}`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

// DELETE /api/creators/[id]/verify - Unverify a creator (super users only)
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()
const { id } = await params

try {
const session = await getSession()
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthorized unverification attempt for creator: ${id}`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

// Check if user is a super user
const currentUser = await db.select().from(user).where(eq(user.id, session.user.id)).limit(1)

if (!currentUser[0]?.isSuperUser) {
logger.warn(`[${requestId}] Non-super user attempted to unverify creator: ${id}`)
return NextResponse.json({ error: 'Only super users can unverify creators' }, { status: 403 })
}

// Check if creator exists
const existingCreator = await db
.select()
.from(templateCreators)
.where(eq(templateCreators.id, id))
.limit(1)

if (existingCreator.length === 0) {
logger.warn(`[${requestId}] Creator not found for unverification: ${id}`)
return NextResponse.json({ error: 'Creator not found' }, { status: 404 })
}

// Update creator verified status to false
await db
.update(templateCreators)
.set({ verified: false, updatedAt: new Date() })
.where(eq(templateCreators.id, id))

logger.info(`[${requestId}] Creator unverified: ${id} by super user: ${session.user.id}`)

return NextResponse.json({
message: 'Creator unverified successfully',
creatorId: id,
})
} catch (error) {
logger.error(`[${requestId}] Error unverifying creator ${id}`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const CreateCreatorProfileSchema = z.object({
details: CreatorProfileDetailsSchema.optional(),
})

// GET /api/creator-profiles - Get creator profiles for current user
// GET /api/creators - Get creator profiles for current user
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
const { searchParams } = new URL(request.url)
Expand Down Expand Up @@ -81,7 +81,7 @@ export async function GET(request: NextRequest) {
}
}

// POST /api/creator-profiles - Create a new creator profile
// POST /api/creators - Create a new creator profile
export async function POST(request: NextRequest) {
const requestId = generateRequestId()

Expand Down
68 changes: 59 additions & 9 deletions apps/sim/app/templates/[id]/template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { VerifiedBadge } from '@/components/ui/verified-badge'
import { useSession } from '@/lib/auth-client'
import { createLogger } from '@/lib/logs/console/logger'
import { getBaseUrl } from '@/lib/urls/utils'
Expand Down Expand Up @@ -64,6 +65,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
const [isEditing, setIsEditing] = useState(false)
const [isApproving, setIsApproving] = useState(false)
const [isRejecting, setIsRejecting] = useState(false)
const [isVerifying, setIsVerifying] = useState(false)
const [hasWorkspaceAccess, setHasWorkspaceAccess] = useState<boolean | null>(null)
const [workspaces, setWorkspaces] = useState<
Array<{ id: string; name: string; permissions: string }>
Expand Down Expand Up @@ -462,6 +464,32 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
}
}

const handleToggleVerification = async () => {
if (isVerifying || !template?.creator?.id) return

setIsVerifying(true)
try {
const endpoint = `/api/creators/${template.creator.id}/verify`
const method = template.creator.verified ? 'DELETE' : 'POST'

const response = await fetch(endpoint, { method })

if (response.ok) {
// Refresh page to show updated verification status
window.location.reload()
} else {
const error = await response.json()
logger.error('Error toggling verification:', error)
alert(`Failed to ${template.creator.verified ? 'unverify' : 'verify'} creator`)
}
} catch (error) {
logger.error('Error toggling verification:', error)
alert('An error occurred while toggling verification')
} finally {
setIsVerifying(false)
}
}

/**
* Shares the template to X (Twitter)
*/
Expand Down Expand Up @@ -718,9 +746,12 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
</div>
)}
{/* Creator name */}
<span className='font-medium text-[#8B8B8B] text-[14px]'>
{template.creator?.name || 'Unknown'}
</span>
<div className='flex items-center gap-[4px]'>
<span className='font-medium text-[#8B8B8B] text-[14px]'>
{template.creator?.name || 'Unknown'}
</span>
{template.creator?.verified && <VerifiedBadge size='md' />}
</div>
</div>

{/* Credentials needed */}
Expand Down Expand Up @@ -849,9 +880,25 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
template.creator.details?.websiteUrl ||
template.creator.details?.contactEmail) && (
<div className='mt-8'>
<h3 className='mb-4 font-sans font-semibold text-base text-foreground'>
About the Creator
</h3>
<div className='mb-4 flex items-center justify-between'>
<h3 className='font-sans font-semibold text-base text-foreground'>
About the Creator
</h3>
{isSuperUser && template.creator && (
<Button
variant={template.creator.verified ? 'active' : 'default'}
onClick={handleToggleVerification}
disabled={isVerifying}
className='h-[28px] rounded-[6px] text-[12px]'
>
{isVerifying
? 'Updating...'
: template.creator.verified
? 'Unverify Creator'
: 'Verify Creator'}
</Button>
)}
</div>
<div className='flex items-start gap-4'>
{/* Creator profile image */}
{template.creator.profileImageUrl ? (
Expand All @@ -871,9 +918,12 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
{/* Creator details */}
<div className='flex-1'>
<div className='mb-[5px] flex items-center gap-3'>
<h4 className='font-sans font-semibold text-base text-foreground'>
{template.creator.name}
</h4>
<div className='flex items-center gap-[6px]'>
<h4 className='font-sans font-semibold text-base text-foreground'>
{template.creator.name}
</h4>
{template.creator.verified && <VerifiedBadge size='md' />}
</div>

{/* Social links */}
<div className='flex items-center gap-[12px]'>
Expand Down
8 changes: 7 additions & 1 deletion apps/sim/app/templates/components/template-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Star, User } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { VerifiedBadge } from '@/components/ui/verified-badge'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview'
Expand All @@ -21,6 +22,7 @@ interface TemplateCardProps {
className?: string
state?: WorkflowState
isStarred?: boolean
isVerified?: boolean
}

export function TemplateCardSkeleton({ className }: { className?: string }) {
Expand Down Expand Up @@ -125,6 +127,7 @@ function TemplateCardInner({
className,
state,
isStarred = false,
isVerified = false,
}: TemplateCardProps) {
const router = useRouter()
const params = useParams()
Expand Down Expand Up @@ -276,7 +279,10 @@ function TemplateCardInner({
<User className='h-[18px] w-[18px] text-[#888888]' />
</div>
)}
<span className='truncate font-medium text-[#888888] text-[12px]'>{author}</span>
<div className='flex items-center gap-[4px]'>
<span className='truncate font-medium text-[#888888] text-[12px]'>{author}</span>
{isVerified && <VerifiedBadge size='sm' />}
</div>
</div>

<div className='flex flex-shrink-0 items-center gap-[6px] font-medium text-[#888888] text-[12px]'>
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/templates/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface Template {
details?: CreatorProfileDetails | null
referenceType: 'user' | 'organization'
referenceId: string
verified?: boolean
} | null
views: number
stars: number
Expand Down Expand Up @@ -203,6 +204,7 @@ export default function Templates({
stars={template.stars}
state={template.state}
isStarred={template.isStarred}
isVerified={template.creator?.verified || false}
/>
))
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Star, User } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { VerifiedBadge } from '@/components/ui/verified-badge'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview'
Expand All @@ -21,6 +22,7 @@ interface TemplateCardProps {
className?: string
state?: WorkflowState
isStarred?: boolean
isVerified?: boolean
}

export function TemplateCardSkeleton({ className }: { className?: string }) {
Expand Down Expand Up @@ -126,6 +128,7 @@ function TemplateCardInner({
className,
state,
isStarred = false,
isVerified = false,
}: TemplateCardProps) {
const router = useRouter()
const params = useParams()
Expand Down Expand Up @@ -277,7 +280,10 @@ function TemplateCardInner({
<User className='h-[18px] w-[18px] text-[#888888]' />
</div>
)}
<span className='truncate font-medium text-[#888888] text-[12px]'>{author}</span>
<div className='flex items-center gap-[4px]'>
<span className='truncate font-medium text-[#888888] text-[12px]'>{author}</span>
{isVerified && <VerifiedBadge size='sm' />}
</div>
</div>

<div className='flex flex-shrink-0 items-center gap-[6px] font-medium text-[#888888] text-[12px]'>
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/templates/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface Template {
details?: CreatorProfileDetails | null
referenceType: 'user' | 'organization'
referenceId: string
verified?: boolean
} | null
views: number
stars: number
Expand Down Expand Up @@ -223,6 +224,7 @@ export default function Templates({
stars={template.stars}
state={template.state}
isStarred={template.isStarred}
isVerified={template.creator?.verified || false}
/>
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function TemplateDeploy({ workflowId, onDeploymentComplete }: TemplateDep

setLoadingCreators(true)
try {
const response = await fetch('/api/creator-profiles')
const response = await fetch('/api/creators')
if (response.ok) {
const data = await response.json()
const profiles = (data.profiles || []).map((profile: any) => ({
Expand Down
Loading
Loading