feat: add redirect processing script

This commit is contained in:
Michael Zetterberg
2025-05-21 13:24:08 +02:00
parent e2a4fa6c07
commit c52da4bec6
7 changed files with 171 additions and 0 deletions

2
apps/scandic-redirect/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
scripts/csv/*.csv
scripts/json/*.json

View File

@@ -6,3 +6,20 @@ This function will be called by the `web` app's middleware to check if the incom
The "source of truth" for which URLs should be redirected where will be provided by the SEO team and put in a JSON file within this app. The "source of truth" for which URLs should be redirected where will be provided by the SEO team and put in a JSON file within this app.
If no match for the incoming request is found, the request is passed on through the middleware. If no match for the incoming request is found, the request is passed on through the middleware.
## Update the redirects from the source
The Excel source file used is located at:
https://scandichotelsab.sharepoint.com/:x:/s/921-ContentNewweb/ETGStOQAARtJhJXG9dy8ijYBccpmKhLVjS2SF_2E69QrAQ
- Open it
- Each domain/language has its own sheet
- Export each sheet into their respective language code
- File > Export > Download as CSV UTF-8
- Save as [lang].csv in `./scripts/csv` folder
- Run the `update` script target
- E.g. `yarn workspace @scandic-hotels/scandic-redirect update`
- Commit and push the JSON files in `./netlify/functions/data`.
- Create a PR
- Profit!

View File

@@ -5,5 +5,11 @@
"packageManager": "yarn@4.6.0", "packageManager": "yarn@4.6.0",
"dependencies": { "dependencies": {
"@netlify/functions": "^3.0.0" "@netlify/functions": "^3.0.0"
},
"devDependencies": {
"convert-csv-to-json": "^3.4.0"
},
"scripts": {
"update": "node ./scripts/update.mjs"
} }
} }

View File

@@ -0,0 +1,138 @@
import fs from 'node:fs';
import path from 'node:path';
import csvToJson from 'convert-csv-to-json';
const langs = ['da', 'de', 'en', 'fi', 'no', 'sv'];
const csvHeaders = {
current: 'Current URL',
redirect: 'Redirect URL',
};
function csvFilePath(lang) {
return `${import.meta.dirname}/csv/${lang}.csv`;
}
function jsonFilePath(lang) {
return `${import.meta.dirname}/json/${lang}.json`;
}
function outputFilepath(lang) {
return path.resolve(
import.meta.dirname,
`../netlify/functions/data/${lang}.json`
);
}
function removeDomain(str) {
return str.replace(
/^https?:\/\/((www|test|stage|prod)\.)?scandichotels.(com|de|dk|fi|no|se)/,
''
);
}
function akamaiRedirect(str) {
return str.replace(
/^https?:\/\/((www|test|stage|prod)\.)?scandichotels.(com|de|dk|fi|no|se)/,
(...match) => {
if (match[3]) {
switch (match[3]) {
case 'com':
return '/en';
case 'de':
return '/de';
case 'dk':
return '/da';
case 'fi':
return '/fi';
case 'no':
return '/no';
case 'se':
return '/sv';
}
}
return '';
}
);
}
function checkPrerequisites() {
const missingLangs = langs.reduce((acc, lang) => {
const filepath = csvFilePath(lang);
if (!fs.existsSync(filepath)) {
acc.push(filepath);
}
return acc;
}, []);
if (missingLangs.length > 0) {
console.error(`Missing CSV file:\n${missingLangs.join('\n')}`);
process.exit(1);
}
}
// convert-csv-to-json writes async without callback support
// so we workaround it be overriding console.log which it uses when it is done
async function convertCsvToJson() {
return new Promise((resolve, reject) => {
const _consoleLog = console.log;
let resolved = 0;
console.log = function (str) {
if (str.indexOf('File saved:') >= 0) {
resolved++;
}
if (resolved === langs.length) {
console.log = _consoleLog;
resolve();
}
};
for (const lang of langs) {
csvToJson
.utf8Encoding()
.fieldDelimiter(',')
.generateJsonFileFromCsv(csvFilePath(lang), jsonFilePath(lang));
}
setTimeout(() => {
reject('timeout');
}, 5000);
});
}
async function makeOutput() {
for (const lang of langs) {
try {
const json = JSON.parse(
fs.readFileSync(jsonFilePath(lang), {
encoding: 'utf-8',
})
);
if (Array.isArray(json)) {
const finalUrls = json.reduce((acc, url) => {
const from = removeDomain(akamaiRedirect(url[csvHeaders.current]));
const to = removeDomain(url[csvHeaders.redirect]);
return {
...acc,
[from]: to,
};
}, {});
fs.writeFileSync(outputFilepath(lang), JSON.stringify(finalUrls), {
encoding: 'utf-8',
});
} else {
throw new Error(`JSON was not an array: ${jsonFilePath(lang)}`);
}
} catch (e) {
console.error(e);
}
}
}
checkPrerequisites();
await convertCsvToJson();
await makeOutput();

View File

@@ -7052,6 +7052,7 @@ __metadata:
resolution: "@scandic-hotels/scandic-redirect@workspace:apps/scandic-redirect" resolution: "@scandic-hotels/scandic-redirect@workspace:apps/scandic-redirect"
dependencies: dependencies:
"@netlify/functions": "npm:^3.0.0" "@netlify/functions": "npm:^3.0.0"
convert-csv-to-json: "npm:^3.4.0"
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@@ -11775,6 +11776,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"convert-csv-to-json@npm:^3.4.0":
version: 3.4.0
resolution: "convert-csv-to-json@npm:3.4.0"
checksum: 10c0/06ec3cb348591322b9b2083464eff41907688d6eb4cb8b364d7d969ea08eb559d872733292034f72dcf404fe3150def092194ccfe14d89ded3b35dfc694fdb87
languageName: node
linkType: hard
"convert-source-map@npm:^2.0.0": "convert-source-map@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "convert-source-map@npm:2.0.0" resolution: "convert-source-map@npm:2.0.0"