diff --git a/.eslintrc.js b/.eslintrc.js index 7adea8c7..04c57f10 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,7 @@ module.exports = { es2020: true, }, - plugins: ['@typescript-eslint', 'file-progress'], + plugins: ['@typescript-eslint', 'unused-imports', 'file-progress'], // global rules for all file types rules: { @@ -39,9 +39,15 @@ module.exports = { ], rules: { + 'no-unused-vars': 'off', // same as "@typescript-eslint/no-unused-vars": "off", + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }, + ], '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'error', '@typescript-eslint/array-type': 'error', '@typescript-eslint/ban-tslint-comment': 'error', '@typescript-eslint/consistent-type-definitions': 'error', diff --git a/package-lock.json b/package-lock.json index c47e68fd..5acb0416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5792,6 +5792,21 @@ } } }, + "eslint-plugin-unused-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz", + "integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://nexus.tegonal.com/repository/npm-proxy/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/package.json b/package.json index 7a9cdb35..26e1f1bc 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "eslint": "^7.28.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-file-progress": "^1.1.1", + "eslint-plugin-unused-imports": "^2.0.0", "prettier": "^2.3.1", "shelljs": "^0.8.4", "ts-loader": "^8.3.0", diff --git a/server/api/controllers/controller.ts b/server/api/controllers/controller.ts index a8722129..9352e782 100644 --- a/server/api/controllers/controller.ts +++ b/server/api/controllers/controller.ts @@ -5,35 +5,24 @@ * and the profit contribution agreement available at https://www.my-d.org/ProfitContributionAgreement */ -import OsmService from '../services/osm.service'; import WikidataService from '../services/wikidata.service'; import l from '../../common/logger'; import generateLocationData from '../services/generateLocationData.service'; import { locations } from '../../../config/locations'; import { fountain_property_metadata } from '../../../config/fountain.properties'; import NodeCache from 'node-cache'; -import { conflate } from '../services/conflate.data.service'; -import applyImpliedPropertiesOsm from '../services/applyImplied.service'; -import { - essenceOf, - defaultCollectionEnhancement, - fillInMissingWikidataFountains, - fillWikipediaSummary, -} from '../services/processing.service'; -import { updateCacheWithFountain } from '../services/database.service'; +import { essenceOf, fillWikipediaSummary } from '../services/processing.service'; import { extractProcessingErrors } from './processing-errors.controller'; import { getImageInfo, getImgsOfCat } from '../services/wikimedia.service'; import { getCatExtract, getImgClaims } from '../services/claims.wm'; import { isBlacklisted } from '../services/categories.wm'; -import haversine from 'haversine'; -import _ from 'lodash'; import { MAX_IMG_SHOWN_IN_GALLERY, LAZY_ARTIST_NAME_LOADING_i41db, //,CACHE_FOR_HRS_i45db } from '../../common/constants'; import sharedConstants from './../../common/shared-constants'; import { Request, Response } from 'express'; -import { getSingleBooleanQueryParam, getSingleNumberQueryParam, getSingleStringQueryParam } from './utils'; +import { getSingleBooleanQueryParam, getSingleStringQueryParam } from './utils'; import { Fountain, FountainCollection, GalleryValue, isDatabase } from '../../common/typealias'; import { hasWikiCommonsCategories } from '../../common/wikimedia-types'; import { ImageLike } from '../../../config/text2img'; @@ -88,30 +77,15 @@ export class Controller { // Function to return detailed fountain information // When requesting detailed information for a single fountain, there are two types of queries getSingle(req: Request, res: Response): void { - // const start = new Date(); - // let what: string; const queryType = getSingleStringQueryParam(req, 'queryType'); const refresh = getSingleBooleanQueryParam(req, 'refresh', /* isOptional = */ true); - if (queryType === 'byCoords') { - const city = getSingleStringQueryParam(req, 'city'); - l.info(`controller.js getSingle byCoords: refresh: ${refresh} , city: ` + city); - - // byCoords will return the nearest fountain to the given coordinates. - // The databases are queried and fountains are reprocessed for this - reprocessFountainAtCoords(req, res, city); - // what = 'reprocessFountainAtCoords'; - } else if (queryType === 'byId') { - l.info(`controller.js getSingle by: refresh: ${refresh}`); - // byId will look into the fountain cache and return the fountain with the given identifier - byId(req, res, req.query.idval?.toString() ?? ''); - // what = 'byId'; + if (queryType === 'byId') { + l.info(`controller.js getSingle byId: refresh: ${refresh}`); + byId(req, res); } else { - res.status(400).send('only byCoords and byId supported'); + res.status(400).send('only byId supported'); } - // const end = new Date(); - // const elapse = (end - start)/1000; - //l.info('controller.js getSingle: '+what+' finished after '+elapse.toFixed(1)+' secs'); } // Function to return all fountain information for a location. @@ -120,7 +94,7 @@ export class Controller { const city = getSingleStringQueryParam(req, 'city'); const refresh = getSingleBooleanQueryParam(req, 'refresh', /* isOptional = */ true); - // if a refresh is requested or if no data is in the cache, then reprocessess the fountains + // if a refresh is requested or if no data is in the cache, then reprocesses the fountains if (refresh || cityCache.keys().indexOf(city) === -1) { l.info(`controller.js byLocation: refresh: ${refresh} , city: ` + city); generateLocationData(city) @@ -249,12 +223,14 @@ function sendJson(resp: Response, obj: Record | undefined, dbg: str /** * Function to respond to request by returning the fountain as defined by the provided identifier */ -function byId(req: Request, res: Response, dbg: string): Promise { +function byId(req: Request, res: Response): Promise { const city = getSingleStringQueryParam(req, 'city'); const database = getSingleStringQueryParam(req, 'database'); if (!isDatabase(database)) { return new Promise((_, reject) => reject('unsupported database given: ' + database)); } + const idval = getSingleStringQueryParam(req, 'idval'); + const dbg = idval; let name = 'unkNamById'; // l.info('controller.js byId: '+cityS+' '+dbg); @@ -274,9 +250,7 @@ function byId(req: Request, res: Response, dbg: string): Promise(city); } if (fountainCollection !== undefined) { - const fountain = fountainCollection.features.find( - f => f.properties['id_' + database]?.value === req.query.idval - ); + const fountain = fountainCollection.features.find(f => f.properties['id_' + database]?.value === idval); const imgMetaPromises: Promise[] = []; let lazyAdded = 0; const gl = -1; @@ -558,88 +532,89 @@ function byId(req: Request, res: Response, dbg: string): Promise applyImpliedPropertiesOsm(r)) - .catch(e => { - l.error(`controller.js reprocessFountainAtCoords: Error collecting OSM data: ${JSON.stringify(e)} `); - // TODO @ralfhauser, this is an ugly side effect, this does nost stop the program but implies return void - // hence I changed it because we already catch errors in Promise.all - // res.status(500).send(e.stack); - throw e; - }); - - const wikidataPromise = WikidataService - // Fetch all wikidata items within radius - .idsByCenter(lat, lng, radius, dbg) - // Fetch detailed information for fountains based on wikidata ids - .then(r => WikidataService.byIds(r, dbg)) - .catch(e => { - l.error(`Error collecting Wikidata data: ${e}`); - // TODO @ralfhauser, same same as above - // res.status(500).send(e.stack); - throw e; - }); - const debugAll = true; - // When both OSM and Wikidata data have been collected, continue with joint processing - Promise.all([osmPromise, wikidataPromise]) - - // Get any missing wikidata fountains for #212 (fountains not fetched from Wikidata because not listed as fountains, but referenced by fountains of OSM) - .then(r => fillInMissingWikidataFountains(r[0], r[1], dbg)) - - // Conflate osm and wikidata fountains together - .then(r => - conflate( - { - osm: r.osm, - wikidata: r.wikidata, - }, - dbg, - debugAll - ) - ) - - // return only the fountain that is closest to the coordinates of the query - .then(r => { - const distances = _.map(r, f => { - // compute distance to center for each fountain - return haversine(f.geometry.coordinates, [lng, lat], { - unit: 'meter', - format: '[lon,lat]', - }); - }); - // return closest - const closest = r[_.indexOf(distances, _.min(distances))]; - return [closest]; - }) - - // fetch more information about fountains (Artist information, gallery, etc.) - //TOOD @ralfhauser, the last parameter for debugAll was missing undefined is falsy hence I used false - .then(r => defaultCollectionEnhancement(r, dbg, false)) - - // Update cache with newly processed fountain - .then(r => { - const city = getSingleStringQueryParam(req, 'city'); - const closest = updateCacheWithFountain(cityCache, r[0], city); - sendJson(res, closest, 'after updateCacheWithFountain'); - }) - .catch(e => { - l.error(`Error collecting data: ${e.stack}`); - res.status(500).send(e.stack); - }); -} +//TODO #150 re-use part of the logic for id refresh +// function reprocessFountainAtCoords(req: Request, res: Response, dbg: string): void { +// const lat = getSingleNumberQueryParam(req, 'lat'); +// const lng = getSingleNumberQueryParam(req, 'lng'); +// const radius = getSingleNumberQueryParam(req, 'radius'); + +// l.info( +// `controller.js reprocessFountainAtCoords: all fountains near lat:${lat}, lng: ${lng}, radius: ${radius} ` + dbg +// ); + +// // OSM promise +// const osmPromise = OsmService +// // Get data from OSM within given radius +// .byCenter(lat, lng, radius) +// // Process OSM data to apply implied properties +// .then(r => applyImpliedPropertiesOsm(r)) +// .catch(e => { +// l.error(`controller.js reprocessFountainAtCoords: Error collecting OSM data: ${JSON.stringify(e)} `); +// // TODO @ralfhauser, this is an ugly side effect, this does nost stop the program but implies return void +// // hence I changed it because we already catch errors in Promise.all +// // res.status(500).send(e.stack); +// throw e; +// }); + +// const wikidataPromise = WikidataService +// // Fetch all wikidata items within radius +// .idsByCenter(lat, lng, radius, dbg) +// // Fetch detailed information for fountains based on wikidata ids +// .then(r => WikidataService.byIds(r, dbg)) +// .catch(e => { +// l.error(`Error collecting Wikidata data: ${e}`); +// // TODO @ralfhauser, same same as above +// // res.status(500).send(e.stack); +// throw e; +// }); +// const debugAll = true; +// // When both OSM and Wikidata data have been collected, continue with joint processing +// Promise.all([osmPromise, wikidataPromise]) + +// // Get any missing wikidata fountains for #212 (fountains not fetched from Wikidata because not listed as fountains, but referenced by fountains of OSM) +// .then(r => fillInMissingWikidataFountains(r[0], r[1], dbg)) + +// // Conflate osm and wikidata fountains together +// .then(r => +// conflate( +// { +// osm: r.osm, +// wikidata: r.wikidata, +// }, +// dbg, +// debugAll +// ) +// ) + +// // return only the fountain that is closest to the coordinates of the query +// .then(r => { +// const distances = _.map(r, f => { +// // compute distance to center for each fountain +// return haversine(f.geometry.coordinates, [lng, lat], { +// unit: 'meter', +// format: '[lon,lat]', +// }); +// }); +// // return closest +// const closest = r[_.indexOf(distances, _.min(distances))]; +// return [closest]; +// }) + +// // fetch more information about fountains (Artist information, gallery, etc.) +// //TOOD @ralfhauser, the last parameter for debugAll was missing undefined is falsy hence I used false +// .then(r => defaultCollectionEnhancement(r, dbg, false)) + +// // Update cache with newly processed fountain +// .then(r => { +// const city = getSingleStringQueryParam(req, 'city'); +// const closest = updateCacheWithFountain(cityCache, r[0], city); +// sendJson(res, closest, 'after updateCacheWithFountain'); +// }) +// .catch(e => { +// l.error(`Error collecting data: ${e.stack}`); +// res.status(500).send(e.stack); +// }); +// } export function generateLocationDataAndCache(key: string, cityCache: NodeCache): Promise { // trigger a reprocessing of the location's data, based on the key. diff --git a/server/api/controllers/utils.ts b/server/api/controllers/utils.ts index d1c170d4..bcdd1de9 100644 --- a/server/api/controllers/utils.ts +++ b/server/api/controllers/utils.ts @@ -23,5 +23,5 @@ function getSingleQueryParam(req: Request, paramName: string, isOptional: boo // looks like we sometimes get numbers or booleans and not string even though query[x] does not include it in its type signature if (typeof v === type) return v as unknown as T; else if (v === undefined && isOptional) return undefined; - else throw Error(`${paramName} is not a single parameter, was ${JSON.stringify(v)} ${typeof v}`); + else throw Error(`${paramName} is not a single parameter, was ${JSON.stringify(v)} with type ${typeof v}`); } diff --git a/server/common/build.info.ts b/server/common/build.info.ts index daafe49a..900d300b 100644 --- a/server/common/build.info.ts +++ b/server/common/build.info.ts @@ -1,9 +1,9 @@ // this file is automatically generated by git.version.js script const buildInfo = { version: '', - revision: 'c6dfade', - branch: 'develop', - commit_time: '2021-10-27 08:30:58 +0000', - build_time: 'Fri Dec 17 2021 23:35:27 GMT+0100 (Central European Standard Time)', + revision: 'de728a0', + branch: '#147-remove-deprecated-byCoords', + commit_time: '2021-12-17 23:49:37 +0100', + build_time: 'Mon Dec 20 2021 08:44:55 GMT+0100 (Central European Standard Time)', }; export default buildInfo; diff --git a/server/common/swagger/Api.yaml b/server/common/swagger/Api.yaml index c4d99c9a..20b6f6f9 100644 --- a/server/common/swagger/Api.yaml +++ b/server/common/swagger/Api.yaml @@ -58,45 +58,28 @@ paths: - name: queryType in: query type: string - enum: [byCoords, byId] - default: byCoords - description: how to query the fountains + enum: [byId] required: true + description: how to query the fountains - name: database in: query type: string enum: [wikidata, osm] example: wikidata + required: true description: database for which the provided identifier is valid - name: city in: query type: string example: ch-zh - required: false + required: true description: code of city for which fountains are to be served - name: idval in: query type: string example: Q27229889 + required: true description: identifier used for fountain - - name: lat - in: query - schema: - type: float - example: 47.364622 - description: approximate latitude of requested fountain, only required if querying by coords - - name: lng - in: query - schema: - type: float - example: 8.537836 - description: approximate longitude of requested fountain, only required if querying by coords - - name: radius - in: query - schema: - type: float - default: 50.0 - description: radius in meters in which fountains should be searched responses: 200: description: Returns a fountain with metadata collected from OSM