All files / src/api emptyApi.ts

84.21% Statements 16/19
86.66% Branches 13/15
100% Functions 1/1
84.21% Lines 16/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                            62x           5721x 5721x 5721x 5721x 5721x 5721x       5721x     5451x               5721x       5721x       5721x             5721x                                                           5721x       62x   62x    
// Template API used by the generated RTK Query client. The generated code imports
// `emptySplitApi`, while this module centralises the shared base URL and the
// flowsheet query/body injection used throughout the app.
// see also (openapi-config.json)
// and https://redux-toolkit.js.org/rtk-query/usage/code-generation#usage
 
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { toast } from "sonner";
import {
  getCachedFlowsheetAccess,
  isReadOnlyMutationBlocked,
} from "@/lib/flowsheetAccess";
import { baseUrl, resolveApiUrl } from "./apiBaseUrl";
 
const defaultBaseQuery = fetchBaseQuery({
  baseUrl,
  credentials: "include",
});
 
const customBaseQuery = async (args, api, extraOptions) => {
  const url = document.URL;
  const match = url.match(/\/project\/(\d+)/);
  const flowsheetId = match ? Number(match[1]) : null;
  const reducerPath = emptySplitApi.reducerPath;
  const originalUrl = typeof args === "string" ? args : args.url;
  const argsUrl = new URL(resolveApiUrl(originalUrl));
  // RTK Query passes either a bare URL string for simple GETs or a request
  // object with explicit method/body fields for richer requests.
  const method =
    typeof args === "string" ? "GET" : (args.method?.toUpperCase() ?? "GET");
 
  if (flowsheetId) {
    argsUrl.searchParams.set("flowsheet", flowsheetId.toString());
  }
 
  if (typeof args === "string") {
    args = argsUrl.toString();
  } else if (typeof args === "object") {
    // Inject flowsheet into body (for POST, PUT, etc.)
    const isJson =
      args.body &&
      typeof args.body === "object" &&
      !(args.body instanceof FormData);
    const newBody =
      flowsheetId && isJson
        ? { ...args.body, flowsheet: flowsheetId }
        : args.body;
 
    args = {
      ...args,
      url: argsUrl.toString(),
      body: newBody,
    };
  }
 
  const access = getCachedFlowsheetAccess(
    api.getState() as Record<string, unknown>,
    reducerPath,
    flowsheetId,
  );
 
  if (
    isReadOnlyMutationBlocked({
      method,
      pathname: argsUrl.pathname,
      access,
    })
  ) {
    // Return a synthetic 403 before the request leaves the browser so every
    // mutation endpoint gets consistent read-only behaviour, even if the UI
    // surface forgot to disable a control.
    toast.warning("This flowsheet is read-only", {
      description: "Make a copy to edit or solve this flowsheet.",
    });
 
    return {
      error: {
        status: 403,
        data: {
          error: "This flowsheet is shared with read-only access.",
        },
      },
    };
  }
 
  return defaultBaseQuery(args, api, extraOptions);
};
 
// initialize an empty api service that we'll inject endpoints into later as needed
export const emptySplitApi = createApi({
  baseQuery: customBaseQuery,
  endpoints: () => ({}),
});