2
0

🐛 (editor) Fix lost changes when typebot takes a long time to update

Closes #1231
This commit is contained in:
Baptiste Arnaud
2024-02-08 11:03:08 +01:00
parent 5d38b4451a
commit c6489476f9
3 changed files with 134 additions and 133 deletions

View File

@@ -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 })

View File

@@ -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)

View File

@@ -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": [