Skip to content
Merged
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
20 changes: 5 additions & 15 deletions __tests__/current-alias-redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,41 +74,31 @@ describe('redirects for course pages', () => {
id: 42,
__typename: 'Course',
alias: '/math/42/a-course',
currentRevision: {
content: JSON.stringify({
state: {
pages: [
{ id: '527bcd55-977c-489d-a3c8-9fd0feaf51a6', title: 'Foo' },
{ id: 'a47077ca-a9f9-4ab9-bf22-6c26fb3490d8', title: 'Bar' },
],
},
}),
},
})
})

test('redirect first course page to course alias', async () => {
const response = await localTestEnvironment().fetch({
subdomain: 'en',
pathname: '/math/42/527bcd55/xyz',
pathname: '/math/42/527bc/foo',
})

const target = env.createUrl({
subdomain: 'en',
pathname: '/math/42/a-course',
pathname: '/math/42/a-course#527bc',
})
expectToBeRedirectTo(response, target, 301)
})

test('redirect course page url when title was changed', async () => {
test('redirect any course page url to course alias, even with differen title', async () => {
const response = await localTestEnvironment().fetch({
subdomain: 'en',
pathname: '/math/42/a47077ca/xyz',
pathname: '/math/42/a4707/xyz',
})

const target = env.createUrl({
subdomain: 'en',
pathname: '/math/42/a47077ca/bar',
pathname: '/math/42/a-course#a4707',
})
expectToBeRedirectTo(response, target, 301)
})
Expand Down
72 changes: 8 additions & 64 deletions src/current-alias-redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,6 @@ const ApiResult = t.type({
const CourseResult = t.type({
id: t.number,
alias: t.string,
currentRevision: t.type({ content: t.string }),
})

const CourseContent = t.type({
state: t.type({
pages: t.array(t.type({ title: t.string, id: t.string })),
}),
})

async function getPathInfo(
Expand Down Expand Up @@ -94,9 +87,6 @@ async function getPathInfo(
... on Course {
id
alias
currentRevision {
content
}
}
... on Comment {
id
Expand Down Expand Up @@ -136,53 +126,29 @@ async function getPathInfo(

const isTrashedComment = uuid.__typename === 'Comment' && uuid.trashed
let currentPath: string = ''
let hash: string = ''

if (coursePageId !== null) {
if (!CourseResult.is(uuid)) return null

try {
const courseContent = JSON.parse(uuid.currentRevision.content) as unknown

if (!CourseContent.is(courseContent)) return null

if (courseContent.state.pages.at(0)?.id?.startsWith(coursePageId)) {
currentPath = uuid.alias
} else {
const coursePage = courseContent.state.pages.find((page) =>
page.id.startsWith(coursePageId),
)

if (coursePage === undefined) {
// This case should never happen => return course alias as a fallback
currentPath = uuid.alias
} else {
const subject = uuid.alias.split('/').at(1) ?? 'serlo'
const slugTitle = toSlug(coursePage.title)
const shortPageId = coursePage.id.split('-').at(0)
if (!shortPageId) {
currentPath = uuid.alias
} else {
currentPath = `/${subject}/${uuid.id}/${shortPageId}/${slugTitle}`
}
}
}
} catch {
return null
}
currentPath = uuid.alias
hash = `#${coursePageId}`
} else {
currentPath = isTrashedComment
? `error/deleted/${uuid.__typename}`
: uuid.legacyObject !== undefined
? uuid.legacyObject.alias
: (uuid.alias ?? path)

if (uuid.legacyObject !== undefined && !isTrashedComment) {
hash = `#comment-${uuid.id ?? 0}`
}
}

const result = {
currentPath,
instance: uuid.instance,
...(uuid.legacyObject !== undefined && !isTrashedComment
? { hash: `#comment-${uuid.id ?? 0}` }
: {}),
...(hash ? { hash } : {}),
}

await env.PATH_INFO_KV.put(cacheKey, JSON.stringify(result), {
Expand All @@ -199,25 +165,3 @@ async function getPathInfo(
function gql(strings: TemplateStringsArray): string {
return strings[0]
}

// Copied from https://github.com/serlo/api.serlo.org/blob/ce94045b513e59da1ddd191b498fe01f6ff6aa0a/packages/server/src/schema/uuid/abstract-uuid/resolvers.ts#L685-L703
// Try to keep both functions in sync
function toSlug(name: string) {
return name
.toLowerCase()
.replace(/ä/g, 'ae')
.replace(/ö/g, 'oe')
.replace(/ü/g, 'ue')
.replace(/ß/g, 'ss')
.replace(/á/g, 'a')
.replace(/é/g, 'e')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u')
.replace(/ñ/g, 'n')
.replace(/ /g, '-') // replace spaces with hyphens
.replace(/[^\w-]+/g, '') // remove all non-word chars including _
.replace(/--+/g, '-') // replace multiple hyphens
.replace(/^-+/, '') // trim starting hyphen
.replace(/-+$/, '') // trim end hyphen
}