mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 06:58:07 -05:00
feat(creators): add verification for creators (#2135)
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }> }
|
||||
114
apps/sim/app/api/creators/[id]/verify/route.ts
Normal file
114
apps/sim/app/api/creators/[id]/verify/route.ts
Normal file
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
@@ -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 }>
|
||||
@@ -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)
|
||||
*/
|
||||
@@ -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 */}
|
||||
@@ -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 ? (
|
||||
@@ -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]'>
|
||||
|
||||
@@ -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'
|
||||
@@ -21,6 +22,7 @@ interface TemplateCardProps {
|
||||
className?: string
|
||||
state?: WorkflowState
|
||||
isStarred?: boolean
|
||||
isVerified?: boolean
|
||||
}
|
||||
|
||||
export function TemplateCardSkeleton({ className }: { className?: string }) {
|
||||
@@ -125,6 +127,7 @@ function TemplateCardInner({
|
||||
className,
|
||||
state,
|
||||
isStarred = false,
|
||||
isVerified = false,
|
||||
}: TemplateCardProps) {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
@@ -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]'>
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface Template {
|
||||
details?: CreatorProfileDetails | null
|
||||
referenceType: 'user' | 'organization'
|
||||
referenceId: string
|
||||
verified?: boolean
|
||||
} | null
|
||||
views: number
|
||||
stars: number
|
||||
@@ -203,6 +204,7 @@ export default function Templates({
|
||||
stars={template.stars}
|
||||
state={template.state}
|
||||
isStarred={template.isStarred}
|
||||
isVerified={template.creator?.verified || false}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -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'
|
||||
@@ -21,6 +22,7 @@ interface TemplateCardProps {
|
||||
className?: string
|
||||
state?: WorkflowState
|
||||
isStarred?: boolean
|
||||
isVerified?: boolean
|
||||
}
|
||||
|
||||
export function TemplateCardSkeleton({ className }: { className?: string }) {
|
||||
@@ -126,6 +128,7 @@ function TemplateCardInner({
|
||||
className,
|
||||
state,
|
||||
isStarred = false,
|
||||
isVerified = false,
|
||||
}: TemplateCardProps) {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
@@ -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]'>
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface Template {
|
||||
details?: CreatorProfileDetails | null
|
||||
referenceType: 'user' | 'organization'
|
||||
referenceId: string
|
||||
verified?: boolean
|
||||
} | null
|
||||
views: number
|
||||
stars: number
|
||||
@@ -223,6 +224,7 @@ export default function Templates({
|
||||
stars={template.stars}
|
||||
state={template.state}
|
||||
isStarred={template.isStarred}
|
||||
isVerified={template.creator?.verified || false}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
33
apps/sim/components/ui/verified-badge.tsx
Normal file
33
apps/sim/components/ui/verified-badge.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface VerifiedBadgeProps {
|
||||
className?: string
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
export function VerifiedBadge({ className, size = 'md' }: VerifiedBadgeProps) {
|
||||
const sizeMap = {
|
||||
sm: 12,
|
||||
md: 14,
|
||||
lg: 16,
|
||||
}
|
||||
|
||||
const dimension = sizeMap[size]
|
||||
|
||||
return (
|
||||
<div className={cn('inline-flex flex-shrink-0', className)} title='Verified Creator'>
|
||||
<svg
|
||||
width={dimension}
|
||||
height={dimension}
|
||||
viewBox='0 0 16 16'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M16 8.375C16 8.93437 15.8656 9.45312 15.5969 9.92813C15.3281 10.4031 14.9688 10.775 14.5156 11.0344C14.5281 11.1188 14.5344 11.25 14.5344 11.4281C14.5344 12.275 14.25 12.9937 13.6875 13.5875C13.1219 14.1844 12.4406 14.4812 11.6438 14.4812C11.2875 14.4812 10.9469 14.4156 10.625 14.2844C10.375 14.7969 10.0156 15.2094 9.54375 15.525C9.075 15.8438 8.55937 16 8 16C7.42812 16 6.90938 15.8469 6.44688 15.5344C5.98125 15.225 5.625 14.8094 5.375 14.2844C5.05312 14.4156 4.71562 14.4812 4.35625 14.4812C3.55937 14.4812 2.875 14.1844 2.30312 13.5875C1.73125 12.9937 1.44687 12.2719 1.44687 11.4281C1.44687 11.3344 1.45938 11.2031 1.48125 11.0344C1.02813 10.7719 0.66875 10.4031 0.4 9.92813C0.134375 9.45312 0 8.93437 0 8.375C0 7.78125 0.15 7.23438 0.446875 6.74062C0.74375 6.24687 1.14375 5.88125 1.64375 5.64375C1.5125 5.2875 1.44687 4.92812 1.44687 4.57188C1.44687 3.72813 1.73125 3.00625 2.30312 2.4125C2.875 1.81875 3.55937 1.51875 4.35625 1.51875C4.7125 1.51875 5.05312 1.58438 5.375 1.71563C5.625 1.20312 5.98438 0.790625 6.45625 0.475C6.925 0.159375 7.44063 0 8 0C8.55937 0 9.075 0.159375 9.54375 0.471875C10.0125 0.7875 10.375 1.2 10.625 1.7125C10.9469 1.58125 11.2844 1.51562 11.6438 1.51562C12.4406 1.51562 13.1219 1.8125 13.6875 2.40937C14.2531 3.00625 14.5344 3.725 14.5344 4.56875C14.5344 4.9625 14.475 5.31875 14.3562 5.64062C14.8562 5.87813 15.2563 6.24375 15.5531 6.7375C15.85 7.23438 16 7.78125 16 8.375ZM7.65938 10.7844L10.9625 5.8375C11.0469 5.70625 11.0719 5.5625 11.0437 5.40938C11.0125 5.25625 10.9344 5.13438 10.8031 5.05312C10.6719 4.96875 10.5281 4.94063 10.375 4.9625C10.2188 4.9875 10.0938 5.0625 10 5.19375L7.09062 9.56875L5.75 8.23125C5.63125 8.1125 5.49375 8.05625 5.34062 8.0625C5.18437 8.06875 5.05 8.125 4.93125 8.23125C4.825 8.3375 4.77187 8.47187 4.77187 8.63437C4.77187 8.79375 4.825 8.92813 4.93125 9.0375L6.77187 10.8781L6.8625 10.95C6.96875 11.0219 7.07812 11.0562 7.18437 11.0562C7.39375 11.0531 7.55313 10.9656 7.65938 10.7844Z'
|
||||
fill='#33B4FF'
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export function useOrganizations() {
|
||||
* Fetch creator profile for a user
|
||||
*/
|
||||
async function fetchCreatorProfile(userId: string): Promise<CreatorProfile | null> {
|
||||
const response = await fetch(`/api/creator-profiles?userId=${userId}`)
|
||||
const response = await fetch(`/api/creators?userId=${userId}`)
|
||||
|
||||
// Treat 404 as "no profile"
|
||||
if (response.status === 404) {
|
||||
@@ -133,9 +133,7 @@ export function useSaveCreatorProfile() {
|
||||
details: details && Object.keys(details).length > 0 ? details : undefined,
|
||||
}
|
||||
|
||||
const url = existingProfileId
|
||||
? `/api/creator-profiles/${existingProfileId}`
|
||||
: '/api/creator-profiles'
|
||||
const url = existingProfileId ? `/api/creators/${existingProfileId}` : '/api/creators'
|
||||
const method = existingProfileId ? 'PUT' : 'POST'
|
||||
|
||||
const response = await fetch(url, {
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface TemplateCreator {
|
||||
email?: string
|
||||
website?: string
|
||||
profileImageUrl?: string | null
|
||||
verified?: boolean
|
||||
details?: {
|
||||
about?: string
|
||||
xUrl?: string
|
||||
|
||||
1
packages/db/migrations/0115_redundant_cerebro.sql
Normal file
1
packages/db/migrations/0115_redundant_cerebro.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "template_creators" ADD COLUMN "verified" boolean DEFAULT false NOT NULL;
|
||||
7702
packages/db/migrations/meta/0115_snapshot.json
Normal file
7702
packages/db/migrations/meta/0115_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -799,6 +799,13 @@
|
||||
"when": 1764468360258,
|
||||
"tag": "0114_wise_sunfire",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 115,
|
||||
"version": "7",
|
||||
"when": 1764477997303,
|
||||
"tag": "0115_redundant_cerebro",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1335,6 +1335,7 @@ export const templateCreators = pgTable(
|
||||
name: text('name').notNull(),
|
||||
profileImageUrl: text('profile_image_url'),
|
||||
details: jsonb('details'),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
createdBy: text('created_by').references(() => user.id, { onDelete: 'set null' }),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
||||
|
||||
Reference in New Issue
Block a user