diff --git a/.env.test b/.env.test index 0f034e22..3896b778 100644 --- a/.env.test +++ b/.env.test @@ -7,6 +7,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 new file mode 100644 index 00000000..f05c4081 --- /dev/null +++ b/__tests__/revalidating-cache.test.ts @@ -0,0 +1,181 @@ +// @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 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({ + 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: "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", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + 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", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + 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", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + 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", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + 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", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + 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", + Authorization: "Bearer i9yUwqwgVLfvQ+4f8TnNRZcmHOYOKuUOTpZraUIWUCc=", + }, + body: JSON.stringify({ type: "tag", paths: ["!wrong-tag", "another tag"] }), + }) + expect(res.status).toBe(422) + }, + }) + }) +}) 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/artikel/[...pathElements]/article.dpl-cms.graphql b/app/artikel/[...pathElements]/article.dpl-cms.graphql new file mode 100644 index 00000000..b655210a --- /dev/null +++ b/app/artikel/[...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/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/artikel/[...pathElements]/page.tsx b/app/artikel/[...pathElements]/page.tsx new file mode 100644 index 00000000..04f09830 --- /dev/null +++ b/app/artikel/[...pathElements]/page.tsx @@ -0,0 +1,19 @@ +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 = [...pathElements].join("/") + const data = await loadArticle(path) + return
{JSON.stringify(data, null, 2)}
+} + +export default Page + +export async function generateStaticParams() { + return [] +} 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/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 new file mode 100644 index 00000000..b9b81cba --- /dev/null +++ b/app/cache/revalidate/route.ts @@ -0,0 +1,61 @@ +import { revalidatePath, revalidateTag } from "next/cache" +import { NextRequest, NextResponse } from "next/server" +import { z } from "zod" + +import goConfig from "@/lib/config/goConfig" + +import { resolveRevalidationPath } from "./helper" + +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 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) + 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 => { + const pathToRevalidate = resolveRevalidationPath({ path, params }) + if (pathToRevalidate) { + // eslint-disable-next-line no-console + console.log("Revalidating path:", pathToRevalidate) + revalidatePath(pathToRevalidate) + } + }) + break + } + + return NextResponse.json({ params }) + } catch (e) { + console.error(body) + // TODO: Error logging + console.error(e) + return NextResponse.json({ error: "Wrong input" }, { status: 422 }) + } +} + +export const dynamic = "force-dynamic" 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..5d6843c0 100644 --- a/lib/config/dpl-cms/dplCmsConfig.ts +++ b/lib/config/dpl-cms/dplCmsConfig.ts @@ -1,32 +1,35 @@ -import { QueryClient } from "@tanstack/react-query" - -import { - 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 - }) - - return dplConfiguration ?? null -} +// import { +// GetDplCmsConfigurationQuery, +// useGetDplCmsConfigurationQuery, +// } from "@/lib/graphql/generated/dpl-cms/graphql" + +// 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 + return {} } export const getDplCmsUniloginConfig = async () => { const config = await getDplCmsConfig() + // @ts-ignore return config?.unilogin ?? null } 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 diff --git a/lib/graphql/fetchers/dpl-cms.fetcher.ts b/lib/graphql/fetchers/dpl-cms.fetcher.ts index cfc4df4f..3812e822 100644 --- a/lib/graphql/fetchers/dpl-cms.fetcher.ts +++ b/lib/graphql/fetchers/dpl-cms.fetcher.ts @@ -5,12 +5,12 @@ export function fetcher( ) { 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 => { + // console.log("debug req", JSON.stringify({ query, variables })) const res = await fetch(dplCmsGraphqlEndpoint, { method: "POST", ...{ @@ -22,15 +22,11 @@ export function fetcher( }, body: JSON.stringify({ query, variables }), }) - const json = await res.json() - if (json.errors) { const { message } = json.errors[0] - throw new Error(message) } - return json.data } } diff --git a/lib/graphql/generated/dpl-cms/graphql.tsx b/lib/graphql/generated/dpl-cms/graphql.tsx index 55fdbd3b..f337f935 100644 --- a/lib/graphql/generated/dpl-cms/graphql.tsx +++ b/lib/graphql/generated/dpl-cms/graphql.tsx @@ -549,6 +549,10 @@ export type Query = { info: SchemaInformation; /** Load a NodeArticle entity by id */ nodeArticle?: Maybe; + /** Load a Route by path. */ + route?: Maybe; + /** NextJs SSG Node Information. */ + ssgNodeInformation: SsgNodeInformation; }; @@ -559,6 +563,68 @@ export type QueryNodeArticleArgs = { 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 +646,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'; @@ -798,6 +873,13 @@ 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 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: 'ParagraphFilteredEventList' } | { __typename: 'ParagraphManualEventList' } | { __typename: 'ParagraphTextBody', body?: { __typename?: 'Text', value?: string | null } | null }> | null } | null } | { __typename?: 'RouteRedirect' } | null }; + export type GetDplCmsConfigurationQueryVariables = Exact<{ [key: string]: never; }>; @@ -861,6 +943,68 @@ 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 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==