From dcbc40c159a8217d96720983c9f0d071fc04090c Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Fri, 29 Nov 2024 17:11:42 +0100 Subject: [PATCH 1/9] Creating cache revalidation endpoint withj tests --- __tests__/revalidating-cache.test.ts | 125 +++++++++++++++++++++++++++ app/cache/revalidate/route.ts | 45 ++++++++++ 2 files changed, 170 insertions(+) create mode 100644 __tests__/revalidating-cache.test.ts create mode 100644 app/cache/revalidate/route.ts diff --git a/__tests__/revalidating-cache.test.ts b/__tests__/revalidating-cache.test.ts new file mode 100644 index 00000000..9996cabb --- /dev/null +++ b/__tests__/revalidating-cache.test.ts @@ -0,0 +1,125 @@ +// @vitest-environment node +import { testApiHandler } from "next-test-api-route-handler" +import { revalidatePath, revalidateTag } from "next/cache" +import { describe, test } from "vitest" + +// @ts-ignore +import * as revalidateCacheHandler from "@/app/cache/revalidate/route" + +vi.mock("next/cache", () => ({ + revalidatePath: vi.fn(), + revalidateTag: vi.fn(), +})) + +beforeEach(() => { + // Mock the revalidatePath and revalidateTag functions + // @ts-ignore + revalidatePath.mockReturnValue(true) + // @ts-ignore + revalidateTag.mockReturnValue(true) +}) + +describe("Revalidate cache test combination of payloads", () => { + test("That the cache revalidation endpoint returns 422 upon wrong input", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "unknown-type" }), + }) + expect(res.status).toBe(422) + }, + }) + + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "tag", theTags: ["tag1", "tag2"] }), + }) + expect(res.status).toBe(422) + }, + }) + }) + + test("That the cache revalidation endpoint returns 200 upon successful tag invalidation", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "tag", tags: ["tag1", "tag2"] }), + }) + expect(res.status).toBe(200) + }, + }) + }) + + test("That the cache revalidation endpoint returns 200 upon successful path invalidation", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "path", paths: ["/some/path", "/some/path"] }), + }) + expect(res.status).toBe(200) + }, + }) + }) +}) + +describe("Revalidate cache test different path and tag formats", async () => { + test("That the cache revalidation endpoint returns 422 if a wrongly formatted path is given", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "path", paths: ["some/path", "/some/path"] }), + }) + expect(res.status).toBe(422) + }, + }) + }) + test("That the cache revalidation endpoint returns 422 if a wrongly formatted path is given", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "path", paths: ["/some/*path", "/some/path"] }), + }) + expect(res.status).toBe(422) + }, + }) + }) + test("That the cache revalidation endpoint returns 422 if a wrongly formatted tag is given", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "tag", paths: ["!wrong-tag", "another tag"] }), + }) + expect(res.status).toBe(422) + }, + }) + }) +}) diff --git a/app/cache/revalidate/route.ts b/app/cache/revalidate/route.ts new file mode 100644 index 00000000..7cbe347f --- /dev/null +++ b/app/cache/revalidate/route.ts @@ -0,0 +1,45 @@ +import { revalidatePath, revalidateTag } from "next/cache" +import { NextRequest, NextResponse } from "next/server" +import { z } from "zod" + +// const paramsSchema = z.object({ +// type: z.union([z.literal("tag"), z.literal("path")]), +// }) + +const paramsSchema = z.union([ + z.object({ + type: z.literal("tag"), + tags: z.array(z.string().regex(/^[a-zA-Z0-9-]+$/)), + }), + z.object({ + type: z.literal("path"), + paths: z.array(z.string().regex(/^\/[a-zA-Z0-9-\/]+$/)), + }), +]) + +export async function POST(request: NextRequest) { + const body = await request.json() + try { + const params = paramsSchema.parse(body) + + switch (params.type) { + case "tag": + params.tags.forEach(tag => { + revalidateTag(tag) + }) + break + case "path": + params.paths.forEach(path => { + revalidatePath(path) + }) + break + } + + return NextResponse.json({ params }) + } catch (e) { + // console.log("ze error", e) + return NextResponse.json({ error: "Wrong input" }, { status: 422 }) + } +} + +export const dynamic = "force-dynamic" From 84ed7bc1db6d4f8824f5a154738ecf16c5d767a4 Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Sat, 30 Nov 2024 13:18:12 +0100 Subject: [PATCH 2/9] Add bearer token protection of revalidate endpoint --- .env.test | 4 +- __tests__/revalidating-cache.test.ts | 70 +++++++++++++++++++++++++--- app/cache/revalidate/route.ts | 13 ++++-- lib/config/resolvers/cache.ts | 9 ++++ lib/config/resolvers/index.ts | 2 + 5 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 lib/config/resolvers/cache.ts diff --git a/.env.test b/.env.test index 6b8c2ad3..990ca183 100644 --- a/.env.test +++ b/.env.test @@ -8,6 +8,8 @@ UNILOGIN_API_URL=https://et-broker.unilogin.dk UNILOGIN_CLIENT_ID=https://stg.ereolengo.itkdev.dk/ UNILOGIN_CLIENT_SECRET=XXX UNILOGIN_SESSION_SECRET=XXX +UNILOGIN_WELLKNOWN_URL=https://hi-i-am-well-known-url.com NEXT_PUBLIC_APP_URL=https://hellboy.the-movie.com -UNILOGIN_WELLKNOWN_URL=https://hi-i-am-well-known-url.com + +REVALIDATE_CACHE_SECRET=i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc= diff --git a/__tests__/revalidating-cache.test.ts b/__tests__/revalidating-cache.test.ts index 9996cabb..f05c4081 100644 --- a/__tests__/revalidating-cache.test.ts +++ b/__tests__/revalidating-cache.test.ts @@ -19,6 +19,41 @@ beforeEach(() => { revalidateTag.mockReturnValue(true) }) +describe("Revalidate cache test access via bearer token", () => { + test("That the cache revalidation endpoint returns 401 if no bearer token is provided", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "tag", tags: ["tag1", "tag2"] }), + }) + expect(res.status).toBe(401) + }, + }) + }) + + test("That the cache revalidation endpoint returns 200 if a valid bearer token is provided", async () => { + await testApiHandler({ + appHandler: revalidateCacheHandler, + url: `/cache/revalidate`, + async test({ fetch }) { + const res = await fetch({ + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + body: JSON.stringify({ type: "tag", tags: ["tag1", "tag2"] }), + }) + expect(res.status).toBe(200) + }, + }) + }) +}) + describe("Revalidate cache test combination of payloads", () => { test("That the cache revalidation endpoint returns 422 upon wrong input", async () => { await testApiHandler({ @@ -27,7 +62,10 @@ describe("Revalidate cache test combination of payloads", () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "unknown-type" }), }) expect(res.status).toBe(422) @@ -40,7 +78,10 @@ describe("Revalidate cache test combination of payloads", () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "tag", theTags: ["tag1", "tag2"] }), }) expect(res.status).toBe(422) @@ -55,7 +96,10 @@ describe("Revalidate cache test combination of payloads", () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "tag", tags: ["tag1", "tag2"] }), }) expect(res.status).toBe(200) @@ -70,7 +114,10 @@ describe("Revalidate cache test combination of payloads", () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "path", paths: ["/some/path", "/some/path"] }), }) expect(res.status).toBe(200) @@ -87,7 +134,10 @@ describe("Revalidate cache test different path and tag formats", async () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "path", paths: ["some/path", "/some/path"] }), }) expect(res.status).toBe(422) @@ -101,7 +151,10 @@ describe("Revalidate cache test different path and tag formats", async () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "path", paths: ["/some/*path", "/some/path"] }), }) expect(res.status).toBe(422) @@ -115,7 +168,10 @@ describe("Revalidate cache test different path and tag formats", async () => { async test({ fetch }) { const res = await fetch({ method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, body: JSON.stringify({ type: "tag", paths: ["!wrong-tag", "another tag"] }), }) expect(res.status).toBe(422) diff --git a/app/cache/revalidate/route.ts b/app/cache/revalidate/route.ts index 7cbe347f..9fe516aa 100644 --- a/app/cache/revalidate/route.ts +++ b/app/cache/revalidate/route.ts @@ -2,9 +2,7 @@ import { revalidatePath, revalidateTag } from "next/cache" import { NextRequest, NextResponse } from "next/server" import { z } from "zod" -// const paramsSchema = z.object({ -// type: z.union([z.literal("tag"), z.literal("path")]), -// }) +import goConfig from "@/lib/config/goConfig" const paramsSchema = z.union([ z.object({ @@ -18,6 +16,13 @@ const paramsSchema = z.union([ ]) export async function POST(request: NextRequest) { + const secret = goConfig("cache.revalidate.secret") + const authToken = (request.headers.get("authorization") ?? "").split("Bearer ").at(1) + + if (!authToken || authToken !== secret) { + return NextResponse.json({ error: "Not authorized" }, { status: 401 }) + } + const body = await request.json() try { const params = paramsSchema.parse(body) @@ -37,7 +42,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ params }) } catch (e) { - // console.log("ze error", e) + // TODO: Error logging return NextResponse.json({ error: "Wrong input" }, { status: 422 }) } } diff --git a/lib/config/resolvers/cache.ts b/lib/config/resolvers/cache.ts new file mode 100644 index 00000000..dfdb37a6 --- /dev/null +++ b/lib/config/resolvers/cache.ts @@ -0,0 +1,9 @@ +const cache = { + "cache.revalidate.secret": () => { + if (process.env.REVALIDATE_CACHE_SECRET) { + return process.env.REVALIDATE_CACHE_SECRET + } + }, +} + +export default cache diff --git a/lib/config/resolvers/index.ts b/lib/config/resolvers/index.ts index 3c498f96..d2efba70 100644 --- a/lib/config/resolvers/index.ts +++ b/lib/config/resolvers/index.ts @@ -1,4 +1,5 @@ import app from "./app" +import cache from "./cache" import search from "./search" import serviceFbi from "./service.fbi" import serviceUnilogin from "./service.unilogin" @@ -10,6 +11,7 @@ export const resolvers = { ...serviceUnilogin, ...search, ...token, + ...cache, } export type TResolvers = typeof resolvers From f0b94654c7c502a6e8e4a22aba23eba867db8e25 Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Wed, 11 Dec 2024 15:46:09 +0100 Subject: [PATCH 3/9] Loads of experiments with cache tags etc --- .../[...pathElements]/article.dpl-cms.graphql | 35 ++ .../articles-ssg.dpl-cms.graphql | 14 + app/article/[...pathElements]/loadArticle.ts | 41 ++ .../[...pathElements]/loadArticlesSsg.ts | 45 ++ app/article/[...pathElements]/page.tsx | 15 + app/article/[id]/article.dpl-cms.graphql | 14 - app/article/[id]/loadArticle.ts | 15 - app/article/[id]/page.tsx | 18 - app/auth/token/refresh/route.ts | 53 -- app/cache/revalidate/route.ts | 5 + app/work/[id]/read/page.tsx | 20 - codegen.ts | 4 +- lib/config/dpl-cms/dplCmsConfig.ts | 48 +- lib/graphql/fetchers/dpl-cms.fetcher.ts | 12 +- lib/graphql/generated/dpl-cms/graphql.tsx | 509 +++++++++++++++++- lib/graphql/generated/fbi/graphql.tsx | 84 +++ next.config.mjs | 9 + package.json | 10 +- yarn.lock | 143 ++--- 19 files changed, 876 insertions(+), 218 deletions(-) create mode 100644 app/article/[...pathElements]/article.dpl-cms.graphql create mode 100644 app/article/[...pathElements]/articles-ssg.dpl-cms.graphql create mode 100644 app/article/[...pathElements]/loadArticle.ts create mode 100644 app/article/[...pathElements]/loadArticlesSsg.ts create mode 100644 app/article/[...pathElements]/page.tsx delete mode 100644 app/article/[id]/article.dpl-cms.graphql delete mode 100644 app/article/[id]/loadArticle.ts delete mode 100644 app/article/[id]/page.tsx delete mode 100644 app/auth/token/refresh/route.ts delete mode 100644 app/work/[id]/read/page.tsx diff --git a/app/article/[...pathElements]/article.dpl-cms.graphql b/app/article/[...pathElements]/article.dpl-cms.graphql new file mode 100644 index 00000000..b655210a --- /dev/null +++ b/app/article/[...pathElements]/article.dpl-cms.graphql @@ -0,0 +1,35 @@ +query getArticle($id: ID!) { + nodeArticle(id: $id) { + title + subtitle + paragraphs { + __typename + ... on ParagraphTextBody { + body { + value + } + } + } + } +} + +query getArticleByRoute($path: String!) { + route(path: $path) { + ... on RouteInternal { + entity { + ... on NodeArticle { + title + subtitle + paragraphs { + __typename + ... on ParagraphTextBody { + body { + value + } + } + } + } + } + } + } +} diff --git a/app/article/[...pathElements]/articles-ssg.dpl-cms.graphql b/app/article/[...pathElements]/articles-ssg.dpl-cms.graphql new file mode 100644 index 00000000..49686eaf --- /dev/null +++ b/app/article/[...pathElements]/articles-ssg.dpl-cms.graphql @@ -0,0 +1,14 @@ +query extractArticles($pageSize: Int!, $cursor: Cursor) { + nodeArticles(first: $pageSize, after: $cursor) { + nodes { + id + path + } + edges { + cursor + } + pageInfo { + hasNextPage + } + } +} diff --git a/app/article/[...pathElements]/loadArticle.ts b/app/article/[...pathElements]/loadArticle.ts new file mode 100644 index 00000000..e45c57fe --- /dev/null +++ b/app/article/[...pathElements]/loadArticle.ts @@ -0,0 +1,41 @@ +import { cacheTag } from "next/dist/server/use-cache/cache-tag" + +import { fetcher } from "@/lib/graphql/fetchers/dpl-cms.fetcher" +import { + GetArticleByRouteDocument, + GetArticleByRouteQuery, +} from "@/lib/graphql/generated/dpl-cms/graphql" + +// import { unstable_cache } from "next/cache"; + +const loadArticle = async (path: string) => { + // "use cache" + // const queryClient = getQueryClient() + // Make sure that we do not use stale data + // since we are using cacheTags for invalidation. + // queryClient.setDefaultOptions({ queries: { staleTime: 0 } }) + // const result = await queryClient.fetchQuery({ + // queryKey: useGetArticleByRouteQuery.getKey({ path }), + // queryFn: useGetArticleByRouteQuery.fetcher({ path }), + // }) + // const data: GetArticleByRouteQuery = result.data + // const getArticle = unstable_cache( + // async (path: string) =>fetcher( + // GetArticleByRouteDocument, + // { path } + // )(), + // ['my-app-user'] + // ); + + const result = await fetcher( + GetArticleByRouteDocument, + { path } + )() + + const data: GetArticleByRouteQuery = result.data + + cacheTag("abe") + return data +} + +export default loadArticle diff --git a/app/article/[...pathElements]/loadArticlesSsg.ts b/app/article/[...pathElements]/loadArticlesSsg.ts new file mode 100644 index 00000000..7468b29c --- /dev/null +++ b/app/article/[...pathElements]/loadArticlesSsg.ts @@ -0,0 +1,45 @@ +import getQueryClient from "@/lib/getQueryClient" +import { + ExtractArticlesQuery, + useExtractArticlesQuery, +} from "@/lib/graphql/generated/dpl-cms/graphql" + +const loadArticlesSsg = async () => { + const queryClient = getQueryClient() + const pageSize = 100 + // We have a while loop. + // Just to be safe we do not allow more than 100 iterations. + const maxRuns = 100 + let runNumber = 0 + + const { + nodeArticles: { nodes, edges, pageInfo }, + } = await queryClient.fetchQuery({ + queryKey: useExtractArticlesQuery.getKey({ pageSize }), + queryFn: useExtractArticlesQuery.fetcher({ pageSize }), + }) + let allNodes = nodes + let allEdges = edges + let cursor = edges[edges.length - 1]?.cursor + + while (pageInfo.hasNextPage && runNumber < maxRuns) { + const { + nodeArticles: { nodes: newNodes, edges: newEdges }, + } = await queryClient.fetchQuery({ + queryKey: useExtractArticlesQuery.getKey({ pageSize, cursor }), + queryFn: useExtractArticlesQuery.fetcher({ pageSize, cursor }), + }) + + allNodes = [...allNodes, ...newNodes] + allEdges = [...allEdges, ...newEdges] + cursor = newEdges[newEdges.length - 1]?.cursor + // eslint-disable-next-line no-console + console.log({ allNodes, allEdges, cursor }) + + runNumber += 1 + } + + return { nodes: allNodes } +} + +export default loadArticlesSsg diff --git a/app/article/[...pathElements]/page.tsx b/app/article/[...pathElements]/page.tsx new file mode 100644 index 00000000..610e4c89 --- /dev/null +++ b/app/article/[...pathElements]/page.tsx @@ -0,0 +1,15 @@ +import loadArticle from "./loadArticle" + +// import loadArticlesSsg from "./loadArticlesSsg" + +const Page = async (props: { params: Promise<{ pathElements: string[] }> }) => { + const params = await props.params + + const { pathElements } = params + + const path = ["/artikler", ...pathElements].join("/") + const data = await loadArticle(path) + return
{JSON.stringify(data, null, 2)}
+} + +export default Page diff --git a/app/article/[id]/article.dpl-cms.graphql b/app/article/[id]/article.dpl-cms.graphql deleted file mode 100644 index 165345ba..00000000 --- a/app/article/[id]/article.dpl-cms.graphql +++ /dev/null @@ -1,14 +0,0 @@ -query getArticle($id: ID!) { - nodeArticle(id: $id) { - title - subtitle - paragraphs { - __typename - ... on ParagraphTextBody { - body { - value - } - } - } - } -} diff --git a/app/article/[id]/loadArticle.ts b/app/article/[id]/loadArticle.ts deleted file mode 100644 index 5f91e267..00000000 --- a/app/article/[id]/loadArticle.ts +++ /dev/null @@ -1,15 +0,0 @@ -import getQueryClient from "@/lib/getQueryClient" -import { GetArticleQuery, useGetArticleQuery } from "@/lib/graphql/generated/dpl-cms/graphql" - -const loadArticle = async (id: string) => { - const queryClient = getQueryClient() - - const data = await queryClient.fetchQuery({ - queryKey: useGetArticleQuery.getKey({ id }), - queryFn: useGetArticleQuery.fetcher({ id }), - }) - - return data -} - -export default loadArticle diff --git a/app/article/[id]/page.tsx b/app/article/[id]/page.tsx deleted file mode 100644 index 5fbd9598..00000000 --- a/app/article/[id]/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Suspense } from "react" - -import loadArticle from "./loadArticle" - -const Page = async (props: { params: Promise<{ id: string }> }) => { - const params = await props.params - - const { id } = params - - const data = await loadArticle(id) - return ( - Loading...

}> -
{JSON.stringify(data, null, 2)}
-
- ) -} - -export default Page diff --git a/app/auth/token/refresh/route.ts b/app/auth/token/refresh/route.ts deleted file mode 100644 index 26d57980..00000000 --- a/app/auth/token/refresh/route.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import * as client from "openid-client" -import { z } from "zod" - -import goConfig from "@/lib/config/goConfig" -import { getUniloginClientConfig } from "@/lib/session/oauth/uniloginClient" -import { getSession, setTokensOnSession } from "@/lib/session/session" -import { TTokenSet } from "@/lib/types/session" - -const sessionTokenSchema = z.object({ - isLoggedIn: z.boolean(), - access_token: z.string(), - refresh_token: z.string(), -}) - -export async function GET(request: NextRequest, response: NextResponse) { - const appUrl = String(goConfig("app.url")) - const config = await getUniloginClientConfig() - // TODO: Fix refresh token flow with new openid-client. - - const session = await getSession() - const frontpage = `${appUrl}/` - - // If the user is not logged in, we redirect to the frontpage. - if (!session.isLoggedIn) { - return NextResponse.redirect(frontpage, { headers: response.headers }) - } - const redirect = request.nextUrl.searchParams.get("redirect") - // We need the redirect URL to be present in the query string. - if (!redirect) { - return NextResponse.redirect(frontpage, { headers: response.headers }) - } - - try { - // TODO: Consider if we want to handle different types of sessions than unilogin. - const tokens = sessionTokenSchema.parse(session) - const newTokens = client.refreshTokenGrant(config, tokens.refresh_token) as unknown as TTokenSet - setTokensOnSession(session, newTokens) - await session.save() - } catch (error) { - // TODO: maybe distinguish between ZodError and other errors? - // TODO: Should we redirect to an end-of-session page? - // Session is corrupt so we need to destroy it. - session.destroy() - - const isZodError = error instanceof z.ZodError - console.error(isZodError ? JSON.stringify(error.errors) : error) - } finally { - return NextResponse.redirect(redirect, { headers: response.headers }) - } -} - -export const dynamic = "force-dynamic" diff --git a/app/cache/revalidate/route.ts b/app/cache/revalidate/route.ts index 9fe516aa..f8852610 100644 --- a/app/cache/revalidate/route.ts +++ b/app/cache/revalidate/route.ts @@ -30,11 +30,15 @@ export async function POST(request: NextRequest) { switch (params.type) { case "tag": params.tags.forEach(tag => { + // eslint-disable-next-line no-console + console.log("Revalidating tag:", tag) revalidateTag(tag) }) break case "path": params.paths.forEach(path => { + // eslint-disable-next-line no-console + console.log("Revalidating path:", path) revalidatePath(path) }) break @@ -43,6 +47,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ params }) } catch (e) { // TODO: Error logging + console.error(e) return NextResponse.json({ error: "Wrong input" }, { status: 422 }) } } diff --git a/app/work/[id]/read/page.tsx b/app/work/[id]/read/page.tsx deleted file mode 100644 index 2f261079..00000000 --- a/app/work/[id]/read/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client" - -import React from "react" - -import Reader from "@/components/shared/publizonReader/PublizonReader" - -function Page({ searchParams: { id } }: { searchParams: { id: string } }) { - const handleBack = () => { - window.history.back() - } - - return ( -
-
- handleBack()} type="demo" identifier={id} /> -
- ) -} - -export default Page diff --git a/codegen.ts b/codegen.ts index a4bb69ba..81ebb4c8 100644 --- a/codegen.ts +++ b/codegen.ts @@ -5,7 +5,7 @@ import goConfig from "./lib/config/goConfig" const { loadEnvConfig } = require("@next/env") loadEnvConfig(process.cwd()) - +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" const config: CodegenConfig = { overwrite: true, generates: { @@ -15,7 +15,7 @@ const config: CodegenConfig = { schema: { [`${process.env.NEXT_PUBLIC_GRAPHQL_SCHEMA_ENDPOINT_DPL_CMS}`]: { headers: { - Authorization: `Basic ${process.env.GRAPHQL_SCHEMA_ENDPOINT_BASIC_TOKEN_DPL_CMS}`, + Authorization: `Basic Z3JhcGhxbF9jb25zdW1lcjp0ZXN0`, }, }, }, diff --git a/lib/config/dpl-cms/dplCmsConfig.ts b/lib/config/dpl-cms/dplCmsConfig.ts index 699ae769..81634f1f 100644 --- a/lib/config/dpl-cms/dplCmsConfig.ts +++ b/lib/config/dpl-cms/dplCmsConfig.ts @@ -1,32 +1,50 @@ -import { QueryClient } from "@tanstack/react-query" +import { connection } from "next/server" +import { fetcher } from "@/lib/graphql/fetchers/dpl-cms.fetcher" import { + GetDplCmsConfigurationDocument, GetDplCmsConfigurationQuery, - useGetDplCmsConfigurationQuery, } from "@/lib/graphql/generated/dpl-cms/graphql" -const queryDplCmsConfig = async (queryClient: QueryClient) => { - const { dplConfiguration } = await queryClient.fetchQuery({ - queryKey: useGetDplCmsConfigurationQuery.getKey(), - queryFn: useGetDplCmsConfigurationQuery.fetcher(), - // Cache 5 minutes unless invalidated - staleTime: 5 * 60 * 1000, // 5 mins - }) +// import { +// GetDplCmsConfigurationQuery, +// useGetDplCmsConfigurationQuery, +// } from "@/lib/graphql/generated/dpl-cms/graphql" - return dplConfiguration ?? null -} +// eslint-disable-next-line +// const queryDplCmsConfig = async (queryClient: QueryClient) => { +// const { dplConfiguration } = await queryClient.fetchQuery({ +// queryKey: useGetDplCmsConfigurationQuery.getKey(), +// queryFn: useGetDplCmsConfigurationQuery.fetcher(), +// // Cache 5 minutes unless invalidated +// staleTime: 5 * 60 * 1000, // 5 mins +// }) + +// return dplConfiguration ?? null +// } + +// eslint-disable-next-line +// const queryDplCmsConfig = async (queryClient: QueryClient) => { +// return null +// } // eslint-disable-next-line prefer-const -let dplCmsConfigClient = new QueryClient({}) +// let dplCmsConfigClient = new QueryClient({}) const getDplCmsConfig = async () => { - const result = await queryDplCmsConfig(dplCmsConfigClient) - - return result + // const result = await queryDplCmsConfig(dplCmsConfigClient) + await connection() + const result = await fetcher( + GetDplCmsConfigurationDocument + )() + const data: GetDplCmsConfigurationQuery = result.data + + return data } export const getDplCmsUniloginConfig = async () => { const config = await getDplCmsConfig() + // @ts-ignore return config?.unilogin ?? null } diff --git a/lib/graphql/fetchers/dpl-cms.fetcher.ts b/lib/graphql/fetchers/dpl-cms.fetcher.ts index cfc4df4f..6e43205b 100644 --- a/lib/graphql/fetchers/dpl-cms.fetcher.ts +++ b/lib/graphql/fetchers/dpl-cms.fetcher.ts @@ -1,7 +1,7 @@ export function fetcher( query: string, variables?: TVariables, - options?: RequestInit["headers"] + options?: RequestInit | RequestInit["headers"] ) { const dplCmsGraphqlEndpoint = process.env.NEXT_PUBLIC_GRAPHQL_SCHEMA_ENDPOINT_DPL_CMS const dplCmsGraphqlBasicToken = process.env.NEXT_PUBLIC_GRAPHQL_BASIC_TOKEN_DPL_CMS @@ -10,15 +10,17 @@ export function fetcher( throw new Error("Missing DPL CMS GraphQL endpoint or basic token") } - return async (): Promise => { + return async (): Promise<{ data: TData; headers: Headers }> => { + // eslint-disable-next-line no-console + console.log("I am fetching dpl cms data") const res = await fetch(dplCmsGraphqlEndpoint, { method: "POST", ...{ headers: { "Content-Type": "application/json", Authorization: `Basic ${dplCmsGraphqlBasicToken}`, - ...options, }, + ...options, }, body: JSON.stringify({ query, variables }), }) @@ -31,6 +33,8 @@ export function fetcher( throw new Error(message) } - return json.data + const responseHeaders = res.headers + + return { data: json.data, headers: responseHeaders } } } diff --git a/lib/graphql/generated/dpl-cms/graphql.tsx b/lib/graphql/generated/dpl-cms/graphql.tsx index 55fdbd3b..cfa8d783 100644 --- a/lib/graphql/generated/dpl-cms/graphql.tsx +++ b/lib/graphql/generated/dpl-cms/graphql.tsx @@ -14,6 +14,7 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + Cursor: { input: unknown; output: unknown; } Email: { input: unknown; output: unknown; } Html: { input: unknown; output: unknown; } PhoneNumber: { input: unknown; output: unknown; } @@ -51,6 +52,45 @@ export type AddressCountry = { name?: Maybe; }; +/** A paginated set of results. */ +export type Connection = { + /** The edges of this connection. */ + edges: Array; + /** The nodes of the edges of this connection. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: ConnectionPageInfo; +}; + +/** Information about the page in a connection. */ +export type ConnectionPageInfo = { + __typename?: 'ConnectionPageInfo'; + /** The cursor for the last element in this page. */ + endCursor?: Maybe; + /** Whether there are more pages in this connection. */ + hasNextPage: Scalars['Boolean']['output']; + /** Whether there are previous pages in this connection. */ + hasPreviousPage: Scalars['Boolean']['output']; + /** The cursor for the first element in this page. */ + startCursor?: Maybe; +}; + +/** Choose how your sorts will occur and on which field. */ +export enum ConnectionSortKeys { + /** Sort by creation date */ + CreatedAt = 'CREATED_AT', + /** Sort by promoted status. */ + Promoted = 'PROMOTED', + /** Sort by sticky status. */ + Sticky = 'STICKY', + /** Sort by entity title. */ + Title = 'TITLE', + /** Sort by updated date */ + UpdatedAt = 'UPDATED_AT', + /** Sort by term weight. */ + Weight = 'WEIGHT' +} + /** A Date range has a start and an end. */ export type DateRange = { __typename?: 'DateRange'; @@ -79,6 +119,22 @@ export type DplConfiguration = { unilogin?: Maybe; }; +/** + * An edge in a connection. + * Provides the cursor to fetch data based on the position of the associated + * node. Specific edge implementations may provide more information about the + * relationship they represent. + */ +export type Edge = { + cursor: Scalars['Cursor']['output']; + node: EdgeNode; +}; + +/** This entity is accessible over an Edge connection. */ +export type EdgeNode = { + id: Scalars['ID']['output']; +}; + /** A file object to represent an managed file. */ export type File = { __typename?: 'File'; @@ -233,6 +289,73 @@ export type MediaVideo = MediaInterface & { status: Scalars['Boolean']['output']; }; +/** Entity type menu. */ +export type Menu = MenuInterface & { + __typename?: 'Menu'; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** The menu items. */ + items: Array; + /** The menu name. */ + name: Scalars['String']['output']; +}; + +/** List of menus available to load. */ +export enum MenuAvailable { + /** Administration */ + Admin = 'ADMIN', + /** Udvikling */ + Devel = 'DEVEL', + /** Main navigation */ + Main = 'MAIN', + /** Tools */ + Tools = 'TOOLS' +} + +/** Entity type menu. */ +export type MenuInterface = { + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** The menu items. */ + items: Array; + /** The menu name. */ + name: Scalars['String']['output']; +}; + +/** A menu item defined in the CMS. */ +export type MenuItem = { + __typename?: 'MenuItem'; + /** Attributes of this menu item. */ + attributes: MenuItemAttributes; + /** Child menu items of this menu item. */ + children: Array; + /** The description of the menu item. */ + description?: Maybe; + /** Whether this menu item is intended to be expanded. */ + expanded: Scalars['Boolean']['output']; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** Whether this menu item links to an internal route. */ + internal: Scalars['Boolean']['output']; + /** The language of the menu item. */ + langcode: Language; + /** The route this menu item uses. Route loading can be disabled per menu type. */ + route?: Maybe; + /** The title of the menu item. */ + title: Scalars['String']['output']; + /** The URL of the menu item. */ + url?: Maybe; +}; + +/** Menu item options set within the CMS. */ +export type MenuItemAttributes = { + __typename?: 'MenuItemAttributes'; + class?: Maybe; +}; + +/** Entity type menu. */ +export type MenuUnion = Menu; + /** The schema's entry-point for mutations. */ export type Mutation = { __typename?: 'Mutation'; @@ -241,7 +364,7 @@ export type Mutation = { }; /** Brug artikler til nyhedspræget indhold med en begrænset levetid. */ -export type NodeArticle = NodeInterface & { +export type NodeArticle = EdgeNode & NodeInterface & { __typename?: 'NodeArticle'; /** Bibliotek */ branch?: Maybe; @@ -296,6 +419,21 @@ export type NodeArticle = NodeInterface & { title: Scalars['String']['output']; }; +/** A paginated set of results for NodeArticle. */ +export type NodeArticleConnection = Connection & { + __typename?: 'NodeArticleConnection'; + edges: Array; + nodes: Array; + pageInfo: ConnectionPageInfo; +}; + +/** Edge for NodeArticle. */ +export type NodeArticleEdge = Edge & { + __typename?: 'NodeArticleEdge'; + cursor: Scalars['Cursor']['output']; + node: NodeArticle; +}; + /** Entity type node. */ export type NodeInterface = { /** Tidspunktet hvor indholdselementet sidst blev redigeret. */ @@ -464,6 +602,21 @@ export type ParagraphContentSliderAutomatic = ParagraphInterface & { underlinedTitle?: Maybe; }; +/** Link med ikoner. Designet til jpg, jpeg, png, pdf, mp3, mov, mp4, og mpeg filer */ +export type ParagraphFiles = ParagraphInterface & { + __typename?: 'ParagraphFiles'; + /** The time that the Paragraph was created. */ + created: DateTime; + /** Filer */ + files?: Maybe>; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** The paragraphs entity language code. */ + langcode: Language; + /** Publiceret */ + status: Scalars['Boolean']['output']; +}; + /** Denne paragraph viser en liste af arrangementer filtreret på kategori, tags og filialer. */ export type ParagraphFilteredEventList = ParagraphInterface & { __typename?: 'ParagraphFilteredEventList'; @@ -505,6 +658,21 @@ export type ParagraphInterface = { status: Scalars['Boolean']['output']; }; +/** Links med ikoner. Designet til interne/eksterne links og links til søgeresultater ␣. */ +export type ParagraphLinks = ParagraphInterface & { + __typename?: 'ParagraphLinks'; + /** The time that the Paragraph was created. */ + created: DateTime; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** The paragraphs entity language code. */ + langcode: Language; + /** Link */ + link: Array; + /** Publiceret */ + status: Scalars['Boolean']['output']; +}; + /** Dette afsnit vil vise en liste over arrangementer, der er manuelt valgt. */ export type ParagraphManualEventList = ParagraphInterface & { __typename?: 'ParagraphManualEventList'; @@ -522,6 +690,45 @@ export type ParagraphManualEventList = ParagraphInterface & { title?: Maybe; }; +/** Entity type paragraph. */ +export type ParagraphMedias = ParagraphInterface & { + __typename?: 'ParagraphMedias'; + /** The time that the Paragraph was created. */ + created: DateTime; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** The paragraphs entity language code. */ + langcode: Language; + /** Billeder */ + medias: Array; + /** Publiceret */ + status: Scalars['Boolean']['output']; +}; + +/** Denne paragraph bruges til at anbefale et enkelt materiale. */ +export type ParagraphRecommendation = ParagraphInterface & { + __typename?: 'ParagraphRecommendation'; + /** The time that the Paragraph was created. */ + created: DateTime; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** + * Dette bestemmer om, et billede skal positioneres til venstre eller højre.
Hvis den ikke er slået til (standardadfærd), placeres billedet til + * venstre, hvis den er slået til, placeres billedet til højre.␣ + */ + imagePositionRight?: Maybe; + /** The paragraphs entity language code. */ + langcode: Language; + /** + * Titlen på det anbefalede materiale. Hvis du tilføjer en titel, vil + * beskrivelsesteksten af materialet ikke blive autoudfyldt. + */ + recommendationTitle?: Maybe; + /** Publiceret */ + status: Scalars['Boolean']['output']; +}; + /** En basal, formateret brødtekst */ export type ParagraphTextBody = ParagraphInterface & { __typename?: 'ParagraphTextBody'; @@ -538,7 +745,22 @@ export type ParagraphTextBody = ParagraphInterface & { }; /** Entity type paragraph. */ -export type ParagraphUnion = ParagraphAccordion | ParagraphBanner | ParagraphBreadcrumbChildren | ParagraphCardGridAutomatic | ParagraphCardGridManual | ParagraphContentSlider | ParagraphContentSliderAutomatic | ParagraphFilteredEventList | ParagraphManualEventList | ParagraphTextBody; +export type ParagraphUnion = ParagraphAccordion | ParagraphBanner | ParagraphBreadcrumbChildren | ParagraphCardGridAutomatic | ParagraphCardGridManual | ParagraphContentSlider | ParagraphContentSliderAutomatic | ParagraphFiles | ParagraphFilteredEventList | ParagraphLinks | ParagraphManualEventList | ParagraphMedias | ParagraphRecommendation | ParagraphTextBody | ParagraphVideo; + +/** Indtast URL'en til den video, du vil indlejre. */ +export type ParagraphVideo = ParagraphInterface & { + __typename?: 'ParagraphVideo'; + /** The time that the Paragraph was created. */ + created: DateTime; + /** Indlejr video */ + embedVideo?: Maybe; + /** The Universally Unique IDentifier (UUID). */ + id: Scalars['ID']['output']; + /** The paragraphs entity language code. */ + langcode: Language; + /** Publiceret */ + status: Scalars['Boolean']['output']; +}; /** The schema's entry-point for queries. */ export type Query = { @@ -547,8 +769,33 @@ export type Query = { dplConfiguration?: Maybe; /** Schema information. */ info: SchemaInformation; + /** Load a Menu by name. */ + menu?: Maybe; /** Load a NodeArticle entity by id */ nodeArticle?: Maybe; + /** List of all NodeArticle on the platform. */ + nodeArticles: NodeArticleConnection; + /** Load a ParagraphFiles entity by id */ + paragraphFiles?: Maybe; + /** Load a ParagraphLinks entity by id */ + paragraphLinks?: Maybe; + /** Load a ParagraphMedias entity by id */ + paragraphMedias?: Maybe; + /** Load a ParagraphRecommendation entity by id */ + paragraphRecommendation?: Maybe; + /** Load a ParagraphVideo entity by id */ + paragraphVideo?: Maybe; + /** Load a Route by path. */ + route?: Maybe; + /** NextJs SSG Node Information. */ + ssgNodeInformation: SsgNodeInformation; +}; + + +/** The schema's entry-point for queries. */ +export type QueryMenuArgs = { + langcode?: InputMaybe; + name: MenuAvailable; }; @@ -559,6 +806,120 @@ export type QueryNodeArticleArgs = { revision?: InputMaybe; }; + +/** The schema's entry-point for queries. */ +export type QueryNodeArticlesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + langcode?: InputMaybe; + last?: InputMaybe; + reverse?: InputMaybe; + sortKey?: InputMaybe; +}; + + +/** The schema's entry-point for queries. */ +export type QueryParagraphFilesArgs = { + id: Scalars['ID']['input']; + langcode?: InputMaybe; + revision?: InputMaybe; +}; + + +/** The schema's entry-point for queries. */ +export type QueryParagraphLinksArgs = { + id: Scalars['ID']['input']; + langcode?: InputMaybe; + revision?: InputMaybe; +}; + + +/** The schema's entry-point for queries. */ +export type QueryParagraphMediasArgs = { + id: Scalars['ID']['input']; + langcode?: InputMaybe; + revision?: InputMaybe; +}; + + +/** The schema's entry-point for queries. */ +export type QueryParagraphRecommendationArgs = { + id: Scalars['ID']['input']; + langcode?: InputMaybe; + revision?: InputMaybe; +}; + + +/** The schema's entry-point for queries. */ +export type QueryParagraphVideoArgs = { + id: Scalars['ID']['input']; + langcode?: InputMaybe; + revision?: InputMaybe; +}; + + +/** The schema's entry-point for queries. */ +export type QueryRouteArgs = { + langcode?: InputMaybe; + path: Scalars['String']['input']; +}; + + +/** The schema's entry-point for queries. */ +export type QuerySsgNodeInformationArgs = { + type: Scalars['String']['input']; +}; + +/** Routes represent incoming requests that resolve to content. */ +export type Route = { + /** Whether this route is internal or external. */ + internal: Scalars['Boolean']['output']; + /** URL of this route. */ + url: Scalars['String']['output']; +}; + +/** A list of possible entities that can be returned by URL. */ +export type RouteEntityUnion = NodeArticle; + +/** Route outside of this website. */ +export type RouteExternal = Route & { + __typename?: 'RouteExternal'; + /** Whether this route is internal or external. */ + internal: Scalars['Boolean']['output']; + /** URL of this route. */ + url: Scalars['String']['output']; +}; + +/** Route within this website. */ +export type RouteInternal = Route & { + __typename?: 'RouteInternal'; + /** Breadcrumb links for this route. */ + breadcrumbs?: Maybe>; + /** Content assigned to this route. */ + entity?: Maybe; + /** Whether this route is internal or external. */ + internal: Scalars['Boolean']['output']; + /** URL of this route. */ + url: Scalars['String']['output']; +}; + +/** Redirect to another URL with status. */ +export type RouteRedirect = Route & { + __typename?: 'RouteRedirect'; + /** Whether this route is internal or external. */ + internal: Scalars['Boolean']['output']; + /** Utility prop. Always true for redirects. */ + redirect: Scalars['Boolean']['output']; + /** Suggested status for redirect. Eg 301. */ + status: Scalars['Int']['output']; + /** URL of this route. */ + url: Scalars['String']['output']; +}; + +/** Route types that can exist in the system. */ +export type RouteUnion = RouteExternal | RouteInternal | RouteRedirect; + /** Schema information provided by the system. */ export type SchemaInformation = { __typename?: 'SchemaInformation'; @@ -580,6 +941,15 @@ export enum SortDirection { Desc = 'DESC' } +/** Schema information provided by the system. */ +export type SsgNodeInformation = { + __typename?: 'SsgNodeInformation'; + /** The connected cache tags. */ + cacheTags?: Maybe; + /** The node UUID. */ + nid?: Maybe; +}; + /** The schema's entry-point for subscriptions. */ export type Subscription = { __typename?: 'Subscription'; @@ -796,7 +1166,22 @@ export type GetArticleQueryVariables = Exact<{ }>; -export type GetArticleQuery = { __typename?: 'Query', nodeArticle?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null }> | null } | null }; +export type GetArticleQuery = { __typename?: 'Query', nodeArticle?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFiles' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphLinks' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphMedias' } | { __typename: 'ParagraphRecommendation' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null } | { __typename: 'ParagraphVideo' }> | null } | null }; + +export type GetArticleByRouteQueryVariables = Exact<{ + path: Scalars['String']['input']; +}>; + + +export type GetArticleByRouteQuery = { __typename?: 'Query', route?: { __typename?: 'RouteExternal' } | { __typename?: 'RouteInternal', entity?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFiles' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphLinks' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphMedias' } | { __typename: 'ParagraphRecommendation' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null } | { __typename: 'ParagraphVideo' }> | null } | null } | { __typename?: 'RouteRedirect' } | null }; + +export type ExtractArticlesQueryVariables = Exact<{ + pageSize: Scalars['Int']['input']; + cursor?: InputMaybe; +}>; + + +export type ExtractArticlesQuery = { __typename?: 'Query', nodeArticles: { __typename?: 'NodeArticleConnection', nodes: Array<{ __typename?: 'NodeArticle', id: string, path: string }>, edges: Array<{ __typename?: 'NodeArticleEdge', cursor: unknown }>, pageInfo: { __typename?: 'ConnectionPageInfo', hasNextPage: boolean } } }; export type GetDplCmsConfigurationQueryVariables = Exact<{ [key: string]: never; }>; @@ -861,6 +1246,124 @@ useSuspenseGetArticleQuery.getKey = (variables: GetArticleQueryVariables) => ['g useGetArticleQuery.fetcher = (variables: GetArticleQueryVariables, options?: RequestInit['headers']) => fetcher(GetArticleDocument, variables, options); +export const GetArticleByRouteDocument = ` + query getArticleByRoute($path: String!) { + route(path: $path) { + ... on RouteInternal { + entity { + ... on NodeArticle { + title + subtitle + paragraphs { + __typename + ... on ParagraphTextBody { + body { + value + } + } + } + } + } + } + } +} + `; + +export const useGetArticleByRouteQuery = < + TData = GetArticleByRouteQuery, + TError = unknown + >( + variables: GetArticleByRouteQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } + ) => { + + return useQuery( + { + queryKey: ['getArticleByRoute', variables], + queryFn: fetcher(GetArticleByRouteDocument, variables), + ...options + } + )}; + +useGetArticleByRouteQuery.getKey = (variables: GetArticleByRouteQueryVariables) => ['getArticleByRoute', variables]; + +export const useSuspenseGetArticleByRouteQuery = < + TData = GetArticleByRouteQuery, + TError = unknown + >( + variables: GetArticleByRouteQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseSuspenseQueryOptions['queryKey'] } + ) => { + + return useSuspenseQuery( + { + queryKey: ['getArticleByRouteSuspense', variables], + queryFn: fetcher(GetArticleByRouteDocument, variables), + ...options + } + )}; + +useSuspenseGetArticleByRouteQuery.getKey = (variables: GetArticleByRouteQueryVariables) => ['getArticleByRouteSuspense', variables]; + + +useGetArticleByRouteQuery.fetcher = (variables: GetArticleByRouteQueryVariables, options?: RequestInit['headers']) => fetcher(GetArticleByRouteDocument, variables, options); + +export const ExtractArticlesDocument = ` + query extractArticles($pageSize: Int!, $cursor: Cursor) { + nodeArticles(first: $pageSize, after: $cursor) { + nodes { + id + path + } + edges { + cursor + } + pageInfo { + hasNextPage + } + } +} + `; + +export const useExtractArticlesQuery = < + TData = ExtractArticlesQuery, + TError = unknown + >( + variables: ExtractArticlesQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } + ) => { + + return useQuery( + { + queryKey: ['extractArticles', variables], + queryFn: fetcher(ExtractArticlesDocument, variables), + ...options + } + )}; + +useExtractArticlesQuery.getKey = (variables: ExtractArticlesQueryVariables) => ['extractArticles', variables]; + +export const useSuspenseExtractArticlesQuery = < + TData = ExtractArticlesQuery, + TError = unknown + >( + variables: ExtractArticlesQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseSuspenseQueryOptions['queryKey'] } + ) => { + + return useSuspenseQuery( + { + queryKey: ['extractArticlesSuspense', variables], + queryFn: fetcher(ExtractArticlesDocument, variables), + ...options + } + )}; + +useSuspenseExtractArticlesQuery.getKey = (variables: ExtractArticlesQueryVariables) => ['extractArticlesSuspense', variables]; + + +useExtractArticlesQuery.fetcher = (variables: ExtractArticlesQueryVariables, options?: RequestInit['headers']) => fetcher(ExtractArticlesDocument, variables, options); + export const GetDplCmsConfigurationDocument = ` query getDplCmsConfiguration { dplConfiguration { diff --git a/lib/graphql/generated/fbi/graphql.tsx b/lib/graphql/generated/fbi/graphql.tsx index 9841e287..caa41982 100644 --- a/lib/graphql/generated/fbi/graphql.tsx +++ b/lib/graphql/generated/fbi/graphql.tsx @@ -229,6 +229,12 @@ export type ComplexSearchSuggestion = { __typename?: 'ComplexSearchSuggestion'; /** The suggested term which can be searched for */ term: Scalars['String']['output']; + /** + * A unique identifier for tracking user interactions with this suggestion. + * It is generated in the response and should be included in subsequent + * API calls when this suggestion is selected. + */ + traceId: Scalars['String']['output']; /** The type of suggestion */ type: Scalars['String']['output']; /** A work related to the term */ @@ -780,8 +786,12 @@ export type Manifestation = { languages?: Maybe; /** Details about the latest printing of this manifestation */ latestPrinting?: Maybe; + /** Identification of the local id of this manifestation */ + localId?: Maybe; /** Tracks on music album, sheet music content, or articles/short stories etc. in this manifestation */ manifestationParts?: Maybe; + /** Field for presenting bibliographic records in MARC format */ + marc?: Maybe; /** The type of material of the manifestation based on bibliotek.dk types */ materialTypes: Array; /** Notes about the manifestation */ @@ -804,6 +814,8 @@ export type Manifestation = { review?: Maybe; /** Series for this manifestation */ series: Array; + /** Material that can be identified as sheet music */ + sheetMusicCategories?: Maybe; /** Information about on which shelf in the library this manifestation can be found */ shelfmark?: Maybe; /** The source of the manifestation, e.g. own library catalogue (Bibliotekskatalog) or online source e.g. Filmstriben, Ebook Central, eReolen Global etc. */ @@ -814,6 +826,12 @@ export type Manifestation = { tableOfContents?: Maybe; /** Different kinds of titles for this work */ titles: ManifestationTitles; + /** + * A unique identifier for tracking user interactions with this manifestation. + * It is generated in the response and should be included in subsequent + * API calls when this manifestation is selected. + */ + traceId: Scalars['String']['output']; /** id of the manifestaion unit */ unit?: Maybe; /** Universes for this manifestation */ @@ -902,6 +920,33 @@ export type Manifestations = { mostRelevant: Array; }; +export type Marc = { + __typename?: 'Marc'; + /** Gets the MARC record collection for the given record identifier, containing either standalone or head and/or section and volume records. */ + getMarcByRecordId?: Maybe; +}; + + +export type MarcGetMarcByRecordIdArgs = { + recordId: Scalars['String']['input']; +}; + +export type MarcRecord = { + __typename?: 'MarcRecord'; + /** The library agency */ + agencyId: Scalars['String']['output']; + /** The bibliographic record identifier */ + bibliographicRecordId: Scalars['String']['output']; + /** The MARC record collection content as marcXchange XML string */ + content: Scalars['String']['output']; + /** The serialization format of the MARC record content. Defaults to 'marcXchange' */ + contentSerializationFormat: Scalars['String']['output']; + /** Flag indicating whether or not the record is deleted */ + deleted: Scalars['Boolean']['output']; + /** The marc record identifier */ + id: Scalars['String']['output']; +}; + export type MaterialType = { __typename?: 'MaterialType'; /** jed 1.1 - the general materialtype */ @@ -1077,6 +1122,14 @@ export type MoodTagRecommendResponse = { work: Work; }; +export type MusicalExercise = { + __typename?: 'MusicalExercise'; + /** The types of instrument 'schools' intended to practise with */ + display: Array; + /** Information whether material is intended for practising and in combination with an instrument */ + forExercise: Scalars['Boolean']['output']; +}; + export type Mutation = { __typename?: 'Mutation'; elba: ElbaServices; @@ -1106,6 +1159,7 @@ export enum NoteTypeEnum { Dissertation = 'DISSERTATION', Edition = 'EDITION', EstimatedPlayingTimeForGames = 'ESTIMATED_PLAYING_TIME_FOR_GAMES', + ExpectedPublicationDate = 'EXPECTED_PUBLICATION_DATE', Frequency = 'FREQUENCY', MusicalEnsembleOrCast = 'MUSICAL_ENSEMBLE_OR_CAST', NotSpecified = 'NOT_SPECIFIED', @@ -1214,6 +1268,8 @@ export type Query = { localSuggest: LocalSuggestResponse; manifestation?: Maybe; manifestations: Array>; + /** Field for presenting bibliographic records in MARC format */ + marc: Marc; mood: MoodQueries; ors: OrsQuery; /** Get recommendations */ @@ -1615,6 +1671,20 @@ export type Setting = SubjectInterface & { type: SubjectTypeEnum | '%future added value'; }; +export type SheetMusicCategory = { + __typename?: 'SheetMusicCategory'; + /** The types of chamber music material covers */ + chamberMusicTypes: Array; + /** The types of choir material covers */ + choirTypes: Array; + /** The types of instruments material covers */ + instruments: Array; + /** Material intended to practice with */ + musicalExercises?: Maybe; + /** The types of orchestra material covers */ + orchestraTypes: Array; +}; + export type Shelfmark = { __typename?: 'Shelfmark'; /** A postfix to the shelfmark, eg. 99.4 Christensen, Inger. f. 1935 */ @@ -1714,6 +1784,12 @@ export type Suggestion = { __typename?: 'Suggestion'; /** The suggested term which can be searched for */ term: Scalars['String']['output']; + /** + * A unique identifier for tracking user interactions with this suggestion. + * It is generated in the response and should be included in subsequent + * API calls when this suggestion is selected. + */ + traceId: Scalars['String']['output']; /** The type of suggestion: creator, subject or title */ type: SuggestionTypeEnum | '%future added value'; /** A work related to the term */ @@ -1866,6 +1942,8 @@ export type Work = { mainLanguages: Array; /** Details about the manifestations of this work */ manifestations: Manifestations; + /** Field for presenting bibliographic records in MARC format */ + marc?: Maybe; /** The type of material of the manifestation based on bibliotek.dk types */ materialTypes: Array; /** Relations to other manifestations */ @@ -1875,6 +1953,12 @@ export type Work = { /** Subjects for this work */ subjects: SubjectContainer; titles: WorkTitles; + /** + * A unique identifier for tracking user interactions with this work. + * It is generated in the response and should be included in subsequent + * API calls when this work is selected. + */ + traceId: Scalars['String']['output']; /** Literary/movie universes this work is part of, e.g. Wizarding World, Marvel Universe */ universes: Array; /** Unique identification of the work based on work-presentation id e.g work-of:870970-basis:54029519 */ diff --git a/next.config.mjs b/next.config.mjs index f4019068..d2ae052d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,7 +1,16 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + logging: { + fetches: { + fullUrl: true, + }, + }, + experimental: { serverSourceMaps: true, + // We cannot use "use cache" directive with react query in canary. + // When it is working properly, we can add this back. + // dynamicIO: true, }, images: { remotePatterns: [ diff --git a/package.json b/package.json index 703f7d04..41c90f89 100644 --- a/package.json +++ b/package.json @@ -5,19 +5,19 @@ "type": "module", "scripts": { "build-storybook": "storybook build", - "build": "next build", + "build": "NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" next build", "ci-check": "yarn typecheck && yarn lint && yarn format:check && yarn test:unit:once", "codegen:all-rest-services": "orval", "codegen:covers": "rm -rf src/lib/rest/cover-service-api/generated/model/*.* && orval --project coverService", - "codegen:graphql": "graphql-codegen --require tsconfig-paths/register --config codegen.ts", + "codegen:graphql": "NODE_TLS_REJECT_UNAUTHORIZED=0 graphql-codegen --require tsconfig-paths/register --config codegen.ts", "codegen:publizon": "rm -rf lib/rest/publizon-api/generated/model/*.* && orval --project publizonAdapter", "dev:https": "next dev --experimental-https", - "dev": "next dev", + "dev": "NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" next dev", "format:check": "prettier --check .", "format:write": "prettier --write .", "lint": "next lint", "start:with-server-source-maps": "NODE_OPTIONS='--enable-source-maps=true' next start", - "start": "next start", + "start": "NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" next start", "storybook": "storybook dev -p 6006", "test:accessibility": "test-storybook", "test:unit:once": "vitest run", @@ -43,7 +43,7 @@ "iron-session": "^8.0.3", "lodash": "^4.17.21", "lucide-react": "^0.446.0", - "next": "15.0.3", + "next": "^15.1.0", "next-drupal": "^2.0.0-beta.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/yarn.lock b/yarn.lock index 04bbeea3..8ee28f74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2870,6 +2870,11 @@ resolved "https://registry.yarnpkg.com/@next/env/-/env-15.0.3.tgz#a2e9bf274743c52b74d30f415f3eba750d51313a" integrity sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA== +"@next/env@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/env/-/env-15.1.0.tgz#35b00a5f60ff10dc275182928c325d25c29379ae" + integrity sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w== + "@next/eslint-plugin-next@15.0.3": version "15.0.3" resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.0.3.tgz#ce953098036d462f6901e423cc6445fc165b78c4" @@ -2882,70 +2887,70 @@ resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz" integrity sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA== -"@next/swc-darwin-arm64@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz#4c40c506cf3d4d87da0204f4cccf39e6bdc46a71" - integrity sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw== +"@next/swc-darwin-arm64@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.0.tgz#30cb89220e719244c9fa7391641e515a078ade46" + integrity sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw== "@next/swc-darwin-x64@14.2.15": version "14.2.15" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz#b7baeedc6a28f7545ad2bc55adbab25f7b45cb89" integrity sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg== -"@next/swc-darwin-x64@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz#8e06cacae3dae279744f9fbe88dea679ec2c1ca3" - integrity sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw== +"@next/swc-darwin-x64@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.0.tgz#c24c4f5d1016dd161da32049305b0ddddfc80951" + integrity sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q== "@next/swc-linux-arm64-gnu@14.2.15": version "14.2.15" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz#fa13c59d3222f70fb4cb3544ac750db2c6e34d02" integrity sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw== -"@next/swc-linux-arm64-gnu@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz#c144ad1f21091b9c6e1e330ecc2d56188763191d" - integrity sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw== +"@next/swc-linux-arm64-gnu@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.0.tgz#08ed540ecdac74426a624cc7d736dc709244b004" + integrity sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q== "@next/swc-linux-arm64-musl@14.2.15": version "14.2.15" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz#30e45b71831d9a6d6d18d7ac7d611a8d646a17f9" integrity sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ== -"@next/swc-linux-arm64-musl@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz#3ccb71c6703bf421332f177d1bb0e10528bc73a2" - integrity sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA== +"@next/swc-linux-arm64-musl@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.0.tgz#dfddbd40087d018266aa92515ec5b3e251efa6dd" + integrity sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg== "@next/swc-linux-x64-gnu@14.2.15": version "14.2.15" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz#5065db17fc86f935ad117483f21f812dc1b39254" integrity sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA== -"@next/swc-linux-x64-gnu@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz#b90aa9b07001b4000427c35ab347a9273cbeebb3" - integrity sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w== +"@next/swc-linux-x64-gnu@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.0.tgz#a7b5373a1b28c0acecbc826a3790139fc0d899e5" + integrity sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw== "@next/swc-linux-x64-musl@14.2.15": version "14.2.15" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz#3c4a4568d8be7373a820f7576cf33388b5dab47e" integrity sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ== -"@next/swc-linux-x64-musl@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz#0ac9724fb44718fc97bfea971ac3fe17e486590e" - integrity sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA== +"@next/swc-linux-x64-musl@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.0.tgz#b82a29903ee2f12d8b64163ddf208ac519869550" + integrity sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg== "@next/swc-win32-arm64-msvc@14.2.15": version "14.2.15" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz#fb812cc4ca0042868e32a6a021da91943bb08b98" integrity sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g== -"@next/swc-win32-arm64-msvc@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz#932437d4cf27814e963ba8ae5f033b4421fab9ca" - integrity sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ== +"@next/swc-win32-arm64-msvc@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.0.tgz#98deae6cb1fccfb6a600e9faa6aa714402a9ab9a" + integrity sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg== "@next/swc-win32-ia32-msvc@14.2.15": version "14.2.15" @@ -2957,10 +2962,10 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz#18d68697002b282006771f8d92d79ade9efd35c4" integrity sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g== -"@next/swc-win32-x64-msvc@15.0.3": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz#940a6f7b370cdde0cc67eabe945d9e6d97e0be9f" - integrity sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA== +"@next/swc-win32-x64-msvc@15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.0.tgz#4b04a6a667c41fecdc63db57dd71ca7e84d0946b" + integrity sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -4334,12 +4339,12 @@ resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== -"@swc/helpers@0.5.13": - version "0.5.13" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c" - integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w== +"@swc/helpers@0.5.15": + version "0.5.15" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== dependencies: - tslib "^2.4.0" + tslib "^2.8.0" "@swc/helpers@0.5.5": version "0.5.5" @@ -4365,10 +4370,10 @@ dependencies: "@swc/counter" "^0.1.3" -"@tanstack/query-core@5.59.13": - version "5.59.13" - resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.13.tgz" - integrity sha512-Oou0bBu/P8+oYjXsJQ11j+gcpLAMpqW42UlokQYEz4dE7+hOtVO9rVuolJKgEccqzvyFzqX4/zZWY+R/v1wVsQ== +"@tanstack/query-core@5.62.3": + version "5.62.3" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.62.3.tgz#7cfde68f7d78807faebee2a2bb1e31b81067f46b" + integrity sha512-Jp/nYoz8cnO7kqhOlSv8ke/0MJRJVGuZ0P/JO9KQ+f45mpN90hrerzavyTKeSoT/pOzeoOUkv1Xd0wPsxAWXfg== "@tanstack/query-devtools@5.58.0": version "5.58.0" @@ -4383,11 +4388,11 @@ "@tanstack/query-devtools" "5.58.0" "@tanstack/react-query@^5.59.0": - version "5.59.14" - resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.14.tgz" - integrity sha512-2cM4x3Ka4Thl7/wnjf++EMGA2Is/RgPynn83D4kfGiJOGSjb5T2D3EEOlC8Nt6U2htLS3imOXjOSMEjC3K7JNg== + version "5.62.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.62.3.tgz#c6766b1764dcf924f6ed5fd88daf8d246e2f5c14" + integrity sha512-y2zDNKuhgiuMgsKkqd4AcsLIBiCfEO8U11AdrtAUihmLbRNztPrlcZqx2lH1GacZsx+y1qRRbCcJLYTtF1vKsw== dependencies: - "@tanstack/query-core" "5.59.13" + "@tanstack/query-core" "5.62.3" "@testing-library/dom@10.4.0": version "10.4.0" @@ -10061,29 +10066,6 @@ next-test-api-route-handler@^4.0.12: cookie "^1.0.1" core-js "^3.38.1" -next@15.0.3: - version "15.0.3" - resolved "https://registry.yarnpkg.com/next/-/next-15.0.3.tgz#804f5b772e7570ef1f088542a59860914d3288e9" - integrity sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw== - dependencies: - "@next/env" "15.0.3" - "@swc/counter" "0.1.3" - "@swc/helpers" "0.5.13" - busboy "1.6.0" - caniuse-lite "^1.0.30001579" - postcss "8.4.31" - styled-jsx "5.1.6" - optionalDependencies: - "@next/swc-darwin-arm64" "15.0.3" - "@next/swc-darwin-x64" "15.0.3" - "@next/swc-linux-arm64-gnu" "15.0.3" - "@next/swc-linux-arm64-musl" "15.0.3" - "@next/swc-linux-x64-gnu" "15.0.3" - "@next/swc-linux-x64-musl" "15.0.3" - "@next/swc-win32-arm64-msvc" "15.0.3" - "@next/swc-win32-x64-msvc" "15.0.3" - sharp "^0.33.5" - "next@^13.4 || ^14": version "14.2.15" resolved "https://registry.npmjs.org/next/-/next-14.2.15.tgz" @@ -10107,6 +10089,29 @@ next@15.0.3: "@next/swc-win32-ia32-msvc" "14.2.15" "@next/swc-win32-x64-msvc" "14.2.15" +next@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/next/-/next-15.1.0.tgz#be847cf67ac94ae23b57f3ea6d10642f3fc1ad69" + integrity sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw== + dependencies: + "@next/env" "15.1.0" + "@swc/counter" "0.1.3" + "@swc/helpers" "0.5.15" + busboy "1.6.0" + caniuse-lite "^1.0.30001579" + postcss "8.4.31" + styled-jsx "5.1.6" + optionalDependencies: + "@next/swc-darwin-arm64" "15.1.0" + "@next/swc-darwin-x64" "15.1.0" + "@next/swc-linux-arm64-gnu" "15.1.0" + "@next/swc-linux-arm64-musl" "15.1.0" + "@next/swc-linux-x64-gnu" "15.1.0" + "@next/swc-linux-x64-musl" "15.1.0" + "@next/swc-win32-arm64-msvc" "15.1.0" + "@next/swc-win32-x64-msvc" "15.1.0" + sharp "^0.33.5" + nimma@0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/nimma/-/nimma-0.2.2.tgz" @@ -12626,7 +12631,7 @@ tslib@^1.14.1: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== From c762ee856abdbb193395e4d63acec8e2ffb69c2f Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Wed, 11 Dec 2024 23:39:42 +0100 Subject: [PATCH 4/9] Bypass dplcms config for now Makes noise when generating code --- lib/config/dpl-cms/dplCmsConfig.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lib/config/dpl-cms/dplCmsConfig.ts b/lib/config/dpl-cms/dplCmsConfig.ts index 81634f1f..5d6843c0 100644 --- a/lib/config/dpl-cms/dplCmsConfig.ts +++ b/lib/config/dpl-cms/dplCmsConfig.ts @@ -1,11 +1,3 @@ -import { connection } from "next/server" - -import { fetcher } from "@/lib/graphql/fetchers/dpl-cms.fetcher" -import { - GetDplCmsConfigurationDocument, - GetDplCmsConfigurationQuery, -} from "@/lib/graphql/generated/dpl-cms/graphql" - // import { // GetDplCmsConfigurationQuery, // useGetDplCmsConfigurationQuery, @@ -32,14 +24,7 @@ import { // let dplCmsConfigClient = new QueryClient({}) const getDplCmsConfig = async () => { - // const result = await queryDplCmsConfig(dplCmsConfigClient) - await connection() - const result = await fetcher( - GetDplCmsConfigurationDocument - )() - const data: GetDplCmsConfigurationQuery = result.data - - return data + return {} } export const getDplCmsUniloginConfig = async () => { From 28c4881acb1d3bc85711ce113e82392578dda429 Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Wed, 11 Dec 2024 23:41:56 +0100 Subject: [PATCH 5/9] Rolling back dpl-cms fetcher And updating generated dpl-cms API changes --- lib/graphql/fetchers/dpl-cms.fetcher.ts | 20 +- lib/graphql/generated/dpl-cms/graphql.tsx | 367 +--------------------- 2 files changed, 10 insertions(+), 377 deletions(-) diff --git a/lib/graphql/fetchers/dpl-cms.fetcher.ts b/lib/graphql/fetchers/dpl-cms.fetcher.ts index 6e43205b..3812e822 100644 --- a/lib/graphql/fetchers/dpl-cms.fetcher.ts +++ b/lib/graphql/fetchers/dpl-cms.fetcher.ts @@ -1,40 +1,32 @@ export function fetcher( query: string, variables?: TVariables, - options?: RequestInit | RequestInit["headers"] + options?: RequestInit["headers"] ) { const dplCmsGraphqlEndpoint = process.env.NEXT_PUBLIC_GRAPHQL_SCHEMA_ENDPOINT_DPL_CMS const dplCmsGraphqlBasicToken = process.env.NEXT_PUBLIC_GRAPHQL_BASIC_TOKEN_DPL_CMS - + // console.log({ dplCmsGraphqlEndpoint }) if (!dplCmsGraphqlEndpoint || !dplCmsGraphqlBasicToken) { throw new Error("Missing DPL CMS GraphQL endpoint or basic token") } - - return async (): Promise<{ data: TData; headers: Headers }> => { - // eslint-disable-next-line no-console - console.log("I am fetching dpl cms data") + return async (): Promise => { + // console.log("debug req", JSON.stringify({ query, variables })) const res = await fetch(dplCmsGraphqlEndpoint, { method: "POST", ...{ headers: { "Content-Type": "application/json", Authorization: `Basic ${dplCmsGraphqlBasicToken}`, + ...options, }, - ...options, }, body: JSON.stringify({ query, variables }), }) - const json = await res.json() - if (json.errors) { const { message } = json.errors[0] - throw new Error(message) } - - const responseHeaders = res.headers - - return { data: json.data, headers: responseHeaders } + return json.data } } diff --git a/lib/graphql/generated/dpl-cms/graphql.tsx b/lib/graphql/generated/dpl-cms/graphql.tsx index cfa8d783..f337f935 100644 --- a/lib/graphql/generated/dpl-cms/graphql.tsx +++ b/lib/graphql/generated/dpl-cms/graphql.tsx @@ -14,7 +14,6 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } - Cursor: { input: unknown; output: unknown; } Email: { input: unknown; output: unknown; } Html: { input: unknown; output: unknown; } PhoneNumber: { input: unknown; output: unknown; } @@ -52,45 +51,6 @@ export type AddressCountry = { name?: Maybe; }; -/** A paginated set of results. */ -export type Connection = { - /** The edges of this connection. */ - edges: Array; - /** The nodes of the edges of this connection. */ - nodes: Array; - /** Information to aid in pagination. */ - pageInfo: ConnectionPageInfo; -}; - -/** Information about the page in a connection. */ -export type ConnectionPageInfo = { - __typename?: 'ConnectionPageInfo'; - /** The cursor for the last element in this page. */ - endCursor?: Maybe; - /** Whether there are more pages in this connection. */ - hasNextPage: Scalars['Boolean']['output']; - /** Whether there are previous pages in this connection. */ - hasPreviousPage: Scalars['Boolean']['output']; - /** The cursor for the first element in this page. */ - startCursor?: Maybe; -}; - -/** Choose how your sorts will occur and on which field. */ -export enum ConnectionSortKeys { - /** Sort by creation date */ - CreatedAt = 'CREATED_AT', - /** Sort by promoted status. */ - Promoted = 'PROMOTED', - /** Sort by sticky status. */ - Sticky = 'STICKY', - /** Sort by entity title. */ - Title = 'TITLE', - /** Sort by updated date */ - UpdatedAt = 'UPDATED_AT', - /** Sort by term weight. */ - Weight = 'WEIGHT' -} - /** A Date range has a start and an end. */ export type DateRange = { __typename?: 'DateRange'; @@ -119,22 +79,6 @@ export type DplConfiguration = { unilogin?: Maybe; }; -/** - * An edge in a connection. - * Provides the cursor to fetch data based on the position of the associated - * node. Specific edge implementations may provide more information about the - * relationship they represent. - */ -export type Edge = { - cursor: Scalars['Cursor']['output']; - node: EdgeNode; -}; - -/** This entity is accessible over an Edge connection. */ -export type EdgeNode = { - id: Scalars['ID']['output']; -}; - /** A file object to represent an managed file. */ export type File = { __typename?: 'File'; @@ -289,73 +233,6 @@ export type MediaVideo = MediaInterface & { status: Scalars['Boolean']['output']; }; -/** Entity type menu. */ -export type Menu = MenuInterface & { - __typename?: 'Menu'; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** The menu items. */ - items: Array; - /** The menu name. */ - name: Scalars['String']['output']; -}; - -/** List of menus available to load. */ -export enum MenuAvailable { - /** Administration */ - Admin = 'ADMIN', - /** Udvikling */ - Devel = 'DEVEL', - /** Main navigation */ - Main = 'MAIN', - /** Tools */ - Tools = 'TOOLS' -} - -/** Entity type menu. */ -export type MenuInterface = { - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** The menu items. */ - items: Array; - /** The menu name. */ - name: Scalars['String']['output']; -}; - -/** A menu item defined in the CMS. */ -export type MenuItem = { - __typename?: 'MenuItem'; - /** Attributes of this menu item. */ - attributes: MenuItemAttributes; - /** Child menu items of this menu item. */ - children: Array; - /** The description of the menu item. */ - description?: Maybe; - /** Whether this menu item is intended to be expanded. */ - expanded: Scalars['Boolean']['output']; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** Whether this menu item links to an internal route. */ - internal: Scalars['Boolean']['output']; - /** The language of the menu item. */ - langcode: Language; - /** The route this menu item uses. Route loading can be disabled per menu type. */ - route?: Maybe; - /** The title of the menu item. */ - title: Scalars['String']['output']; - /** The URL of the menu item. */ - url?: Maybe; -}; - -/** Menu item options set within the CMS. */ -export type MenuItemAttributes = { - __typename?: 'MenuItemAttributes'; - class?: Maybe; -}; - -/** Entity type menu. */ -export type MenuUnion = Menu; - /** The schema's entry-point for mutations. */ export type Mutation = { __typename?: 'Mutation'; @@ -364,7 +241,7 @@ export type Mutation = { }; /** Brug artikler til nyhedspræget indhold med en begrænset levetid. */ -export type NodeArticle = EdgeNode & NodeInterface & { +export type NodeArticle = NodeInterface & { __typename?: 'NodeArticle'; /** Bibliotek */ branch?: Maybe; @@ -419,21 +296,6 @@ export type NodeArticle = EdgeNode & NodeInterface & { title: Scalars['String']['output']; }; -/** A paginated set of results for NodeArticle. */ -export type NodeArticleConnection = Connection & { - __typename?: 'NodeArticleConnection'; - edges: Array; - nodes: Array; - pageInfo: ConnectionPageInfo; -}; - -/** Edge for NodeArticle. */ -export type NodeArticleEdge = Edge & { - __typename?: 'NodeArticleEdge'; - cursor: Scalars['Cursor']['output']; - node: NodeArticle; -}; - /** Entity type node. */ export type NodeInterface = { /** Tidspunktet hvor indholdselementet sidst blev redigeret. */ @@ -602,21 +464,6 @@ export type ParagraphContentSliderAutomatic = ParagraphInterface & { underlinedTitle?: Maybe; }; -/** Link med ikoner. Designet til jpg, jpeg, png, pdf, mp3, mov, mp4, og mpeg filer */ -export type ParagraphFiles = ParagraphInterface & { - __typename?: 'ParagraphFiles'; - /** The time that the Paragraph was created. */ - created: DateTime; - /** Filer */ - files?: Maybe>; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** The paragraphs entity language code. */ - langcode: Language; - /** Publiceret */ - status: Scalars['Boolean']['output']; -}; - /** Denne paragraph viser en liste af arrangementer filtreret på kategori, tags og filialer. */ export type ParagraphFilteredEventList = ParagraphInterface & { __typename?: 'ParagraphFilteredEventList'; @@ -658,21 +505,6 @@ export type ParagraphInterface = { status: Scalars['Boolean']['output']; }; -/** Links med ikoner. Designet til interne/eksterne links og links til søgeresultater ␣. */ -export type ParagraphLinks = ParagraphInterface & { - __typename?: 'ParagraphLinks'; - /** The time that the Paragraph was created. */ - created: DateTime; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** The paragraphs entity language code. */ - langcode: Language; - /** Link */ - link: Array; - /** Publiceret */ - status: Scalars['Boolean']['output']; -}; - /** Dette afsnit vil vise en liste over arrangementer, der er manuelt valgt. */ export type ParagraphManualEventList = ParagraphInterface & { __typename?: 'ParagraphManualEventList'; @@ -690,45 +522,6 @@ export type ParagraphManualEventList = ParagraphInterface & { title?: Maybe; }; -/** Entity type paragraph. */ -export type ParagraphMedias = ParagraphInterface & { - __typename?: 'ParagraphMedias'; - /** The time that the Paragraph was created. */ - created: DateTime; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** The paragraphs entity language code. */ - langcode: Language; - /** Billeder */ - medias: Array; - /** Publiceret */ - status: Scalars['Boolean']['output']; -}; - -/** Denne paragraph bruges til at anbefale et enkelt materiale. */ -export type ParagraphRecommendation = ParagraphInterface & { - __typename?: 'ParagraphRecommendation'; - /** The time that the Paragraph was created. */ - created: DateTime; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** - * Dette bestemmer om, et billede skal positioneres til venstre eller højre.
Hvis den ikke er slået til (standardadfærd), placeres billedet til - * venstre, hvis den er slået til, placeres billedet til højre.␣ - */ - imagePositionRight?: Maybe; - /** The paragraphs entity language code. */ - langcode: Language; - /** - * Titlen på det anbefalede materiale. Hvis du tilføjer en titel, vil - * beskrivelsesteksten af materialet ikke blive autoudfyldt. - */ - recommendationTitle?: Maybe; - /** Publiceret */ - status: Scalars['Boolean']['output']; -}; - /** En basal, formateret brødtekst */ export type ParagraphTextBody = ParagraphInterface & { __typename?: 'ParagraphTextBody'; @@ -745,22 +538,7 @@ export type ParagraphTextBody = ParagraphInterface & { }; /** Entity type paragraph. */ -export type ParagraphUnion = ParagraphAccordion | ParagraphBanner | ParagraphBreadcrumbChildren | ParagraphCardGridAutomatic | ParagraphCardGridManual | ParagraphContentSlider | ParagraphContentSliderAutomatic | ParagraphFiles | ParagraphFilteredEventList | ParagraphLinks | ParagraphManualEventList | ParagraphMedias | ParagraphRecommendation | ParagraphTextBody | ParagraphVideo; - -/** Indtast URL'en til den video, du vil indlejre. */ -export type ParagraphVideo = ParagraphInterface & { - __typename?: 'ParagraphVideo'; - /** The time that the Paragraph was created. */ - created: DateTime; - /** Indlejr video */ - embedVideo?: Maybe; - /** The Universally Unique IDentifier (UUID). */ - id: Scalars['ID']['output']; - /** The paragraphs entity language code. */ - langcode: Language; - /** Publiceret */ - status: Scalars['Boolean']['output']; -}; +export type ParagraphUnion = ParagraphAccordion | ParagraphBanner | ParagraphBreadcrumbChildren | ParagraphCardGridAutomatic | ParagraphCardGridManual | ParagraphContentSlider | ParagraphContentSliderAutomatic | ParagraphFilteredEventList | ParagraphManualEventList | ParagraphTextBody; /** The schema's entry-point for queries. */ export type Query = { @@ -769,22 +547,8 @@ export type Query = { dplConfiguration?: Maybe; /** Schema information. */ info: SchemaInformation; - /** Load a Menu by name. */ - menu?: Maybe; /** Load a NodeArticle entity by id */ nodeArticle?: Maybe; - /** List of all NodeArticle on the platform. */ - nodeArticles: NodeArticleConnection; - /** Load a ParagraphFiles entity by id */ - paragraphFiles?: Maybe; - /** Load a ParagraphLinks entity by id */ - paragraphLinks?: Maybe; - /** Load a ParagraphMedias entity by id */ - paragraphMedias?: Maybe; - /** Load a ParagraphRecommendation entity by id */ - paragraphRecommendation?: Maybe; - /** Load a ParagraphVideo entity by id */ - paragraphVideo?: Maybe; /** Load a Route by path. */ route?: Maybe; /** NextJs SSG Node Information. */ @@ -792,13 +556,6 @@ export type Query = { }; -/** The schema's entry-point for queries. */ -export type QueryMenuArgs = { - langcode?: InputMaybe; - name: MenuAvailable; -}; - - /** The schema's entry-point for queries. */ export type QueryNodeArticleArgs = { id: Scalars['ID']['input']; @@ -807,58 +564,6 @@ export type QueryNodeArticleArgs = { }; -/** The schema's entry-point for queries. */ -export type QueryNodeArticlesArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - langcode?: InputMaybe; - last?: InputMaybe; - reverse?: InputMaybe; - sortKey?: InputMaybe; -}; - - -/** The schema's entry-point for queries. */ -export type QueryParagraphFilesArgs = { - id: Scalars['ID']['input']; - langcode?: InputMaybe; - revision?: InputMaybe; -}; - - -/** The schema's entry-point for queries. */ -export type QueryParagraphLinksArgs = { - id: Scalars['ID']['input']; - langcode?: InputMaybe; - revision?: InputMaybe; -}; - - -/** The schema's entry-point for queries. */ -export type QueryParagraphMediasArgs = { - id: Scalars['ID']['input']; - langcode?: InputMaybe; - revision?: InputMaybe; -}; - - -/** The schema's entry-point for queries. */ -export type QueryParagraphRecommendationArgs = { - id: Scalars['ID']['input']; - langcode?: InputMaybe; - revision?: InputMaybe; -}; - - -/** The schema's entry-point for queries. */ -export type QueryParagraphVideoArgs = { - id: Scalars['ID']['input']; - langcode?: InputMaybe; - revision?: InputMaybe; -}; - - /** The schema's entry-point for queries. */ export type QueryRouteArgs = { langcode?: InputMaybe; @@ -1166,22 +871,14 @@ export type GetArticleQueryVariables = Exact<{ }>; -export type GetArticleQuery = { __typename?: 'Query', nodeArticle?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFiles' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphLinks' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphMedias' } | { __typename: 'ParagraphRecommendation' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null } | { __typename: 'ParagraphVideo' }> | null } | null }; +export type GetArticleQuery = { __typename?: 'Query', nodeArticle?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null }> | null } | null }; export type GetArticleByRouteQueryVariables = Exact<{ path: Scalars['String']['input']; }>; -export type GetArticleByRouteQuery = { __typename?: 'Query', route?: { __typename?: 'RouteExternal' } | { __typename?: 'RouteInternal', entity?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFiles' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphLinks' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphMedias' } | { __typename: 'ParagraphRecommendation' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null } | { __typename: 'ParagraphVideo' }> | null } | null } | { __typename?: 'RouteRedirect' } | null }; - -export type ExtractArticlesQueryVariables = Exact<{ - pageSize: Scalars['Int']['input']; - cursor?: InputMaybe; -}>; - - -export type ExtractArticlesQuery = { __typename?: 'Query', nodeArticles: { __typename?: 'NodeArticleConnection', nodes: Array<{ __typename?: 'NodeArticle', id: string, path: string }>, edges: Array<{ __typename?: 'NodeArticleEdge', cursor: unknown }>, pageInfo: { __typename?: 'ConnectionPageInfo', hasNextPage: boolean } } }; +export type GetArticleByRouteQuery = { __typename?: 'Query', route?: { __typename?: 'RouteExternal' } | { __typename?: 'RouteInternal', entity?: { __typename?: 'NodeArticle', title: string, subtitle?: string | null, paragraphs?: Array<{ __typename: 'ParagraphAccordion' } | { __typename: 'ParagraphBanner' } | { __typename: 'ParagraphBreadcrumbChildren' } | { __typename: 'ParagraphCardGridAutomatic' } | { __typename: 'ParagraphCardGridManual' } | { __typename: 'ParagraphContentSlider' } | { __typename: 'ParagraphContentSliderAutomatic' } | { __typename: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null }> | null } | null } | { __typename?: 'RouteRedirect' } | null }; export type GetDplCmsConfigurationQueryVariables = Exact<{ [key: string]: never; }>; @@ -1308,62 +1005,6 @@ useSuspenseGetArticleByRouteQuery.getKey = (variables: GetArticleByRouteQueryVar useGetArticleByRouteQuery.fetcher = (variables: GetArticleByRouteQueryVariables, options?: RequestInit['headers']) => fetcher(GetArticleByRouteDocument, variables, options); -export const ExtractArticlesDocument = ` - query extractArticles($pageSize: Int!, $cursor: Cursor) { - nodeArticles(first: $pageSize, after: $cursor) { - nodes { - id - path - } - edges { - cursor - } - pageInfo { - hasNextPage - } - } -} - `; - -export const useExtractArticlesQuery = < - TData = ExtractArticlesQuery, - TError = unknown - >( - variables: ExtractArticlesQueryVariables, - options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } - ) => { - - return useQuery( - { - queryKey: ['extractArticles', variables], - queryFn: fetcher(ExtractArticlesDocument, variables), - ...options - } - )}; - -useExtractArticlesQuery.getKey = (variables: ExtractArticlesQueryVariables) => ['extractArticles', variables]; - -export const useSuspenseExtractArticlesQuery = < - TData = ExtractArticlesQuery, - TError = unknown - >( - variables: ExtractArticlesQueryVariables, - options?: Omit, 'queryKey'> & { queryKey?: UseSuspenseQueryOptions['queryKey'] } - ) => { - - return useSuspenseQuery( - { - queryKey: ['extractArticlesSuspense', variables], - queryFn: fetcher(ExtractArticlesDocument, variables), - ...options - } - )}; - -useSuspenseExtractArticlesQuery.getKey = (variables: ExtractArticlesQueryVariables) => ['extractArticlesSuspense', variables]; - - -useExtractArticlesQuery.fetcher = (variables: ExtractArticlesQueryVariables, options?: RequestInit['headers']) => fetcher(ExtractArticlesDocument, variables, options); - export const GetDplCmsConfigurationDocument = ` query getDplCmsConfiguration { dplConfiguration { From be8e048a0546fa811b75a48f2a199256c893f02b Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Wed, 11 Dec 2024 23:43:09 +0100 Subject: [PATCH 6/9] Article -> artikel --- .../articles-ssg.dpl-cms.graphql | 14 ------ app/article/[...pathElements]/loadArticle.ts | 41 ----------------- .../[...pathElements]/loadArticlesSsg.ts | 45 ------------------- .../[...pathElements]/article.dpl-cms.graphql | 0 app/artikel/[...pathElements]/loadArticle.ts | 15 +++++++ .../[...pathElements]/loadArticlesSsg.ts | 45 +++++++++++++++++++ .../[...pathElements]/page.tsx | 6 ++- 7 files changed, 65 insertions(+), 101 deletions(-) delete mode 100644 app/article/[...pathElements]/articles-ssg.dpl-cms.graphql delete mode 100644 app/article/[...pathElements]/loadArticle.ts delete mode 100644 app/article/[...pathElements]/loadArticlesSsg.ts rename app/{article => artikel}/[...pathElements]/article.dpl-cms.graphql (100%) create mode 100644 app/artikel/[...pathElements]/loadArticle.ts create mode 100644 app/artikel/[...pathElements]/loadArticlesSsg.ts rename app/{article => artikel}/[...pathElements]/page.tsx (77%) diff --git a/app/article/[...pathElements]/articles-ssg.dpl-cms.graphql b/app/article/[...pathElements]/articles-ssg.dpl-cms.graphql deleted file mode 100644 index 49686eaf..00000000 --- a/app/article/[...pathElements]/articles-ssg.dpl-cms.graphql +++ /dev/null @@ -1,14 +0,0 @@ -query extractArticles($pageSize: Int!, $cursor: Cursor) { - nodeArticles(first: $pageSize, after: $cursor) { - nodes { - id - path - } - edges { - cursor - } - pageInfo { - hasNextPage - } - } -} diff --git a/app/article/[...pathElements]/loadArticle.ts b/app/article/[...pathElements]/loadArticle.ts deleted file mode 100644 index e45c57fe..00000000 --- a/app/article/[...pathElements]/loadArticle.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { cacheTag } from "next/dist/server/use-cache/cache-tag" - -import { fetcher } from "@/lib/graphql/fetchers/dpl-cms.fetcher" -import { - GetArticleByRouteDocument, - GetArticleByRouteQuery, -} from "@/lib/graphql/generated/dpl-cms/graphql" - -// import { unstable_cache } from "next/cache"; - -const loadArticle = async (path: string) => { - // "use cache" - // const queryClient = getQueryClient() - // Make sure that we do not use stale data - // since we are using cacheTags for invalidation. - // queryClient.setDefaultOptions({ queries: { staleTime: 0 } }) - // const result = await queryClient.fetchQuery({ - // queryKey: useGetArticleByRouteQuery.getKey({ path }), - // queryFn: useGetArticleByRouteQuery.fetcher({ path }), - // }) - // const data: GetArticleByRouteQuery = result.data - // const getArticle = unstable_cache( - // async (path: string) =>fetcher( - // GetArticleByRouteDocument, - // { path } - // )(), - // ['my-app-user'] - // ); - - const result = await fetcher( - GetArticleByRouteDocument, - { path } - )() - - const data: GetArticleByRouteQuery = result.data - - cacheTag("abe") - return data -} - -export default loadArticle diff --git a/app/article/[...pathElements]/loadArticlesSsg.ts b/app/article/[...pathElements]/loadArticlesSsg.ts deleted file mode 100644 index 7468b29c..00000000 --- a/app/article/[...pathElements]/loadArticlesSsg.ts +++ /dev/null @@ -1,45 +0,0 @@ -import getQueryClient from "@/lib/getQueryClient" -import { - ExtractArticlesQuery, - useExtractArticlesQuery, -} from "@/lib/graphql/generated/dpl-cms/graphql" - -const loadArticlesSsg = async () => { - const queryClient = getQueryClient() - const pageSize = 100 - // We have a while loop. - // Just to be safe we do not allow more than 100 iterations. - const maxRuns = 100 - let runNumber = 0 - - const { - nodeArticles: { nodes, edges, pageInfo }, - } = await queryClient.fetchQuery({ - queryKey: useExtractArticlesQuery.getKey({ pageSize }), - queryFn: useExtractArticlesQuery.fetcher({ pageSize }), - }) - let allNodes = nodes - let allEdges = edges - let cursor = edges[edges.length - 1]?.cursor - - while (pageInfo.hasNextPage && runNumber < maxRuns) { - const { - nodeArticles: { nodes: newNodes, edges: newEdges }, - } = await queryClient.fetchQuery({ - queryKey: useExtractArticlesQuery.getKey({ pageSize, cursor }), - queryFn: useExtractArticlesQuery.fetcher({ pageSize, cursor }), - }) - - allNodes = [...allNodes, ...newNodes] - allEdges = [...allEdges, ...newEdges] - cursor = newEdges[newEdges.length - 1]?.cursor - // eslint-disable-next-line no-console - console.log({ allNodes, allEdges, cursor }) - - runNumber += 1 - } - - return { nodes: allNodes } -} - -export default loadArticlesSsg diff --git a/app/article/[...pathElements]/article.dpl-cms.graphql b/app/artikel/[...pathElements]/article.dpl-cms.graphql similarity index 100% rename from app/article/[...pathElements]/article.dpl-cms.graphql rename to app/artikel/[...pathElements]/article.dpl-cms.graphql diff --git a/app/artikel/[...pathElements]/loadArticle.ts b/app/artikel/[...pathElements]/loadArticle.ts new file mode 100644 index 00000000..8e75e61b --- /dev/null +++ b/app/artikel/[...pathElements]/loadArticle.ts @@ -0,0 +1,15 @@ +import { fetcher } from "@/lib/graphql/fetchers/dpl-cms.fetcher" +import { + GetArticleByRouteDocument, + GetArticleByRouteQuery, +} from "@/lib/graphql/generated/dpl-cms/graphql" + +const loadArticle = async (path: string) => { + const data = await fetcher(GetArticleByRouteDocument, { + path, + })() + + return data +} + +export default loadArticle diff --git a/app/artikel/[...pathElements]/loadArticlesSsg.ts b/app/artikel/[...pathElements]/loadArticlesSsg.ts new file mode 100644 index 00000000..6ca2eeb6 --- /dev/null +++ b/app/artikel/[...pathElements]/loadArticlesSsg.ts @@ -0,0 +1,45 @@ +// import getQueryClient from "@/lib/getQueryClient" +// import { +// ExtractArticlesQuery, +// useExtractArticlesQuery, +// } from "@/lib/graphql/generated/dpl-cms/graphql" + +// const loadArticlesSsg = async () => { +// const queryClient = getQueryClient() +// const pageSize = 100 +// // We have a while loop. +// // Just to be safe we do not allow more than 100 iterations. +// const maxRuns = 100 +// let runNumber = 0 + +// const { +// nodeArticles: { nodes, edges, pageInfo }, +// } = await queryClient.fetchQuery({ +// queryKey: useExtractArticlesQuery.getKey({ pageSize }), +// queryFn: useExtractArticlesQuery.fetcher({ pageSize }), +// }) +// let allNodes = nodes +// let allEdges = edges +// let cursor = edges[edges.length - 1]?.cursor + +// while (pageInfo.hasNextPage && runNumber < maxRuns) { +// const { +// nodeArticles: { nodes: newNodes, edges: newEdges }, +// } = await queryClient.fetchQuery({ +// queryKey: useExtractArticlesQuery.getKey({ pageSize, cursor }), +// queryFn: useExtractArticlesQuery.fetcher({ pageSize, cursor }), +// }) + +// allNodes = [...allNodes, ...newNodes] +// allEdges = [...allEdges, ...newEdges] +// cursor = newEdges[newEdges.length - 1]?.cursor +// // eslint-disable-next-line no-console +// console.log({ allNodes, allEdges, cursor }) + +// runNumber += 1 +// } + +// return { nodes: allNodes } +// } + +// export default loadArticlesSsg diff --git a/app/article/[...pathElements]/page.tsx b/app/artikel/[...pathElements]/page.tsx similarity index 77% rename from app/article/[...pathElements]/page.tsx rename to app/artikel/[...pathElements]/page.tsx index 610e4c89..04f09830 100644 --- a/app/article/[...pathElements]/page.tsx +++ b/app/artikel/[...pathElements]/page.tsx @@ -7,9 +7,13 @@ const Page = async (props: { params: Promise<{ pathElements: string[] }> }) => { const { pathElements } = params - const path = ["/artikler", ...pathElements].join("/") + const path = [...pathElements].join("/") const data = await loadArticle(path) return
{JSON.stringify(data, null, 2)}
} export default Page + +export async function generateStaticParams() { + return [] +} From 168ba6a5bbf5757ec8bbb77f36d14b277cd1e711 Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Wed, 11 Dec 2024 23:43:53 +0100 Subject: [PATCH 7/9] Introducing revalidation path resolving depending on content type --- app/cache/revalidate/helper.ts | 32 ++++++++++++++++++++++++++++++++ app/cache/revalidate/route.ts | 13 +++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 app/cache/revalidate/helper.ts diff --git a/app/cache/revalidate/helper.ts b/app/cache/revalidate/helper.ts new file mode 100644 index 00000000..710560b2 --- /dev/null +++ b/app/cache/revalidate/helper.ts @@ -0,0 +1,32 @@ +import { z } from "zod" + +export const paramsSchema = z.union([ + z.object({ + type: z.literal("tag"), + tags: z.array(z.string().regex(/^[a-zA-Z0-9-]+$/)), + }), + z.object({ + type: z.literal("path"), + paths: z.array(z.string().regex(/^\/[a-zA-Z0-9-\/]+$/)), + contentType: z.string().optional(), + }), +]) + +export const resolveRevalidationPath = ({ + path, + params, +}: { + path: string + params: z.infer +}) => { + if (params.type !== "path") { + return + } + + switch (params.contentType ?? "unknown") { + case "article": + return `/artikel${path}` + case "unknown": + return path + } +} diff --git a/app/cache/revalidate/route.ts b/app/cache/revalidate/route.ts index f8852610..f6c95d27 100644 --- a/app/cache/revalidate/route.ts +++ b/app/cache/revalidate/route.ts @@ -4,6 +4,8 @@ import { z } from "zod" import goConfig from "@/lib/config/goConfig" +import { resolveRevalidationPath } from "./helper" + const paramsSchema = z.union([ z.object({ type: z.literal("tag"), @@ -12,6 +14,7 @@ const paramsSchema = z.union([ z.object({ type: z.literal("path"), paths: z.array(z.string().regex(/^\/[a-zA-Z0-9-\/]+$/)), + contentType: z.string().optional(), }), ]) @@ -26,7 +29,6 @@ export async function POST(request: NextRequest) { const body = await request.json() try { const params = paramsSchema.parse(body) - switch (params.type) { case "tag": params.tags.forEach(tag => { @@ -37,9 +39,12 @@ export async function POST(request: NextRequest) { break case "path": params.paths.forEach(path => { - // eslint-disable-next-line no-console - console.log("Revalidating path:", path) - revalidatePath(path) + const pathToRevalidate = resolveRevalidationPath({ path, params }) + if (pathToRevalidate) { + // eslint-disable-next-line no-console + console.log("Revalidating path:", pathToRevalidate) + revalidatePath(pathToRevalidate) + } }) break } From 638e4a4494a3466235ef216b2dce910bf4e347de Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Thu, 12 Dec 2024 10:55:28 +0100 Subject: [PATCH 8/9] Allow : in cache tags - duh --- app/cache/revalidate/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cache/revalidate/route.ts b/app/cache/revalidate/route.ts index f6c95d27..c7f77489 100644 --- a/app/cache/revalidate/route.ts +++ b/app/cache/revalidate/route.ts @@ -9,7 +9,7 @@ import { resolveRevalidationPath } from "./helper" const paramsSchema = z.union([ z.object({ type: z.literal("tag"), - tags: z.array(z.string().regex(/^[a-zA-Z0-9-]+$/)), + tags: z.array(z.string().regex(/^[a-zA-Z0-9-:]+$/)), }), z.object({ type: z.literal("path"), From 5a266732bdc59015e3d5f2bc155e1c260c177b08 Mon Sep 17 00:00:00 2001 From: Mikkel Jakobsen Date: Thu, 12 Dec 2024 10:55:56 +0100 Subject: [PATCH 9/9] Write body in error logging for easier debugging --- app/cache/revalidate/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/cache/revalidate/route.ts b/app/cache/revalidate/route.ts index c7f77489..b9b81cba 100644 --- a/app/cache/revalidate/route.ts +++ b/app/cache/revalidate/route.ts @@ -51,6 +51,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ params }) } catch (e) { + console.error(body) // TODO: Error logging console.error(e) return NextResponse.json({ error: "Wrong input" }, { status: 422 })