🧑💻 (folders) Add folder trpc endpoints (#1218)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced folder management capabilities including creation, deletion, update, listing, and retrieval within workspaces. - Added telemetry tracking for client events, Typebot publish events, and analytics page views. - Enhanced settings to track client events under specific conditions. - Implemented server-side logic for analytics tracking with PostHog integration. - Added API documentation for folder operations (create, delete, get, list, update). - **Refactor** - Updated `onConfirm` function's return type in `ConfirmModal`. - Simplified folder creation process in tests. - Refactored logic for handling file upload blocks and parsing publish events in Typebot publishing. - Migrated handler functions to TRPC endpoints for folder operations. - **Documentation** - Introduced documentation for new folder and telemetry functionalities. - **Chores** - Added new schemas for folders and telemetry events, including event tracking and folder structure. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
6
apps/builder/src/features/telemetry/api/router.ts
Normal file
6
apps/builder/src/features/telemetry/api/router.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { router } from '@/helpers/server/trpc'
|
||||
import { trackClientEvents } from './trackClientEvents'
|
||||
|
||||
export const telemetryRouter = router({
|
||||
trackClientEvents,
|
||||
})
|
||||
95
apps/builder/src/features/telemetry/api/trackClientEvents.ts
Normal file
95
apps/builder/src/features/telemetry/api/trackClientEvents.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { z } from 'zod'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace'
|
||||
import { WorkspaceRole } from '@typebot.io/prisma'
|
||||
import { isWriteTypebotForbidden } from '@/features/typebot/helpers/isWriteTypebotForbidden'
|
||||
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||
import { clientSideCreateEventSchema } from '@typebot.io/schemas'
|
||||
|
||||
export const trackClientEvents = authenticatedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
events: z.array(clientSideCreateEventSchema),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
message: z.literal('success'),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input: { events }, ctx: { user } }) => {
|
||||
const workspaces = await prisma.workspace.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: events
|
||||
.filter((event) => 'workspaceId' in event)
|
||||
.map((event) => (event as { workspaceId: string }).workspaceId),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
members: true,
|
||||
},
|
||||
})
|
||||
const typebots = await prisma.typebot.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: events
|
||||
.filter((event) => 'typebotId' in event)
|
||||
.map((event) => (event as { typebotId: string }).typebotId),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
workspaceId: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isSuspended: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
role: true,
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
for (const event of events) {
|
||||
if ('workspaceId' in event) {
|
||||
const workspace = workspaces.find((w) => w.id === event.workspaceId)
|
||||
const userRole = getUserRoleInWorkspace(user.id, workspace?.members)
|
||||
if (
|
||||
userRole === undefined ||
|
||||
userRole === WorkspaceRole.GUEST ||
|
||||
!workspace
|
||||
)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Workspace not found',
|
||||
})
|
||||
}
|
||||
|
||||
if ('typebotId' in event) {
|
||||
const typebot = typebots.find((t) => t.id === event.typebotId)
|
||||
if (!typebot || (await isWriteTypebotForbidden(typebot, user)))
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Typebot not found',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await trackEvents(events.map((e) => ({ ...e, userId: user.id })))
|
||||
|
||||
return { message: 'success' }
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { env } from '@typebot.io/env'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { parseGroups, Typebot } from '@typebot.io/schemas'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
|
||||
type Props = {
|
||||
existingTypebot: Pick<Typebot, 'id' | 'workspaceId'>
|
||||
userId: string
|
||||
hasFileUploadBlocks: boolean
|
||||
}
|
||||
|
||||
export const parseTypebotPublishEvents = async ({
|
||||
existingTypebot,
|
||||
userId,
|
||||
hasFileUploadBlocks,
|
||||
}: Props) => {
|
||||
if (!env.NEXT_PUBLIC_POSTHOG_KEY) return []
|
||||
const events = []
|
||||
const existingPublishedTypebot = await prisma.publicTypebot.findFirst({
|
||||
where: {
|
||||
typebotId: existingTypebot.id,
|
||||
},
|
||||
select: {
|
||||
version: true,
|
||||
groups: true,
|
||||
settings: true,
|
||||
},
|
||||
})
|
||||
|
||||
const isPublishingFileUploadBlockForTheFirstTime =
|
||||
hasFileUploadBlocks &&
|
||||
(!existingPublishedTypebot ||
|
||||
!parseGroups(existingPublishedTypebot.groups, {
|
||||
typebotVersion: existingPublishedTypebot.version,
|
||||
}).some((group) =>
|
||||
group.blocks.some((block) => block.type === InputBlockType.FILE)
|
||||
))
|
||||
|
||||
if (isPublishingFileUploadBlockForTheFirstTime)
|
||||
events.push({
|
||||
name: 'File upload block published',
|
||||
workspaceId: existingTypebot.workspaceId,
|
||||
typebotId: existingTypebot.id,
|
||||
userId,
|
||||
} as const)
|
||||
|
||||
return events
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { getAuthOptions } from '@/pages/api/auth/[...nextauth]'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||
import { User } from '@typebot.io/schemas'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { getServerSession } from 'next-auth'
|
||||
|
||||
export const trackAnalyticsPageView = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const typebotId = context.params?.typebotId as string | undefined
|
||||
if (!typebotId) return
|
||||
const typebot = await prisma.typebot.findUnique({
|
||||
where: { id: typebotId },
|
||||
select: { workspaceId: true },
|
||||
})
|
||||
if (!typebot) return
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
getAuthOptions({})
|
||||
)
|
||||
await trackEvents([
|
||||
{
|
||||
name: 'Analytics visited',
|
||||
typebotId,
|
||||
userId: (session?.user as User).id,
|
||||
workspaceId: typebot.workspaceId,
|
||||
},
|
||||
])
|
||||
}
|
||||
Reference in New Issue
Block a user