| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- import { cookies, headers } from "next/headers";
- import { revalidatePath } from "next/cache";
- import { NextRequest, NextResponse } from "next/server";
- import {
- BagistoCreateUserOperation,
- BagistoProductInfo,
- BagistoSession,
- BagistoUser,
- ImageInfo,
- } from "@/types/types";
- import {
- BAGISTO_SESSION,
- HIDDEN_PRODUCT_TAG,
- STOREFRONT_KEY,
- } from "../constants";
- import { getServerSession } from "next-auth";
- import {
- CUSTOMER_LOGOUT,
- CUSTOMER_REGISTRATION,
- FORGET_PASSWORD,
- } from "@/graphql/customer/mutations";
- import { DocumentNode } from "graphql";
- import { GRAPHQL_URL, REST_API_URL } from "@/utils/constants";
- import {
- GET_FOOTER,
- GET_THEME_CUSTOMIZATION,
- PAGE_BY_URL_KEY,
- } from "@/graphql";
- import { SUBSCRIBE_TO_NEWSLETTER } from "@/graphql/theme/mutations";
- import { cachedGraphQLRequest } from "@/utils/hooks/useCache";
- import { authOptions } from "@utils/auth";
- import { RegisterInputs } from "@components/customer/RegistrationForm";
- import {
- GetFooterResponse,
- ThemeCustomizationResult,
- ThemeCustomizationResponse,
- PageData,
- } from "@/types/theme/theme-customization";
- type ExtractVariables<T> = T extends { variables: object }
- ? T["variables"]
- : never;
- interface PageByUrlKeyResponse {
- pageByUrlKeypages?: PageData[];
- }
- // rest api fetch
- export async function restApiFetch<T>({
- api,
- method,
- cache = "force-cache",
- headers,
- tags,
- variables,
- isCookies = true,
- guestToken,
- revalidate = 60,
- }: {
- api: string;
- method: "POST" | "GET";
- cache?: RequestCache;
- headers?: HeadersInit | Record<string, string>;
- tags?: string[];
- variables?: ExtractVariables<T>;
- isCookies?: boolean;
- guestToken?: string;
- revalidate?: number;
- }): Promise<{ status: number; body: T } | never> {
- try {
- const apiUrl = api.startsWith("http") ? api : `${REST_API_URL}${api}`;
- let bagistoCartId = "";
- let accessToken: string | undefined = undefined;
- if (isCookies) {
- const cookieStore = await cookies();
- bagistoCartId = cookieStore.get(BAGISTO_SESSION)?.value ?? "";
- const sessions = (await getServerSession(
- authOptions,
- )) as BagistoSession | null;
- accessToken = sessions?.user?.accessToken;
- }
- const baseHeaders: Record<string, string> = {
- "Content-Type": "application/json",
- "X-STOREFRONT-KEY": STOREFRONT_KEY,
- };
- if (accessToken) {
- baseHeaders.Authorization = `Bearer ${accessToken}`;
- } else if (guestToken) {
- baseHeaders.Authorization = `Bearer ${guestToken}`;
- }
- if (bagistoCartId) {
- baseHeaders.Cookie = `${BAGISTO_SESSION}=${bagistoCartId}`;
- }
- if (isCookies && headers) {
- if (headers instanceof Headers) {
- headers.forEach((value, key) => (baseHeaders[key] = value));
- } else {
- Object.assign(baseHeaders, headers);
- }
- }
- console.log('restApiFetch --- baseHeaders:', baseHeaders)
- const param: RequestInit = {
- method: method,
- headers: baseHeaders,
- cache,
- next: {
- revalidate: cache === "no-store" ? 0 : revalidate || 60,
- ...(tags && { tags }),
- },
- };
- if(variables) {
- param.body = JSON.stringify({...variables});
- }
- const result = await fetch(apiUrl, param);
- console.log('restApiFetch --- result:', result)
- const body = await result.json();
- if (body.errors) throw body.errors[0];
- return { status: result.status, body };
- } catch (e) {
- throw e;
- }
- }
- export async function bagistoFetch<T>({
- cache = "force-cache",
- headers,
- query,
- tags,
- variables,
- isCookies = true,
- guestToken,
- revalidate = 60,
- operationName = ''
- }: {
- cache?: RequestCache;
- headers?: HeadersInit | Record<string, string>;
- query: string | DocumentNode;
- tags?: string[];
- variables?: ExtractVariables<T>;
- isCookies?: boolean;
- guestToken?: string;
- revalidate?: number;
- operationName?: string;
- }): Promise<{ status: number; body: T } | never> {
- try {
- const queryString =
- typeof query === "string" ? query : (query.loc?.source?.body ?? "");
- let bagistoCartId = "";
- let accessToken: string | undefined = undefined;
- if (isCookies) {
- const cookieStore = await cookies();
- bagistoCartId = cookieStore.get(BAGISTO_SESSION)?.value ?? "";
- const sessions = (await getServerSession(
- authOptions,
- )) as BagistoSession | null;
- accessToken = sessions?.user?.accessToken;
- }
- const baseHeaders: Record<string, string> = {
- "Content-Type": "application/json",
- "X-STOREFRONT-KEY": STOREFRONT_KEY,
- };
- if (accessToken) {
- baseHeaders.Authorization = `Bearer ${accessToken}`;
- } else if (guestToken) {
- baseHeaders.Authorization = `Bearer ${guestToken}`;
- }
- if (bagistoCartId) {
- baseHeaders.Cookie = `${BAGISTO_SESSION}=${bagistoCartId}`;
- }
- if (isCookies && headers) {
- if (headers instanceof Headers) {
- headers.forEach((value, key) => (baseHeaders[key] = value));
- } else {
- Object.assign(baseHeaders, headers);
- }
- }
- // let cc = await cookies();
- // console.log('bagistoFetch --- url:', GRAPHQL_URL,isCookies);
- // console.log('bagistoFetch --- queryString:', queryString);
- console.log('bagistoFetch --- baseHeaders:', baseHeaders);
- // console.log('bagistoFetch --- cookies:', cc.getAll());
- // console.log('bagistoFetch --- variables:', variables);
- const result = await fetch(GRAPHQL_URL, {
- method: "POST",
- headers: baseHeaders,
- body: JSON.stringify({
- query: queryString,
- ...(variables && { variables }),
- }),
- cache,
- next: {
- revalidate: cache === "no-store" ? 0 : revalidate || 60,
- ...(tags && { tags }),
- },
- });
- const body = await result.json();
- console.log('bagistoFetch --- body:',body);
- if (body.errors) {
- if(operationName === 'GetCartItem' && body.errors[0].message === 'Cart not found') {
- return { status: result.status, body };
- }
- throw body.errors[0]
- }
- return { status: result.status, body };
- } catch (e) {
- throw e;
- }
- }
- export async function bagistoFetchNoSession<T>({
- query,
- tags,
- variables,
- headers,
- cache = "force-cache",
- revalidate = 60,
- }: {
- query: string;
- tags?: string[];
- variables?: ExtractVariables<T>;
- headers?: HeadersInit | Record<string, string>;
- cache?: RequestCache;
- isCookies?: boolean;
- revalidate?: number;
- }): Promise<{ status: number; body: T } | never> {
- try {
- const result = await fetch(GRAPHQL_URL, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-STOREFRONT-KEY": STOREFRONT_KEY,
- "x-locale": "en",
- "x-currency": "USD",
- ...headers,
- },
- body: JSON.stringify({
- ...(query && { query }),
- ...(variables && { variables }),
- }),
- cache,
- next: {
- revalidate: cache === "no-store" ? 0 : revalidate || 60,
- ...(tags && { tags }),
- },
- });
- const body = await result.json();
- if (body.errors) {
- throw body.errors[0];
- }
- return {
- status: result.status,
- body,
- };
- } catch (e) {
- throw { error: e, query };
- }
- }
- export const removeEdgesAndNodes = <T>(array: Array<T>) => {
- return array?.map((edge) => edge);
- };
- const reshapeImages = (images: Array<ImageInfo>, productTitle: string) => {
- const flattened = removeEdgesAndNodes(images);
- return flattened.map((image) => {
- const filename = image?.url.match(/.*\/(.*)\..*/)?.[1];
- return {
- ...image,
- altText: image?.altText || `${productTitle} - ${filename}`,
- };
- });
- };
- const reshapeProduct = (
- product: BagistoProductInfo,
- filterHiddenProducts: boolean = true,
- ) => {
- if (
- !product ||
- (filterHiddenProducts && product.tags?.includes(HIDDEN_PRODUCT_TAG))
- ) {
- return undefined;
- }
- const { images, variants, ...rest } = product;
- return {
- ...rest,
- images: reshapeImages(images, product.title),
- variants: removeEdgesAndNodes(variants),
- };
- };
- export const reshapeProducts = (products: BagistoProductInfo[]) => {
- const reshapedProducts = [];
- for (const product of products) {
- if (product) {
- const reshapedProduct = reshapeProduct(product);
- if (reshapedProduct) {
- reshapedProducts.push(reshapedProduct);
- }
- }
- }
- return reshapedProducts;
- };
- export async function createUserToLogin(
- input: RegisterInputs,
- ): Promise<BagistoUser> {
- try {
- const { passwordConfirmation, ...userInput } = input;
- const res = await bagistoFetch<BagistoCreateUserOperation>({
- query: CUSTOMER_REGISTRATION,
- variables: {
- input: {
- ...userInput,
- confirmPassword: passwordConfirmation,
- status: "1",
- isVerified: "1",
- isSuspended: "0",
- subscribedToNewsLetter: true,
- },
- },
- cache: "no-store",
- revalidate: 3600,
- });
- return res.body.data.createCustomer.customer;
- } catch (error: any) {
- throw new Error(error?.message || "Registration failed");
- }
- }
- export async function logoutUser() {
- try {
- const session = await getServerSession(authOptions);
- const token = session?.user?.accessToken;
- if (!token) {
- return {
- success: false,
- message: "User token missing",
- };
- }
- /**
- * @todo CUSTOMER_LOGOUT 接口会报错,待修复(确定是php后端接口报错)
- */
- const res = await bagistoFetch<{
- data: { createLogout: { logout: { success: boolean; message: string } } };
- variables: { input: { token: string } };
- }>({
- query: CUSTOMER_LOGOUT,
- isCookies: true,
- revalidate: 3600,
- });
- const success = res?.body?.data?.createLogout?.logout?.success ?? false;
- const message =
- res?.body?.data?.createLogout?.logout?.message ?? "Logout executed";
- const cookieStore = await cookies();
- cookieStore.delete(BAGISTO_SESSION);
- return {
- success,
- message,
- };
- } catch (error: unknown) {
- return {
- success: false,
- message: error instanceof Error ? error.message : "Something went wrong",
- };
- }
- }
- export async function recoverUserLogin(
- input: Record<string, unknown>,
- ): Promise<unknown> {
- try {
- return await bagistoFetch<{
- data: unknown;
- variables: Record<string, unknown>;
- }>({
- query: FORGET_PASSWORD,
- variables: {
- ...input,
- },
- cache: "no-store",
- revalidate: 3600,
- });
- } catch (error) {
- return error;
- }
- }
- export async function subscribeUser(
- input: Record<string, unknown>,
- ): Promise<unknown> {
- try {
- return await bagistoFetch<{
- data: unknown;
- variables: Record<string, unknown>;
- }>({
- query: SUBSCRIBE_TO_NEWSLETTER,
- variables: {
- ...input,
- },
- cache: "no-store",
- });
- } catch (error) {
- return error;
- }
- }
- export async function getThemeCustomization(): Promise<ThemeCustomizationResult> {
- try {
- const [{data: footerRes}, {data:servicesRes}] = await Promise.all([
- cachedGraphQLRequest<GetFooterResponse>("static", GET_FOOTER, {
- type: "footer_links",
- }),
- cachedGraphQLRequest<GetFooterResponse>("static", GET_FOOTER, {
- type: "services_content",
- }),
- ]);
- return {
- footer_links: footerRes,
- services_content: servicesRes,
- };
- } catch (err) {
- console.error("ThemeCustomization Error:", err);
- }
- return {
- footer_links: null,
- services_content: null,
- };
- }
- export async function revalidate(req: NextRequest): Promise<NextResponse> {
- const collectionWebhooks = [
- "collections/create",
- "collections/delete",
- "collections/update",
- ];
- const productWebhooks = [
- "products/create",
- "products/delete",
- "products/update",
- ];
- const topic = (await headers()).get("x-bagisto-topic") || "unknown";
- const secret = req.nextUrl.searchParams.get("secret");
- const isCollectionUpdate = collectionWebhooks.includes(topic);
- const isProductUpdate = productWebhooks.includes(topic);
- if (!secret || secret !== process.env.BAGISTO_REVALIDATION_SECRET) {
- return NextResponse.json({ status: 200 });
- }
- if (!isCollectionUpdate && !isProductUpdate) {
- return NextResponse.json({ status: 200, message: "No action needed" });
- }
- if (isProductUpdate) {
- revalidatePath("/", "layout");
- } else if (isCollectionUpdate) {
- revalidatePath("/", "layout");
- }
- return NextResponse.json({
- status: 200,
- revalidated: true,
- topic,
- now: Date.now(),
- });
- }
- export async function getHomePageData(): Promise<ThemeCustomizationResponse> {
- const res = await bagistoFetch<{
- data: ThemeCustomizationResponse;
- variables: { first: number };
- }>({
- query: GET_THEME_CUSTOMIZATION,
- variables: { first: 20 },
- tags: ["theme-customization"],
- revalidate: 60,
- });
- return res.body.data;
- }
- export async function getPage(input: { urlKey: string }): Promise<PageData[]> {
- const res = await bagistoFetch<{
- data: PageByUrlKeyResponse;
- variables: { pageByUrlKey: string };
- }>({
- query: PAGE_BY_URL_KEY,
- cache: "no-store",
- isCookies: false,
- variables: { pageByUrlKey: input.urlKey },
- });
- return res.body.data?.pageByUrlKeypages || [];
- }
|