diff --git a/app.js b/app.js
index 7d1980a5c..13015579b 100644
--- a/app.js
+++ b/app.js
@@ -1,11 +1,22 @@
/* eslint import/order: "off" */
/* eslint n/no-unpublished-require: "off" */
const Sentry = require('@sentry/node')
+const {
+ APP_PORT,
+ ENV,
+ REDIS_URL,
+ SESSION_SECRET,
+ SENTRY_DSN
+} = require('./config')
-Sentry.init({
- dsn: 'https://304866cea16570b04e2090537ae9ac77@o345774.ingest.us.sentry.io/4509252675371008',
- sendDefaultPii: true
-})
+const isDev = ENV === 'development'
+if (!isDev) {
+ Sentry.init({
+ dsn: SENTRY_DSN,
+ sendDefaultPii: false,
+ environment: ENV
+ })
+}
const path = require('path')
const redisClient = require('./helpers/redis-client')
@@ -18,15 +29,12 @@ const RedisStore = require('connect-redis')(session)
const helmet = require('helmet')
const nunjucks = require('nunjucks')
const { xss } = require('express-xss-sanitizer')
-const ApplicationError = require('./helpers/application-error')
const rev = require('./filters/rev')
-const { APP_PORT, ENV, REDIS_URL, SESSION_SECRET } = require('./config')
const addComponentRoutes = require('./routes/add-component')
const app = express()
-const isDev = ENV === 'development'
if (!isDev) {
// Only trust single proxy (Nginx)
@@ -55,7 +63,7 @@ const sessionOptions = {
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true,
- cookie: { secure: !isDev, maxAge: 24 * 60 * 60 * 1000 }
+ cookie: { secure: !isDev, maxAge: 24 * 60 * 60 * 1000 }
}
if (REDIS_URL) {
@@ -87,7 +95,7 @@ app.set('view engine', 'njk')
const njk = expressNunjucks(app, {
watch: isDev,
noCache: false,
- loader: nunjucks.FileSystemLoader,
+ loader: nunjucks.FileSystemLoader
})
njk.env.addFilter('rev', rev)
@@ -107,9 +115,10 @@ app.use(xss())
app.use('/contribute/add-new-component', addComponentRoutes)
// Fallback route to 404
-app.get('*', (req, res, next) => {
- const error = new ApplicationError('Page not found', 404)
- next(error)
+app.use((req, res) => {
+ res.status(404).render('404', {
+ title: 'Page not found'
+ })
})
// The error handler must be registered before any other error middleware and after all controllers
diff --git a/config.js b/config.js
index 4fb6ee709..9be15e7ff 100644
--- a/config.js
+++ b/config.js
@@ -5,19 +5,34 @@ const config = {
GITHUB_REPO_NAME: process.env.GITHUB_REPO_NAME || 'your-default-repo-name',
NOTIFY_PR_TEMPLATE: process.env.NOTIFY_PR_TEMPLATE || '',
NOTIFY_SUBMISSION_TEMPLATE: process.env.NOTIFY_SUBMISSION_TEMPLATE || '',
+ NOTIFY_VERIFICATION_TEMPLATE: process.env.NOTIFY_VERIFICATION_TEMPLATE || '',
NOTIFY_EMAIL: process.env.NOTIFY_EMAIL || 'your-email',
NOTIFY_TOKEN: process.env.NOTIFY_TOKEN || 'your-default-repo-token',
NOTIFY_EMAIL_RETRY_MS:
parseInt(process.env.NOTIFY_EMAIL_RETRY_MS, 10) || 5000,
NOTIFY_EMAIL_MAX_RETRIES:
parseInt(process.env.NOTIFY_EMAIL_MAX_RETRIES, 10) || 5,
+ APP_URL: process.env.APP_URL,
APP_PORT: parseInt(process.env.APP_PORT, 10) || 3001,
REDIS_URL: process.env.REDIS_URL,
REDIS_AUTH_TOKEN: process.env.REDIS_AUTH_TOKEN,
REDIS_PORT: parseInt(process.env.REDIS_PORT, 10) || 6379,
SESSION_SECRET: process.env.SESSION_SECRET || 'your-secret-key',
ENV: process.env.ENV || 'development',
+ SENTRY_DSN: process.env.SENTRY_DSN,
+ ALLOWED_EMAIL_DOMAINS: ['justice.gov.uk'],
COMPONENT_FORM_PAGES: {
+ 'email': {
+ title: 'Enter your email address',
+ fields: {
+ emailAddress: {
+ label: 'Email address',
+ hint: 'Enter an email address ending @justice.gov.uk'
+ }
+ },
+ showOnCya: false,
+ removable: false
+ },
'component-details': {
title: 'Component details',
fields: {
@@ -231,6 +246,35 @@ const config = {
SHARE_YOUR_DETAILS: {
addNameToComponentPage: 'add my name to the component page',
addTeamNameToComponentPage: 'add my team name to the component page'
+ },
+ MESSAGES: {
+ emailVerificationExpired: {
+ type: 'error',
+ title: 'Email verification link expired',
+ text: `Enter your email address below to receive a new verification link.`
+ },
+ emailVerificationSuccess: {
+ type: 'success',
+ title: 'Your email address has been verified',
+ text: 'You can now submit a new component'
+ },
+ emailVerificationInvalidToken: {
+ type: 'error',
+ title: 'Email verification link invalid',
+ text: `Enter your email address below to receive a new verification link.`
+ },
+ componentImageUploaded: (filename) => {
+ return {
+ type: 'success',
+ text: `File ‘${filename}’ has been uploaded.`
+ }
+ },
+ componentImageRemoved: (filename) => {
+ return {
+ type: 'success',
+ message: `File ‘${filename}’ has been removed.`
+ }
+ }
}
}
diff --git a/docs/_data/statusInfo.json b/docs/_data/statusInfo.json
index 705bbfd68..1c6bf8d23 100644
--- a/docs/_data/statusInfo.json
+++ b/docs/_data/statusInfo.json
@@ -12,7 +12,7 @@
"Experimental": {
"link": "How to use ‘experimental’ {{ types }}",
"linkContent": "Anyone can add an ‘experimental’ status {{ type }} to the MoJ Design System. They’re early in development and can be used as a starting point.",
- "statusMessage": "Created"
+ "statusMessage": "Added"
},
"Archived": {
"link": "About ‘archived’ {{ types }}",
diff --git a/docs/_includes/layouts/base.njk b/docs/_includes/layouts/base.njk
index 939032e99..59fe855f6 100644
--- a/docs/_includes/layouts/base.njk
+++ b/docs/_includes/layouts/base.njk
@@ -40,7 +40,7 @@
{% block beforeContent %}{% endblock %}
-
+
{% block content %}
{{ content | safe }}
{% endblock %}
diff --git a/docs/_includes/layouts/partials/building-block-header.njk b/docs/_includes/layouts/partials/building-block-header.njk
index 941eac92a..24a29982a 100644
--- a/docs/_includes/layouts/partials/building-block-header.njk
+++ b/docs/_includes/layouts/partials/building-block-header.njk
@@ -11,8 +11,8 @@
status,
statusInfo[status],
statusDate,
- creatorName,
- creatorTeam
+ contributorName,
+ contributorTeam
) if statusInfo[status] }}
diff --git a/docs/_includes/layouts/partials/page-header.njk b/docs/_includes/layouts/partials/page-header.njk
index 7aa9a6427..96a6cb9ea 100644
--- a/docs/_includes/layouts/partials/page-header.njk
+++ b/docs/_includes/layouts/partials/page-header.njk
@@ -16,16 +16,16 @@
text: title,
caption: subsection,
statusTag: { text: status } if status
- }) }}
+ }) }}
- {{ statusLabel(
- type,
- status,
- statusInfo[status],
- statusDate,
- creatorName,
- creatorTeam
- ) if statusInfo[status] }}
+ {{ statusLabel({
+ type: type,
+ status: status,
+ statusData: statusInfo[status],
+ statusDate: statusDate,
+ contributorName: contributorName,
+ contributorTeam: contributorTeam
+ }) if statusInfo[status] }}
{% if lede %}
{{ lede | safe }}
diff --git a/docs/_includes/macros/status-label/macro.njk b/docs/_includes/macros/status-label/macro.njk
index 009397720..fe403603c 100644
--- a/docs/_includes/macros/status-label/macro.njk
+++ b/docs/_includes/macros/status-label/macro.njk
@@ -1,3 +1,3 @@
-{% macro statusLabel(type, status, statusData, statusDate, creatorName, creatorTeam) %}
- {%- include "./template.njk" -%}
+{% macro statusLabel(params) %}
+ {% include "./template.njk" %}
{% endmacro %}
diff --git a/docs/_includes/macros/status-label/template.njk b/docs/_includes/macros/status-label/template.njk
index 75e19d3c3..a291a138f 100644
--- a/docs/_includes/macros/status-label/template.njk
+++ b/docs/_includes/macros/status-label/template.njk
@@ -1,36 +1,30 @@
{% from "govuk/components/details/macro.njk" import govukDetails %}
-
-
{% set statusAttribution -%}
- {%- if status == "Experimental" %}
- by {{ creatorName | default("someone") }} in {{ creatorTeam | default("the community") }}
- {%- endif %}
- {%- if statusDate %}
- in {{ statusDate }}
+ {%- if params.status == "Experimental" %}
+ {%- if params.contributorTeam %}
+ by {{ params.contributorName | default("someone") }} in {{ params.contributorTeam }}
+ {%- elif params.contributorName %}
+ by {{ contributorName }}
+ {% endif %}
+ {% endif %}
+ {%- if params.statusDate %}
+ in {{ params.statusDate }}
{%- endif %}.
{%- endset %}
-{% set typeSingular = type -%}
-{% set typePlural = type + "s" -%}
+{% set typeSingular = params.type -%}
+{% set typePlural = params.type + "s" -%}
{% set linkText -%}
- {{ statusData.link | renderString({
+ {{ params.statusData.link | renderString({
types: typePlural,
type: typeSingular
}) | upperFirst }}
{%- endset %}
{% set linkHtml -%}
- {{ statusData.linkContent | renderString({
+ {{ params.statusData.linkContent | renderString({
types: typePlural,
type: typeSingular
}) | upperFirst }}
@@ -38,7 +32,7 @@ Arguments to be passed:
{%- endset %}
- {{ statusData.statusMessage }} {{ statusAttribution }}
+ {{ params.statusData.statusMessage }} {{ statusAttribution }}
{{ govukDetails({
diff --git a/docs/stylesheets/components/_layout.scss b/docs/stylesheets/components/_layout.scss
index cb3869b0b..dbf760206 100644
--- a/docs/stylesheets/components/_layout.scss
+++ b/docs/stylesheets/components/_layout.scss
@@ -40,6 +40,19 @@
@include govuk-responsive-padding(6);
@include govuk-media-query(app.$breakpoint) {
- @include govuk-responsive-padding(9);
+ @include govuk-responsive-padding(9, 'top');
+ @include govuk-responsive-padding(9, 'right');
+ @include govuk-responsive-padding(9, 'left');
+ }
+}
+
+.app-layout__back-link {
+ position: absolute;
+ @include govuk-responsive-padding(6, 'right');
+ @include govuk-responsive-padding(6, 'left');
+
+ @include govuk-media-query(app.$breakpoint) {
+ @include govuk-responsive-padding(9, 'right');
+ @include govuk-responsive-padding(9, 'left');
}
}
diff --git a/helpers/mockSessionData/sessionData.js b/helpers/mockSessionData/sessionData.js
index 28f54acd0..c0f04eb1a 100644
--- a/helpers/mockSessionData/sessionData.js
+++ b/helpers/mockSessionData/sessionData.js
@@ -2,9 +2,9 @@ module.exports = {
'/component-details': {
componentName: 'Duis',
componentOverview:
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue. Vestibulum nec dignissim ante. Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n',
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r\n\r\nAliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue. Vestibulum nec dignissim ante.\r\n\r\n Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n',
howIsTheComponentUsed:
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue. Vestibulum nec dignissim ante. Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n'
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue.\r\n\r\nVestibulum nec dignissim ante. Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n'
},
'/component-image': {},
'/accessibility-findings': {
@@ -18,7 +18,7 @@ module.exports = {
'auditDate-month': '1',
'auditDate-year': '2020',
issuesDiscovered:
- 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n'
+ 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi.\r\n\r\n Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante.\r\n\r\n* lklsdkfjl\r\n* sdflkjsdflkjsdflk sdflkjlk lsdfkjlksdfj\r\n\r\nVestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero.\r\n\r\n In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n'
},
'/add-internal-audit': {
internalOrganisation: 'Curabitur',
@@ -26,7 +26,7 @@ module.exports = {
'auditDate-month': '1',
'auditDate-year': '2020',
issuesDiscovered:
- 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n'
+ 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla\r\n\r\nNullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n'
},
'/add-assistive-tech': {
'testingDate-day': '1',
@@ -49,11 +49,21 @@ module.exports = {
componentCodeAvailable: 'yes'
},
'/component-code-details': {
- componentCodeLanguage: 'other',
- componentCodeLanguageOther: 'Nunjucks',
+ componentCodeLanguage: 'nunjucks',
componentCodeUsage:
'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n',
- componentCode: 'test
'
+ componentCode: `{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %}
+
+{% set html %}
+
+ You have 7 days left to send your application.
+ View application.
+
+{% endset %}
+
+{{ govukNotificationBanner({
+ html: html
+}) }}`
},
'/your-details': {
fullName: 'test test',
@@ -70,8 +80,219 @@ module.exports = {
figmaLinkAdditionalInformation: 'only link'
},
'/component-code-details/1': {
+ componentCodeLanguage: 'html',
+ componentCodeUsage: 'Some HTML',
+ componentCode: `
+
+
+
+ You have 7 days left to send your application.
+ View application.
+
+
+
`
+ },
+ '/component-code-details/2': {
componentCodeLanguage: 'css',
componentCodeUsage: 'Some CSS',
- componentCode: 'style'
+ componentCode: `.govuk-notification-banner {
+ font-family: GDS Transport,arial,sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ font-weight: 400;
+ font-size: 1rem;
+ line-height: 1.25;
+ margin-bottom: 30px;
+ border: 5px solid #1d70b8;
+ background-color: #1d70b8
+}
+
+@media print {
+ .govuk-notification-banner {
+ font-family: sans-serif
+ }
+}
+
+@media (min-width: 40.0625em) {
+ .govuk-notification-banner {
+ font-size:1.1875rem;
+ line-height: 1.3157894737
+ }
+}
+
+@media print {
+ .govuk-notification-banner {
+ font-size: 14pt;
+ line-height: 1.15
+ }
+}
+
+@media (min-width: 40.0625em) {
+ .govuk-notification-banner {
+ margin-bottom:50px
+ }
+}
+
+.govuk-notification-banner:focus {
+ outline: 3px solid #fd0
+}
+
+.govuk-notification-banner__header {
+ padding: 2px 15px 5px;
+ border-bottom: 1px solid transparent
+}
+
+@media (min-width: 40.0625em) {
+ .govuk-notification-banner__header {
+ padding:2px 20px 5px
+ }
+}
+
+.govuk-notification-banner__title {
+ font-size: 1rem;
+ line-height: 1.25;
+ font-weight: 700;
+ margin: 0;
+ padding: 0;
+ color: #fff
+}
+
+@media (min-width: 40.0625em) {
+ .govuk-notification-banner__title {
+ font-size:1.1875rem;
+ line-height: 1.3157894737
+ }
+}
+
+@media print {
+ .govuk-notification-banner__title {
+ font-size: 14pt;
+ line-height: 1.15
+ }
+}
+
+.govuk-notification-banner__content {
+ color: #0b0c0c;
+ padding: 15px;
+ background-color: #fff
+}
+
+@media print {
+ .govuk-notification-banner__content {
+ color: #000
+ }
+}
+
+@media (min-width: 40.0625em) {
+ .govuk-notification-banner__content {
+ padding:20px
+ }
+}
+
+.govuk-notification-banner__content>* {
+ box-sizing: border-box;
+ max-width: 605px
+}
+
+.govuk-notification-banner__content>:last-child {
+ margin-bottom: 0
+}
+
+.govuk-notification-banner__heading {
+ font-size: 1.125rem;
+ line-height: 1.1111111111;
+ font-weight: 700;
+ margin: 0 0 15px;
+ padding: 0
+}
+
+@media (min-width: 40.0625em) {
+ .govuk-notification-banner__heading {
+ font-size:1.5rem;
+ line-height: 1.25
+ }
+}
+
+@media print {
+ .govuk-notification-banner__heading {
+ font-size: 18pt;
+ line-height: 1.15
+ }
+}
+
+.govuk-notification-banner__link {
+ font-family: GDS Transport,arial,sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-decoration: underline;
+ text-decoration-thickness: max(1px,.0625rem);
+ text-underline-offset: .1578em
+}
+
+@media print {
+ .govuk-notification-banner__link {
+ font-family: sans-serif
+ }
+}
+
+.govuk-notification-banner__link:hover {
+ text-decoration-thickness: max(3px,.1875rem,.12em);
+ -webkit-text-decoration-skip-ink: none;
+ text-decoration-skip-ink: none;
+ -webkit-text-decoration-skip: none;
+ text-decoration-skip: none
+}
+
+.govuk-notification-banner__link:focus {
+ outline: 3px solid transparent;
+ background-color: #fd0;
+ box-shadow: 0 -2px #fd0,0 4px #0b0c0c;
+ text-decoration: none
+}
+
+@supports not (text-wrap: balance) {
+ .govuk-notification-banner__link:focus {
+ -webkit-box-decoration-break:clone;
+ box-decoration-break: clone
+ }
+}
+
+.govuk-notification-banner__link:link,.govuk-notification-banner__link:visited {
+ color: #1d70b8
+}
+
+.govuk-notification-banner__link:hover {
+ color: #003078
+}
+
+.govuk-notification-banner__link:active,.govuk-notification-banner__link:focus {
+ color: #0b0c0c
+}
+
+.govuk-notification-banner--success {
+ border-color: #00703c;
+ background-color: #00703c
+}
+
+.govuk-notification-banner--success .govuk-notification-banner__link:link,.govuk-notification-banner--success .govuk-notification-banner__link:visited {
+ color: #00703c
+}
+
+.govuk-notification-banner--success .govuk-notification-banner__link:hover {
+ color: #004e2a
+}
+
+.govuk-notification-banner--success .govuk-notification-banner__link:active {
+ color: #00703c
+}
+
+.govuk-notification-banner--success .govuk-notification-banner__link:focus {
+ color: #0b0c0c
+}`
}
+
}
diff --git a/kubernetes-deploy-preview.tpl b/kubernetes-deploy-preview.tpl
index df0a8637f..11df5a9fd 100644
--- a/kubernetes-deploy-preview.tpl
+++ b/kubernetes-deploy-preview.tpl
@@ -63,6 +63,11 @@ spec:
secretKeyRef:
name: notify-submission-template
key: notify-submission-template
+ - name: NOTIFY_VERIFICATION_TEMPLATE
+ valueFrom:
+ secretKeyRef:
+ name: notify-verification-template
+ key: notify-verification-template
- name: NOTIFY_EMAIL
valueFrom:
secretKeyRef:
@@ -83,8 +88,15 @@ spec:
secretKeyRef:
name: notify-email-max-retries
key: notify-email-max-retries
+ - name: SENTRY_DSN
+ valueFrom:
+ secretKeyRef:
+ name: sentry-dsn
+ key: sentry-dsn
- name: BRANCH
value: ${BRANCH}
+ - name: APP_URL
+ value: https://moj-frontend-${BRANCH}.apps.live.cloud-platform.service.justice.gov.uk
- name: REDIS_URL
valueFrom:
secretKeyRef:
@@ -150,4 +162,4 @@ spec:
service:
name: moj-frontend-service-${BRANCH}
port:
- number: 3000
\ No newline at end of file
+ number: 3000
diff --git a/kubernetes-deploy-production.tpl b/kubernetes-deploy-production.tpl
index 153f475b4..1ae24ee3b 100644
--- a/kubernetes-deploy-production.tpl
+++ b/kubernetes-deploy-production.tpl
@@ -50,6 +50,11 @@ spec:
secretKeyRef:
name: notify-submission-template
key: notify-submission-template
+ - name: NOTIFY_VERIFICATION_TEMPLATE
+ valueFrom:
+ secretKeyRef:
+ name: notify-verification-template
+ key: notify-verification-template
- name: NOTIFY_EMAIL
valueFrom:
secretKeyRef:
@@ -70,9 +75,15 @@ spec:
secretKeyRef:
name: notify-email-max-retries
key: notify-email-max-retries
+ - name: SENTRY_DSN
+ valueFrom:
+ secretKeyRef:
+ name: sentry-dsn
+ key: sentry-dsn
- name: BRANCH
value: ${BRANCH}
- - name: REDIS_URL
+ - name: APP_URL
+ value: https://design-patterns.service.justice.gov.uk - name: REDIS_URL
valueFrom:
secretKeyRef:
name: moj-frontend-ec-cluster-output
diff --git a/middleware/component-session.js b/middleware/component-session.js
index 8ba125a2d..a4225dad9 100644
--- a/middleware/component-session.js
+++ b/middleware/component-session.js
@@ -5,7 +5,9 @@ const sanitize = require('sanitize-filename')
const {
MAX_ADD_ANOTHER: maxAddAnother,
ADD_NEW_COMPONENT_ROUTE,
- COMPONENT_FORM_PAGES: formPages
+ ALLOWED_EMAIL_DOMAINS: allowedDomains,
+ COMPONENT_FORM_PAGES: formPages,
+ MESSAGES
} = require('../config')
const { getAnswersForSection } = require('../helpers/check-your-answers')
const ApplicationError = require('../helpers/application-error')
@@ -38,6 +40,22 @@ const getPageData = (req) => {
return formPages[pageData]
}
+const checkEmailDomain = (req, res, next) => {
+ let allowed = false
+ const email = req?.body?.emailAddress
+ console.log({ email })
+ if (email) {
+ const domain = email.split('@').at(-1)
+ console.log({ domain })
+ if (allowedDomains.includes(domain)) {
+ allowed = true
+ }
+ }
+ console.log({ allowed })
+ req.emailDomainAllowed = allowed
+ next()
+}
+
const transformErrorsToErrorList = (errors) => {
return errors.map((error) => ({
text: error.message,
@@ -45,6 +63,12 @@ const transformErrorsToErrorList = (errors) => {
}))
}
+const setCurrentFormPages = (req, res, next) => {
+ const { url, session } = req
+ req.formPages = getCurrentFormPages(url, session)
+ next()
+}
+
const setNextPage = (req, res, next) => {
console.log('setting nextPage')
const amendingAnswers = req?.session?.checkYourAnswers
@@ -80,9 +104,15 @@ const clearSkippedPageData = (req, res, next) => {
// Delete page and subpage data
for (const sessionPage of Object.keys(req.session)) {
if (
- !['started', 'cookie', 'csrfToken', 'checkYourAnswers'].includes(
- sessionPage
- )
+ ![
+ 'started',
+ 'cookie',
+ 'csrfToken',
+ 'checkYourAnswers',
+ 'emailToken',
+ 'emailDomainAllowed',
+ 'verified'
+ ].includes(sessionPage)
) {
console.log(sessionPage)
const parentPage = `/${sessionPage.split('/')[1]}`
@@ -138,7 +168,7 @@ const validateFormDataFileUpload = (err, req, res, next) => {
const validateFormData = (req, res, next) => {
console.log('validate form data')
- const schemaName = req.url.split('/')[1]
+ const schemaName = req.url.split('/').at(1).split('?').at(0)
const schema = require(`../schema/${schemaName}.schema`)
const body = extractBody(req?.url, { ...req.body })
delete body._csrf
@@ -190,6 +220,7 @@ const validateFormData = (req, res, next) => {
}
const saveSession = (req, res, next) => {
+ console.log('saving session')
if (!req.session) req.session = {}
const { _csrf, ...body } = req.body
@@ -298,10 +329,7 @@ const removeFromSession = (req, res, next) => {
const filename = req.session[url]?.componentImage?.originalname
console.log(filename)
if (filename) {
- req.session.sessionFlash = {
- type: 'success',
- message: `File ‘${filename}’ has been removed.`
- }
+ req.session.sessionFlash = MESSAGES.componentImageRemoved(filename)
}
}
@@ -335,6 +363,14 @@ const sessionStarted = (req, res, next) => {
next()
}
+const sessionVerified = (req, res, next) => {
+ if (!req?.session?.verified) {
+ console.error('No verified email for session')
+ return res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/start`)
+ }
+ next()
+}
+
const validateComponentImagePage = (req, res, next) => {
if (req.params.page !== 'component-image') {
const error = new ApplicationError('Invalid page', 400)
@@ -376,8 +412,40 @@ const saveFileToRedis = async (req, res, next) => {
next()
}
+const validatePageParams = (req, res, next) => {
+ const validPages = Object.keys(formPages)
+ let valid = true
+
+ if (req.params.page) {
+ // if page is present it must be in allowlist of configured pages
+ valid = validPages.includes(req.params.page)
+ }
+
+ if (req.params.subpage) {
+ // if subpage is present it must always be a number
+ valid = /^\d+$/.test(req.params.subpage)
+ }
+
+ if (valid) {
+ next()
+ } else {
+ const error = new ApplicationError('Page not found', 404)
+ next(error)
+ }
+}
+
+const setCsrfToken = (req, res, next) => {
+ if (req?.session) {
+ if (!req?.session?.csrfToken) {
+ req.session.csrfToken = crypto.randomBytes(32).toString('hex')
+ }
+ }
+ next()
+}
+
module.exports = {
setNextPage,
+ setCurrentFormPages,
validateFormData,
saveSession,
getFormDataFromSession,
@@ -387,8 +455,12 @@ module.exports = {
getFormSummaryListForRemove,
removeFromSession,
sessionStarted,
+ sessionVerified,
validateFormDataFileUpload,
validateComponentImagePage,
saveFileToRedis,
- clearSkippedPageData
+ clearSkippedPageData,
+ checkEmailDomain,
+ validatePageParams,
+ setCsrfToken
}
diff --git a/middleware/generate-documentation.js b/middleware/generate-documentation.js
index 1fc62a78b..697267210 100644
--- a/middleware/generate-documentation.js
+++ b/middleware/generate-documentation.js
@@ -1,110 +1,100 @@
/* eslint-disable prefer-template */
const moment = require('moment')
+const { MAX_ADD_ANOTHER } = require('../config.js')
+const { ucFirst } = require('../helpers/text-helper.js')
const generateMarkdown = (data, files) => {
+ console.log(data)
const { '/component-details': details } = data
- const documentationDirectory = 'docs/components'
const componentName = details?.componentName || 'unknown-component'
- const sanitizedComponentName = componentName
- .toLowerCase()
- .replace(/[^a-z0-9-]/g, '-')
- .replace(/-+/g, '-')
- const filename = `${documentationDirectory}/${sanitizedComponentName}.md`
- const generateLinksSection = (data) => {
- const noLinks =
- 'No prototypes or design files have been added for this component. If you have used this component in your service and you have a prototype you can share it here.\n'
+ const sanitizedComponentName = ucFirst(
+ componentName
+ .toLowerCase()
+ .replace(/[^a-z0-9-]/g, '-')
+ .replace(/-+/g, '-')
+ )
+ const filename = `${sanitizedComponentName}.md`
+
+ const githubDiscussionLink = (componentName = '') => {
+ return `[${componentName ? `${componentName} ` : ''}Github discussion]({{ githuburl }})`
+ }
+
+ const generateDesignsTabContent = (data) => {
let content = ''
- let prototypeContent = ''
- let figmaContent = ''
- let n = 0
- // Process prototype data
- while (data[`/prototype-url${n > 0 ? `/${n}` : ''}`]) {
- const prototype = data[`/prototype-url${n > 0 ? `/${n}` : ''}`]
- prototypeContent += `
-${prototype?.prototypeUrlAdditionalInformation || ''}
+ if (data['/figma-link']) {
+ content += `A Figma link has been added for this component. There may be more links and resources in the ${githubDiscussionLink(sanitizedComponentName)}.\r\n
-Prototype link (opens in a new tab)
-`
- n++
- }
+### Figma link
- // Process figma data
- n = 0
- while (data[`/figma-link${n > 0 ? `/${n}` : ''}`]) {
- const figma = data[`/figma-link${n > 0 ? `/${n}` : ''}`]
- figmaContent += `
-${figma?.figmaLinkAdditionalInformation || ''}
+ [View the ${sanitizedComponentName} in Figma (opens in a new tab)](${data['/figma-link']?.figmaUrl || ''})\r\n\r\n`
-Figma link (opens in a new tab)
-`
- n++
- }
+ content += `${data['/figma-link']?.figmaLinkAdditionalInformation || ''}\r\n`
- if (prototypeContent.length) {
- content += '### Prototype\n' + prototypeContent
- }
- if (figmaContent.length) {
- content += '\n### Figma\n' + figmaContent
+ content += `### Contribute prototypes and Figma links
+
+ If you have design files that are relevant to this component you can add them to the ${githubDiscussionLink()}. This helps other people to use it in their service.`
+ } else {
+ content = `A Figma link was not included when this component was added.
+
+ There may be more information in the ${githubDiscussionLink(sanitizedComponentName)}. You can also view the component image in the overview.
+
+## Contribute a Figma link
+
+ If you have a Figma link for this component (or a component like it) you can add it to ${githubDiscussionLink()}. This helps other people to use it in their service.`
}
- return content.length ? content : noLinks
+ return content
}
- const generateAccessibilityReportSection = (data, files) => {
- const contentHeading =
- 'If you have had an accessibility audit or tested with users with access needs then you could contribute to this component.\n'
- const noContent =
- 'No accessibility findings have been added for this component.\n'
- let content = ''
- const externalAudit = data['/add-external-audit']
- const externalAuditFile = files?.['/add-external-audit']
- const internalAudit = data['/add-internal-audit']
- const internalAuditFile = files?.['/add-internal-audit']
- const assistiveTech = data['/add-assistive-tech']
- const assistiveTechReport = files?.['/add-assistive-tech']
-
+ const generateAccessibilityTabContent = (data) => {
const formatDate = (day, month, year) => {
if (day && month && year) {
- return moment(`${year}-${month}-${day}`, 'YYYY-M-D').format('MMMM YYYY')
+ return moment(`${year}-${month}-${day}`, 'YYYY-M-D').format(
+ 'D MMMM YYYY'
+ )
}
return null
}
- if (externalAudit && externalAudit.externalOrganisation) {
- const auditDate = formatDate(
+ let content = ''
+ const externalAudit = data['/add-external-audit']
+ const internalAudit = data['/add-internal-audit']
+ const assistiveTech = data['/add-assistive-tech']
+
+ if (externalAudit) {
+ const externalAuditDate = formatDate(
externalAudit['auditDate-day'],
externalAudit['auditDate-month'],
externalAudit['auditDate-year']
)
- if (auditDate) {
- content += `### External audit (${externalAudit.externalOrganisation}) - ${auditDate}\n`
- if (externalAudit.issuesDiscovered) {
- content += externalAudit.issuesDiscovered + '\n\n'
- }
- if (externalAudit.accessibilityReport && externalAuditFile) {
- content += `[Download the accessibility report](/${externalAuditFile.path})\n`
- }
- }
+
+ content += `### External audit
+
+* Conducted by: ${externalAudit.externalOrganisation}
+* Date: ${externalAuditDate}
+
+#### Audit findings
+
+${externalAudit.issuesDiscovered}\r\n`
}
- if (internalAudit && internalAudit.internalOrganisation) {
- const auditDate = formatDate(
+ if (internalAudit) {
+ const internalAuditDate = formatDate(
internalAudit['auditDate-day'],
internalAudit['auditDate-month'],
internalAudit['auditDate-year']
)
- if (auditDate) {
- content += `### Internal audit (${internalAudit.internalOrganisation}) - ${auditDate}\n`
- if (internalAudit.issuesDiscovered) {
- content += internalAudit.issuesDiscovered + '\n\n'
- }
- if (internalAudit.accessibilityReport && internalAuditFile) {
- content += `[Download the accessibility report](/${internalAuditFile.path})\n`
- }
- }
+ content += `### Internal review
+
+* By: ${internalAudit.internalOrganisation}
+* Date: ${internalAuditDate}
+
+#### Review findings
+
+${internalAudit.issuesDiscovered}\r\n`
}
if (assistiveTech) {
@@ -113,94 +103,130 @@ ${figma?.figmaLinkAdditionalInformation || ''}
assistiveTech['testingDate-month'],
assistiveTech['testingDate-year']
)
- if (testingDate) {
- content += `### Assistive Technology audit - ${testingDate}\n`
- if (assistiveTech.issuesDiscovered) {
- content += assistiveTech.issuesDiscovered + '\n\n'
- }
- if (assistiveTech.accessibilityReport && assistiveTechReport) {
- content += `[Download the accessibility report](/${assistiveTechReport.path})\n`
- }
- }
+ content += `### Assistive Technology testing
+
+Date: ${testingDate}
+
+#### Testing details
+
+${assistiveTech.issuesDiscovered}\r\n`
}
- return content ? contentHeading + content : noContent
- }
+ if (externalAudit || internalAudit || assistiveTech) {
+ content = `Accessibility findings have been added for this component. There may be more findings in the ${githubDiscussionLink(sanitizedComponentName)}.\r\n
+
+${content}\r\n`
+ } else {
+ content = `No accessibility findings were included when this component was added. There may be more information in the ${githubDiscussionLink(sanitizedComponentName)}.\r\n`
+ }
+
+ content += `## Contribute accessibility findings
- const generateComponentCodeSection = (data) => {
- const noCode =
- 'No code has been added for this component. If you have examples of how you have used this component in your service then you could help the community. Most users are looking for HTML, Nunjucks, Javascript and CSS or SASS.\n'
+ If you have accessibility findings that are relevant to this component you can add them to the ${githubDiscussionLink()}. This helps other people to use it in their service.`
+ return content
+ }
+
+ const generateCodeTabContent = (data) => {
+ const hljsLang = (lang) => {
+ switch(lang.toLowerCase()) {
+ case 'html':
+ case 'css':
+ case 'scss':
+ case 'javascript':
+ case 'typescript':
+ return lang
+ case 'nunjucks':
+ return 'njk'
+ case 'sass':
+ return 'scss'
+ case 'react':
+ return 'javascript'
+ default:
+ return ''
+ }
+ }
let content = ''
- let n = 1
- while (data[`/component-code${n > 1 ? `-${n}` : ''}`]) {
- const componentCodeDetails =
- data[`/component-code-details${n > 1 ? `-${n}` : ''}`]
+ for(let i=0;i<=MAX_ADD_ANOTHER;i++) {
+ const code = data[`/component-code-details${i === 0 ? '' : `/${i}`}`]
+ if(!code) {
+ break
+ }
+ const language =
+ code?.componentCodeLanguage === 'other'
+ ? code?.componentCodeLanguageOther
+ : code?.componentCodeLanguage
+
+
content += `
+### Example ${i + 1}: ${language}
-### ${componentCodeDetails?.componentCodeLanguage || ''}
+\`\`\`${hljsLang(language)}
+{% raw %}
+${code?.componentCode || ''}
+{% endraw %}
+\`\`\`
-### ${componentCodeDetails?.componentCodeLanguageOther || ''}
+${code?.componentCodeUsage || ''}\r\n\r\n`
+ }
-
+ if (data['/component-code-details']) {
+ content = `Code has been added for this component. There may be other code examples in the ${githubDiscussionLink(sanitizedComponentName)}.
-\`\`\`html
-${componentCodeDetails?.componentCode || ''}
-\`\`\`
+${content}
-
-${componentCodeDetails?.componentCodeUsage || ''}
-`
+## Contribute code for this component
- n++
+If you have code that is relevant to this component you can add it to the ${githubDiscussionLink()}. This helps other people to use it in their service.`
+ } else {
+ content = `No code was included when this contribution was added.
+
+You can use the ${githubDiscussionLink(sanitizedComponentName)} to:
+
+* view other code examples
+* add relevant code`
}
- return content.length ? content : noCode
+
+ console.log(content)
+ return content
}
- const content = `---
-layout: layouts/tabbed-component.njk
-title: ${componentName}
-type: component
+const content = `---
+title: ${ucFirst(componentName)}
+tabs: true
status: Experimental
statusDate: ${moment().format('MMMM YYYY')}
-eleventyNavigation:
- key: ${componentName}
- parent: Components
- excerpt: "${details?.briefDescription || ''}"
+excerpt: "${details?.briefDescription || ''}"
+lede: "${details?.briefDescription || ''}"
+githuburl: https://github.com/ministryofjustice/moj-frontend/discussions/xxx
+${data['/your-details']?.fullName === 'Not shared' ? '' : `contributorName: ${data['/your-details']?.fullName}`}
+${data['/your-details']?.teamName === 'Not shared' ? '' : `contributorTeam: ${data['/your-details']?.teamName}`}
---
-{% tabs "Contents" %}
-
+{% tabs "paginate" %}
{% tab "Overview" %}
-## Overview
-
- ${files?.['/component-image'] ? '' : ''}
+
+## Overview
${details?.componentOverview || ''}
-## How the component is currently used
+### How the component is currently used
${details?.howIsTheComponentUsed || ''}
-{% endtab %}
-
-{% tab "Code" %}
-
-## Help develop existing building blocks in GitHub
-
-After a new building block is published in the design system, you, and other users, have the chance to continue enhancing it. This is done with users adding more information and resources to the component via GitHub.
+### Contribute to this component
+You can help develop this component by adding information to the Github discussion. This helps other people to use it in their service.
-To do this you should:
+{% endtab %}
-- go to the GitHub conversation
-- add your comments, information and resources about the building block
+{% tab "Designs" %}
-## Code
+## Designs
-${generateComponentCodeSection(data)}
+${generateDesignsTabContent(data)}
{% endtab %}
@@ -208,15 +234,15 @@ ${generateComponentCodeSection(data)}
## Accessibility
-${generateAccessibilityReportSection(data, files)}
+${generateAccessibilityTabContent(data)}
{% endtab %}
-{% tab "Links" %}
+{% tab "Code" %}
-## Links
+## Code
-${generateLinksSection(data)}
+${generateCodeTabContent(data)}
{% endtab %}
diff --git a/middleware/notify-email.js b/middleware/notify-email.js
index e98b92978..9ece1dfd5 100644
--- a/middleware/notify-email.js
+++ b/middleware/notify-email.js
@@ -4,46 +4,30 @@ const {
NOTIFY_TOKEN,
NOTIFY_PR_TEMPLATE,
NOTIFY_SUBMISSION_TEMPLATE,
+ NOTIFY_VERIFICATION_TEMPLATE,
NOTIFY_EMAIL,
NOTIFY_EMAIL_RETRY_MS,
- NOTIFY_EMAIL_MAX_RETRIES
+ NOTIFY_EMAIL_MAX_RETRIES,
+ APP_URL
} = require('../config')
const notifyClient = new NotifyClient(NOTIFY_TOKEN)
-const emailAddress = NOTIFY_EMAIL
+const dsTeamEmail = NOTIFY_EMAIL
const sendEmail = async (
templateId,
- prLink = null,
- previewLink = null,
- fileBuffer = null,
- markdown = null,
+ email,
+ personalisation = {},
retries = NOTIFY_EMAIL_MAX_RETRIES,
backoff = NOTIFY_EMAIL_RETRY_MS
) => {
- const personalisation = {}
-
- if (prLink) {
- personalisation.pr_link = prLink
- }
- if (previewLink) {
- personalisation.preview_link = previewLink
- }
-
- if (fileBuffer) {
- personalisation.link_to_file = notifyClient.prepareUpload(fileBuffer)
- }
-
- if (markdown) {
- personalisation.markdown = markdown
- }
for (let attempt = 1; attempt <= retries; attempt++) {
try {
console.log(
- `Sending email to ${emailAddress} using template ${templateId}, attempt ${attempt}`
+ `Sending email to ${email} using template ${templateId}, attempt ${attempt}`
)
- const response = await notifyClient.sendEmail(templateId, emailAddress, {
+ const response = await notifyClient.sendEmail(templateId, email, {
personalisation
})
console.log('Email sent successfully.')
@@ -63,12 +47,36 @@ const sendEmail = async (
}
const sendSubmissionEmail = async (fileBuffer = null, markdown = null) => {
- return sendEmail(NOTIFY_SUBMISSION_TEMPLATE, null, null, fileBuffer, markdown)
+ const personalisation = {}
+
+ if (fileBuffer) {
+ personalisation.link_to_file = notifyClient.prepareUpload(fileBuffer)
+ }
+
+ if (markdown) {
+ personalisation.markdown = markdown
+ }
+
+ return sendEmail(NOTIFY_SUBMISSION_TEMPLATE, dsTeamEmail, personalisation)
}
const sendPrEmail = async ({ url, number }) => {
- const previewUrl = `https://moj-frontend-pr-${number}.apps.live.cloud-platform.service.justice.gov.uk`
- return sendEmail(NOTIFY_PR_TEMPLATE, url, previewUrl)
+ const personalisation = {}
+
+ if (url) {
+ personalisation.pr_link = url
+ }
+ if (number) {
+ personalisation.preview_link = `https://moj-frontend-pr-${number}.apps.live.cloud-platform.service.justice.gov.uk`
+ }
+
+ return sendEmail(NOTIFY_PR_TEMPLATE, dsTeamEmail, personalisation)
+}
+
+const sendVerificationEmail = async (email, token) => {
+ const personalisation = {}
+ personalisation.token_link = `${APP_URL}/contribute/add-new-component/email/verify/${token}`
+ return sendEmail(NOTIFY_VERIFICATION_TEMPLATE, email, personalisation)
}
const handleEmailError = (error) => {
@@ -87,5 +95,6 @@ const handleEmailError = (error) => {
module.exports = {
sendSubmissionEmail,
- sendPrEmail
+ sendPrEmail,
+ sendVerificationEmail
}
diff --git a/middleware/process-subission-data.js b/middleware/process-subission-data.js
index 534177e4b..db56dcb54 100644
--- a/middleware/process-subission-data.js
+++ b/middleware/process-subission-data.js
@@ -1,3 +1,5 @@
+const { generateMarkdown } = require('../middleware/generate-documentation')
+
const redis = require('../helpers/redis-client')
const imageDirectory = 'assets/images'
@@ -48,13 +50,16 @@ const getUniqueFilename = (originalName, existingFilenames) => {
return uniqueName
}
-const processSubmissionFiles = async (sessionData, submissionRef) => {
+const processSubmissionFiles = async (req) => {
+ console.log('processing files')
+ console.log(req)
+ const { session, submissionRef } = req
const submissionFiles = {}
const existingFilenames = new Set()
- for (const key of Object.keys(sessionData)) {
+ for (const key of Object.keys(session)) {
if (!['cookie', 'csrfToken'].includes(key)) {
- const fileData = sessionData[key]
+ const fileData = session[key]
if (
fileData?.componentImage?.redisKey ||
fileData?.accessibilityReport?.redisKey
@@ -82,7 +87,11 @@ const processSubmissionFiles = async (sessionData, submissionRef) => {
return submissionFiles
}
-const processSubmissionData = (sessionData, submissionFiles, submissionRef) => {
+const processSubmissionData = (req, res, next) => {
+ console.log('processing submission data')
+ const sessionData = { ...req.session, ...req.markdown }
+ console.log(sessionData)
+ const { submissionFiles, submissionRef } = req
const submissionData = {}
for (const key in sessionData) {
@@ -94,30 +103,9 @@ const processSubmissionData = (sessionData, submissionFiles, submissionRef) => {
const filename = extractFilename(key)
if (filename.endsWith('.md')) {
// Documentation should be outside of the submission folder
- submissionData[filename] = sessionData[key]
+ submissionData[`docs/components/${filename}`] = sessionData[key]
} else {
const data = Object.assign({}, sessionData[key])
- if (key === '/your-details') {
- // Remove personal data
- data.fullName = 'Not shared'
- data.teamName = 'Not shared'
-
- // Add back in personal details if permission is given
- if (
- sessionData[key]?.shareYourDetails?.includes(
- 'addNameToComponentPage'
- )
- ) {
- data.fullName = sessionData[key].fullName
- }
- if (
- sessionData[key]?.shareYourDetails?.includes(
- 'addTeamNameToComponentPage'
- )
- ) {
- data.teamName = sessionData[key].teamName
- }
- }
if (key.startsWith('/component-code-details')) {
const exampleNum = key.split('/').at(2)
? `-${key.split('/').at(2)}`
@@ -147,7 +135,49 @@ const processSubmissionData = (sessionData, submissionFiles, submissionRef) => {
}
}
- return submissionData
+ req.submissionData = submissionData
+ next()
}
-module.exports = { processSubmissionData, processSubmissionFiles }
+const processPersonalData = (req, res, next) => {
+ console.log('excluding personal data')
+ const personalData = req.session['/your-details']
+ const data = Object.assign({}, personalData)
+ // Remove personal data
+ data.fullName = 'Not shared'
+ data.teamName = 'Not shared'
+
+ // Add back in personal details if permission is given
+ if (personalData?.shareYourDetails?.includes('addNameToComponentPage')) {
+ data.fullName = personalData.fullName
+ }
+ if (personalData?.shareYourDetails?.includes('addTeamNameToComponentPage')) {
+ data.teamName = personalData.teamName
+ }
+ req.session['/your-details'] = data
+ next()
+}
+
+const buildComponentPage = (req, res, next) => {
+ console.log('building component page')
+ const { filename: markdownFilename, content: markdownContent } =
+ generateMarkdown(req.session, req.submissionFiles)
+ req.markdown = {}
+ req.markdownFilename = markdownFilename
+ req.markdownContent = markdownContent
+ req.markdown[markdownFilename] = markdownContent
+ next()
+}
+
+const generateSubmissionRef = (req,res,next) => {
+ req.submissionRef = `submission-${Date.now()}`
+ next()
+}
+
+module.exports = {
+ processSubmissionData,
+ processSubmissionFiles,
+ processPersonalData,
+ buildComponentPage,
+ generateSubmissionRef
+}
diff --git a/package-lock.json b/package-lock.json
index da27aac13..398675ef3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2204,6 +2204,8 @@
},
"node_modules/@es-joy/jsdoccomment": {
"version": "0.50.2",
+ "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz",
+ "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3880,6 +3882,8 @@
},
"node_modules/@puppeteer/browsers": {
"version": "2.10.5",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz",
+ "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5574,6 +5578,8 @@
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"dev": true,
"license": "MIT"
},
@@ -6106,6 +6112,8 @@
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -6353,6 +6361,8 @@
},
"node_modules/acorn": {
"version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -6612,6 +6622,8 @@
},
"node_modules/array-differ": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
+ "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6664,6 +6676,8 @@
},
"node_modules/array-union": {
"version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6675,6 +6689,8 @@
},
"node_modules/array-uniq": {
"version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6756,6 +6772,8 @@
},
"node_modules/arrify": {
"version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6776,6 +6794,8 @@
},
"node_modules/ast-types": {
"version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7112,6 +7132,8 @@
},
"node_modules/bare-fs": {
"version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz",
+ "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
@@ -7134,6 +7156,8 @@
},
"node_modules/bare-os": {
"version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz",
+ "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
@@ -7143,6 +7167,8 @@
},
"node_modules/bare-path": {
"version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
+ "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
@@ -7152,6 +7178,8 @@
},
"node_modules/bare-stream": {
"version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz",
+ "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
@@ -7200,6 +7228,8 @@
},
"node_modules/basic-ftp": {
"version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
+ "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8652,6 +8682,8 @@
},
"node_modules/chromium-bidi/node_modules/mitt": {
"version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"dev": true,
"license": "MIT"
},
@@ -8984,6 +9016,8 @@
},
"node_modules/comment-parser": {
"version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
+ "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9076,6 +9110,8 @@
},
"node_modules/component-emitter": {
"version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
+ "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -9443,6 +9479,8 @@
},
"node_modules/cookiejar": {
"version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
+ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true,
"license": "MIT"
},
@@ -9634,6 +9672,8 @@
},
"node_modules/css-declaration-sorter": {
"version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
+ "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==",
"dev": true,
"license": "ISC",
"engines": {
@@ -9981,6 +10021,8 @@
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10055,6 +10097,8 @@
},
"node_modules/debug": {
"version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -10390,6 +10434,8 @@
},
"node_modules/degenerator": {
"version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10459,6 +10505,8 @@
},
"node_modules/dependency-graph": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
+ "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12775,6 +12823,8 @@
},
"node_modules/extract-zip": {
"version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -12794,6 +12844,8 @@
},
"node_modules/extract-zip/node_modules/get-stream": {
"version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12866,6 +12918,8 @@
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"dev": true,
"license": "MIT"
},
@@ -13669,6 +13723,8 @@
},
"node_modules/get-uri": {
"version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz",
+ "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15063,6 +15119,8 @@
},
"node_modules/htmlparser2": {
"version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
+ "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
@@ -15081,6 +15139,8 @@
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
+ "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -15833,6 +15893,8 @@
},
"node_modules/ip-address": {
"version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15845,6 +15907,8 @@
},
"node_modules/ip-address/node_modules/sprintf-js": {
"version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"dev": true,
"license": "BSD-3-Clause"
},
@@ -16206,6 +16270,8 @@
},
"node_modules/is-json": {
"version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz",
+ "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==",
"dev": true,
"license": "ISC"
},
@@ -18228,11 +18294,15 @@
},
"node_modules/jsbn": {
"version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"dev": true,
"license": "MIT"
},
"node_modules/jsdoc-type-pratt-parser": {
"version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
+ "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -18986,6 +19056,8 @@
},
"node_modules/maximatch": {
"version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz",
+ "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -19054,6 +19126,8 @@
},
"node_modules/meow": {
"version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
+ "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -19151,6 +19225,8 @@
},
"node_modules/mime": {
"version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true,
"license": "MIT",
"bin": {
@@ -19162,6 +19238,8 @@
},
"node_modules/mime-db": {
"version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -19278,6 +19356,8 @@
},
"node_modules/morphdom": {
"version": "2.7.5",
+ "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.5.tgz",
+ "integrity": "sha512-z6bfWFMra7kBqDjQGHud1LSXtq5JJC060viEkQFMBX6baIecpkNr2Ywrn2OQfWP3rXiNFQRPoFjD8/TvJcWcDg==",
"dev": true,
"license": "MIT"
},
@@ -19451,6 +19531,8 @@
},
"node_modules/netmask": {
"version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
+ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -23082,6 +23164,8 @@
},
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23100,6 +23184,8 @@
},
"node_modules/pac-proxy-agent/node_modules/agent-base": {
"version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -23108,6 +23194,8 @@
},
"node_modules/pac-proxy-agent/node_modules/http-proxy-agent": {
"version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23120,6 +23208,8 @@
},
"node_modules/pac-proxy-agent/node_modules/https-proxy-agent": {
"version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23132,6 +23222,8 @@
},
"node_modules/pac-resolver": {
"version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23637,6 +23729,8 @@
},
"node_modules/please-upgrade-node": {
"version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+ "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23728,6 +23822,8 @@
},
"node_modules/postcss-calc": {
"version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz",
+ "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24274,6 +24370,8 @@
},
"node_modules/posthtml": {
"version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz",
+ "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24286,6 +24384,8 @@
},
"node_modules/posthtml-match-helper": {
"version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz",
+ "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -24297,6 +24397,8 @@
},
"node_modules/posthtml-parser": {
"version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz",
+ "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24308,6 +24410,8 @@
},
"node_modules/posthtml-render": {
"version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz",
+ "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24446,6 +24550,8 @@
},
"node_modules/proxy-agent": {
"version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24464,6 +24570,8 @@
},
"node_modules/proxy-agent/node_modules/agent-base": {
"version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -24472,6 +24580,8 @@
},
"node_modules/proxy-agent/node_modules/http-proxy-agent": {
"version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24484,6 +24594,8 @@
},
"node_modules/proxy-agent/node_modules/https-proxy-agent": {
"version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -24496,6 +24608,8 @@
},
"node_modules/proxy-agent/node_modules/lru-cache": {
"version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"dev": true,
"license": "ISC",
"engines": {
@@ -24560,6 +24674,8 @@
},
"node_modules/puppeteer": {
"version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.9.0.tgz",
+ "integrity": "sha512-L0pOtALIx8rgDt24Y+COm8X52v78gNtBOW6EmUcEPci0TYD72SAuaXKqasRIx4JXxmg2Tkw5ySKcpPOwN8xXnQ==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@@ -24580,6 +24696,8 @@
},
"node_modules/puppeteer-core": {
"version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.9.0.tgz",
+ "integrity": "sha512-HFdCeH/wx6QPz8EncafbCqJBqaCG1ENW75xg3cLFMRUoqZDgByT6HSueiumetT2uClZxwqj0qS4qMVZwLHRHHw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -26249,6 +26367,8 @@
},
"node_modules/semantic-release/node_modules/marked": {
"version": "15.0.12",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
+ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"dev": true,
"license": "MIT",
"bin": {
@@ -26351,6 +26471,8 @@
},
"node_modules/semver-compare": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"dev": true,
"license": "MIT"
},
@@ -26411,6 +26533,8 @@
},
"node_modules/send": {
"version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -26432,6 +26556,8 @@
},
"node_modules/send/node_modules/fresh": {
"version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -26440,6 +26566,8 @@
},
"node_modules/send/node_modules/mime-types": {
"version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -26933,6 +27061,8 @@
},
"node_modules/smart-buffer": {
"version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -27083,6 +27213,8 @@
},
"node_modules/socks": {
"version": "2.8.4",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
+ "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -27096,6 +27228,8 @@
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -27109,6 +27243,8 @@
},
"node_modules/socks-proxy-agent/node_modules/agent-base": {
"version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -27240,6 +27376,8 @@
},
"node_modules/split2": {
"version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"dev": true,
"license": "ISC",
"engines": {
@@ -27253,6 +27391,8 @@
},
"node_modules/ssri": {
"version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz",
+ "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -28095,6 +28235,8 @@
},
"node_modules/superagent/node_modules/mime": {
"version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -28284,6 +28426,8 @@
},
"node_modules/tar-fs": {
"version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
+ "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -29307,6 +29451,8 @@
},
"node_modules/urlpattern-polyfill": {
"version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz",
+ "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==",
"dev": true,
"license": "MIT"
},
@@ -30021,6 +30167,8 @@
},
"node_modules/zod": {
"version": "3.25.7",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.7.tgz",
+ "integrity": "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==",
"dev": true,
"license": "MIT",
"funding": {
diff --git a/routes/add-component.js b/routes/add-component.js
index 9b01e4a82..d643b13e9 100644
--- a/routes/add-component.js
+++ b/routes/add-component.js
@@ -1,14 +1,20 @@
const crypto = require('crypto')
+const fs = require('fs')
const express = require('express')
const { xss } = require('express-xss-sanitizer')
const multer = require('multer')
-const { COMPONENT_FORM_PAGES, ADD_NEW_COMPONENT_ROUTE } = require('../config')
+const {
+ COMPONENT_FORM_PAGES,
+ ADD_NEW_COMPONENT_ROUTE,
+ MESSAGES,
+ ENV
+} = require('../config')
const ApplicationError = require('../helpers/application-error')
const { checkYourAnswers } = require('../helpers/check-your-answers')
const getPrTitleAndDescription = require('../helpers/get-pr-title-and-description')
-const sessionData = require('../helpers/mockSessionData/sessionData.js')
+const mockSessionData = require('../helpers/mockSessionData/sessionData.js')
const { urlToTitleCase } = require('../helpers/text-helper')
const {
validateFormData,
@@ -21,19 +27,27 @@ const {
getFormSummaryListForRemove,
removeFromSession,
sessionStarted,
+ sessionVerified,
validateFormDataFileUpload,
saveFileToRedis,
- clearSkippedPageData
+ clearSkippedPageData,
+ checkEmailDomain,
+ validatePageParams,
+ setCsrfToken
} = require('../middleware/component-session')
const { generateMarkdown } = require('../middleware/generate-documentation')
const { pushToGitHub, createPullRequest } = require('../middleware/github-api')
const {
sendSubmissionEmail,
- sendPrEmail
+ sendPrEmail,
+ sendVerificationEmail
} = require('../middleware/notify-email')
const {
+ processPersonalData,
processSubmissionData,
- processSubmissionFiles
+ processSubmissionFiles,
+ buildComponentPage,
+ generateSubmissionRef
} = require('../middleware/process-subission-data')
const verifyCsrf = require('../middleware/verify-csrf')
const upload = multer({
@@ -41,44 +55,18 @@ const upload = multer({
limits: { fileSize: 10 * 1024 * 1024 } // 10MB
})
const router = express.Router()
-
-const validatePageParams = (req, res, next) => {
- const validPages = Object.keys(COMPONENT_FORM_PAGES)
- let valid = true
-
- if (req.params.page) {
- // if page is present it must be in allowlist of configured pages
- valid = validPages.includes(req.params.page)
- }
-
- if (req.params.subpage) {
- // if subpage is present it must always be a number
- valid = /^\d+$/.test(req.params.subpage)
- }
-
- if (valid) {
- next()
- } else {
- const error = new ApplicationError('Page not found', 404)
- next(error)
- }
-}
-
const checkYourAnswersPath = 'check-your-answers'
-const setCsrfToken = (req, res, next) => {
- if (req?.session) {
- if (!req?.session?.csrfToken) {
- req.session.csrfToken = crypto.randomBytes(32).toString('hex')
- }
- }
- next()
-}
+router.get('/error', (req, res, next) => {
+ const error = new ApplicationError('ohno')
+ next(error)
+})
router.all('*', setCsrfToken)
// TODO: Why is this a get * ? Can it not just be set in the get /checkYourAnswersPath route below?
router.get('*', (req, res, next) => {
+ console.log('setting cya visited')
if (req?.session) {
if (req?.url.endsWith(checkYourAnswersPath)) {
console.log('visited checkYourAnswersPath')
@@ -89,11 +77,6 @@ router.get('*', (req, res, next) => {
next()
})
-router.get('/error', (req, res, next) => {
- const error = new ApplicationError('Uh oh!', 500)
- next(error)
-})
-
// Check your answers page
router.get(
`/${checkYourAnswersPath}`,
@@ -104,7 +87,9 @@ router.get(
res.render(checkYourAnswersPath, {
submitUrl: req.originalUrl,
sections,
- csrfToken: req?.session?.csrfToken
+ csrfToken: req?.session?.csrfToken,
+ isDev: ENV === 'development',
+ generateLink: `${ADD_NEW_COMPONENT_ROUTE}/generate-markdown`
})
}
)
@@ -116,7 +101,7 @@ if (process.env.DEV_DUMMY_DATA) {
return next(new Error('Session not available'))
}
- Object.assign(req.session, sessionData)
+ Object.assign(req.session, mockSessionData)
req.session.save((err) => {
if (err) {
return next(err)
@@ -126,8 +111,49 @@ if (process.env.DEV_DUMMY_DATA) {
})
}
+if (ENV === 'development') {
+ router.get('/generate-markdown', (req, res, next) => {
+ if (!req.session) {
+ return next(new Error('Session not available'))
+ }
+
+ if (!req.session.checkYourAnswers) {
+ Object.assign(req.session, mockSessionData)
+ req.session.save((err) => {
+ if (err) {
+ return next(err)
+ }
+ })
+ }
+ next()
+ },
+ processPersonalData,
+ generateSubmissionRef,
+ async (req, res, next) => {
+ console.log('processing files')
+ req.submissionFiles = await processSubmissionFiles(req)
+ next()
+ },
+ buildComponentPage,
+ async (req,res) => {
+ const { markdownFilename: filename, markdownContent: content} = req
+ fs.writeFile(`docs/components/${filename}`, content, (err) => {
+ if (err) {
+ console.error(err)
+ } else {
+ // file written successfully
+ setTimeout(() => {
+ res.redirect(`/components/${filename.split('.').at(0).toLowerCase()}`)
+ }, 2000)
+ }
+ })
+ })
+}
+
// Start
router.get('/start', (req, res) => {
+ console.log('get start')
+
delete req.session.checkYourAnswers
req.session.started = true
console.log('Start session')
@@ -135,6 +161,7 @@ router.get('/start', (req, res) => {
title: 'Submit a component',
csrfToken: req?.session?.csrfToken
})
+ console.log('after render')
})
// Confirmation page
@@ -144,12 +171,138 @@ router.get('/confirmation', (req, res) => {
})
})
-router.all('*', sessionStarted) // Check that we have a session in progress
+router.get('/email/verify/:token', (req, res) => {
+ if (!req?.session?.emailToken) {
+ // session expired
+ req.session.sessionFlash = MESSAGES.emailVerificationExpired
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email`)
+ } else {
+ if (req.params.token === req.session.emailToken) {
+ // verified
+ req.session.verified = true
+ req.session.sessionFlash = MESSAGES.emailVerificationSuccess
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/component-details`)
+ } else {
+ // token invalid
+ req.session.sessionFlash = MESSAGES.emailVerificationInvalidToken
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email`)
+ }
+ }
+})
+
+router.get('/email', (req, res) => {
+ req.session.started = true
+
+ if (req.query.reset === 'true') {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete req.session['/email']
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete req.session.emailToken
+ }
+
+ res.render('email', {
+ page: COMPONENT_FORM_PAGES.email,
+ submitUrl: req.originalUrl,
+ csrfToken: req?.session?.csrfToken
+ })
+})
+
+// For all following routed we must have a session in progress
+router.all('*', sessionStarted)
router.post('/start', verifyCsrf, (req, res) => {
- res.redirect('/contribute/add-new-component/component-details')
+ if (
+ process.env.SKIP_VERIFICATION === 'true' &&
+ process.env.DEV_VERIFIED_EMAIL
+ ) {
+ req.session['/email'] = { emailAddress: process.env.DEV_VERIFIED_EMAIL }
+ req.session.emailDomainAllowed = true
+ req.session.verified = true
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/component-details`)
+ } else {
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email`)
+ }
})
+router.post(
+ '/email',
+ xss(),
+ verifyCsrf,
+ validateFormData,
+ checkEmailDomain,
+ (req, res, next) => {
+ if (req.emailDomainAllowed) {
+ saveSession(req, res, next)
+ } else {
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email/not-allowed`)
+ }
+ },
+ // generate token
+ (req, res, next) => {
+ req.session.emailToken = crypto.randomBytes(32).toString('hex')
+ next()
+ },
+ // send email
+ async (req, res) => {
+ if (req.emailDomainAllowed) {
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email/check`)
+ const token = req?.session?.emailToken
+ const email = req?.session?.['/email']?.emailAddress
+ if (token && email) {
+ try {
+ await sendVerificationEmail(email, token)
+ } catch (error) {
+ console.error(`Error sending verification email: ${error}`)
+ }
+ }
+ }
+ }
+)
+
+router.get('/email/check', (req, res) => {
+ res.render('email-check', {
+ page: {
+ title: 'Check your email',
+ email: req?.session?.['/email']?.emailAddress
+ }
+ })
+})
+
+router.get('/email/resend', (req, res) => {
+ res.render('email-resend', {
+ submitUrl: req.originalUrl,
+ csrfToken: req?.session?.csrfToken,
+ page: {
+ title: 'If you’re having problems with the email',
+ email: req?.session?.['/email']?.emailAddress
+ }
+ })
+})
+
+router.post('/email/resend', xss(), verifyCsrf, async (req, res) => {
+ res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email/check`)
+ const token = req?.session?.emailToken
+ const email = req?.session?.['/email']?.emailAddress
+ if (token && email) {
+ try {
+ await sendVerificationEmail(email, token)
+ } catch (error) {
+ console.error(`Error sending verification email: ${error}`)
+ }
+ }
+})
+
+router.get('/email/not-allowed', (req, res) => {
+ res.render('email-not-allowed', {
+ page: {
+ title: 'You cannot submit a component'
+ }
+ })
+})
+
+// For all following routed we must have verified an email address
+router.all('*', sessionVerified)
+
// Remove form page
router.get(
['/remove/:page', '/remove/:page/:subpage'],
@@ -209,6 +362,7 @@ router.get(
canAddAnother,
getBackLink,
(req, res) => {
+ console.log('get page/subpage')
console.log(`CYA: ${req?.session?.checkYourAnswers}`)
res.render(`${req.params.page}`, {
page: COMPONENT_FORM_PAGES[req.params.page],
@@ -224,31 +378,28 @@ router.get(
}
)
-
-
// "Check Your Answers" form submission
router.post(
`/${checkYourAnswersPath}`,
verifyCsrf,
getRawSessionText,
+ processPersonalData,
+ generateSubmissionRef,
+ async (req, res, next) => {
+ console.log('processing files')
+ req.submissionFiles = await processSubmissionFiles(req)
+ next()
+ },
+ buildComponentPage,
+ processSubmissionData,
async (req, res) => {
- const submissionRef = `submission-${Date.now()}`
- const submissionFiles = await processSubmissionFiles(
- req.session,
- submissionRef
- )
- // console.log(submissionFiles)
- const { filename: markdownFilename, content: markdownContent } =
- generateMarkdown(req.session, submissionFiles)
- const markdown = {}
- markdown[markdownFilename] = markdownContent
- const { sessionText } = req
- const session = { ...req.session, ...markdown }
- const sessionData = processSubmissionData(
+ const {
session,
- submissionFiles,
- submissionRef
- )
+ sessionText,
+ submissionData,
+ submissionRef,
+ markdownContent
+ } = req
req.session.regenerate((err) => {
if (err) {
@@ -258,7 +409,7 @@ router.post(
})
try {
- const branchName = await pushToGitHub(sessionData, submissionRef)
+ const branchName = await pushToGitHub(submissionData, submissionRef)
const { title, description } = getPrTitleAndDescription(session)
const pr = await createPullRequest(branchName, title, description)
await sendPrEmail(pr)
@@ -283,10 +434,9 @@ router.post(
validateFormData,
(req, res, next) => {
if (req.file) {
- req.session.sessionFlash = {
- type: 'success',
- message: `File ‘${req.file.originalname}’ has been uploaded.`
- }
+ req.session.sessionFlash = MESSAGES.componentImageUploaded(
+ req.file.originalname
+ )
saveSession(req, res, next)
} else {
// Skipping saving as no new file uploaded
diff --git a/schema/email.schema.js b/schema/email.schema.js
new file mode 100644
index 000000000..46aaa0a70
--- /dev/null
+++ b/schema/email.schema.js
@@ -0,0 +1,15 @@
+const Joi = require('joi')
+
+const schema = Joi.object({
+ emailAddress: Joi.string()
+ .email({ tlds: { allow: false } })
+ .required()
+ .messages({
+ 'string.email':
+ 'Enter an email address ending @justice.gov.uk',
+ 'string.empty': 'Enter your email address',
+ 'any.required': 'Enter your email address'
+ })
+})
+
+module.exports = schema
diff --git a/views/common/base.njk b/views/common/base.njk
index 607007095..59fe855f6 100644
--- a/views/common/base.njk
+++ b/views/common/base.njk
@@ -38,9 +38,9 @@
-
+
{% block beforeContent %}{% endblock %}
-
+
{% block content %}
{{ content | safe }}
{% endblock %}
diff --git a/views/common/contributions.njk b/views/common/contributions.njk
new file mode 100644
index 000000000..29d5d59d7
--- /dev/null
+++ b/views/common/contributions.njk
@@ -0,0 +1,49 @@
+{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
+{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
+{% from "moj/components/alert/macro.njk" import mojAlert %}{% extends "base.njk" %}
+{% from "govuk/components/button/macro.njk" import govukButton %}
+{% from "macros/page-title.njk" import pageTitle %}
+{% from "macros/form.njk" import form %}
+
+{% block beforeContent %}
+
+ {% if backLink %}
+ {{ govukBackLink({
+ text: "Back",
+ href: backLink
+ }) }}
+ {% endif %}
+
+{% endblock %}
+
+{% block content %}
+
+ {% if errorList %}
+ {{ govukErrorSummary({
+ titleText: "There is a problem",
+ errorList: errorList
+ }) }}
+ {% endif %}
+
+ {% if sessionFlash.text %}
+ {{ mojAlert({
+ variant: sessionFlash.type,
+ title: sessionFlash.title,
+ text: sessionFlash.text,
+ showTitleAsHeading: true,
+ role: "alert"
+ })}}
+ {% endif %}
+
+ {% block pageTitle %}
+ {{ pageTitle({
+ caption: 'Submit a component',
+ title: page.title
+ })}}
+ {% endblock %}
+
+ {% block form %}{% endblock %}
+
+
+
+{% endblock %}
diff --git a/views/common/macros/form.njk b/views/common/macros/form.njk
new file mode 100644
index 000000000..67f1cad6a
--- /dev/null
+++ b/views/common/macros/form.njk
@@ -0,0 +1,8 @@
+{% from "govuk/macros/attributes.njk" import govukAttributes %}
+{% macro form(params) %}
+
+{% endmacro %}
diff --git a/views/common/macros/page-title.njk b/views/common/macros/page-title.njk
new file mode 100644
index 000000000..63781f75b
--- /dev/null
+++ b/views/common/macros/page-title.njk
@@ -0,0 +1,6 @@
+{% macro pageTitle(params)%}
+ {% if params.title %}
+ {{ params.caption }}
+ {% endif %}
+ {{ params.title }}
+{% endmacro %}
diff --git a/views/common/partials/side-navigation.njk b/views/common/partials/side-navigation.njk
index 0bbc1c3b4..5f2af32ae 100644
--- a/views/common/partials/side-navigation.njk
+++ b/views/common/partials/side-navigation.njk
@@ -406,13 +406,6 @@
-
- Setting up coded prototypes
-
-
-
-
-
Deploying coded prototypes
diff --git a/views/community/pages/accessibility-findings-more.njk b/views/community/pages/accessibility-findings-more.njk
deleted file mode 100644
index 0b5bd5126..000000000
--- a/views/community/pages/accessibility-findings-more.njk
+++ /dev/null
@@ -1,46 +0,0 @@
-{% from "govuk/components/character-count/macro.njk" import govukCharacterCount %}
-{% from "govuk/components/button/macro.njk" import govukButton %}
-{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
-{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
-{% extends "base.njk" %}
-{% block content %}
-
- {% if backLink %}
- {{ govukBackLink({
- text: "Back",
- href: backLink
- }) }}
- {% endif %}
- {% if errorList %}
- {{ govukErrorSummary({
- titleText: "There is a problem",
- errorList: errorList
- }) }}
- {% endif %}
-
-
-
-{% endblock %}
diff --git a/views/community/pages/accessibility-findings.njk b/views/community/pages/accessibility-findings.njk
index a336462d2..8402d85e2 100644
--- a/views/community/pages/accessibility-findings.njk
+++ b/views/community/pages/accessibility-findings.njk
@@ -1,120 +1,104 @@
{% from "govuk/components/radios/macro.njk" import govukRadios %}
-{% from "govuk/components/button/macro.njk" import govukButton %}
-{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
-{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
-{% extends "base.njk" %}
-{% block content %}
-
- {% if backLink %}
- {{ govukBackLink({
- text: "Back",
- href: backLink
- }) }}
- {% endif %}
- {% if errorList %}
- {{ govukErrorSummary({
- titleText: "There is a problem",
- errorList: errorList
- }) }}
- {% endif %}
-
-
+ {
+ value: "no",
+ text: "No"
+ }
+ ],
+ errorMessage: formErrors.hasComponentBeenTestedUsingAssistiveTechnology,
+ value: formData.hasComponentBeenTestedUsingAssistiveTechnology
+ }) }}
+
+ {{ govukButton({
+ text: 'Continue',
+ preventDoubleClick: true
+ }) }}
+
+ {% endcall %}
{% endblock %}
diff --git a/views/community/pages/add-assistive-tech.njk b/views/community/pages/add-assistive-tech.njk
index 41d009e03..ee143083d 100644
--- a/views/community/pages/add-assistive-tech.njk
+++ b/views/community/pages/add-assistive-tech.njk
@@ -2,88 +2,68 @@
{% from "govuk/components/date-input/macro.njk" import govukDateInput %}
{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %}
{% from "govuk/components/character-count/macro.njk" import govukCharacterCount %}
-{% from "govuk/components/button/macro.njk" import govukButton %}
-{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
-{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
-{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
-{% from "govuk/components/tag/macro.njk" import govukTag %}
-{% extends "base.njk" %}
-{% block content %}
-
- {% if backLink %}
- {{ govukBackLink({
- text: "Back",
- href: backLink
- }) }}
- {% endif %}
- {% if errorList %}
- {{ govukErrorSummary({
- titleText: "There is a problem",
- errorList: errorList
- }) }}
- {% endif %}
-
-
+ {{ govukCharacterCount({
+ id: "issues-discovered",
+ name: "issuesDiscovered",
+ maxwords: 250,
+ label: {
+ text: page.fields.issuesDiscovered.label,
+ classes: "govuk-label--m",
+ isPageHeading: true
+ },
+ errorMessage: formErrors.issuesDiscovered,
+ value: formData.issuesDiscovered
+ }) }}
+
+ {{ govukButton({
+ text: 'Continue',
+ preventDoubleClick: true
+ }) }}
+
+ {% endcall %}
{% endblock %}
diff --git a/views/community/pages/add-external-audit.njk b/views/community/pages/add-external-audit.njk
index 01e157faf..a1d14e892 100644
--- a/views/community/pages/add-external-audit.njk
+++ b/views/community/pages/add-external-audit.njk
@@ -1,98 +1,78 @@
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/date-input/macro.njk" import govukDateInput %}
-{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %}
{% from "govuk/components/character-count/macro.njk" import govukCharacterCount %}
-{% from "govuk/components/button/macro.njk" import govukButton %}
-{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
-{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
-{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
-{% from "govuk/components/tag/macro.njk" import govukTag %}
-{% extends "base.njk" %}
-{% block content %}
-
- {% if backLink %}
- {{ govukBackLink({
- text: "Back",
- href: backLink
- }) }}
- {% endif %}
- {% if errorList %}
- {{ govukErrorSummary({
- titleText: "There is a problem",
- errorList: errorList
- }) }}
- {% endif %}
-
-
+
+ {{ govukButton({
+ text: 'Continue',
+ preventDoubleClick: true
+ }) }}
+
+ {% endcall %}
{% endblock %}
diff --git a/views/community/pages/add-internal-audit.njk b/views/community/pages/add-internal-audit.njk
index 739bc1c1c..1886659de 100644
--- a/views/community/pages/add-internal-audit.njk
+++ b/views/community/pages/add-internal-audit.njk
@@ -1,100 +1,79 @@
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/date-input/macro.njk" import govukDateInput %}
-{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %}
{% from "govuk/components/character-count/macro.njk" import govukCharacterCount %}
-{% from "govuk/components/button/macro.njk" import govukButton %}
-{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
-{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
-{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
-{% from "govuk/components/tag/macro.njk" import govukTag %}
-{% extends "base.njk" %}
-{% block content %}
-
- {% if backLink %}
- {{ govukBackLink({
- text: "Back",
- href: backLink
- }) }}
- {% endif %}
- {% if errorList %}
- {{ govukErrorSummary({
- titleText: "There is a problem",
- errorList: errorList
- }) }}
- {% endif %}
-
-
+ {{ govukCharacterCount({
+ id: "issues-discovered",
+ name: "issuesDiscovered",
+ maxwords: 250,
+ label: {
+ text: page.fields.issuesDiscovered.label,
+ classes: "govuk-label--m",
+ isPageHeading: true
+ },
+ errorMessage: formErrors.issuesDiscovered,
+ value: formData.issuesDiscovered
+ }) }}
+
+ {{ govukButton({
+ text: 'Continue',
+ preventDoubleClick: true
+ }) }}
+
+ {% endcall %}
{% endblock %}
diff --git a/views/community/pages/check-your-answers.njk b/views/community/pages/check-your-answers.njk
index 16dfc7cf9..afedcd928 100644
--- a/views/community/pages/check-your-answers.njk
+++ b/views/community/pages/check-your-answers.njk
@@ -1,42 +1,68 @@
{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
-{% from "macros/summary-card.njk" import summaryCard %}
{% from "govuk/components/button/macro.njk" import govukButton %}
+{% from "macros/summary-card.njk" import summaryCard %}
+{% from "macros/page-title.njk" import pageTitle %}
+{% from "macros/form.njk" import form %}
+
{% extends "base.njk" %}
+
+{% block beforeContent %}
+
+ {% if backLink %}
+ {{ govukBackLink({
+ text: "Back",
+ href: backLink
+ }) }}
+ {% endif %}
+
+{% endblock %}
+
{% block content %}