-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sports): add dynamic CMS configured redirect using middleware
The redirects are fetched periodically from CMS's GraphQL endpoint for siteSettings.redirects. The redirects' cache is in-memory and per pod, and the cache is refreshed from CMS by default between 6–9 minutes (i.e. 5–10min interval but a bit tighter and under 10min). Redirects' cache updating interval can be configured using environment variables: - MIN_REDIRECTS_UPDATE_INTERVAL_MS (defaults to 6min in milliseconds) - MAX_REDIRECTS_UPDATE_INTERVAL_MS (defaults to 9min in millseconds) Also update generated GraphQL schemas i.e. ran: ```bash cd apps/sports-helsinki/ yarn generate:graphql cp ./src/components/domain/graphql/generated/graphql.tsx \ ../../packages/components/src/types/generated/ cd ../../ npx prettier ./packages/components/src/types/generated/graphql.tsx -w rm -fr ./apps/sports-helsinki/src/components/ ``` and as a consequence disambiguate conflicting PageInfo types, PageInfo, PageUriInfo and VenueProxyPageInfo. refs LIIKUNTA-655
- Loading branch information
1 parent
c0d9b08
commit 6170274
Showing
27 changed files
with
2,728 additions
and
338 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
apps/sports-helsinki/src/edge-runtime-compatible/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
## Edge runtime compatible source code | ||
|
||
Basically for [Next.js's middleware compatibility](https://nextjs.org/docs/pages/building-your-application/routing/middleware#runtime): | ||
|
||
> Middleware currently only supports the Edge runtime. | ||
See | ||
|
||
- https://nextjs.org/docs/pages/api-reference/edge | ||
- https://nextjs.org/docs/pages/building-your-application/routing/middleware |
39 changes: 39 additions & 0 deletions
39
apps/sports-helsinki/src/edge-runtime-compatible/__tests__/isObject.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { isObject } from '../typeguards'; | ||
|
||
describe('isObject returns true', () => { | ||
it.each([ | ||
{}, | ||
{ 1: undefined }, | ||
{ key: 'value' }, | ||
{ key: 'value', anotherKey: 'another value' }, | ||
{ a: { b: 1, 2: { 3: [4, 5, 6] } } }, | ||
])('%s', (value) => { | ||
expect(isObject(value)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('isObject returns false', () => { | ||
it.each([ | ||
null, | ||
undefined, | ||
NaN, | ||
0, | ||
'', | ||
' ', | ||
'test', | ||
1, | ||
123, | ||
123.5, | ||
[], | ||
['non-empty array'], | ||
Array(1).fill(1), | ||
new Set(), | ||
new Set([1, 2, 3]), | ||
new Map(), | ||
new Map([['key', 'value']]), | ||
Symbol('symbol'), | ||
BigInt(123), | ||
])('%s', (value) => { | ||
expect(isObject(value)).toBe(false); | ||
}); | ||
}); |
34 changes: 34 additions & 0 deletions
34
apps/sports-helsinki/src/edge-runtime-compatible/__tests__/isStringOrNull.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { isStringOrNull } from '../typeguards'; | ||
|
||
describe('isStringOrNull returns true', () => { | ||
it.each(['', ' ', 'test', '匹', null])('%s', (value) => { | ||
expect(isStringOrNull(value)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('isStringOrNull returns false', () => { | ||
it.each([ | ||
{}, | ||
undefined, | ||
NaN, | ||
0, | ||
1, | ||
123, | ||
123.5, | ||
{ 1: undefined }, | ||
{ key: 'value' }, | ||
{ key: 'value', anotherKey: 'another value' }, | ||
{ a: { b: 1, 2: { 3: [4, 5, 6] } } }, | ||
[], | ||
['non-empty array'], | ||
Array(1).fill(1), | ||
new Set(), | ||
new Set([1, 2, 3]), | ||
new Map(), | ||
new Map([['key', 'value']]), | ||
Symbol('symbol'), | ||
BigInt(123), | ||
])('%s', (value) => { | ||
expect(isStringOrNull(value)).toBe(false); | ||
}); | ||
}); |
36 changes: 36 additions & 0 deletions
36
apps/sports-helsinki/src/edge-runtime-compatible/__tests__/isValidRedirectAddress.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { isValidRedirectAddress } from '../isValidRedirectAddress'; | ||
|
||
describe('isValidRedirectAddress returns true', () => { | ||
it.each([ | ||
'/', | ||
'/questions/198606/can-i-use-commas-in-a-url', | ||
'/fi/search?searchType=Venue&text=test+a+text+search&venueOrderBy=wheelchair', | ||
'/wiki/File:Artistic_swimming_women%27s_team_medal_ceremony_at_Tokyo_2020.jpg', | ||
'/wiki/2020年夏季奧林匹克運動會團體韻律泳比賽', | ||
'/wiki/Καλλιτεχνική_κολύμβηση_στους_Θερινούς_Ολυμπιακούς_Αγώνες_2020_–_Ομαδικό_γυναικών#Αποτελέσματα', | ||
'/auth/realms/helsinki-tunnistus/protocol/openid-connect/auth?client_id=kukkuu-ui&redirect_uri=' + | ||
'https%3A%2F%2Fkummilapset.hel.fi%2Fcallback&response_type=code&scope=openid+profile+email&state=' + | ||
'5df5f78691853f7ef4f5db2d02fb0e20&code_challenge=ZbOcQqOA-lLNB93o_OnaCJo7QcINCfx-cfVbMrZwDFD&' + | ||
'code_challenge_method=S256&response_mode=query', | ||
'/fi/أطفال-الثقافة/', | ||
])('%s', (pathname) => { | ||
expect(isValidRedirectAddress(pathname)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('isValidRedirectAddress returns false', () => { | ||
it.each([ | ||
'', | ||
' ', | ||
' /', | ||
'/ ', | ||
' / ', | ||
'\n/', | ||
'//', | ||
'/`', | ||
'https://example.org/', | ||
'https://liikunta.hel.fi/search', | ||
])('%s', (pathname) => { | ||
expect(isValidRedirectAddress(pathname)).toBe(false); | ||
}); | ||
}); |
137 changes: 137 additions & 0 deletions
137
apps/sports-helsinki/src/edge-runtime-compatible/__tests__/parseRedirects.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { parseRedirects } from '../parseRedirects'; | ||
import type { AppLanguage, Redirects } from '../types'; | ||
|
||
let consoleWarningSpy: jest.SpyInstance; | ||
|
||
beforeEach(() => { | ||
consoleWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('parseRedirects', () => { | ||
type TestInput = { | ||
encodedRedirects: string | null | undefined; | ||
language: AppLanguage; | ||
expectedOutput: Redirects; | ||
expectedWarnings: string[]; | ||
}; | ||
|
||
it.each<TestInput>([ | ||
{ | ||
encodedRedirects: null, | ||
language: 'fi', | ||
expectedOutput: {}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: undefined, | ||
language: 'fi', | ||
expectedOutput: {}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: 'null', | ||
language: 'fi', | ||
expectedOutput: {}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: '[]', | ||
language: 'fi', | ||
expectedOutput: {}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: '[{"/a": "/b"}]', | ||
language: 'fi', | ||
expectedOutput: { '/a': '/b', '/fi/a': '/b' }, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: | ||
'[{"/a": "/b"},' + | ||
'{"": ""},' + | ||
'{"invalid-from-path": "/valid-to-path"},' + | ||
'{"/test": "https://example.org/other-site/", "/c": "/d"},' + | ||
'{"/valid-from-path": "invalid-to-path"},' + | ||
'{"invalid-from-path": "invalid-to-path"}]', | ||
language: 'fi', | ||
expectedOutput: { '/a': '/b', '/fi/a': '/b', '/c': '/d', '/fi/c': '/d' }, | ||
expectedWarnings: [ | ||
'Ignoring invalid fi redirect: invalid-from-path -> /valid-to-path', | ||
'Ignoring invalid fi redirect: /test -> https://example.org/other-site/', | ||
'Ignoring invalid fi redirect: /valid-from-path -> invalid-to-path', | ||
'Ignoring invalid fi redirect: invalid-from-path -> invalid-to-path', | ||
], | ||
}, | ||
{ | ||
encodedRedirects: '[{"/a": "/b"}]', | ||
language: 'sv', | ||
expectedOutput: { '/a': '/b', '/sv/a': '/b' }, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: '[{"/a": "/b"}]', | ||
language: 'en', | ||
expectedOutput: { '/a': '/b', '/en/a': '/b' }, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: | ||
'[{"/wiki/ακ#Απ": "/中-cn/to_go/2020年?search=with+text&type=new#info"}]', | ||
language: 'en', | ||
expectedOutput: { | ||
'/en/wiki/ακ#Απ': '/中-cn/to_go/2020年?search=with+text&type=new#info', | ||
'/wiki/ακ#Απ': '/中-cn/to_go/2020年?search=with+text&type=new#info', | ||
}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: '[{"/a": "/b"}, {"/c": "/d"}]', | ||
language: 'fi', | ||
expectedOutput: { | ||
'/a': '/b', | ||
'/c': '/d', | ||
'/fi/a': '/b', | ||
'/fi/c': '/d', | ||
}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: '{"/a": "/b", "/c": "/d"}', | ||
language: 'en', | ||
expectedOutput: { | ||
'/a': '/b', | ||
'/c': '/d', | ||
'/en/a': '/b', | ||
'/en/c': '/d', | ||
}, | ||
expectedWarnings: [], | ||
}, | ||
{ | ||
encodedRedirects: '[{"/a/b/c": "/d"}, {"/e/f/": "/g/h/"}]', | ||
language: 'sv', | ||
expectedOutput: { | ||
'/a/b/c': '/d', | ||
'/e/f/': '/g/h/', | ||
'/sv/a/b/c': '/d', | ||
'/sv/e/f/': '/g/h/', | ||
}, | ||
expectedWarnings: [], | ||
}, | ||
])( | ||
'parseRedirects($encodedRedirects, "$language") == $expectedOutput with expected warnings', | ||
({ encodedRedirects, language, expectedOutput, expectedWarnings }) => { | ||
expect(parseRedirects(encodedRedirects, language)).toStrictEqual( | ||
expectedOutput | ||
); | ||
for (const warning of expectedWarnings) { | ||
expect(consoleWarningSpy).toHaveBeenCalledWith(warning); | ||
} | ||
expect(consoleWarningSpy).toHaveBeenCalledTimes(expectedWarnings.length); | ||
} | ||
); | ||
}); |
3 changes: 3 additions & 0 deletions
3
apps/sports-helsinki/src/edge-runtime-compatible/constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// TODO: For some reason middleware cannot read `'@events-helsinki/components` package without breaking the build | ||
export const LOCALES = ['fi', 'en', 'sv'] as const; // i18n.locales.join('|'); | ||
export const DEFAULT_LANGUAGE = 'fi'; |
35 changes: 35 additions & 0 deletions
35
apps/sports-helsinki/src/edge-runtime-compatible/isValidRedirectAddress.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* Check if the given pathname is a valid redirect address | ||
* @param pathname The pathname to check | ||
* @returns True if the pathname is a valid redirect address, otherwise false | ||
*/ | ||
export function isValidRedirectAddress(pathname: unknown): boolean { | ||
/** | ||
* Unicode character class escapes (e.g. \p{Letter}): | ||
* | ||
* L = Letter, includes categories: | ||
* - Lu = Uppercase Letter = https://www.compart.com/en/unicode/category/Lu for e.g. "A" (Latin capital letter A) | ||
* - Ll = Lowercase Letter = https://www.compart.com/en/unicode/category/Ll for e.g. "a" (Latin small letter A) | ||
* - Lt = Titlecase Letter = https://www.compart.com/en/unicode/category/Lt for e.g. "Lj" (Latin capital letter lj) | ||
* - Lm = Modifier Letter = https://www.compart.com/en/unicode/category/Lm for e.g. "ʼ" (Modifier Letter Apostrophe) | ||
* - Lo = Other Letter = https://www.compart.com/en/unicode/category/Lo for e.g. "ƒ" (Latin small letter f with hook) | ||
* | ||
* N = Number, includes categories: | ||
* - Nd = Decimal Number = https://www.compart.com/en/unicode/category/Nd for e.g. "5" (Digit Five) | ||
* - Nl = Letter Number = https://www.compart.com/en/unicode/category/Nl for e.g. "Ⅷ" (Roman Numeral Eight) | ||
* - No = Other Number = https://www.compart.com/en/unicode/category/No for e.g. "½" (Vulgar Fraction One Half) | ||
* | ||
* Pc = Connector Punctuation = https://www.compart.com/en/unicode/category/Pc for e.g. "_" (underscore) | ||
* Pd = Dash Punctuation = https://www.compart.com/en/unicode/category/Pd for e.g. "-" (hyphen-minus) | ||
* Sc = Currency Symbol = https://www.compart.com/en/unicode/category/Sc for e.g. "$" (Dollar sign) | ||
* | ||
* See documentation: | ||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape | ||
* https://unicode.org/reports/tr18/#General_Category_Property | ||
*/ | ||
return ( | ||
typeof pathname === 'string' && | ||
(pathname == '/' || | ||
/^(?:\/[\p{L}\p{N}\p{Pc}\p{Pd}\p{Sc}#%&+,.:;=?@]+)+\/?$/u.test(pathname)) | ||
); | ||
} |
Oops, something went wrong.