Skip to content

Commit

Permalink
Merge pull request #18 from apostrophecms/pro-4691-redirect-to-other-…
Browse files Browse the repository at this point in the history
…locales

Pro 4691 redirect to other locales
  • Loading branch information
ValJed committed Oct 18, 2023
2 parents 3220410 + cfa35cd commit eeb5daa
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 4 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This is a basic workflow to help you get started with Actions

name: tests

# Controls when the action will run.
on:
push:
branches: [ "main" ]
pull_request:
branches: [ '*' ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
mongodb-version: ['4.4', '5.0', '6.0']

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Git checkout
uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.8.0
with:
mongodb-version: ${{ matrix.mongodb-version }}

- run: npm install

- run: npm test
env:
CI: true
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Adds possibility to redirect from a locale to another one using internal redirects.

## 1.2.3

- Fixes redirections when using locale prefixes.
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,22 @@ module.exports = {
}
};
```


## Redirecting to other locales

It's possible to redirect from one locale to another one, with external redirections,
since you manually define the url to redirect to.

As for internal redirects (relationships with pages), this works across locales as well,
but keep in mind that you will only see the internal redirects that target the current locale when managing redirects.
To find redirects that target an internal page in a different locale, switch locales before viewing "Manage Redirects."

A note for developers: a query builder called `currentLocaleTarget` hides redirects that have relationships to other locales (different from the current one).
If you want to get all redirects whatever the locale of their internal redirects you can undo this behavior using the query builder:
```javascript
const redirects = await self.apos.modules['@apostrophecms/redirect']
.find(req)
.currentLocaleTarget(false)
.toArray();
```
50 changes: 48 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ module.exports = {
if (!doc.title) {
doc.title = doc.redirectSlug;
}
},
setCurrentLocale(req, doc) {
const internalPage = doc._newPage && doc._newPage[0];

doc.targetLocale = internalPage && doc.urlType === 'internal'
? internalPage.aposLocale.replace(/:.*$/, '')
: null;
}
}
};
Expand Down Expand Up @@ -135,7 +142,7 @@ module.exports = {
add.statusCode.def = options.statusCode.toString();
}

add._newPage.withType = self.options.withType;
add._newPage.withType = options.withType;

return {
add,
Expand All @@ -149,20 +156,39 @@ module.exports = {
try {
const slug = req.originalUrl;
const [ pathOnly ] = slug.split('?');

const results = await self
.find(req, { $or: [ { redirectSlug: slug }, { redirectSlug: pathOnly } ] })
.currentLocaleTarget(false)
.relationships(false)
.project({
_id: 1,
redirectSlug: 1,
targetLocale: 1,
externalUrl: 1,
urlType: 1
})
.toArray();

if (!results.length) {
return await emitAndRedirectOrNext();
}

const target = results.find(({ redirectSlug }) => redirectSlug === slug) ||
const foundTarget = results.find(({ redirectSlug }) => redirectSlug === slug) ||
results.find(({
redirectSlug,
ignoreQueryString
}) => redirectSlug === pathOnly && ignoreQueryString);

const localizedReq = foundTarget.urlType === 'internal' &&
req.locale !== foundTarget.targetLocale
? req.clone({ locale: foundTarget.targetLocale })
: req;

const target = foundTarget.urlType === 'internal'
? await self.find(localizedReq, { _id: foundTarget._id }).toObject()
: foundTarget;

if (!target) {
return await emitAndRedirectOrNext();
}
Expand Down Expand Up @@ -194,6 +220,26 @@ module.exports = {
}
};
},
queries(self, query) {
return {
builders: {
currentLocaleTarget: {
def: true,
launder(val) {
return self.apos.launder.booleanOrNull(val);
},
finalize() {
const active = query.get('currentLocaleTarget');
const { locale } = query.req;

if (active && locale) {
query.and({ $or: [ { targetLocale: null }, { targetLocale: locale } ] });
}
}
}
}
};
},
methods(self) {
return {
addUnlocalizedMigration() {
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"lint": "npm run eslint",
"eslint": "eslint .",
"test": "npm run lint"
"test": "npm run lint && mocha"
},
"repository": {
"type": "git",
Expand All @@ -16,12 +16,14 @@
"author": "Apostrophe Technologies",
"license": "MIT",
"devDependencies": {
"apostrophe": "github:apostrophecms/apostrophe",
"eslint": "^7.9.0",
"eslint-config-apostrophe": "^3.4.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1"
"eslint-plugin-standard": "^4.0.1",
"mocha": "^10.2.0"
}
}
144 changes: 144 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const assert = require('assert').strict;
const t = require('apostrophe/test-lib/util.js');

describe('@apostrophecms/redirect', function () {
let apos;
let redirectModule;

this.timeout(t.timeout);

after(async function() {
await t.destroy(apos);
});

before(async function() {
apos = await t.create({
root: module,
testModule: true,
modules: getAppConfig()
});

redirectModule = apos.modules['@apostrophecms/redirect'];
await insertPages(apos);
});

this.afterEach(async function() {
await apos.doc.db.deleteMany({ type: '@apostrophecms/redirect' });
});

it('should allow to redirect to external URLs', async function() {
const req = apos.task.getReq();
const instance = redirectModule.newInstance();
await redirectModule.insert(req, {
...instance,
title: 'external redirect',
urlType: 'external',
redirectSlug: '/page-1',
externalUrl: 'http://localhost:3000/page-2'
});
const redirected = await apos.http.get('http://localhost:3000/page-1');

assert.equal(redirected, '<title>page 2</title>\n');
});

it('should allow to redirect to internal pages', async function() {
const req = apos.task.getReq();
const instance = redirectModule.newInstance();
const page2 = await apos.page.find(req, { title: 'page 2' }).toObject();
await redirectModule.insert(req, {
...instance,
title: 'internal redirect',
urlType: 'internal',
redirectSlug: '/page-1',
_newPage: [ page2 ]
});
const redirected = await apos.http.get('http://localhost:3000/page-1');

assert.equal(redirected, '<title>page 2</title>\n');
});

it('should allow to redirect to internal pages in other locales', async function() {
const req = apos.task.getReq();
const reqFr = apos.task.getReq({ locale: 'fr' });
const instance = redirectModule.newInstance();
const pageFr = await apos.page.find(reqFr, { title: 'page fr' }).toObject();
await redirectModule.insert(req, {
...instance,
title: 'internal redirect',
urlType: 'internal',
redirectSlug: '/page-1',
_newPage: [ pageFr ]
});

const redirected = await apos.http.get('http://localhost:3000/page-1');
assert.equal(redirected, '<title>page fr</title>\n');
});
});

async function insertPages(apos) {
const req = apos.task.getReq();
const frReq = apos.task.getReq({ locale: 'fr' });
const defaultPageModule = apos.modules['default-page'];
const pageInstance = defaultPageModule.newInstance();

await apos.page.insert(req, '_home', 'lastChild', {
...pageInstance,
title: 'page 1',
slug: '/page-1'
});
await apos.page.insert(req, '_home', 'lastChild', {
...pageInstance,
title: 'page 2',
slug: '/page-2'
});
await apos.page.insert(frReq, '_home', 'lastChild', {
...pageInstance,
title: 'page fr',
slug: '/page-fr'
});
}

function getAppConfig() {
return {
'@apostrophecms/express': {
options: {
session: { secret: 'supersecret' },
port: 3000
}
},
'@apostrophecms/i18n': {
options: {
defaultLocale: 'en',
locales: {
en: { label: 'English' },
fr: {
label: 'French',
prefix: '/fr'
}
}
}
},
'@apostrophecms/redirect': {},
'default-page': {},
article: {
extend: '@apostrophecms/piece-type',
options: {
alias: 'article'
}
},
topic: {
extend: '@apostrophecms/piece-type',
options: {
alias: 'topic'
},
fields: {
add: {
description: {
label: 'Description',
type: 'string'
}
}
}
}
};
}
3 changes: 3 additions & 0 deletions test/modules/default-page/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extend: '@apostrophecms/page-type'
};
1 change: 1 addition & 0 deletions test/modules/default-page/views/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<title>{{ data.page.title }}</title>
9 changes: 9 additions & 0 deletions test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"/**": "This package.json file is not actually installed.",
" * ": "Apostrophe requires that all npm modules to be loaded by moog",
" */": "exist in package.json at project level, which for a test is here",
"dependencies": {
"apostrophe": "git+https://github.com/apostrophecms/apostrophe.git",
"@apostrophecms/redirect": "git+https://github.com/apostrophecms/redirect.git"
}
}

0 comments on commit eeb5daa

Please sign in to comment.