diff --git a/playground/components/global/DrupalForm.vue b/playground/components/global/DrupalForm.vue new file mode 100644 index 00000000..94af36c8 --- /dev/null +++ b/playground/components/global/DrupalForm.vue @@ -0,0 +1,14 @@ + + + diff --git a/playground/middleware/redirect.global.ts b/playground/middleware/redirect.global.ts deleted file mode 100644 index fe904174..00000000 --- a/playground/middleware/redirect.global.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default defineNuxtRouteMiddleware((to, from) => { - const config = useRuntimeConfig().public.drupalCe - - switch (true) { - case /^\/((en|de)\/)?user.*$/.test(to.path): - case /^\/((en|de)\/)?admin.*$/.test(to.path): - case /^\/((en|de)\/)?(node\/(add|[^/]+\/(edit|delete|revisions|translations))|entity_clone\/node\/[^/]+)/.test( - to.path, - ): - case /^\/((en|de)\/)?node\/[^/]+\/layout$/.test(to.path): - return navigateTo(`${config.drupalBaseUrl}${to.fullPath}`, { - external: true, - redirectCode: 301, - }) - } -}) diff --git a/src/module.ts b/src/module.ts index 4eba8e84..9218b748 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,5 +1,5 @@ import { fileURLToPath } from 'url' -import { defineNuxtModule, addPlugin, addServerPlugin, createResolver, addImportsDir, addServerHandler } from '@nuxt/kit' +import { defineNuxtModule, addServerPlugin, createResolver, addImportsDir, addServerHandler } from '@nuxt/kit' import { defu } from 'defu' import type { NuxtOptionsWithDrupalCe } from './types' @@ -59,11 +59,13 @@ export default defineNuxtModule({ const { resolve } = createResolver(import.meta.url) const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) nuxt.options.build.transpile.push(runtimeDir) - addPlugin(resolve(runtimeDir, 'plugin')) if (options.serverLogLevel) { addServerPlugin(resolve(runtimeDir, 'server/plugins/errorLogger')) } addImportsDir(resolve(runtimeDir, 'composables/useDrupalCe')) + addServerHandler({ + handler: resolve(runtimeDir, 'server/middleware/drupalFormHandler'), + }) const publicOptions = { ...options } // Server options are not needed in the client bundle. diff --git a/src/runtime/composables/useDrupalCe/index.ts b/src/runtime/composables/useDrupalCe/index.ts index 10636f38..ca073d74 100644 --- a/src/runtime/composables/useDrupalCe/index.ts +++ b/src/runtime/composables/useDrupalCe/index.ts @@ -4,7 +4,7 @@ import { appendResponseHeader } from 'h3' import type { UseFetchOptions } from '#app' import type { $Fetch, NitroFetchRequest } from 'nitropack' import { getDrupalBaseUrl, getMenuBaseUrl } from './server' -import { useRuntimeConfig, useState, useFetch, navigateTo, createError, h, resolveComponent, setResponseStatus, useNuxtApp, useRequestHeaders, ref, watch } from '#imports' +import { useRuntimeConfig, useState, useFetch, navigateTo, createError, h, resolveComponent, setResponseStatus, useNuxtApp, useRequestHeaders, ref, watch, useRequestEvent } from '#imports' export const useDrupalCe = () => { const config = useRuntimeConfig().public.drupalCe @@ -116,9 +116,36 @@ export const useDrupalCe = () => { page_layout: 'default', title: '', })) + const serverResponse = useState('server-response', () => null) useFetchOptions.key = `page-${path}` + const page = ref(null) + const pageError = ref(null) - const { data: page, error } = await useCeApi(path, useFetchOptions, true) + if (import.meta.server) { + serverResponse.value = useRequestEvent(nuxtApp).context.drupalCeCustomPageResponse + } + + // Check if the page data is already provided, e.g. by a form response. + if (serverResponse.value) { + if (serverResponse.value._data) { + page.value = serverResponse.value._data + passThroughHeaders(nuxtApp, serverResponse.value.headers) + } else if (serverResponse.value.error) { + pageError.value = serverResponse.value.error + } + // Clear the server response state after it was sent to the client. + if (import.meta.client) { + serverResponse.value = null + } + } else { + const { data, error } = await useCeApi(path, useFetchOptions, true) + page.value = data.value + pageError.value = error.value + } + + if (page.value?.messages) { + pushMessagesToState(page.value.messages) + } if (page?.value?.redirect) { await callWithNuxt(nuxtApp, navigateTo, [ @@ -128,13 +155,11 @@ export const useDrupalCe = () => { return pageState } - if (error.value) { - overrideErrorHandler ? overrideErrorHandler(error) : pageErrorHandler(error, { config, nuxtApp }) - page.value = error.value?.data + if (pageError.value) { + overrideErrorHandler ? overrideErrorHandler(pageError) : pageErrorHandler(pageError, { config, nuxtApp }) + page.value = pageError.value?.data } - page.value?.messages && pushMessagesToState(page.value.messages) - pageState.value = page return page } diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts deleted file mode 100644 index 83dcea71..00000000 --- a/src/runtime/plugin.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { defineNuxtPlugin } from '#app' - -export default defineNuxtPlugin((nuxtApp) => { -}) diff --git a/src/runtime/server/middleware/drupalFormHandler.ts b/src/runtime/server/middleware/drupalFormHandler.ts new file mode 100644 index 00000000..86445ee1 --- /dev/null +++ b/src/runtime/server/middleware/drupalFormHandler.ts @@ -0,0 +1,40 @@ +import { defineEventHandler, readFormData } from 'h3' +import { getDrupalBaseUrl } from '../../composables/useDrupalCe/server' +import { useRuntimeConfig } from '#imports' + +export default defineEventHandler(async (event) => { + const { ceApiEndpoint } = useRuntimeConfig().public.drupalCe + + if (event.node.req.method === 'POST') { + const formData = await readFormData(event) + + if (formData) { + const targetUrl = event.node.req.url + const response = await $fetch.raw(getDrupalBaseUrl() + ceApiEndpoint + targetUrl, { + method: 'POST', + body: formData, + }).catch((error) => { + event.context.drupalCeCustomPageResponse = { + error: { + data: error, + statusCode: error.statusCode || 400, + message: error.message || 'Error when POSTing form data (drupalFormHandler).', + }, + } + }) + + if (response) { + event.context.drupalCeCustomPageResponse = { + _data: response._data, + headers: Object.fromEntries(response.headers.entries()), + } + } + } else { + throw createError({ + statusCode: 400, + statusMessage: 'Bad Request', + message: 'POST requests without form data are not supported (drupalFormHandler).', + }) + } + } +})