Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improves link information for departures with Open Graph (and minor SEO improvements) #287

Merged
merged 7 commits into from
Apr 19, 2024
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,28 @@ This will set correct version in releases on Github.
```sh
yarn generate-widget-version
```

## Sitemap & Stop Place overview

Sitemap is generated automatically and has an overview of all links to departure
pages which can be crawled and searchable through search engines.

Sitemap is generated as part of a build step with correct URLs specified in the
`<org.>.json` files. If you want to generate manually you can run command:

```bash
yarn next-sitemap --config next-sitemap.js
```

### Updating StopPlaces data

If the National Stop Register has changed (new stop places etc), we can generate
a new data layer by running:

```
node scripts/generate-stopplaces/download-and-generate.js
```

This will remove the static cache file and regenerate the data. Doing a deploy
will create sitemap as part of the build step and update all departure URLs with
the new data.
31 changes: 26 additions & 5 deletions next-sitemap.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
const orgId = process.env.NEXT_PUBLIC_PLANNER_ORG_ID;
const {
listStopPlaces,
} = require('./scripts/generate-stopplaces/list-stop-places');

function getEnvironmentUrls() {
const orgId = process.env.NEXT_PUBLIC_PLANNER_ORG_ID;
function getOrgData() {
const org = require(`./orgs/${orgId}.json`);
return org.urls.sitemapUrls;
return {
sitemapUrls: org.urls.sitemapUrls,
authPrefix: authIdToPrefix(org.authorityId),
};
}

const environmentUrls = getEnvironmentUrls();
const orgData = getOrgData();
const environmentUrls = orgData.sitemapUrls;
const environment = process.env.NEXT_PUBLIC_ENVIRONMENT;

/** @type {import('next-sitemap').IConfig} */
Expand All @@ -19,7 +26,9 @@ module.exports = {
},

// Adds path as it doesn't support dynamic routes.
additionalPaths(config) {
async additionalPaths(config) {
const data = await listStopPlaces(orgData.authPrefix);

const result = [];
// @TODO Consider if we should prepopulate this for better SEO for
// quays.
Expand All @@ -29,6 +38,18 @@ module.exports = {
priority: 0.7,
lastmod: new Date().toISOString(),
});

for (const id of data) {
result.push({
loc: `/departures/${encodeURIComponent(id)}`,
changefreq: 'always',
priority: 0.3,
});
}
return result;
},
};

function authIdToPrefix(authId) {
return authId.split(':')[0];
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@mapbox/polyline": "^1.2.1",
"@react-aria/focus": "^3.16.2",
"@react-hook/resize-observer": "^1.2.6",
"@resvg/resvg-js": "^2.6.2",
"@turf/centroid": "^7.0.0-alpha.113",
"bunyan": "^1.8.15",
"compare-versions": "^6.1.0",
Expand All @@ -56,6 +57,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-syntax-highlighter": "^15.5.0",
"satori": "^0.10.13",
"swr": "^2.2.5",
"uuid": "^9.0.1",
"zod": "^3.22.4"
Expand Down
Binary file added public/fonts/Roboto-Bold.ttf
Binary file not shown.
Binary file added public/fonts/Roboto-Regular.ttf
Binary file not shown.
60 changes: 60 additions & 0 deletions scripts/generate-stopplaces/data-layer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const fs = require('fs/promises');
const { join } = require('path');

const STOP_PLACE_URL = 'https://api.entur.io/stop-places/v1/read/stop-places';
const MAX_NUMBERS_FOR_ITERATIONS = 1000;

const CACHE_FILE = join(__dirname, 'stop-places.json');
module.exports.CACHE_FILE = CACHE_FILE;

module.exports.getAll = async function getAll() {
try {
const cache = await fs.readFile(CACHE_FILE);
return JSON.parse(cache);
} catch (e) {
console.log('No cache file found, fetching all stop places');
}

const data = await listAllStopPlaces([]);
await fs.writeFile(CACHE_FILE, JSON.stringify(data, null, 2));
return data;
};

async function listAllStopPlaces(arr = [], skip = 0) {
console.log(`Fetching from skip ${skip} (total ${arr.length})`);
const result = await fetch(
`${STOP_PLACE_URL}?count=${MAX_NUMBERS_FOR_ITERATIONS}&skip=${skip}`,
);
if (!result.ok) {
const error = await result.json();
throw new Error('Unable to fetch data', error);
}
const data = await result.json();

if (data.length < MAX_NUMBERS_FOR_ITERATIONS) {
return arr;
} else {
await delay();
return listAllStopPlaces(
arr.concat(
data.map((stopPlace) => ({
id: stopPlace.id,
public: stopPlace.publication === 'PUBLIC',
validBetween: stopPlace.validBetween?.[0]
? [
stopPlace.validBetween?.[0]?.fromDate,
stopPlace.validBetween?.[0]?.toDate,
]
: [],
zones:
stopPlace.tariffZones?.tariffZoneRef?.map((zone) => zone.ref) ?? [],
})),
),
skip + data.length,
);
}
}

function delay(time = 500) {
return new Promise((resolve) => setTimeout(resolve, 500));
}
15 changes: 15 additions & 0 deletions scripts/generate-stopplaces/download-and-generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { getAll, CACHE_FILE } = require('./data-layer');
const fs = require('fs/promises');

main();
async function main() {
try {
console.log('Deleting cache file');
await fs.unlink(CACHE_FILE);
} catch (e) {
console.error('Unable to delete cache file');
}

const data = await getAll();
console.log(`Generated and cached ${data.length} stop places`);
}
33 changes: 33 additions & 0 deletions scripts/generate-stopplaces/list-stop-places.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { getAll } = require('./data-layer');

module.exports.listStopPlaces = async function listStopPlaces(authorityPrefix) {
const data = await getAll();
const ids = data
.filter(isValid)
.filter(createAuthorityPredicate(authorityPrefix))
.map((i) => i.id);

return ids;
};

function isValid(item) {
if (!item.validBetween) {
// assume it is invalid if no validBetween is defined
return false;
}
const to = item.validBetween[1];

// To is not defined. meaning it is still valid
if (!to) {
return true;
}

return false;
}
function createAuthorityPredicate(authorityPrefix) {
return function isOfAuthority(item) {
return item.zones.some((zone) =>
zone.toLowerCase().startsWith(authorityPrefix.toLowerCase()),
);
};
}
Loading
Loading