From 808f10037f51f51880661aef3ba8e53794d2d2de Mon Sep 17 00:00:00 2001 From: msa46 Date: Sat, 15 Aug 2026 20:55:56 +0200 Subject: [PATCH] feat: redirect legacy bachelor routes to data science --- astro.config.mjs | 2 ++ src/config/legacy-bachelor-redirects.mjs | 22 ++++++++++++++++++++ src/config/sidebar.mjs | 2 +- tests/programme-content.test.mjs | 4 ++++ tests/programme-redirects.test.mjs | 26 ++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/config/legacy-bachelor-redirects.mjs create mode 100644 tests/programme-redirects.test.mjs diff --git a/astro.config.mjs b/astro.config.mjs index 3c70b31..8dd0a93 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,6 +1,7 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import { sidebar } from './src/config/sidebar.mjs'; +import { legacyBachelorRedirects } from './src/config/legacy-bachelor-redirects.mjs'; const site = process.env.SITE || 'http://localhost:4321'; const base = process.env.BASE || '/'; @@ -12,6 +13,7 @@ const matomoConsentScript = `${normalizedBase}/matomo-consent.js`; export default defineConfig({ site, base, + redirects: legacyBachelorRedirects, integrations: [ starlight({ title: 'Incognito Wiki', diff --git a/src/config/legacy-bachelor-redirects.mjs b/src/config/legacy-bachelor-redirects.mjs new file mode 100644 index 0000000..7c93aa1 --- /dev/null +++ b/src/config/legacy-bachelor-redirects.mjs @@ -0,0 +1,22 @@ +const routes = [ + '', + 'year-1/', + 'year-1/project-1-1', 'year-1/project-1-2', + 'year-1/block-1/discrete-mathematics', 'year-1/block-1/introduction-to-data-science-and-artifical-intelligence', 'year-1/block-1/procedural-programming', + 'year-1/block-2/calculus', 'year-1/block-2/logic', 'year-1/block-2/objects-in-programming', + 'year-1/block-4/data-structures-and-algorithms', 'year-1/block-4/linear-algebra', 'year-1/block-4/principles-of-data-science', + 'year-1/block-5/computational-and-cognitive-neuroscience', 'year-1/block-5/numerical-methods', 'year-1/block-5/software-engineering', + 'year-2/', 'year-2/project-2-1', 'year-2/project-2-2', 'year-2/honours-programme', + 'year-2/block-1/databases', 'year-2/block-1/graph-theory', 'year-2/block-1/probability-and-statistics', + 'year-2/block-2/machine-learning', 'year-2/block-2/reasoning-techniques', 'year-2/block-2/simulation-and-statisical-analysis', + 'year-2/block-4/human-computer-interaction-and-affective-computing', 'year-2/block-4/mathematical-modelling', 'year-2/block-4/natural-language-processing', + 'year-2/block-5/game-theory', 'year-2/block-5/introduction-to-image-and-video-processing', 'year-2/block-5/linear-programming', 'year-2/block-5/philosophy-and-artificial-intelligence', + 'year-3/', 'year-3/bachelors-thesis', 'year-3/project-3-1', 'year-3/study-abroad', 'year-3/honours-programme', + 'year-3/block-1/prolog', 'year-3/block-1/robotics-and-embedded-systems', 'year-3/block-1/semantic-web', 'year-3/block-1/software-and-systems-verification', + 'year-3/block-2/introduction-to-bio-informatics', 'year-3/block-2/logic-for-artificial-intelligence', 'year-3/block-2/parallel-programming', 'year-3/block-2/quantum-computation', 'year-3/block-2/recommender-systems', 'year-3/block-2/secure-web-applications', + 'year-3/block-4/data-analysis', 'year-3/block-4/intelligent-systems', 'year-3/block-4/operations-research-case-studies', +]; + +export const legacyBachelorRedirects = Object.freeze(Object.fromEntries( + routes.sort().map((route) => [`/bachelor/${route}`, `/data-science-and-ai/${route}`]), +)); diff --git a/src/config/sidebar.mjs b/src/config/sidebar.mjs index c8ac468..e736715 100644 --- a/src/config/sidebar.mjs +++ b/src/config/sidebar.mjs @@ -7,7 +7,7 @@ export const sidebar = [ { label: 'Bachelor', items: [ - { slug: 'bachelor' }, + { slug: 'data-science-and-ai' }, { label: 'Year 1', items: [ diff --git a/tests/programme-content.test.mjs b/tests/programme-content.test.mjs index 3d6752b..3cca236 100644 --- a/tests/programme-content.test.mjs +++ b/tests/programme-content.test.mjs @@ -18,4 +18,8 @@ test('Data Science & AI owns the former generic bachelor content tree', async () assert.doesNotMatch(content, /src\/content\/docs\/bachelor\//, `${file} has an old destination`); assert.doesNotMatch(content, /href="\.\/bachelor\/"/, `${file} has an old public link`); } + + const sidebar = await readFile('src/config/sidebar.mjs', 'utf8'); + assert.doesNotMatch(sidebar, /slug: 'bachelor'/); + assert.match(sidebar, /slug: 'data-science-and-ai'/); }); diff --git a/tests/programme-redirects.test.mjs b/tests/programme-redirects.test.mjs new file mode 100644 index 0000000..deb52fc --- /dev/null +++ b/tests/programme-redirects.test.mjs @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict'; +import { readdir } from 'node:fs/promises'; +import test from 'node:test'; +import { legacyBachelorRedirects } from '../src/config/legacy-bachelor-redirects.mjs'; + +async function routes(directory, prefix = '') { + const entries = await readdir(directory, { withFileTypes: true }); + const found = []; + for (const entry of entries) { + const relative = `${prefix}${entry.name}`; + if (entry.isDirectory()) found.push(...await routes(`${directory}/${entry.name}`, `${relative}/`)); + else if (/\.mdx?$/.test(entry.name)) { + const stem = relative.replace(/\.mdx?$/, '').replace(/index$/, ''); + found.push(`/data-science-and-ai/${stem}`.replace(/\/+/g, '/')); + } + } + return found.sort(); +} + +test('every Data Science & AI page has an exact legacy bachelor redirect', async () => { + const canonical = await routes('src/content/docs/data-science-and-ai'); + const expected = Object.fromEntries(canonical.map((target) => [target.replace('/data-science-and-ai/', '/bachelor/'), target])); + assert.deepEqual(legacyBachelorRedirects, expected); + assert.equal(legacyBachelorRedirects['/bachelor/'], '/data-science-and-ai/'); + assert.equal(legacyBachelorRedirects['/bachelor/year-1/block-2/calculus'], '/data-science-and-ai/year-1/block-2/calculus'); +});