Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/source/configuration/environmentvariables.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ You can also generate builds on your continuous integration, then deploy them an

```{glossary}
:sorted:
`RAZZLE_API_SUFFIX`
Overrides the API traversal suffix that Volto appends to API URLs.
The default suffix is `/++api++`.
Set this variable to use a different suffix, or set `config.settings.apiSuffix` directly in your Volto configuration.
This setting takes precedence over `RAZZLE_LEGACY_TRAVERSE`.

`RAZZLE_LEGACY_TRAVERSE`
If `true`, Volto will construct API URLs without the `/++api++` prefix.

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/config/Settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface SettingsConfig {
actions_raising_api_errors: string[];
internalApiPath: string | undefined;
websockets: string | false;
apiSuffix: string | undefined;
legacyTraverse: string | false;
cookieExpires: number;
nonContentRoutes: Array<string | RegExp>;
Expand Down
1 change: 1 addition & 0 deletions packages/volto/news/+configurable-api-suffix.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the `apiSuffix` setting and `RAZZLE_API_SUFFIX` environment variable to configure the API traversal suffix.
2 changes: 2 additions & 0 deletions packages/volto/src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ let config = {
internalApiPath: process.env.RAZZLE_INTERNAL_API_PATH || undefined,
subpathPrefix: process.env.RAZZLE_SUBPATH_PREFIX || '',
websockets: process.env.RAZZLE_WEBSOCKETS || false,
// Overrides the API traversal suffix. An empty string disables it.
apiSuffix: process.env.RAZZLE_API_SUFFIX,
// TODO: legacyTraverse to be removed when the use of the legacy traverse is deprecated.
legacyTraverse: process.env.RAZZLE_LEGACY_TRAVERSE || false,
cookieExpires: 15552000, //in seconds. Default is 6 month (15552000)
Expand Down
4 changes: 2 additions & 2 deletions packages/volto/src/helpers/Api/APIResourceWithAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import superagent from 'superagent';
import config from '@plone/volto/registry';
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
import { getApiSuffix, stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';

/**
* Get a resource image/file with authenticated (if token exist) API headers
Expand All @@ -17,7 +17,7 @@ import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
export const getAPIResourceWithAuth = (req) =>
new Promise((resolve, reject) => {
const { settings } = config;
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
const apiSuffix = getApiSuffix();
let apiPath = '';

if (settings.internalApiPath && __SERVER__) {
Expand Down
3 changes: 2 additions & 1 deletion packages/volto/src/helpers/Api/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Cookies from 'universal-cookie';
import config from '@plone/volto/registry';
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
import {
getApiSuffix,
stripQuerystring,
stripSubpathPrefix,
} from '@plone/volto/helpers/Url/Url';
Expand All @@ -22,7 +23,7 @@ const methods = ['get', 'post', 'put', 'patch', 'del'];
*/
export function formatUrl(path) {
const { settings } = config;
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
const apiSuffix = getApiSuffix();

if (path.startsWith('http://') || path.startsWith('https://')) return path;

Expand Down
7 changes: 7 additions & 0 deletions packages/volto/src/helpers/Api/Api.plone.rest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ vi.mock('superagent', () => ({
}));

beforeAll(() => {
config.settings.apiSuffix = undefined;
config.settings.legacyTraverse = false;
});

Expand All @@ -41,4 +42,10 @@ describe('Api', () => {
const promise = api.get('https://example.com');
expect(promise.request.url).toBe('https://example.com');
});
it('uses the configured API suffix', () => {
config.settings.apiSuffix = '/custom-api';
const promise = api.get('/test');
expect(promise.request.url).toBe(`${settings.apiPath}/custom-api/test`);
config.settings.apiSuffix = undefined;
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import superagent from 'superagent';
import config from '@plone/volto/registry';
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
import { getApiSuffix, stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';

/**
* Get a resource from Plone Backend (/++api++) with authenticated (if token exist) API headers
Expand All @@ -12,7 +12,7 @@ import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
export const getPloneBackendAPIResourceWithAuth = (req) =>
new Promise((resolve, reject) => {
const { settings } = config;
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
const apiSuffix = getApiSuffix();
let apiPath = '';

if (settings.internalApiPath && __SERVER__) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('getPloneBackendAPIResourceWithAuth', () => {
apiPath: 'http://localhost:3000',
devProxyToApiPath: 'http://localhost:8080/Plone',
internalApiPath: undefined,
apiSuffix: undefined,
});
});

Expand Down Expand Up @@ -57,4 +58,15 @@ describe('getPloneBackendAPIResourceWithAuth', () => {
'http://localhost:8080/Plone/@portrait/admin',
);
});

it('uses apiSuffix in preference to legacyTraverse in development', async () => {
config.settings.legacyTraverse = true;
config.settings.apiSuffix = '/custom-api';

await getPloneBackendAPIResourceWithAuth(request);

expect(superagent.get).toHaveBeenCalledWith(
'http://localhost:8080/Plone/custom-api/@portrait/admin',
);
});
});
6 changes: 3 additions & 3 deletions packages/volto/src/helpers/Sitemap/Sitemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import superagent from 'superagent';
import map from 'lodash/map';
import zlib from 'zlib';
import { toPublicURL } from '@plone/volto/helpers/Url/Url';
import { getApiSuffix, toPublicURL } from '@plone/volto/helpers/Url/Url';
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';

import config from '@plone/volto/registry';
Expand All @@ -22,7 +22,7 @@ export const SITEMAP_BATCH_SIZE = 5000;
export const generateSitemap = (_req, start = 0, size = undefined) =>
new Promise((resolve) => {
const { settings } = config;
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
const apiSuffix = getApiSuffix();
const apiPath = settings.internalApiPath ?? settings.apiPath;
const request = superagent.get(
`${apiPath}${apiSuffix}/@search?metadata_fields=modified&b_start=${start}&b_size=${
Expand Down Expand Up @@ -64,7 +64,7 @@ export const generateSitemap = (_req, start = 0, size = undefined) =>
export const generateSitemapIndex = (_req, gzip = false) =>
new Promise((resolve) => {
const { settings } = config;
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
const apiSuffix = getApiSuffix();
const apiPath = settings.internalApiPath ?? settings.apiPath;
const request = superagent.get(
`${apiPath}${apiSuffix}/@search?metadata_fields=modified&b_size=0&use_site_search_settings=1`,
Expand Down
13 changes: 12 additions & 1 deletion packages/volto/src/helpers/Url/Url.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ export function addAppURL(url) {
: `${settings.apiPath}${url}`;
}

/**
* Get the API traversal suffix.
*
* An explicitly configured apiSuffix takes precedence over legacyTraverse.
* @returns {string} API traversal suffix.
*/
export function getApiSuffix() {
const { settings } = config;
return settings.apiSuffix ?? (settings.legacyTraverse ? '' : '/++api++');
}

/**
* Given a URL expands it to the backend URL
* Useful when you have to actually call the backend from the
Expand All @@ -193,7 +204,7 @@ export function addAppURL(url) {
*/
export function expandToBackendURL(path) {
const { settings } = config;
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
const apiSuffix = getApiSuffix();
let adjustedPath;
if (path.startsWith('http://') || path.startsWith('https://')) {
// flattenToAppURL first if we get a full URL
Expand Down
15 changes: 15 additions & 0 deletions packages/volto/src/helpers/Url/Url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from './Url';

beforeEach(() => {
config.settings.apiSuffix = undefined;
config.settings.legacyTraverse = false;
});

Expand Down Expand Up @@ -372,6 +373,20 @@ describe('Url', () => {
const href = `https://plone.org/api/ca/my-page`;
expect(expandToBackendURL(href)).toBe('https://plone.org/api/ca/my-page');
});
it('uses apiSuffix in preference to legacyTraverse', () => {
settings.apiPath = 'https://plone.org/api';
settings.legacyTraverse = true;
settings.apiSuffix = '/custom-api';
const href = `https://plone.org/api/ca/my-page`;
expect(expandToBackendURL(href)).toBe(
'https://plone.org/api/custom-api/ca/my-page',
);
});
it('allows apiSuffix to disable the suffix', () => {
settings.apiSuffix = '';
const href = `/ca/my-page`;
expect(expandToBackendURL(href)).toBe('https://plone.org/api/ca/my-page');
});
it('expandToBackendURL test full URL - deployed seamless', () => {
settings.apiPath = 'https://plone.org';
const href = `https://plone.org/ca/my-page`;
Expand Down
3 changes: 3 additions & 0 deletions packages/volto/src/start-client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export default function client() {
if (window.env.RAZZLE_INTERNAL_API_PATH) {
config.settings.internalApiPath = window.env.RAZZLE_INTERNAL_API_PATH;
}
if (typeof window.env.RAZZLE_API_SUFFIX !== 'undefined') {
config.settings.apiSuffix = window.env.RAZZLE_API_SUFFIX;
}
// TODO: To be removed when the use of the legacy traverse is deprecated.
if (window.env.RAZZLE_LEGACY_TRAVERSE) {
config.settings.legacyTraverse = true;
Expand Down
Loading