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