diff --git a/server/models/views/alerts-and-warnings.js b/server/models/views/alerts-and-warnings.js
index 65ef129d3..0a109a20f 100644
--- a/server/models/views/alerts-and-warnings.js
+++ b/server/models/views/alerts-and-warnings.js
@@ -2,36 +2,39 @@ const { bingKeyMaps } = require('../../config')
const config = require('../../config')
class ViewModel {
- constructor ({ location, place, floods, station, error }) {
+ constructor ({ q, location, place, floods = [], station, canonical, error }) {
Object.assign(this, {
- q: location,
+ q,
+ station,
map: station ? 'map-station' : 'map',
- station: station || null,
- placeName: place ? place.name : '',
- placeBbox: place ? place.bbox2k : [],
- placeCentre: place ? place.center : [],
+ placeName: place?.name || '',
+ placeBbox: place?.bbox2k || [],
+ placeCentre: place?.center || [],
timestamp: Date.now(),
- error: error ? true : null,
+ metaCanonical: canonical,
+ canonicalUrl: canonical,
+ error: !!error,
displayGetWarningsLink: true,
displayLongTermLink: true,
- isEngland: place ? place.isEngland.is_england : null,
- isDummyData: floods ? floods.isDummyData : false,
+ isEngland: place?.isEngland?.is_england,
+ isDummyData: floods?.isDummyData,
floodRiskUrl: config.floodRiskUrl
})
+ if (this.station && this.station.agency_name) {
+ this.pageTitle = `${this.station.agency_name} - flood alerts and warnings`
+ } else {
+ const pageTitle = place?.name ?? location
+
+ this.pageTitle = `${pageTitle ? pageTitle + ' - f' : 'F'}lood alerts and warnings`
+ }
+
if (error) {
this.pageTitle = 'Sorry, there is currently a problem searching a location'
- } else {
- if (this.station && this.station.agency_name) {
- this.pageTitle = `${this.station.agency_name} - flood alerts and warnings`
- } else {
- this.pageTitle = `${location ? location + ' - f' : 'F'}lood alerts and warnings`
- }
}
- this.countFloods = floods ? floods.floods.length : 0
- this.floods = floods
- ? floods.groups.map(item => item)
- : []
+
+ this.countFloods = floods?.floods?.length ?? 0
+ this.floods = floods?.groups?.map(item => item)
this.expose = {
station: this.station,
diff --git a/server/routes/alerts-and-warnings.js b/server/routes/alerts-and-warnings.js
index f371f2b6c..5bbccd591 100644
--- a/server/routes/alerts-and-warnings.js
+++ b/server/routes/alerts-and-warnings.js
@@ -1,92 +1,175 @@
+const qs = require('qs')
const joi = require('@hapi/joi')
+const boom = require('@hapi/boom')
const ViewModel = require('../models/views/alerts-and-warnings')
const Floods = require('../models/floods')
const locationService = require('../services/location')
const util = require('../util')
+const { slugify } = require('./lib/utils')
+
+const page = 'alerts-and-warnings'
+const QUERY_STRING_LOCATION_MAX_LENGTH = 200
+
+function renderLocationNotFound (location, h) {
+ return h.view('location-not-found', { pageTitle: 'Error: Find location - Check for flooding', href: page, location }).takeover()
+}
+
+function renderNotFound (location) {
+ return boom.notFound(`Location ${location} not found`)
+}
+
+function createQueryParametersString (queryObject) {
+ const { q, location, ...otherParameters } = queryObject
+ const queryString = qs.stringify(otherParameters, { addQueryPrefix: true, encode: false })
+ return queryString
+}
+
+async function routeHandler (request, h) {
+ let location = request.query.q || request.payload?.location
+
+ request.yar.set('q', { location })
+
+ const direction = request.query.direction === 'downstream' ? 'd' : 'u'
+
+ let model, floods
+
+ if (request.query.station) {
+ const station = await request.server.methods.flood.getStationById(request.query.station, direction)
+
+ const warningsAlerts = await request.server.methods.flood.getWarningsAlertsWithinStationBuffer(station.rloi_id)
+ floods = new Floods({ floods: warningsAlerts })
+ model = new ViewModel({ location, floods, station })
+ return h.view(page, { model })
+ }
+
+ if (!location) {
+ const data = await request.server.methods.flood.getFloods()
+ floods = new Floods(data)
+ model = new ViewModel({ location, floods })
+ return h.view(page, { model })
+ }
+
+ location = util.cleanseLocation(location)
+
+ const [place] = await locationService.find(location)
+
+ if (!place) {
+ if (request.method === 'get') {
+ return renderNotFound(location)
+ }
+
+ return renderLocationNotFound(location, h)
+ }
+
+ if (!place.isEngland.is_england) {
+ request.logger.warn({
+ situation: 'Location search error: Valid response but location not in England.'
+ })
+
+ if (request.method === 'post') {
+ return renderLocationNotFound(location, h)
+ }
+ }
+
+ const queryString = createQueryParametersString(request.query)
+
+ return h.redirect(`/${page}/${slugify(place?.name)}${queryString}`).permanent()
+}
+
+async function locationRouteHandler (request, h) {
+ const canonicalUrl = request.url.origin + request.url.pathname
+ const location = util.cleanseLocation(request.params.location)
+
+ const [place] = await locationService.find(location)
+
+ if (location.match(/^england$/i)) {
+ return h.redirect(`/${page}`)
+ }
+
+ if (slugify(place?.name) !== location) {
+ return renderNotFound(location)
+ }
+
+ if (!place?.isEngland.is_england) {
+ request.logger.warn({
+ situation: 'Location search error: Valid response but location not in England.'
+ })
+
+ return renderNotFound(location)
+ }
+
+ // Data passed to floods model so the schema is the same as cached floods
+ const data = await request.server.methods.flood.getFloodsWithin(place.bbox2k)
+ const floods = new Floods(data)
+ const model = new ViewModel({ location, place, floods, canonical: canonicalUrl, q: request.yar.get('q')?.location })
+ request.yar.set('q', null)
+ return h.view(page, { model })
+}
+
+function failActionHandler (request, h) {
+ request.logger.warn({
+ situation: 'Location search error: Invalid or no string input.'
+ })
+
+ const location = request.query.q || request.payload?.location
+
+ if (!location) {
+ return h.redirect(page).takeover()
+ } else {
+ return renderLocationNotFound(location, h)
+ }
+}
module.exports = [{
method: 'GET',
- path: '/alerts-and-warnings',
- handler: async (request, h) => {
- const { q: location } = request.query
- let model, place, floods
-
- const direction = request.query.direction === 'downstream' ? 'd' : 'u'
- const station = request.query.station
- ? await request.server.methods.flood.getStationById(request.query.station, direction)
- : null
-
- if (station) {
- const warningsAlerts = await request.server.methods.flood.getWarningsAlertsWithinStationBuffer(station.rloi_id)
- floods = new Floods({ floods: warningsAlerts })
- model = new ViewModel({ location, place, floods, station })
- return h.view('alerts-and-warnings', { model })
- } else if (typeof location === 'undefined' || location === '' || location.match(/^england$/i)) {
- floods = new Floods(await request.server.methods.flood.getFloods())
- model = new ViewModel({ location, place, floods, station })
- return h.view('alerts-and-warnings', { model })
- } else {
- try {
- [place] = await locationService.find(util.cleanseLocation(location))
- } catch (error) {
- request.logger.warn({
- situation: `Location search error: [${error.name}] [${error.message}]`,
- err: error
- })
- const floods = new Floods(await request.server.methods.flood.getFloods())
- model = new ViewModel({ location, place, floods, station, error })
- return h.view('alerts-and-warnings', { model })
- }
-
- if (!place) {
- model = new ViewModel({ location, place, floods, station })
- return h.view('alerts-and-warnings', { model })
- }
-
- if (!place?.isEngland.is_england) {
- // If no place return empty floods
- model = new ViewModel({ location, place, floods, station })
- return h.view('alerts-and-warnings', { model })
- } else {
- // Data passed to floods model so the schema is the same as cached floods
- const data = await request.server.methods.flood.getFloodsWithin(place.bbox2k)
- floods = new Floods(data)
- model = new ViewModel({ location, place, floods, station })
- return h.view('alerts-and-warnings', { model })
- }
+ path: `/${page}`,
+ handler: routeHandler,
+ options: {
+ validate: {
+ query: joi.object({
+ q: joi.string().trim().max(QUERY_STRING_LOCATION_MAX_LENGTH),
+ station: joi.string(),
+ btn: joi.string(),
+ ext: joi.string(),
+ fid: joi.string(),
+ lyr: joi.string(),
+ v: joi.string()
+ }),
+ failAction: failActionHandler
}
- },
+ }
+},
+{
+ method: 'GET',
+ path: `/${page}/{location}`,
+ handler: locationRouteHandler,
options: {
validate: {
+ params: joi.object({
+ location: joi.string().lowercase()
+ }),
query: joi.object({
- q: joi.string().allow('').trim().max(200),
station: joi.string(),
btn: joi.string(),
ext: joi.string(),
fid: joi.string(),
lyr: joi.string(),
v: joi.string()
- })
+ }),
+ failAction: failActionHandler
}
}
-}, {
+},
+{
method: 'POST',
- path: '/alerts-and-warnings',
- handler: async (request, h) => {
- const { location } = request.payload
- if (location === '') {
- return h.redirect(`/alerts-and-warnings?q=${location}`)
- }
- return h.redirect(`/alerts-and-warnings?q=${encodeURIComponent(util.cleanseLocation(location))}`)
- },
+ path: `/${page}`,
+ handler: routeHandler,
options: {
validate: {
payload: joi.object({
- location: joi.string().allow('').trim().max(200).required()
+ location: joi.string().allow('').trim().max(QUERY_STRING_LOCATION_MAX_LENGTH).required()
}),
- failAction: (request, h, _err) => {
- return h.view('alerts-and-warnings').takeover()
- }
+ failAction: failActionHandler
}
}
}]
diff --git a/server/services/lib/bing-results-parser.js b/server/services/lib/bing-results-parser.js
index 6f6f7ad55..441023941 100644
--- a/server/services/lib/bing-results-parser.js
+++ b/server/services/lib/bing-results-parser.js
@@ -8,7 +8,7 @@ async function bingResultsParser (bingData, getIsEngland) {
const allowedConfidences = ['medium', 'high']
const allowedTypes = [
- 'populatedplace', 'postcode1', 'postcode3', 'admindivision2', 'neighborhood'
+ 'populatedplace', 'postcode1', 'postcode3', 'admindivision2', 'neighborhood', 'religiousstructure', 'roadblock'
]
const data = set.resources
.filter(r => allowedConfidences.includes(r.confidence.toLowerCase()))
diff --git a/server/views/layout.html b/server/views/layout.html
index f9e35eb87..60dada24d 100644
--- a/server/views/layout.html
+++ b/server/views/layout.html
@@ -30,7 +30,7 @@
{% if metaCanonical %}
-
+
{% endif %}