🐛 (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(
|
||||
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 })
|
||||
|
@ -37,6 +37,7 @@ const typebotUpdateSchemaPick = {
|
||||
whatsAppCredentialsId: true,
|
||||
riskLevel: true,
|
||||
events: true,
|
||||
updatedAt: true,
|
||||
} as const
|
||||
|
||||
export const updateTypebot = authenticatedProcedure
|
||||
@ -67,12 +68,6 @@ 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(
|
||||
@ -80,143 +75,143 @@ export const updateTypebot = authenticatedProcedure
|
||||
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.publicId) {
|
||||
if (isCloudProdInstance() && typebot.publicId.length < 4)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Public id should be at least 4 characters long',
|
||||
})
|
||||
if (
|
||||
typebot.customDomain &&
|
||||
existingTypebot.customDomain !== typebot.customDomain &&
|
||||
(await isCustomDomainNotAvailable(typebot.customDomain))
|
||||
existingTypebot.publicId !== typebot.publicId &&
|
||||
(await isPublicIdNotAvailable(typebot.publicId))
|
||||
)
|
||||
throw new TRPCError({
|
||||
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) =>
|
||||
/^([a-z0-9]+-[a-z0-9]*)*$/.test(str) || /^[a-z0-9]*$/.test(str)
|
||||
|
@ -4401,6 +4401,9 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Typebot V6"
|
||||
@ -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": [
|
||||
|
Reference in New Issue
Block a user