Skip to content

Commit

Permalink
feat(api): maintenance mode (#857)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdanilowicz committed Apr 17, 2024
1 parent b76d725 commit 864616f
Show file tree
Hide file tree
Showing 21 changed files with 270 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-dingos-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vue-demo-store": minor
---

Add maintenance mode page
5 changes: 5 additions & 0 deletions .changeset/mean-elephants-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/helpers-next": minor
---

Add isMaintenanceMode to check if backend is available
5 changes: 5 additions & 0 deletions .changeset/strong-kangaroos-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware/api-client": minor
---

Add error and success callbacks
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export const sidebar = [
text: "Custom Products extension",
link: "/getting-started/features/custom-products",
},
{
text: "Maintenance mode",
link: "/getting-started/features/maintenance-mode",
},
],
},
{
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/getting-started/features/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ nav:
Collection of specific Composable Frontends Features and documentation how to use them.
<PageRef page="sitemap" title="Sitemap" sub="A Sitemap is generated by combining two sitemaps, Frontend and Shopware admin." />
<PageRef page="wishlist" title="Wishlist" sub="How to use the built-in wishlist API to create wishlist functionalities in your application." />
<PageRef page="maintenance-mode" title="Maintenance mode" sub="How to use maintenance mode with the Composable Frontends app" />

## Shopware Extensions

Expand Down
88 changes: 88 additions & 0 deletions apps/docs/src/getting-started/features/maintenance-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
head:
- - meta
- name: og:title
content: "Maintenance mode - Shopware Frontends"
- - meta
- name: og:description
content: "Example of implementation maintenance mode page"
- - meta
- name: og:image
content: "https://frontends-og-image.vercel.app/Integration:%20**Maintenance%20Mode**?fontSize=100px"
---

# Maintenance mode

You can activate the maintenance mode of your store by selecting your sales channel and then activating the maintenance mode under Status

## Detecting maintenance mode via API

Maintenance mode is returned as an error from all of the endpoints. We can detect it by using `onErrorHandler` callback function in `createAPIClient` init api method.

```ts
const apiClient = createAPIClient({
baseURL: shopwareEndpoint,
accessToken: shopwareAccessToken,
contextToken: Cookies.get("sw-context-token"),
onContextChanged(newContextToken) {
Cookies.set("sw-context-token", newContextToken, {
expires: 365, // days
path: "/",
sameSite: "lax",
});
},
onErrorHandler(response) {
const error = response._data?.errors?.find((element) => {
return element.code === "FRAMEWORK__API_SALES_CHANNEL_MAINTENANCE_MODE";
});
},
});
```

## Displaying maintenance page

:::warning
This example is for Nuxt 3 apps
:::

### Throwing MAINTENANCE_MODE error

Every error thrown within the application is automatically caught and the `error.vue` page is displayed.

```ts
onErrorHandler(response) {
const error = response._data?.errors?.find((element) => {
return element.code === "FRAMEWORK__API_SALES_CHANNEL_MAINTENANCE_MODE";
});

if (error) {
throw createError({
statusCode: 503,
statusMessage: "MAINTENANCE_MODE",
});
}
},
```

### Displaying maintenance mode page

```vue
// error.vue
<script setup lang="ts">
const props = defineProps<{
error: {
statusCode: number;
statusMessage: string;
message: string;
};
}>();
const isMaintenanceMode = computed(() => {
return props.error.statusMessage === "MAINTENANCE_MODE";
});
</script>
<template>
<div v-if="isMaintenanceMode">Maintenance Mode Page Content</div>
</template>
```
49 changes: 49 additions & 0 deletions packages/api-client-next/src/createApiClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,53 @@ describe("createAPIClient", () => {
}),
);
});

it("should trigger success callback", async () => {
const app = createApp().use(
"/context",
eventHandler(async (event) => {
return {};
}),
);

const baseURL = await createPortAndGetUrl(app);

const successCallback = vi.fn().mockImplementation((param: string) => {});

const client = createAPIClient<operations, operationPaths>({
accessToken: "123",
contextToken: "456",
baseURL,
onSuccessHandler: successCallback,
});

await client.invoke("readContext get /context");
expect(successCallback).toHaveBeenCalled();
});

it("should trigger fail callback", async () => {
const app = createApp().use(
"/context",
eventHandler(async (event) => {
throw new Error("Api error");
}),
);

const baseURL = await createPortAndGetUrl(app);

const errorCallback = vi.fn().mockImplementation((param: string) => {});

const client = createAPIClient<operations, operationPaths>({
accessToken: "123",
contextToken: "456",
baseURL,
onErrorHandler: errorCallback,
});

try {
await client.invoke("readContext get /context");
} catch (e) {}

expect(errorCallback).toHaveBeenCalled();
});
});
9 changes: 9 additions & 0 deletions packages/api-client-next/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ofetch } from "ofetch";
import type { FetchResponse } from "ofetch";
import type {
operations as defaultOperations,
paths as defaultPaths,
Expand Down Expand Up @@ -91,6 +92,8 @@ export function createAPIClient<
accessToken?: string;
contextToken?: string;
onContextChanged?: (newContextToken: string) => void;
onErrorHandler?: (response: FetchResponse<any>) => void;
onSuccessHandler?: (response: FetchResponse<any>) => void;
defaultHeaders?: ClientHeaders;
}) {
const defaultHeaders = createHeaders({
Expand All @@ -105,6 +108,9 @@ export function createAPIClient<
// async onRequest({ request, options }) {},
// async onRequestError({ request, options, error }) {},
async onResponse(context) {
if (params.onSuccessHandler) {
params.onSuccessHandler(context.response);
}
if (
context.response.headers.has("sw-context-token") &&
defaultHeaders["sw-context-token"] !==
Expand All @@ -118,6 +124,9 @@ export function createAPIClient<
}
},
async onResponseError({ request, response, options }) {
if (params.onErrorHandler) {
params.onErrorHandler(response);
}
errorInterceptor(response);
},
});
Expand Down
1 change: 1 addition & 0 deletions packages/helpers/src/cms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type { LayoutConfiguration } from "./getCmsLayoutConfiguration";
export * from "./getBackgroundImageUrl";
export * from "./buildUrlPrefix";
export * from "./layoutClasses";
export * from "./isMaintenanceMode";

/**
* Returns the main page object depending of the type of the CMS page.
Expand Down
30 changes: 30 additions & 0 deletions packages/helpers/src/cms/isMaintenanceMode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { isMaintenanceMode } from "./index";

describe("isMaintenanceMode", () => {
it("should return true", () => {
expect(
isMaintenanceMode([
{
status: "503",
code: "FRAMEWORK__API_SALES_CHANNEL_MAINTENANCE_MODE",
title: "Service Unavailable",
detail: "The sales channel is in maintenance mode.",
meta: { parameters: [] },
},
]),
).toBe(true);
});

it("should return false", () => {
expect(
isMaintenanceMode([
{
status: "404",
code: "test",
title: "Not Found",
},
]),
).toBe(false);
});
});
14 changes: 14 additions & 0 deletions packages/helpers/src/cms/isMaintenanceMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function isMaintenanceMode<
T extends {
code?: string;
},
>(errors: [T]): boolean {
return (
!!errors.find((element) => {
return (
element.code === "FRAMEWORK__API_SALES_CHANNEL_MAINTENANCE_MODE" ??
false
);
}) ?? false
);
}
1 change: 1 addition & 0 deletions packages/helpers/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe("helpers - test global API", () => {
],
"isCategory": [Function],
"isLandingPage": [Function],
"isMaintenanceMode": [Function],
"isProduct": [Function],
"relativeUrlSlash": [Function],
"urlIsAbsolute": [Function],
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt3-module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"dependencies": {
"@nuxt/kit": "3.11.2",
"@shopware-pwa/composables-next": "workspace:*",
"@shopware-pwa/helpers-next": "workspace:*",
"@shopware/api-client": "workspace:*",
"defu": "6.1.4",
"js-cookie": "3.0.5"
Expand Down
12 changes: 12 additions & 0 deletions packages/nuxt3-module/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import {
// useCookie,
useState,
createShopwareContext,
createError,
} from "#imports";
import { ref } from "vue";
import Cookies from "js-cookie";
import { createAPIClient } from "@shopware/api-client";

import { isMaintenanceMode } from "@shopware-pwa/helpers-next";

export default defineNuxtPlugin((NuxtApp) => {
const runtimeConfig = useRuntimeConfig();
const shopwareEndpoint =
Expand Down Expand Up @@ -36,6 +39,15 @@ export default defineNuxtPlugin((NuxtApp) => {
sameSite: "lax",
});
},
onErrorHandler(response) {
const error = isMaintenanceMode(response._data?.errors ?? []);
if (error) {
throw createError({
statusCode: 503,
statusMessage: "MAINTENANCE_MODE",
});
}
},
});

NuxtApp.vueApp.provide("apiClient", apiClient);
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 864616f

Please sign in to comment.