🐛 (editor) Fix lost changes when typebot takes a long time to update
Closes #1231
This commit is contained in:
@@ -208,7 +208,11 @@ export const TypebotProvider = ({
|
|||||||
const saveTypebot = useCallback(
|
const saveTypebot = useCallback(
|
||||||
async (updates?: Partial<TypebotV6>) => {
|
async (updates?: Partial<TypebotV6>) => {
|
||||||
if (!localTypebot || !typebot || isReadOnly) return
|
if (!localTypebot || !typebot || isReadOnly) return
|
||||||
const typebotToSave = { ...localTypebot, ...updates }
|
const typebotToSave = {
|
||||||
|
...localTypebot,
|
||||||
|
...updates,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
}
|
||||||
if (dequal(omit(typebot, 'updatedAt'), omit(typebotToSave, 'updatedAt')))
|
if (dequal(omit(typebot, 'updatedAt'), omit(typebotToSave, 'updatedAt')))
|
||||||
return
|
return
|
||||||
setLocalTypebot({ ...typebotToSave })
|
setLocalTypebot({ ...typebotToSave })
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const typebotUpdateSchemaPick = {
|
|||||||
whatsAppCredentialsId: true,
|
whatsAppCredentialsId: true,
|
||||||
riskLevel: true,
|
riskLevel: true,
|
||||||
events: true,
|
events: true,
|
||||||
|
updatedAt: true,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const updateTypebot = authenticatedProcedure
|
export const updateTypebot = authenticatedProcedure
|
||||||
@@ -67,12 +68,6 @@ export const updateTypebot = authenticatedProcedure
|
|||||||
title: 'Typebot V5',
|
title: 'Typebot V5',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
updatedAt: z
|
|
||||||
.date()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
'Used for checking if there is a newer version of the typebot in the database'
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
.output(
|
||||||
@@ -80,143 +75,143 @@ export const updateTypebot = authenticatedProcedure
|
|||||||
typebot: typebotV6Schema,
|
typebot: typebotV6Schema,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(
|
.mutation(async ({ input: { typebotId, typebot }, ctx: { user } }) => {
|
||||||
async ({ input: { typebotId, typebot, updatedAt }, ctx: { user } }) => {
|
const existingTypebot = await prisma.typebot.findFirst({
|
||||||
const existingTypebot = await prisma.typebot.findFirst({
|
where: {
|
||||||
where: {
|
id: typebotId,
|
||||||
id: typebotId,
|
},
|
||||||
},
|
select: {
|
||||||
select: {
|
version: true,
|
||||||
version: true,
|
id: true,
|
||||||
id: true,
|
customDomain: true,
|
||||||
customDomain: true,
|
publicId: true,
|
||||||
publicId: true,
|
collaborators: {
|
||||||
collaborators: {
|
select: {
|
||||||
select: {
|
userId: true,
|
||||||
userId: true,
|
type: true,
|
||||||
type: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
workspace: {
|
},
|
||||||
select: {
|
workspace: {
|
||||||
id: true,
|
select: {
|
||||||
plan: true,
|
id: true,
|
||||||
isSuspended: true,
|
plan: true,
|
||||||
isPastDue: true,
|
isSuspended: true,
|
||||||
members: {
|
isPastDue: true,
|
||||||
select: {
|
members: {
|
||||||
userId: true,
|
select: {
|
||||||
role: true,
|
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 (
|
if (
|
||||||
!existingTypebot?.id ||
|
typebot.updatedAt &&
|
||||||
(await isWriteTypebotForbidden(existingTypebot, user))
|
new Date(existingTypebot?.updatedAt).getTime() >
|
||||||
)
|
typebot.updatedAt.getTime()
|
||||||
throw new TRPCError({
|
)
|
||||||
code: 'NOT_FOUND',
|
throw new TRPCError({
|
||||||
message: 'Typebot not found',
|
code: 'CONFLICT',
|
||||||
})
|
message: 'Found newer version of the typebot in database',
|
||||||
|
})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
updatedAt &&
|
typebot.customDomain &&
|
||||||
updatedAt.getTime() > new Date(existingTypebot?.updatedAt).getTime()
|
existingTypebot.customDomain !== typebot.customDomain &&
|
||||||
)
|
(await isCustomDomainNotAvailable(typebot.customDomain))
|
||||||
throw new TRPCError({
|
)
|
||||||
code: 'CONFLICT',
|
throw new TRPCError({
|
||||||
message: 'Found newer version of the typebot in database',
|
code: 'BAD_REQUEST',
|
||||||
})
|
message: 'Custom domain not available',
|
||||||
|
})
|
||||||
|
|
||||||
|
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 (
|
if (
|
||||||
typebot.customDomain &&
|
existingTypebot.publicId !== typebot.publicId &&
|
||||||
existingTypebot.customDomain !== typebot.customDomain &&
|
(await isPublicIdNotAvailable(typebot.publicId))
|
||||||
(await isCustomDomainNotAvailable(typebot.customDomain))
|
|
||||||
)
|
)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'Custom domain not available',
|
message: 'Public id not available',
|
||||||
})
|
})
|
||||||
|
|
||||||
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)
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: 'BAD_REQUEST',
|
|
||||||
message: 'WhatsApp can be enabled only on a Pro workspaces',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const migratedTypebot = await migrateTypebot(
|
|
||||||
typebotSchema.parse(newTypebot)
|
|
||||||
)
|
|
||||||
|
|
||||||
return { typebot: migratedTypebot }
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
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 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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const migratedTypebot = await migrateTypebot(
|
||||||
|
typebotSchema.parse(newTypebot)
|
||||||
|
)
|
||||||
|
|
||||||
|
return { typebot: migratedTypebot }
|
||||||
|
})
|
||||||
|
|
||||||
const isPublicIdValid = (str: string) =>
|
const isPublicIdValid = (str: string) =>
|
||||||
/^([a-z0-9]+-[a-z0-9]*)*$/.test(str) || /^[a-z0-9]*$/.test(str)
|
/^([a-z0-9]+-[a-z0-9]*)*$/.test(str) || /^[a-z0-9]*$/.test(str)
|
||||||
|
|||||||
@@ -4401,6 +4401,9 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Typebot V6"
|
"title": "Typebot V6"
|
||||||
@@ -7003,15 +7006,14 @@
|
|||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"type": "array"
|
"type": "array"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Typebot V5"
|
"title": "Typebot V5"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Used for checking if there is a newer version of the typebot in the database"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
Reference in New Issue
Block a user