Skip to content

Commit

Permalink
馃悰 (editor) Fix lost changes when typebot takes a long time to update
Browse files Browse the repository at this point in the history
Closes #1231
  • Loading branch information
baptisteArno committed Feb 8, 2024
1 parent 5d38b44 commit c648947
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ export const TypebotProvider = ({
const saveTypebot = useCallback(
async (updates?: Partial<TypebotV6>) => {
if (!localTypebot || !typebot || isReadOnly) return
const typebotToSave = { ...localTypebot, ...updates }
const typebotToSave = {
...localTypebot,
...updates,
updatedAt: new Date(),
}
if (dequal(omit(typebot, 'updatedAt'), omit(typebotToSave, 'updatedAt')))
return
setLocalTypebot({ ...typebotToSave })
Expand Down
241 changes: 118 additions & 123 deletions apps/builder/src/features/typebot/api/updateTypebot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const typebotUpdateSchemaPick = {
whatsAppCredentialsId: true,
riskLevel: true,
events: true,
updatedAt: true,
} as const

export const updateTypebot = authenticatedProcedure
Expand Down Expand Up @@ -67,156 +68,150 @@ export const updateTypebot = authenticatedProcedure
title: 'Typebot V5',
}),
]),
updatedAt: z
.date()
.optional()
.describe(
'Used for checking if there is a newer version of the typebot in the database'
),
})
)
.output(
z.object({
typebot: typebotV6Schema,
})
)
.mutation(
async ({ input: { typebotId, typebot, updatedAt }, ctx: { user } }) => {
const existingTypebot = await prisma.typebot.findFirst({
where: {
id: typebotId,
},
select: {
version: true,
id: true,
customDomain: true,
publicId: true,
collaborators: {
select: {
userId: true,
type: true,
},
.mutation(async ({ input: { typebotId, typebot }, ctx: { user } }) => {
const existingTypebot = await prisma.typebot.findFirst({
where: {
id: typebotId,
},
select: {
version: true,
id: true,
customDomain: true,
publicId: true,
collaborators: {
select: {
userId: true,
type: true,
},
workspace: {
select: {
id: true,
plan: true,
isSuspended: true,
isPastDue: true,
members: {
select: {
userId: true,
role: true,
},
},
workspace: {
select: {
id: true,
plan: true,
isSuspended: true,
isPastDue: true,
members: {
select: {
userId: true,
role: true,
},
},
},
updatedAt: true,
},
updatedAt: true,
},
})

if (
!existingTypebot?.id ||
(await isWriteTypebotForbidden(existingTypebot, user))
)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Typebot not found',
})

if (
!existingTypebot?.id ||
(await isWriteTypebotForbidden(existingTypebot, user))
)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Typebot not found',
})
if (
typebot.updatedAt &&
new Date(existingTypebot?.updatedAt).getTime() >
typebot.updatedAt.getTime()
)
throw new TRPCError({
code: 'CONFLICT',
message: 'Found newer version of the typebot in database',
})

if (
updatedAt &&
updatedAt.getTime() > new Date(existingTypebot?.updatedAt).getTime()
)
throw new TRPCError({
code: 'CONFLICT',
message: 'Found newer version of the typebot in database',
})
if (
typebot.customDomain &&
existingTypebot.customDomain !== typebot.customDomain &&
(await isCustomDomainNotAvailable(typebot.customDomain))
)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Custom domain not available',
})

if (
typebot.customDomain &&
existingTypebot.customDomain !== typebot.customDomain &&
(await isCustomDomainNotAvailable(typebot.customDomain))
)
if (typebot.publicId) {
if (isCloudProdInstance() && typebot.publicId.length < 4)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Custom domain not available',
message: 'Public id should be at least 4 characters long',
})

if (typebot.publicId) {
if (isCloudProdInstance() && typebot.publicId.length < 4)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Public id should be at least 4 characters long',
})
if (
existingTypebot.publicId !== typebot.publicId &&
(await isPublicIdNotAvailable(typebot.publicId))
)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Public id not available',
})
}

if (
typebot.settings?.whatsApp?.isEnabled &&
!hasProPerks(existingTypebot.workspace)
) {
existingTypebot.publicId !== typebot.publicId &&
(await isPublicIdNotAvailable(typebot.publicId))
)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'WhatsApp can be enabled only on a Pro workspaces',
message: 'Public id not available',
})
}
}

const newTypebot = await prisma.typebot.update({
where: {
id: existingTypebot.id,
},
data: {
version: typebot.version ?? undefined,
name: typebot.name,
icon: typebot.icon,
selectedThemeTemplateId: typebot.selectedThemeTemplateId,
events: typebot.events ?? undefined,
groups: typebot.groups
? await sanitizeGroups(existingTypebot.workspace.id)(typebot.groups)
: undefined,
theme: typebot.theme ? typebot.theme : undefined,
settings: typebot.settings
? sanitizeSettings(
typebot.settings,
existingTypebot.workspace.plan,
'update'
)
: undefined,
folderId: typebot.folderId,
variables: typebot.variables,
edges: typebot.edges,
resultsTablePreferences:
typebot.resultsTablePreferences === null
? Prisma.DbNull
: typebot.resultsTablePreferences,
publicId:
typebot.publicId === null
? null
: typebot.publicId && isPublicIdValid(typebot.publicId)
? typebot.publicId
: undefined,
customDomain:
typebot.customDomain === null ? null : typebot.customDomain,
isClosed: typebot.isClosed,
whatsAppCredentialsId: typebot.whatsAppCredentialsId ?? undefined,
},
if (
typebot.settings?.whatsApp?.isEnabled &&
!hasProPerks(existingTypebot.workspace)
) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'WhatsApp can be enabled only on a Pro workspaces',
})
}

const migratedTypebot = await migrateTypebot(
typebotSchema.parse(newTypebot)
)
const newTypebot = await prisma.typebot.update({
where: {
id: existingTypebot.id,
},
data: {
version: typebot.version ?? undefined,
name: typebot.name,
icon: typebot.icon,
selectedThemeTemplateId: typebot.selectedThemeTemplateId,
events: typebot.events ?? undefined,
groups: typebot.groups
? await sanitizeGroups(existingTypebot.workspace.id)(typebot.groups)
: undefined,
theme: typebot.theme ? typebot.theme : undefined,
settings: typebot.settings
? sanitizeSettings(
typebot.settings,
existingTypebot.workspace.plan,
'update'
)
: undefined,
folderId: typebot.folderId,
variables: typebot.variables,
edges: typebot.edges,
resultsTablePreferences:
typebot.resultsTablePreferences === null
? Prisma.DbNull
: typebot.resultsTablePreferences,
publicId:
typebot.publicId === null
? null
: typebot.publicId && isPublicIdValid(typebot.publicId)
? typebot.publicId
: undefined,
customDomain:
typebot.customDomain === null ? null : typebot.customDomain,
isClosed: typebot.isClosed,
whatsAppCredentialsId: typebot.whatsAppCredentialsId ?? undefined,
updatedAt: typebot.updatedAt,
},
})

return { typebot: migratedTypebot }
}
)
const migratedTypebot = await migrateTypebot(
typebotSchema.parse(newTypebot)
)

return { typebot: migratedTypebot }
})

const isPublicIdValid = (str: string) =>
/^([a-z0-9]+-[a-z0-9]*)*$/.test(str) || /^[a-z0-9]*$/.test(str)
10 changes: 6 additions & 4 deletions apps/docs/openapi/builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -4401,6 +4401,9 @@
}
]
}
},
"updatedAt": {
"type": "string"
}
},
"title": "Typebot V6"
Expand Down Expand Up @@ -7003,15 +7006,14 @@
},
"events": {
"type": "array"
},
"updatedAt": {
"type": "string"
}
},
"title": "Typebot V5"
}
]
},
"updatedAt": {
"type": "string",
"description": "Used for checking if there is a newer version of the typebot in the database"
}
},
"required": [
Expand Down

0 comments on commit c648947

Please sign in to comment.