diff --git a/backend/src/services/publish.js b/backend/src/services/publish.js index 0c2a094..a144598 100644 --- a/backend/src/services/publish.js +++ b/backend/src/services/publish.js @@ -151,8 +151,8 @@ module.exports = class PublishService { } /** - * Save dev content to stage environment - * This is the first step in the publishing workflow: dev -> stage -> production + * Save dev content to stage environment (non-blocking) + * Returns immediately, processing continues in background. */ static async saveToStage(projectId, currentUser) { if (!projectId) { @@ -173,47 +173,43 @@ module.exports = class PublishService { updatedById: currentUser?.id || null, }); - try { - const summary = await this.withProjectPublishLock( - projectId, - async (transaction) => { - await publishEvent.update( - { - started_at: new Date(), - status: EVENT_STATUS.RUNNING, - error_message: null, - updatedById: currentUser?.id || null, - }, - { transaction }, - ); + // Process in background + setImmediate(async () => { + try { + const summary = await this.withProjectPublishLock( + projectId, + async (transaction) => { + await publishEvent.update( + { + started_at: new Date(), + status: EVENT_STATUS.RUNNING, + updatedById: currentUser?.id || null, + }, + { transaction }, + ); + return this.copyDevToStage(projectId, currentUser, transaction); + }, + ); - return this.copyDevToStage(projectId, currentUser, transaction); - }, - ); + await publishEvent.update({ + status: EVENT_STATUS.SUCCESS, + finished_at: new Date(), + pages_copied: summary.pages_copied, + audios_copied: summary.audios_copied, + updatedById: currentUser?.id || null, + }); + } catch (error) { + await publishEvent.update({ + status: EVENT_STATUS.FAILED, + finished_at: new Date(), + error_message: error.message, + updatedById: currentUser?.id || null, + }); + console.error('[SaveToStage] Background error:', error); + } + }); - await publishEvent.update({ - status: EVENT_STATUS.SUCCESS, - finished_at: new Date(), - pages_copied: summary.pages_copied, - audios_copied: summary.audios_copied, - error_message: null, - updatedById: currentUser?.id || null, - }); - - return { - success: true, - publishEventId: publishEvent.id, - summary, - }; - } catch (error) { - await publishEvent.update({ - status: EVENT_STATUS.FAILED, - finished_at: new Date(), - error_message: error.message, - updatedById: currentUser?.id || null, - }); - throw error; - } + return { success: true, publishEventId: publishEvent.id }; } /** diff --git a/frontend/src/hooks/useConstructorPageActions.ts b/frontend/src/hooks/useConstructorPageActions.ts index 6f159c8..8d359d0 100644 --- a/frontend/src/hooks/useConstructorPageActions.ts +++ b/frontend/src/hooks/useConstructorPageActions.ts @@ -232,29 +232,12 @@ export function useConstructorPageActions({ return; } - // First save current changes await saveConstructor(); try { setIsSavingToStage(true); - - const response = await axios.post<{ - success: boolean; - summary?: { pages_copied: number; audios_copied: number }; - }>('/publish/save-to-stage', { projectId }); - - const pagesCopied = response.data?.summary?.pages_copied ?? 0; - const audiosCopied = response.data?.summary?.audios_copied ?? 0; - - if (pagesCopied === 0) { - onError?.( - 'No pages were found to copy. Make sure you have dev pages saved before publishing to stage.', - ); - } else { - onSuccess?.( - `Successfully saved to stage: ${pagesCopied} page${pagesCopied !== 1 ? 's' : ''} and ${audiosCopied} audio track${audiosCopied !== 1 ? 's' : ''} copied.`, - ); - } + await axios.post('/publish/save-to-stage', { projectId }); + onSuccess?.('Saved to stage.'); } catch (error: unknown) { const axiosError = error as { response?: { data?: { message?: string } };