Skip to content

Commit

Permalink
feat(next)!: change "slug" search param to "path" in preview/revalida…
Browse files Browse the repository at this point in the history
…te urls

BREAKING CHANGE:
When Drupal uses the Preview Url and the Revalidate Url, the "slug" search param
has been renamed to "path".

Fixes #718
  • Loading branch information
JohnAlbin committed Mar 15, 2024
1 parent a49f641 commit 4bce663
Show file tree
Hide file tree
Showing 21 changed files with 103 additions and 103 deletions.
10 changes: 5 additions & 5 deletions examples/example-graphql/pages/api/revalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
let slug = request.query.slug as string
let path = request.query.path as string
const secret = request.query.secret as string

// Validate secret.
if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) {
return response.status(401).json({ message: "Invalid secret." })
}

// Validate slug.
if (!slug) {
return response.status(400).json({ message: "Invalid slug." })
// Validate path.
if (!path) {
return response.status(400).json({ message: "Invalid path." })
}

try {
await response.revalidate(slug)
await response.revalidate(path)

return response.json({})
} catch (error) {
Expand Down
16 changes: 8 additions & 8 deletions examples/example-marketing/pages/api/revalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
let slug = request.query.slug as string
let path = request.query.path as string
const secret = request.query.secret as string

// Validate secret.
if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) {
return response.status(401).json({ message: "Invalid secret." })
}

// Validate slug.
if (!slug) {
return response.status(400).json({ message: "Invalid slug." })
// Validate path.
if (!path) {
return response.status(400).json({ message: "Invalid path." })
}

// Fix for home slug.
if (slug === process.env.DRUPAL_FRONT_PAGE) {
slug = "/"
// Fix for homepage.
if (path === process.env.DRUPAL_FRONT_PAGE) {
path = "/"
}

try {
await response.revalidate(slug)
await response.revalidate(path)

return response.json({})
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions examples/example-router-migration/app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import type { Metadata, ResolvingMetadata } from "next"
import type { DrupalNode, JsonApiParams } from "next-drupal"

async function getNode(slug: string[]) {
const path = slug.join("/")
const path = `/${slug.join("/")}`

const params: JsonApiParams = {}

const draftData = getDraftData()

if (draftData.slug === `/${path}`) {
if (draftData.path === path) {
params.resourceVersion = draftData.resourceVersion
}

Expand Down
10 changes: 5 additions & 5 deletions examples/example-router-migration/app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ import type { NextRequest } from "next/server"

async function handler(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const slug = searchParams.get("slug")
const path = searchParams.get("path")
const secret = searchParams.get("secret")

// Validate secret.
if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) {
return new Response("Invalid secret.", { status: 401 })
}

// Validate slug.
if (!slug) {
return new Response("Invalid slug.", { status: 400 })
// Validate path.
if (!path) {
return new Response("Invalid path.", { status: 400 })
}

try {
revalidatePath(slug)
revalidatePath(path)

return new Response("Revalidated.")
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ protected static function buildUrl(NextSiteInterface $site, string $path): strin
return Url::fromUri("{$site->getBaseUrl()}/api/revalidate", [
'query' => [
'secret' => $site->getPreviewSecret(),
'slug' => $path,
'path' => $path,
],
])->toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,12 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
*/
public function generate(NextSiteInterface $next_site, EntityInterface $entity, string $resource_version = NULL): ?Url {
$query = [];
$query['slug'] = $slug = $entity->toUrl()->toString();
$query['path'] = $path = $entity->toUrl()->toString();
$query['uuid'] = $this->user->uuid();

// Create a secret based on the timestamp, slug and the user uuid.
// Create a secret based on the timestamp, path and the user uuid.
$query['timestamp'] = $timestamp = $this->time->getRequestTime();
$query['secret'] = $secret = $this->previewSecretGenerator->generate($timestamp . $slug . $resource_version . $this->user->uuid());
$query['secret'] = $secret = $this->previewSecretGenerator->generate($timestamp . $path . $resource_version . $this->user->uuid());

// Generate a JWT and store it temporarily so that we can retrieve it on
// validate.
Expand All @@ -158,10 +158,10 @@ public function generate(NextSiteInterface $next_site, EntityInterface $entity,
public function validate(Request $request) {
$body = Json::decode($request->getContent());

// Validate the slug.
// We do not check for existing slug. We let the next.js site handle this.
if (empty($body['slug'])) {
throw new InvalidPreviewUrlRequest("Field 'slug' is missing");
// Validate the path.
// We do not check for existing path. We let the next.js site handle this.
if (empty($body['path'])) {
throw new InvalidPreviewUrlRequest("Field 'path' is missing");
}

// Validate the uuid.
Expand All @@ -184,7 +184,7 @@ public function validate(Request $request) {
throw new InvalidPreviewUrlRequest("Field 'secret' is missing");
}

if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['slug'] . $body['resourceVersion'] . $body['uuid'])) {
if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['path'] . $body['resourceVersion'] . $body['uuid'])) {
throw new InvalidPreviewUrlRequest("The provided secret is invalid.");
}

Expand Down
2 changes: 1 addition & 1 deletion modules/next/src/Entity/NextSite.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ public function getRevalidateUrlForPath(string $path): ?Url {
}

$query = [
'slug' => $path,
'path' => $path,
];

if ($secret = $this->getRevalidateSecret()) {
Expand Down
18 changes: 9 additions & 9 deletions modules/next/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
*/
public function generate(NextSiteInterface $next_site, EntityInterface $entity, string $resource_version = NULL): ?Url {
$query = [];
$query['slug'] = $slug = $entity->toUrl()->toString();
$query['path'] = $path = $entity->toUrl()->toString();

// Create a secret based on the timestamp, slug, scope and resource version.
// Create a secret based on the timestamp, path, scope and resource version.
$query['timestamp'] = $timestamp = $this->time->getRequestTime();
$query['secret'] = $this->previewSecretGenerator->generate($timestamp . $slug . $resource_version);
$query['secret'] = $this->previewSecretGenerator->generate($timestamp . $path . $resource_version);

return Url::fromUri($next_site->getPreviewUrl(), [
'query' => $query,
Expand All @@ -143,10 +143,10 @@ public function generate(NextSiteInterface $next_site, EntityInterface $entity,
public function validate(Request $request) {
$body = Json::decode($request->getContent());

// Validate the slug.
// We do not check for existing slug. We let the next.js site handle this.
if (empty($body['slug'])) {
throw new InvalidPreviewUrlRequest("Field 'slug' is missing");
// Validate the path.
// We do not check for existing path. We let the next.js site handle this.
if (empty($body['path'])) {
throw new InvalidPreviewUrlRequest("Field 'path' is missing");
}

// Validate the timestamp.
Expand All @@ -164,12 +164,12 @@ public function validate(Request $request) {
throw new InvalidPreviewUrlRequest("Field 'secret' is missing");
}

if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['slug'] . $body['resourceVersion'])) {
if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['path'] . $body['resourceVersion'])) {
throw new InvalidPreviewUrlRequest("The provided secret is invalid.");
}

return [
'path' => $body['slug'],
'path' => $body['path'],
'maxAge' => (int) $this->configuration['secret_expiration'],
];
}
Expand Down
4 changes: 2 additions & 2 deletions modules/next/tests/src/Kernel/Entity/NextSiteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ public function testGetRevalidateUrlForPath() {
$this->assertNull($marketing->getRevalidateUrlForPath('/foo'));

$marketing->setRevalidateUrl('http://example.com/api/revalidate');
$this->assertSame('http://example.com/api/revalidate?slug=/foo', $marketing->getRevalidateUrlForPath('/foo')->toString());
$this->assertSame('http://example.com/api/revalidate?path=/foo', $marketing->getRevalidateUrlForPath('/foo')->toString());

$marketing->setRevalidateSecret('12345');
$this->assertSame('http://example.com/api/revalidate?slug=/foo&secret=12345', $marketing->getRevalidateUrlForPath('/foo')->toString());
$this->assertSame('http://example.com/api/revalidate?path=/foo&secret=12345', $marketing->getRevalidateUrlForPath('/foo')->toString());
}

}
18 changes: 9 additions & 9 deletions modules/next/tests/src/Kernel/Plugin/PathRevalidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function testRevalidate() {
$page->save();
$this->container->get('kernel')->terminate(Request::create('/'), new Response());

$client->request('GET', 'http://blog.com/api/revalidate?slug=/node/2')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?path=/node/2')->shouldBeCalled()->willReturn(new GuzzleResponse());
$blog_site->setRevalidateUrl('http://blog.com/api/revalidate')->save();
$page = $this->createNode();
$page->save();
Expand All @@ -104,8 +104,8 @@ public function testRevalidate() {
],
])->save();

$client->request('GET', 'http://marketing.com/api/revalidate?slug=/node/3&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?slug=/node/3')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://marketing.com/api/revalidate?path=/node/3&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?path=/node/3')->shouldBeCalled()->willReturn(new GuzzleResponse());
$page = $this->createNode();
$page->save();
$this->container->get('kernel')->terminate(Request::create('/'), new Response());
Expand All @@ -114,12 +114,12 @@ public function testRevalidate() {
'additional_paths' => "/\n/blog",
])->save();

$client->request('GET', 'http://marketing.com/api/revalidate?slug=/node/3&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://marketing.com/api/revalidate?slug=/&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://marketing.com/api/revalidate?slug=/blog&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?slug=/node/3')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?slug=/')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?slug=/blog')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://marketing.com/api/revalidate?path=/node/3&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://marketing.com/api/revalidate?path=/&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://marketing.com/api/revalidate?path=/blog&secret=12345')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?path=/node/3')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?path=/')->shouldBeCalled()->willReturn(new GuzzleResponse());
$client->request('GET', 'http://blog.com/api/revalidate?path=/blog')->shouldBeCalled()->willReturn(new GuzzleResponse());
$page = $this->createNode();
$page->save();
$this->container->get('kernel')->terminate(Request::create('/'), new Response());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ public function testGenerate() {
$this->setCurrentUser($user);
$preview_url = $this->nextSite->getPreviewUrlForEntity($page);
$query = $preview_url->getOption('query');
$this->assertNotEmpty($query['slug']);
$this->assertNotEmpty($query['path']);
$this->assertNotEmpty($query['timestamp']);
$this->assertNotEmpty($query['secret']);
$this->assertSame($query['plugin'], 'simple_oauth');

// Test the secret.
/** @var \Drupal\next\PreviewSecretGeneratorInterface $secret_generator */
$secret_generator = \Drupal::service('next.preview_secret_generator');
$this->assertSame($query['secret'], $secret_generator->generate($query['timestamp'] . $query['slug'] . $query['resourceVersion']));
$this->assertSame($query['secret'], $secret_generator->generate($query['timestamp'] . $query['path'] . $query['resourceVersion']));
}

/**
Expand Down Expand Up @@ -143,7 +143,7 @@ public function testValidateSecret() {

$this->expectExceptionMessage('The provided secret is invalid.');
$query = $preview_url->getOption('query');
$query['slug'] = '/random-slug';
$query['path'] = '/random-path';
$request = Request::create('/', 'POST', [], [], [], [], Json::encode($query));
$preview_url_generator->validate($request);

Expand All @@ -162,26 +162,26 @@ public function testValidateSecret() {
*/
public function providerValidateForInvalidBody() {
return [
[[], "Field 'slug' is missing"],
[['slug' => '/node/1'], "Field 'timestamp' is missing"],
[[], "Field 'path' is missing"],
[['path' => '/node/1'], "Field 'timestamp' is missing"],
[
[
'slug' => '/node/1',
'path' => '/node/1',
'timestamp' => strtotime('now'),
],
"Field 'secret' is missing",
],
[
[
'slug' => '/node/1',
'path' => '/node/1',
'timestamp' => strtotime('-60 seconds'),
'secret' => 'secret',
],
"The provided secret has expired.",
],
[
[
'slug' => '/node/1',
'path' => '/node/1',
'timestamp' => strtotime('60 seconds'),
'secret' => 'secret',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function testPrepare() {
$response = $this->container->get('http_kernel')->handle($request);
$this->setRawContent($response->getContent());

$preview_url = 'https://blog.com/api/preview?slug=/node/1';
$preview_url = 'https://blog.com/api/preview?path=/node/1';
$fields = $this->xpath("//iframe[contains(@src, '$preview_url')]");
$this->assertCount(1, $fields);

Expand Down
18 changes: 9 additions & 9 deletions packages/next-drupal/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,11 +1023,11 @@ export class DrupalClient {
}

async validateDraftUrl(searchParams: URLSearchParams): Promise<Response> {
const slug = searchParams.get("slug")
const path = searchParams.get("path")

this.debug(`Fetching draft url validation for ${slug}.`)
this.debug(`Fetching draft url validation for ${path}.`)

// Fetch the headless CMS to check if the provided `slug` exists
// Fetch the headless CMS to check if the provided `path` exists
let response: Response
try {
// Validate the draft url.
Expand All @@ -1047,8 +1047,8 @@ export class DrupalClient {

this.debug(
response.status !== 200
? `Could not validate slug, ${slug}`
: `Validated slug, ${slug}`
? `Could not validate path, ${path}`
: `Validated path, ${path}`
)

return response
Expand All @@ -1060,7 +1060,7 @@ export class DrupalClient {
options?: Parameters<NextApiResponse["setDraftMode"]>[0]
) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { slug, resourceVersion, plugin, secret, scope, ...draftData } =
const { path, resourceVersion, plugin, secret, scope, ...draftData } =
request.query
const useDraftMode = options?.enable

Expand Down Expand Up @@ -1104,15 +1104,15 @@ export class DrupalClient {
// Adds preview data for use in app router pages.
cookies.push(
`${DRAFT_DATA_COOKIE_NAME}=${encodeURIComponent(
JSON.stringify({ slug, resourceVersion, ...draftData })
JSON.stringify({ path, resourceVersion, ...draftData })
)}; Path=/; HttpOnly; SameSite=None; Secure`
)
}
response.setHeader("Set-Cookie", cookies)

// We can safely redirect to the slug since this has been validated on the
// We can safely redirect to the path since this has been validated on the
// server.
response.writeHead(307, { Location: slug })
response.writeHead(307, { Location: path })

this.debug(`${useDraftMode ? "Draft" : "Preview"} mode enabled.`)

Expand Down
Loading

0 comments on commit 4bce663

Please sign in to comment.