diff --git a/.babelrc b/.babelrc index 7c1000df..394f0590 100644 --- a/.babelrc +++ b/.babelrc @@ -5,12 +5,5 @@ ], "plugins": [ "@babel/plugin-transform-runtime" - ], - "env": { - "development": { - "plugins": [ - "react-refresh/babel" - ] - } - } + ] } \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index e4334a2c..7d3ced80 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,5 @@ { - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", "parserOptions": { "sourceType": "module" }, @@ -7,37 +7,33 @@ "rules": { "no-plusplus": [0], "no-restricted-syntax": [0], - "no-restricted-globals": "off", "no-console": 0, "no-nested-ternary": 0, - "no-continue": "off", - "max-len": 0, - "no-unused-vars": 0, - "guard-for-in": 0, - "no-underscore-dangle": 0, - "func-names": 0, - "import/named": [1], + "no-unused-vars": 1, + "class-methods-use-this": 1, "import/order": 0, "import/prefer-default-export": 0, + "import/no-extraneous-dependencies": [ + 2, + { + "devDependencies": [ + "webpack.*.js" + ] + } + ], "react/no-unused-prop-types": 0, "react/forbid-prop-types": 0, "react/jsx-filename-extension": [1, { "extensions": [".js"] }], "react/no-array-index-key": [0], "react/destructuring-assignment": 0, - "react/jsx-closing-bracket-location": 0, - "react/jsx-closing-tag-location": 0, - "react/jsx-curly-spacing": 0, - "react/jsx-equals-spacing": 0, - "react/jsx-first-prop-new-line": 0, - "react/jsx-indent": 0, - "react/jsx-indent-props": 0, - "react/jsx-max-props-per-line": 0, - "react/jsx-one-expression-per-line": 0, - "react/jsx-props-no-multi-spaces": 0, - "react/jsx-tag-spacing": 0, - "react/jsx-wrap-multilines": 0, + "react/jsx-props-no-spreading": 1, + "react/jsx-fragments": 1, + "react/function-component-definition": 0, + "react/no-unused-class-component-methods": 1, + "react/no-unstable-nested-components": 1, "jsx-a11y/img-has-alt": [0], - "jsx-a11y/alt-text": 0 + "jsx-a11y/alt-text": 0, + "prettier/prettier": 0 }, "env": { "browser": true, diff --git a/Dockerfile b/Dockerfile index 97c29a42..f0e8a7b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,6 @@ RUN apt-get update \ && cp ./azcopy /usr/bin/ \ && rm -rf /var/lib/apt/lists/* -ENV NODE_OPTIONS=--openssl-legacy-provider - ENV WORK /opt/publisher RUN mkdir -p ${WORK} diff --git a/compose.yaml b/compose.yaml index 436b86dc..6e9cbb8f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,11 +2,15 @@ services: redis: image: redis + logging: + driver: none ports: - 6379:6379 publisher-postgres: image: postgres + logging: + driver: none ports: - 5433:5432 environment: @@ -41,6 +45,8 @@ services: publisher-render: image: hsl-map-publisher pull_policy: never # use the same build as publisher-server + logging: + driver: none ports: - 5000:5000 environment: diff --git a/package.json b/package.json index 5d568425..77b64f17 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "scripts": { "eslint-check": "eslint --print-config . | eslint-config-prettier-check", "prettier": "prettier \"src/**/*.{js,css}\" \"scripts/**/*.js\" \"migrations/**/*.js\" \"seeds/**/*.js\" \"*.js\" --write", - "lint": "eslint \"src/**/*.js\" \"scripts/**/*.js\" --fix", + "lint": "eslint \"src/**/*.js\" \"scripts/**/*.js\"", "build": "cross-env NODE_ENV=production webpack --config webpack.prod.js", "serve": "serve -l 5000 dist", - "start": "webpack-serve ./webpack.dev.js", + "start": "webpack serve -c ./webpack.dev.js", "start:production": "serve -l 5000 dist", "server": "node -r dotenv/config scripts/server", "server:production": "node -r dotenv/config scripts/server.js", @@ -49,82 +49,72 @@ }, "devDependencies": { "@babel/core": "^7.23.9", + "@babel/eslint-parser": "^7.24.5", "@babel/plugin-transform-runtime": "^7.23.9", "@babel/preset-env": "^7.23.9", "@babel/preset-react": "^7.23.3", - "babel-eslint": "^9.0.0", - "babel-loader": "^8.3.0", - "babel-plugin-transform-object-rest-spread": "^6.26.0", + "babel-loader": "^9.1.3", "cross-env": "^5.1.1", - "css-loader": "^0.28.7", - "eslint": "^5.8.0", - "eslint-config-airbnb": "^17.1.0", - "eslint-config-prettier": "^3.1.0", + "css-loader": "~6", + "dotenv-webpack": "^8.1.0", + "eslint": "~8", + "eslint-config-airbnb": "~19.0.4", + "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-webpack": "^0.10.1", - "eslint-loader": "^2.1.1", "eslint-plugin-import": "^2.14.0", "eslint-plugin-jsx-a11y": "^6.1.2", - "eslint-plugin-prettier": "^3.0.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.11.1", + "eslint-plugin-react-hooks": "^4", + "eslint-webpack-plugin": "^4.1.0", + "html-webpack-plugin": "^5.6.0", "husky": "^8.0.0", "lint-staged": "^13.0.2", - "prettier": "^1.14.3", - "react-refresh": "^0.14.0", - "webpack-cli": "^3.0.8", - "webpack-serve": "^1.0.4", - "worker-loader": "^2.0.0" + "prettier": "^3.2.5", + "style-loader": "~4.0.0", + "webpack-cli": "~5.1.4", + "webpack-dev-server": "~5.0.4", + "worker-loader": "^3.0.8" }, "dependencies": { + "@apollo/client": "^3.10.4", "@azure/storage-blob": "^10.4.0", - "@koa/cors": "^2.2.1", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@koa/cors": "^5.0.0", "@turf/turf": "^6.5.0", - "apollo-cache-inmemory": "^1.1.1", - "apollo-client": "^2.0.3", - "apollo-link": "^1.2.2", - "apollo-link-http": "^1.2.0", - "babel-polyfill": "^6.26.0", "bullmq": "^1.86.2", - "cheerio": "=1.0.0-rc.3", + "cheerio": "1.0.0-rc.12", "classnames": "^2.2.5", - "clean-webpack-plugin": "^0.1.19", "dotenv": "^8.1.0", - "dotenv-webpack": "^1.7.0", "fs-extra": "9.0.0", - "graphql": "^0.11.7", - "graphql-tag": "^2.5.0", + "graphql": "^16.8.1", "haversine": "^1.1.1", "hsl-map-style": "hsldevcom/hsl-map-style#master", - "html-webpack-plugin": "^4.0.0-alpha", "ioredis": "^5.0.6", "knex": "^2.0.0", "koa": "^2.5.3", "koa-body": "^6.0.1", "koa-json-body": "^5.3.0", - "koa-router": "^7.4.0", - "koa-session": "^5.10.1", + "koa-router": "^12.0.1", + "koa-session": "^6.4.0", "lodash": "^4.17.21", "moment": "^2.29.4", - "node-fetch": "^1.7.3", + "node-fetch": "~2", "p-map": "^1.2.0", "pdf-merger-js": "^3.4.0", "pg": "^8.7.3", "prop-types": "^15.6.0", - "puppeteer": "^13.7.0", + "puppeteer": "^22.9.0", "qrcode": "^1.0.0", "qs": "^6.5.2", - "raw-loader": "^0.5.1", - "react": "18.2.0", - "react-apollo": "^2.0.1", - "react-dom": "18.2.0", + "react": "~18.2", + "react-dom": "~18.2", "react-measure": "^2.5.2", "recompose": "^0.30.0", - "segseg": "^0.2.2", - "serve": "^13.0.2", - "style-loader": "^0.19.0", + "segseg": "^1.0.0", + "serve": "^14.2.3", "uuid": "^3.1.0", "viewport-mercator-project": "^4.1.1", - "webpack": "^4.12.1", + "webpack": "^5.91.0", "webpack-merge": "^4.1.3" } } diff --git a/scripts/generator.js b/scripts/generator.js index 5075f336..bbd90c36 100644 --- a/scripts/generator.js +++ b/scripts/generator.js @@ -8,7 +8,6 @@ const moment = require('moment'); const { AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_KEY, PUBLISHER_RENDER_URL } = require('../constants'); -const CLIENT_URL = PUBLISHER_RENDER_URL; const RENDER_TIMEOUT = 10 * 60 * 1000; const MAX_RENDER_ATTEMPTS = 3; const SCALE = 96 / 72; @@ -18,7 +17,7 @@ const cwd = process.cwd(); const pdfOutputDir = path.join(cwd, 'output'); -const pdfPath = id => path.join(pdfOutputDir, `${id}.pdf`); +const pdfPath = (id) => path.join(pdfOutputDir, `${id}.pdf`); async function initialize() { browser = await puppeteer.launch({ @@ -49,13 +48,13 @@ async function renderComponent(options) { await page.exposeFunction('serverLog', log); - page.on('error', error => { + page.on('error', (error) => { page.close(); browser.close(); onError(error); }); - page.on('console', message => { + page.on('console', (message) => { const { url, lineNumber, columnNumber } = message.location(); if (['error', 'warning', 'log'].includes(message.type())) { onInfo( @@ -72,10 +71,14 @@ async function renderComponent(options) { timeout: RENDER_TIMEOUT, }); - const { error = null, width, height } = await page.evaluate( + const { + error = null, + width, + height, + } = await page.evaluate( () => - new Promise(resolve => { - window.callPhantom = opts => resolve(opts); + new Promise((resolve) => { + window.callPhantom = (opts) => resolve(opts); }), ); @@ -123,9 +126,9 @@ async function generate(options) { onInfo('Creating new browser instance'); await initialize(); } - const timeout = new Promise((resolve, reject) => - setTimeout(reject, RENDER_TIMEOUT, new Error('Render timeout')), - ); + const timeout = new Promise((resolve, reject) => { + setTimeout(reject, RENDER_TIMEOUT, new Error('Render timeout')); + }); const posterUploaded = await Promise.race([renderComponent(options), timeout]); const uploadFailed = !posterUploaded && AZURE_STORAGE_ACCOUNT && AZURE_STORAGE_KEY; diff --git a/scripts/store.js b/scripts/store.js index 3ea31e34..364f2234 100644 --- a/scripts/store.js +++ b/scripts/store.js @@ -22,7 +22,7 @@ cleanup(() => { function convertKeys(object, converter) { const obj = {}; - Object.keys(object).forEach(key => { + Object.keys(object).forEach((key) => { obj[converter(key)] = object[key]; }); return obj; @@ -46,7 +46,7 @@ async function getBuilds() { .orderBy('build.created_at', 'desc') .groupBy('build.id'); - return rows.map(row => convertKeys(row, camelCase)); + return rows.map((row) => convertKeys(row, camelCase)); } async function getBuild({ id }) { @@ -87,8 +87,8 @@ async function getBuild({ id }) { .groupBy('poster.id'); const build = convertKeys(buildRow, camelCase); - const posters = posterRows.map(row => convertKeys(row, camelCase)); - return Object.assign({}, build, { posters }); + const posters = posterRows.map((row) => convertKeys(row, camelCase)); + return { ...build, posters }; } async function addBuild({ title }) { @@ -101,16 +101,12 @@ async function addBuild({ title }) { } async function updateBuild({ id, status }) { - await knex('build') - .where({ id }) - .update({ status, updated_at: knex.fn.now() }); + await knex('build').where({ id }).update({ status, updated_at: knex.fn.now() }); return { id }; } async function removeBuild({ id }) { - await knex('build') - .where({ id }) - .update({ status: 'REMOVED', updated_at: knex.fn.now() }); + await knex('build').where({ id }).update({ status: 'REMOVED', updated_at: knex.fn.now() }); return { id }; } @@ -145,17 +141,13 @@ async function addPoster({ buildId, component, template, props, order }) { ), order, }); - await knex('build') - .where('id', buildId) - .update({ updated_at: knex.fn.now() }); + await knex('build').where('id', buildId).update({ updated_at: knex.fn.now() }); return { id }; } async function updatePoster({ id, status }) { - await knex('poster') - .where({ id }) - .update({ status, updated_at: knex.fn.now() }); + await knex('poster').where({ id }).update({ status, updated_at: knex.fn.now() }); return { id }; } @@ -164,9 +156,7 @@ async function removePoster({ id }) { .returning('build_id') .where({ id }) .update({ status: 'REMOVED', updated_at: knex.fn.now() }); - await knex('build') - .where('id', buildId[0].build_id) - .update({ updated_at: knex.fn.now() }); + await knex('build').where('id', buildId[0].build_id).update({ updated_at: knex.fn.now() }); return { id }; } @@ -197,11 +187,7 @@ async function getTemplateImage(slot) { return emptySlot; } - const dbImg = await knex - .select('*') - .from('template_images') - .where({ name: imageName }) - .first(); + const dbImg = await knex.select('*').from('template_images').where({ name: imageName }).first(); if (dbImg) { return merge({}, slot, { image: dbImg }); @@ -216,19 +202,17 @@ async function getTemplateImages(template) { } return merge({}, template, { - areas: await pMap(template.areas, async area => + areas: await pMap(template.areas, async (area) => merge({}, area, { - slots: await pMap(area.slots, slot => getTemplateImage(slot)), + slots: await pMap(area.slots, (slot) => getTemplateImage(slot)), }), ), }); } async function getTemplates() { - const templates = await knex('template') - .select('*') - .orderBy('created_at', 'asc'); - return pMap(templates, template => getTemplateImages(template)); + const templates = await knex('template').select('*').orderBy('created_at', 'asc'); + return pMap(templates, (template) => getTemplateImages(template)); } async function addTemplate({ label }) { @@ -241,11 +225,7 @@ async function addTemplate({ label }) { } async function getTemplate({ id }, withImages = true) { - const templateRow = await knex - .select('*') - .from('template') - .where({ id }) - .first(); + const templateRow = await knex.select('*').from('template').where({ id }).first(); if (!withImages) { return templateRow; @@ -256,7 +236,7 @@ async function getTemplate({ id }, withImages = true) { // Not exported. Saves the passed images into the database. async function saveAreaImages(slots) { - return pMap(slots, async slot => { + return pMap(slots, async (slot) => { if (!slot.image) { return slot; } @@ -297,7 +277,7 @@ async function saveTemplate(template) { const { id } = template; const existingTemplate = await getTemplate({ id }, false); - const savedAreas = await pMap(template.areas, async area => + const savedAreas = await pMap(template.areas, async (area) => merge({}, area, { slots: await saveAreaImages(area.slots), }), @@ -325,37 +305,31 @@ async function removeTemplate({ id }) { throw error; } - await knex('template') - .where({ id }) - .del(); + await knex('template').where({ id }).del(); return { id }; } async function getImages() { - return knex('template_images') - .select('*') - .orderBy('updated_at', 'asc'); + return knex('template_images').select('*').orderBy('updated_at', 'asc'); } function templateHasImage(template, imageName) { - return template.areas.some(area => - area.slots.some(slot => get(slot, 'image.name', '_') === imageName), + return template.areas.some((area) => + area.slots.some((slot) => get(slot, 'image.name', '_') === imageName), ); } async function removeImage({ name }) { const templates = await knex('template').select('*'); - if (templates.some(template => templateHasImage(template, name))) { + if (templates.some((template) => templateHasImage(template, name))) { const error = new Error(`Image '${name}' is in use in one or more templates!`); error.status = 400; throw error; } - await knex('template_images') - .where({ name }) - .del(); + await knex('template_images').where({ name }).del(); return { name }; } @@ -398,9 +372,9 @@ async function getStopInfo({ stopId, date }) { const stopData = await response.json(); const { stop } = stopData.data; - const routeSegments = flatMap(stop.siblings.nodes, node => node.routeSegments.nodes); - const routeIds = routeSegments.map(routeSegment => routeSegment.routeId); - const modes = flatMap(routeSegments, node => node.route.nodes.map(route => route.mode)); + const routeSegments = flatMap(stop.siblings.nodes, (node) => node.routeSegments.nodes); + const routeIds = routeSegments.map((routeSegment) => routeSegment.routeId); + const modes = flatMap(routeSegments, (node) => node.route.nodes.map((route) => route.mode)); const city = stop.shortId.match(/^[a-zA-Z]*/)[0]; // Get the first letters of the id. const { stopZone } = stop; diff --git a/src/components/a3Timetable/a3TableRows.js b/src/components/a3Timetable/a3TableRows.js index bf65b6cc..13c8e2fa 100644 --- a/src/components/a3Timetable/a3TableRows.js +++ b/src/components/a3Timetable/a3TableRows.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Row, WrappingRow } from 'components/util'; -import { sortBy, keys, pickBy, groupBy } from 'lodash'; +import { sortBy, keys, pickBy } from 'lodash'; import { trimRouteId } from 'util/domain'; import classnames from 'classnames'; import TableHeader from './a3TableHeader'; @@ -9,7 +9,7 @@ import RouteDiagram from 'components/routeDiagram/routeDiagramContainer'; import styles from './a3TableRows.css'; -const Departure = props => ( +const Departure = (props) => (