├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Procfile ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.controller.spec.ts ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── data │ ├── data.controller.spec.ts │ ├── data.controller.ts │ ├── data.module.ts │ ├── data.service.spec.ts │ ├── data.service.ts │ └── interface.ts ├── helpers.ts ├── main.ts ├── s3 │ ├── s3.module.ts │ ├── s3.service.spec.ts │ └── s3.service.ts └── task │ ├── task.module.ts │ ├── task.service.spec.ts │ └── task.service.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | !! THE API IS NOW CLOSED, IT WAS COSTING ME MONEY TO HOST AND KEEP LIVE IF YOU WANT YOU CAN FORK IT YOU WILL NEED AN S3 BUCKET !! 2 | 3 | # CoronavirusAPI-France 4 | API permettant de récupérer et d'exploiter les données sur le Coronavirus en France actualisées chaque jours a Minuit pour chaque départements et globalement pour la France entière via de simple requetes GET qui enverront une réponse en JSON. 5 | 6 | ### Le jeu de données initial provient de Santé Publique France ### 7 | [Le lien](https://www.data.gouv.fr/fr/datasets/synthese-des-indicateurs-de-suivi-de-lepidemie-covid-19/) 8 | 9 | ### Réalisation ### 10 | [Florian Zemma Gailleton](https://www.linkedin.com/in/florian-zemma-gailleton-607031121) 11 | 12 | ##### REMARQUE : les données sont actualisées chaque jours a minuit si l'API ne trouve pas les données pour le jour même elle vous fournira celle du jour d'avant 13 | ##### NOTE : The data are updated at midnigth everyday, if the API doesn't have yet the data she will send you yesterday's data automatically. 14 | 15 | ##### Le format de la date doit être au format français DD/MM/YYYY // Date format have to be the french format DD/MM/YYYY. 16 | **EXEMPLE**: 11-10-2021 17 | 18 | ## Légende données: 19 | **'date'** = Date 20 | 21 | **'dep'** = Département 22 | 23 | **'reg'** = Région 24 | 25 | **'lib_dep'** = libellé département 26 | 27 | **'lib_reg'** = libellé région 28 | 29 | **'hosp'** = Nombre de patients actuellement hospitalisés pour COVID-19. 30 | 31 | **'incid_hosp'** = Nombre de nouveaux patients hospitalisés au cours des dernières 24h. 32 | 33 | **'rea'** = Nombre de patients actuellement en réanimation ou en soins intensifs. 34 | 35 | **'incid_rea'** = Nombre de nouveaux patients admis en réanimation au cours des dernières 24h. 36 | 37 | **'rad'** = Nombre cumulé de patients ayant été hospitalisés pour COVID-19 et de retour à domicile en raison de l'amélioration de leur état de santé. 38 | 39 | **'incid_rad'** = Nouveaux retours à domicile au cours des dernières 24h. 40 | 41 | **'dchosp'** = Décès à l’hôpital 42 | 43 | **'incid_dchosp'** = Nouveaux patients décédés à l’hôpital au cours des dernières 24h. 44 | 45 | **'esms_dc'** = Décès en ESMS 46 | 47 | **'dc_tot'** = Cumul des décès (cumul des décès constatés à l'hôpital et en EMS) 48 | 49 | **'conf'** = Nombre de cas confirmés 50 | 51 | **'conf_j1'** = Nombre de nouveaux cas confirmés (J-1 date de résultats) 52 | 53 | **'pos'** = Nombre de personnes déclarées positives (J-3 date de prélèvement) 54 | 55 | **'pos_7j'** = Nombre de personnes déclarées positives sur une semaine (J-3 date de prélèvement) 56 | 57 | **'esms_cas'** = Cas confirmés en ESMS 58 | 59 | **'tx_pos'** = Taux de positivité des tests virologiques (Le taux de positivité correspond au nombre de personnes testées positives (RT-PCR et test antigénique) pour la première fois depuis plus de 60 jours rapporté au nombre total de personnes testées positives ou négatives sur une période donnée ; et qui n‘ont jamais été testées positive dans les 60 jours précédents.) 60 | 61 | **'tx_incid'** = Taux d'incidence (activité épidémique : Le taux d'incidence correspond au nombre de personnes testées positives (RT-PCR et test antigénique) pour la première fois depuis plus de 60 jours rapporté à la taille de la population. Il est exprimé pour 100 000 habitants) 62 | 63 | **'TO'** = Taux d'occupation : tension hospitalière sur la capacité en réanimation (Proportion de patients atteints de COVID-19 actuellement en réanimation, en soins intensifs, ou en unité de surveillance continue rapportée au total des lits en capacité initiale, c’est-à-dire avant d’augmenter les capacités de lits de réanimation dans un hôpital). 64 | 65 | **'R'** = Facteur de reproduction du virus (évolution du R0 : Le nombre de reproduction du virus : c’est le nombre moyen de personnes qu’une personne infectée peut contaminer. Si le R effectif est supérieur à 1, l’épidémie se développe ; s’il est inférieur à 1, l’épidémie régresse) 66 | 67 | ## GET Les données globales connues a l'heure actuelle pour la France / GET the global data knows at the actual hour for France 68 | 69 | **GET** "https://coronavirusapifr.herokuapp.com/data/live/france" 70 | 71 | **Résulats/Results :** 72 | ```javascript 73 | [ 74 | { 75 | "date": "2021-11-11", 76 | "tx_pos": null, 77 | "tx_incid": null, 78 | "TO": 0.226374060893634, 79 | "R": null, 80 | "rea": 1145, 81 | "hosp": 6952, 82 | "rad": 430063, 83 | "dchosp": 91147, 84 | "incid_rea": 35, 85 | "incid_hosp": 212, 86 | "incid_rad": 146, 87 | "incid_dchosp": 17, 88 | "conf": 7256643, 89 | "conf_j1": 12603, 90 | "pos": null, 91 | "esms_dc": 26897, 92 | "dc_tot": 118044, 93 | "pos_7j": null, 94 | "cv_dose1": null, 95 | "esms_cas": 218906 96 | } 97 | ] 98 | ``` 99 | 100 | ## GET Données globales pour la FRANCE pour une date précise // GET global data for FRANCE at a given date. 101 | 102 | **ROUTE** "https://coronavirusapifr.herokuapp.com/data/france-by-date/:DATE" 103 | 104 | Le format de la date doit être au format français DD/MM/YYYY // Date format have to be the french format DD/MM/YYYY. 105 | 106 | **EXEMPLE**: 11-10-2021 107 | 108 | **GET** "https://coronavirusapifr.herokuapp.com/data/france-by-date/11-10-2021" 109 | 110 | **Résultats/Results :** 111 | ```javascript 112 | [ 113 | { 114 | "date": "2021-10-11", 115 | "tx_pos": 1.04644469248643, 116 | "tx_incid": 44.4207736289036, 117 | "TO": 0.226374060893634, 118 | "R": 0.936604475208371, 119 | "rea": 1145, 120 | "hosp": 6729, 121 | "rad": 423734, 122 | "dchosp": 90204, 123 | "incid_rea": 58, 124 | "incid_hosp": 226, 125 | "incid_rad": 193, 126 | "incid_dchosp": 30, 127 | "conf": 7057631, 128 | "conf_j1": 1120, 129 | "pos": 6758, 130 | "esms_dc": 26864, 131 | "dc_tot": 117068, 132 | "pos_7j": 29813, 133 | "cv_dose1": null, 134 | "esms_cas": 218350 135 | } 136 | ] 137 | 138 | ``` 139 | ## GET Les données détaillées par DÉPARTEMENT connues a l'heure ACTUELLE // GET all live data for any DEPARTMENT. 140 | 141 | **GET** "https://coronavirusapifr.herokuapp.com/data/live/departements" 142 | 143 | **Résultats/Results:** 144 | ```javascript 145 | [ 146 | { 147 | "dep": 1, 148 | "date": "2021-11-11", 149 | "reg": 84, 150 | "lib_dep": "Ain", 151 | "lib_reg": "Auvergne et Rhône-Alpes", 152 | "tx_pos": null, 153 | "tx_incid": null, 154 | "TO": 0.182468694096601, 155 | "R": null, 156 | "hosp": 39, 157 | "rea": 4, 158 | "rad": 3048, 159 | "dchosp": 644, 160 | "reg_rea": 102, 161 | "incid_hosp": 0, 162 | "incid_rea": 0, 163 | "incid_rad": 0, 164 | "incid_dchosp": 0, 165 | "reg_incid_rea": 3, 166 | "pos": null, 167 | "pos_7j": null, 168 | "cv_dose1": null 169 | }, 170 | { 171 | "dep": 2, 172 | "date": "2021-11-11", 173 | "reg": 32, 174 | "lib_dep": "Aisne", 175 | "lib_reg": "Hauts-de-France", 176 | "tx_pos": null, 177 | "tx_incid": null, 178 | "TO": 0.21689497716895, 179 | "R": null, 180 | "hosp": 50, 181 | "rea": 8, 182 | "rad": 4534, 183 | "dchosp": 1157, 184 | "reg_rea": 95, 185 | "incid_hosp": 1, 186 | "incid_rea": 0, 187 | "incid_rad": 0, 188 | "incid_dchosp": 0, 189 | "reg_incid_rea": 2, 190 | "pos": null, 191 | "pos_7j": null, 192 | "cv_dose1": null 193 | }, 194 | ...........] 195 | 196 | ``` 197 | 198 | ## GET Les données connues à l'heure ACTUELLE pour un DÉPARTEMENT précis // Get the data knows at the moment for one DEPARTMENT. 199 | 200 | **ROUTE** "https://coronavirusapifr.herokuapp.com/data/live/departement/:DEPARTEMENT" 201 | 202 | **EXEMPLE:** "https://coronavirusapifr.herokuapp.com/data/live/departement/rhone" 203 | 204 | [Liste des départements](https://www.regions-et-departements.fr/departements-francais) 205 | 206 | **Résultats/Results :** 207 | ```javascript 208 | [ 209 | { 210 | "dep": 69, 211 | "date": "2021-11-11", 212 | "reg": 84, 213 | "lib_dep": "Rhône", 214 | "lib_reg": "Auvergne et Rhône-Alpes", 215 | "tx_pos": null, 216 | "tx_incid": null, 217 | "TO": 0.182468694096601, 218 | "R": null, 219 | "hosp": 144, 220 | "rea": 31, 221 | "rad": 16847, 222 | "dchosp": 3351, 223 | "reg_rea": 102, 224 | "incid_hosp": 8, 225 | "incid_rea": 0, 226 | "incid_rad": 8, 227 | "incid_dchosp": 1, 228 | "reg_incid_rea": 3, 229 | "pos": null, 230 | "pos_7j": null, 231 | "cv_dose1": null 232 | } 233 | ] 234 | ``` 235 | 236 | ## GET Données détaillées par DÉPARTEMENTS pour une date précise // GET detailed data by DEPARTMENTS at a given date. 237 | 238 | **ROUTE** "https://coronavirusapifr.herokuapp.com/data/departements-by-date/:DATE" 239 | 240 | Le format de la date doit être au format français DD/MM/YYYY // Date format have to be the french format DD/MM/YYYY. 241 | 242 | **EXEMPLE**: 11-10-2021 243 | 244 | **GET** "https://coronavirusapifr.herokuapp.com/data/departements-by-date/11-10-2021" 245 | 246 | **Résultats/Results :** 247 | ```javascript 248 | [ 249 | { 250 | "dep": 1, 251 | "date": "2021-10-11", 252 | "reg": 84, 253 | "lib_dep": "Ain", 254 | "lib_reg": "Auvergne et Rhône-Alpes", 255 | "tx_pos": 1, 256 | "tx_incid": 36.0755302874626, 257 | "TO": 0.175313059033989, 258 | "R": null, 259 | "hosp": 45, 260 | "rea": 4, 261 | "rad": 3033, 262 | "dchosp": 641, 263 | "reg_rea": 98, 264 | "incid_hosp": 1, 265 | "incid_rea": 0, 266 | "incid_rad": 0, 267 | "incid_dchosp": 0, 268 | "reg_incid_rea": 8, 269 | "pos": 58, 270 | "pos_7j": 237, 271 | "cv_dose1": null 272 | }, 273 | { 274 | "dep": 2, 275 | "date": "2021-10-11", 276 | "reg": 32, 277 | "lib_dep": "Aisne", 278 | "lib_reg": "Hauts-de-France", 279 | "tx_pos": 0.559040934219517, 280 | "tx_incid": 17.1086398631309, 281 | "TO": 0.207762557077626, 282 | "R": null, 283 | "hosp": 38, 284 | "rea": 6, 285 | "rad": 4492, 286 | "dchosp": 1144, 287 | "reg_rea": 91, 288 | "incid_hosp": 2, 289 | "incid_rea": 1, 290 | "incid_rad": 0, 291 | "incid_dchosp": 0, 292 | "reg_incid_rea": 11, 293 | "pos": 21, 294 | "pos_7j": 90, 295 | "cv_dose1": null 296 | }, 297 | .......] 298 | ``` 299 | 300 | ## GET Toutes les données disponibles pour un DÉPARTEMENT précis // GET all the data for one DEPARTMENT. 301 | 302 | **ROUTE** "https://coronavirusapifr.herokuapp.com/data/departement/:DEPARTEMENT" 303 | 304 | **EXEMPLE:** "https://coronavirusapifr.herokuapp.com/data/departement/rhone" 305 | 306 | [Liste des départements](https://www.regions-et-departements.fr/departements-francais) 307 | 308 | **Résultats/Results :** 309 | ```javascript 310 | [ 311 | { 312 | "dep": 69, 313 | "date": "2020-03-18", 314 | "reg": 84, 315 | "lib_dep": "Rhône", 316 | "lib_reg": "Auvergne et Rhône-Alpes", 317 | "tx_pos": null, 318 | "tx_incid": null, 319 | "TO": 0.0626118067978533, 320 | "R": null, 321 | "hosp": 36, 322 | "rea": 8, 323 | "rad": 10, 324 | "dchosp": 0, 325 | "reg_rea": 35, 326 | "incid_hosp": null, 327 | "incid_rea": null, 328 | "incid_rad": null, 329 | "incid_dchosp": null, 330 | "reg_incid_rea": null, 331 | "pos": null, 332 | "pos_7j": null, 333 | "cv_dose1": null 334 | }, 335 | { 336 | "dep": 69, 337 | "date": "2020-03-19", 338 | "reg": 84, 339 | "lib_dep": "Rhône", 340 | "lib_reg": "Auvergne et Rhône-Alpes", 341 | "tx_pos": null, 342 | "tx_incid": null, 343 | "TO": 0.132379248658318, 344 | "R": null, 345 | "hosp": 210, 346 | "rea": 33, 347 | "rad": 48, 348 | "dchosp": 12, 349 | "reg_rea": 79, 350 | "incid_hosp": 244, 351 | "incid_rea": 28, 352 | "incid_rad": 46, 353 | "incid_dchosp": 17, 354 | "reg_incid_rea": 44, 355 | "pos": null, 356 | "pos_7j": null, 357 | "cv_dose1": null 358 | }, 359 | ....... ] 360 | ``` 361 | 362 | ## GET Les données disponibles pour un DÉPARTEMENT précis a une DATE précise // GET all the data for one DEPARTMENT at a given DATE. 363 | 364 | **ROUTE** "https://coronavirusapifr.herokuapp.com/data/departement/:DEPARTEMENT/:DATE" 365 | 366 | **EXEMPLE:** "https://coronavirusapifr.herokuapp.com/data/departement/rhone/10-11-2021" 367 | 368 | [Liste des départements](https://www.regions-et-departements.fr/departements-francais) 369 | 370 | **Résultats/Results :** 371 | ```javascript 372 | [ 373 | { 374 | "dep": 69, 375 | "date": "2021-11-10", 376 | "reg": 84, 377 | "lib_dep": "Rhône", 378 | "lib_reg": "Auvergne et Rhône-Alpes", 379 | "tx_pos": null, 380 | "tx_incid": null, 381 | "TO": 0.180679785330948, 382 | "R": null, 383 | "hosp": 145, 384 | "rea": 32, 385 | "rad": 16839, 386 | "dchosp": 3350, 387 | "reg_rea": 101, 388 | "incid_hosp": 16, 389 | "incid_rea": 5, 390 | "incid_rad": 9, 391 | "incid_dchosp": 1, 392 | "reg_incid_rea": 14, 393 | "pos": null, 394 | "pos_7j": null, 395 | "cv_dose1": 80.7 396 | } 397 | ] 398 | ``` 399 | 400 | ## GET Les données connues a l'heure ACTUELLE pour une RÉGION précise // GET all the data for one REGION. 401 | 402 | **ROUTE** "// https://coronavirusapifr.herokuapp.com/data/live/region/:REGION" 403 | 404 | **EXEMPLE:** "https://coronavirusapifr.herokuapp.com/data/live/region/occitanie" 405 | 406 | [Liste des départements](https://www.regions-et-departements.fr/regions-francaises) 407 | 408 | **Résultats/Results :** 409 | ```javascript 410 | [ 411 | { 412 | "dep": 9, 413 | "date": "2021-11-12", 414 | "reg": 76, 415 | "lib_dep": "Ariège", 416 | "lib_reg": "Occitanie", 417 | "tx_pos": null, 418 | "tx_incid": null, 419 | "TO": 0.19831223628692, 420 | "R": null, 421 | "hosp": 10, 422 | "rea": 4, 423 | "rad": 562, 424 | "dchosp": 102, 425 | "reg_rea": 94, 426 | "incid_hosp": 3, 427 | "incid_rea": 3, 428 | "incid_rad": 2, 429 | "incid_dchosp": 0, 430 | "reg_incid_rea": 18, 431 | "pos": null, 432 | "pos_7j": null, 433 | "cv_dose1": null 434 | }, 435 | { 436 | "dep": 11, 437 | "date": "2021-11-12", 438 | "reg": 76, 439 | "lib_dep": "Aude", 440 | "lib_reg": "Occitanie", 441 | "tx_pos": null, 442 | "tx_incid": null, 443 | "TO": 0.19831223628692, 444 | "R": null, 445 | "hosp": 39, 446 | "rea": 5, 447 | "rad": 1537, 448 | "dchosp": 413, 449 | "reg_rea": 94, 450 | "incid_hosp": 0, 451 | "incid_rea": 0, 452 | "incid_rad": 0, 453 | "incid_dchosp": 0, 454 | "reg_incid_rea": 18, 455 | "pos": null, 456 | "pos_7j": null, 457 | "cv_dose1": null 458 | }, 459 | ........ 460 | ] 461 | ``` 462 | 463 | ## GET Toutes les données disponibles pour une RÉGION précise // GET all the data for one REGION. 464 | 465 | **ROUTE** "// https://coronavirusapifr.herokuapp.com/data/region/:REGION" 466 | 467 | **EXEMPLE:** "https://coronavirusapifr.herokuapp.com/data/region/occitanie" 468 | 469 | [Liste des régions](https://www.regions-et-departements.fr/regions-francaises) 470 | 471 | **Résultats/Results :** 472 | ```javascript 473 | [ 474 | { 475 | "dep":9 476 | "date":"2020-03-18", 477 | "reg":76, 478 | "lib_dep":"Ariège", 479 | "lib_reg":"Occitanie", 480 | "tx_pos":null, 481 | "tx_incid":null, 482 | "TO":0.0822784810126582 483 | ,"R":null, 484 | "hosp":1, 485 | "rea":1, 486 | "rad":2, 487 | "dchosp":0, 488 | "reg_rea":39, 489 | "incid_hosp":null, 490 | "incid_rea":null, 491 | "incid_rad":null, 492 | "incid_dchosp":null, 493 | "reg_incid_rea":null, 494 | "pos":null, 495 | "pos_7j":null, 496 | "cv_dose1":null 497 | }, 498 | { 499 | "dep":9, 500 | "date":"2020-03-19", 501 | "reg":76, 502 | "lib_dep":"Ariège", 503 | "lib_reg":"Occitanie", 504 | "tx_pos":null 505 | ,"tx_incid":null, 506 | "TO":0.124472573839662, 507 | "R":null, 508 | "hosp":1, 509 | "rea":1, 510 | "rad":2, 511 | "dchosp":0, 512 | "reg_rea":55, 513 | "incid_hosp":0, 514 | "incid_rea":0, 515 | "incid_rad":0, 516 | "incid_dchosp":0, 517 | "reg_incid_rea":29, 518 | "pos":null, 519 | "pos_7j":null, 520 | "cv_dose1":null 521 | }, 522 | ........ 523 | ] 524 | ``` 525 | 526 | ## GET Les données disponibles pour une RÉGION précise a une DATE précise // GET all the data for one REGION at a given DATE. 527 | 528 | **ROUTE** "https://coronavirusapifr.herokuapp.com/data/region/:REGION/:DATE" 529 | 530 | **EXEMPLE:** "https://coronavirusapifr.herokuapp.com/data/region/occitanie/10-11-2021" 531 | 532 | [Liste des régions](https://www.regions-et-departements.fr/regions-francaises) 533 | 534 | **Résultats/Results :** 535 | ```javascript 536 | [ 537 | { 538 | "dep": 9, 539 | "date": "2021-11-10", 540 | "reg": 76, 541 | "lib_dep": "Ariège", 542 | "lib_reg": "Occitanie", 543 | "tx_pos": null, 544 | "tx_incid": null, 545 | "TO": 0.175105485232068, 546 | "R": null, 547 | "hosp": 9, 548 | "rea": 2, 549 | "rad": 560, 550 | "dchosp": 102, 551 | "reg_rea": 83, 552 | "incid_hosp": 0, 553 | "incid_rea": 0, 554 | "incid_rad": 0, 555 | "incid_dchosp": 0, 556 | "reg_incid_rea": 3, 557 | "pos": null, 558 | "pos_7j": null, 559 | "cv_dose1": 85.6 560 | }, 561 | { 562 | "dep": 11, 563 | "date": "2021-11-10", 564 | "reg": 76, 565 | "lib_dep": "Aude", 566 | "lib_reg": "Occitanie", 567 | "tx_pos": null, 568 | "tx_incid": null, 569 | "TO": 0.175105485232068, 570 | "R": null, 571 | "hosp": 40, 572 | "rea": 5, 573 | "rad": 1536, 574 | "dchosp": 413, 575 | "reg_rea": 83, 576 | "incid_hosp": 2, 577 | "incid_rea": 0, 578 | "incid_rad": 1, 579 | "incid_dchosp": 0, 580 | "reg_incid_rea": 3, 581 | "pos": null, 582 | "pos_7j": null, 583 | "cv_dose1": 73.2 584 | }, 585 | ........] 586 | ``` 587 | 588 | ### Le jeu de données initial provient de Santé Publique France ### 589 | [Le lien](https://www.data.gouv.fr/fr/datasets/synthese-des-indicateurs-de-suivi-de-lepidemie-covid-19/) 590 | 591 | ### Réalisation ### 592 | [Florian Zemma Gailleton](https://www.linkedin.com/in/florian-zemma-gailleton-607031121) 593 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "covid-19-fr-api", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "engines": { 9 | "node": "12.19.0", 10 | "npm": "8.1.0" 11 | }, 12 | "scripts": { 13 | "prebuild": "rimraf dist", 14 | "build": "nest build", 15 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 16 | "start": "nest start", 17 | "start:dev": "nest start --watch", 18 | "start:debug": "nest start --debug --watch", 19 | "start:prod": "node dist/main", 20 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 21 | "test": "jest", 22 | "test:watch": "jest --watch", 23 | "test:cov": "jest --coverage", 24 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 25 | "test:e2e": "jest --config ./test/jest-e2e.json" 26 | }, 27 | "dependencies": { 28 | "@aws-sdk/client-s3": "^3.42.0", 29 | "@hapi/joi": "^17.1.1", 30 | "@nestjs/axios": "0.0.3", 31 | "@nestjs/common": "^8.0.0", 32 | "@nestjs/config": "^1.1.0", 33 | "@nestjs/core": "^8.0.0", 34 | "@nestjs/platform-express": "^8.0.0", 35 | "@nestjs/schedule": "^1.0.1", 36 | "axios": "^0.24.0", 37 | "cache-manager": "^3.6.0", 38 | "cache-manager-redis-store": "^2.0.0", 39 | "date-fns": "^2.25.0", 40 | "papaparse": "^5.3.1", 41 | "reflect-metadata": "^0.1.13", 42 | "rimraf": "^3.0.2", 43 | "rxjs": "^7.2.0", 44 | "scramjet": "^4.36.0", 45 | "xhr2": "^0.2.1" 46 | }, 47 | "devDependencies": { 48 | "@nestjs/cli": "^8.0.0", 49 | "@nestjs/schematics": "^8.0.0", 50 | "@nestjs/testing": "^8.0.0", 51 | "@types/cron": "^1.7.3", 52 | "@types/express": "^4.17.13", 53 | "@types/jest": "^27.0.1", 54 | "@types/node": "^16.11.9", 55 | "@types/papaparse": "^5.3.1", 56 | "@types/supertest": "^2.0.11", 57 | "@typescript-eslint/eslint-plugin": "^5.0.0", 58 | "@typescript-eslint/parser": "^5.0.0", 59 | "eslint": "^8.0.1", 60 | "eslint-config-prettier": "^8.3.0", 61 | "eslint-plugin-prettier": "^4.0.0", 62 | "jest": "^27.2.5", 63 | "prettier": "^2.3.2", 64 | "source-map-support": "^0.5.20", 65 | "supertest": "^6.1.3", 66 | "ts-jest": "^27.0.3", 67 | "ts-loader": "^9.2.3", 68 | "ts-node": "^10.0.0", 69 | "tsconfig-paths": "^3.10.1", 70 | "typescript": "^4.3.5" 71 | }, 72 | "jest": { 73 | "moduleFileExtensions": [ 74 | "js", 75 | "json", 76 | "ts" 77 | ], 78 | "rootDir": "src", 79 | "testRegex": ".*\\.spec\\.ts$", 80 | "transform": { 81 | "^.+\\.(t|j)s$": "ts-jest" 82 | }, 83 | "collectCoverageFrom": [ 84 | "**/*.(t|j)s" 85 | ], 86 | "coverageDirectory": "../coverage", 87 | "testEnvironment": "node" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import * as redisStore from 'cache-manager-redis-store'; 3 | import { CacheModule, Module } from '@nestjs/common'; 4 | import { ConfigModule, ConfigService } from '@nestjs/config'; 5 | import { ScheduleModule } from '@nestjs/schedule'; 6 | import { AppController } from './app.controller'; 7 | import { AppService } from './app.service'; 8 | import { DataModule } from './data/data.module'; 9 | import { TaskModule } from './task/task.module'; 10 | import { TaskService } from './task/task.service'; 11 | import { S3Module } from './s3/s3.module'; 12 | import * as Joi from '@hapi/joi'; 13 | 14 | @Module({ 15 | imports: [ 16 | DataModule, 17 | ScheduleModule.forRoot(), 18 | ConfigModule.forRoot({ 19 | validationSchema: Joi.object({ 20 | REDIS_URL: Joi.string().required(), 21 | }), 22 | isGlobal: true, 23 | }), 24 | TaskModule, 25 | HttpModule, 26 | S3Module, 27 | CacheModule.registerAsync({ 28 | isGlobal: true, 29 | imports: [ConfigModule], 30 | inject: [ConfigService], 31 | useFactory: (configService: ConfigService) => ({ 32 | store: redisStore, 33 | url: configService.get('REDIS_URL'), 34 | ttl: 18000, 35 | }), 36 | }), 37 | ], 38 | controllers: [AppController], 39 | providers: [AppService, TaskService], 40 | }) 41 | export class AppModule {} 42 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/data/data.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { DataController } from './data.controller'; 3 | 4 | describe('DataController', () => { 5 | let controller: DataController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [DataController], 10 | }).compile(); 11 | 12 | controller = module.get(DataController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/data/data.controller.ts: -------------------------------------------------------------------------------- 1 | import { CovidData } from './interface'; 2 | import { DataService } from './data.service'; 3 | import { 4 | CacheInterceptor, 5 | CacheKey, 6 | Controller, 7 | Get, 8 | Param, 9 | UseInterceptors, 10 | } from '@nestjs/common'; 11 | 12 | @Controller('data') 13 | export class DataController { 14 | constructor(private dataService: DataService) {} 15 | 16 | @CacheKey('live/france') 17 | @UseInterceptors(CacheInterceptor) 18 | @Get('live/france') 19 | async getLiveData(): Promise> { 20 | return await this.dataService.getLiveData(); 21 | } 22 | 23 | @CacheKey('live/departements') 24 | @UseInterceptors(CacheInterceptor) 25 | @Get('live/departements') 26 | async getLiveDataByDep(): Promise { 27 | return this.dataService.getLiveDataForAllDepartement(); 28 | } 29 | 30 | @Get('live/departement/:name') 31 | async getLiveDataByDepName( 32 | @Param('name') name: string, 33 | ): Promise { 34 | return await this.dataService.getLiveDataByDepartementName(name); 35 | } 36 | 37 | @Get('live/region/:name') 38 | async getLiveDataByRegName( 39 | @Param('name') name: string, 40 | ): Promise { 41 | return await this.dataService.getLiveDataByRegionName(name); 42 | } 43 | 44 | @Get('france-by-date/:date') 45 | async getDataFRByDate( 46 | @Param('date') date: string, 47 | ): Promise> { 48 | return await this.dataService.getDataFRByDate(date); 49 | } 50 | 51 | @Get('departements-by-date/:date') 52 | async getDataDepByDate( 53 | @Param('date') date: string, 54 | ): Promise { 55 | return await this.dataService.getDataDepartementByDate(date); 56 | } 57 | 58 | @Get('departement/:name') 59 | async getDataForOneDep( 60 | @Param('name') name: string, 61 | ): Promise { 62 | return await this.dataService.getDataByDepartementName(name); 63 | } 64 | 65 | @Get('departement/:name/:date') 66 | async getDataForOneDepByDate( 67 | @Param('name') name: string, 68 | @Param('date') date: string, 69 | ): Promise { 70 | return await this.dataService.getDataByDepartementNameByDate(name, date); 71 | } 72 | 73 | @Get('region/:name') 74 | async getDataByRegName( 75 | @Param('name') name: string, 76 | ): Promise { 77 | return this.dataService.getDataByRegionName(name); 78 | } 79 | 80 | @Get('region/:name/:date') 81 | async getDataByRegNameByDate( 82 | @Param('name') name: string, 83 | @Param('date') date: string, 84 | ): Promise { 85 | return await this.dataService.getDataByRegionNameByDate(name, date); 86 | } 87 | 88 | @Get('update') 89 | updateData(): void { 90 | return this.dataService.updateData(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/data/data.module.ts: -------------------------------------------------------------------------------- 1 | import { S3Module } from './../s3/s3.module'; 2 | import { TaskModule } from './../task/task.module'; 3 | import { Module } from '@nestjs/common'; 4 | import { DataController } from './data.controller'; 5 | import { DataService } from './data.service'; 6 | 7 | @Module({ 8 | controllers: [DataController], 9 | imports: [TaskModule, S3Module], 10 | providers: [DataService], 11 | }) 12 | export class DataModule {} 13 | -------------------------------------------------------------------------------- /src/data/data.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { DataService } from './data.service'; 3 | 4 | describe('DataService', () => { 5 | let service: DataService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [DataService], 10 | }).compile(); 11 | 12 | service = module.get(DataService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/data/data.service.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import { S3Service } from './../s3/s3.service'; 3 | import { TaskService } from './../task/task.service'; 4 | import { CovidData } from './interface'; 5 | import { Injectable } from '@nestjs/common'; 6 | import { format, startOfYesterday } from 'date-fns'; 7 | import * as helper from '../helpers'; 8 | 9 | @Injectable() 10 | export class DataService { 11 | constructor(private taskService: TaskService, private s3Service: S3Service) {} 12 | async getLiveData(): Promise { 13 | const covidDataListFR: CovidData[] = await this.s3Service.getFileS3( 14 | 'covidDataFR.json', 15 | ); 16 | const yesterdayDate: string = format(startOfYesterday(), 'yyyy-MM-dd'); 17 | const todayDate: string = format(new Date(), 'yyyy-MM-dd'); 18 | let yesterdayData: CovidData[] | null = null; 19 | const dataOfToday: CovidData[] = covidDataListFR.filter( 20 | (data: CovidData) => data.date === todayDate, 21 | ); 22 | 23 | if (!dataOfToday.length) { 24 | yesterdayData = covidDataListFR.filter( 25 | (data: CovidData) => data.date === yesterdayDate, 26 | ); 27 | } 28 | return dataOfToday.length 29 | ? dataOfToday 30 | : yesterdayData.length 31 | ? yesterdayData 32 | : 'No data found'; 33 | } 34 | 35 | async getLiveDataForAllDepartement(): Promise { 36 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 37 | 'covidDataDep.json', 38 | ); 39 | const yesterdayDate: string = format(startOfYesterday(), 'yyyy-MM-dd'); 40 | const todayDate: string = format(new Date(), 'yyyy-MM-dd'); 41 | let yesterdayData: CovidData[] | null = null; 42 | const dataOfToday: CovidData[] = covidDataListDEP.filter( 43 | (data: CovidData) => data.date === todayDate, 44 | ); 45 | 46 | if (!dataOfToday.length) { 47 | yesterdayData = covidDataListDEP.filter( 48 | (data: CovidData) => data.date === yesterdayDate, 49 | ); 50 | } 51 | 52 | return dataOfToday.length 53 | ? dataOfToday 54 | : yesterdayData.length 55 | ? yesterdayData 56 | : 'No data found'; 57 | } 58 | 59 | async getLiveDataByDepartementName( 60 | name: string, 61 | ): Promise { 62 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 63 | 'covidDataDep.json', 64 | ); 65 | const yesterdayDate: string = format(startOfYesterday(), 'yyyy-MM-dd'); 66 | const todayDate: string = format(new Date(), 'yyyy-MM-dd'); 67 | let yesterdayData: CovidData[] | null = null; 68 | const dataOfToday: CovidData[] = covidDataListDEP.filter( 69 | (data: CovidData) => 70 | data.date === todayDate && 71 | helper.removeAccentAndLowercase(data.lib_dep) === 72 | helper.removeAccentAndLowercase(name), 73 | ); 74 | if (!dataOfToday.length) { 75 | yesterdayData = covidDataListDEP.filter( 76 | (data: CovidData) => 77 | data.date === yesterdayDate && 78 | helper.removeAccentAndLowercase(data.lib_dep) === 79 | helper.removeAccentAndLowercase(name), 80 | ); 81 | } 82 | return dataOfToday.length 83 | ? dataOfToday 84 | : yesterdayData.length 85 | ? yesterdayData 86 | : 'No data found'; 87 | } 88 | 89 | async getLiveDataByRegionName(name: string): Promise { 90 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 91 | 'covidDataDep.json', 92 | ); 93 | const yesterdayDate: string = format(startOfYesterday(), 'yyyy-MM-dd'); 94 | const todayDate: string = format(new Date(), 'yyyy-MM-dd'); 95 | let yesterdayData: CovidData[] | null = null; 96 | const dataOfToday: CovidData[] = covidDataListDEP.filter( 97 | (data: CovidData) => 98 | data.date === todayDate && 99 | helper.removeAccentAndLowercase(data.lib_reg) === 100 | helper.removeAccentAndLowercase(name), 101 | ); 102 | if (!dataOfToday.length) { 103 | yesterdayData = covidDataListDEP.filter( 104 | (data: CovidData) => 105 | data.date === yesterdayDate && 106 | helper.removeAccentAndLowercase(data.lib_reg) === 107 | helper.removeAccentAndLowercase(name), 108 | ); 109 | } 110 | return dataOfToday.length 111 | ? dataOfToday 112 | : yesterdayData.length 113 | ? yesterdayData 114 | : 'No data found'; 115 | } 116 | 117 | async getDataByDepartementName(name: string): Promise { 118 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 119 | 'covidDataDep.json', 120 | ); 121 | const dataByDepepartment: CovidData[] = covidDataListDEP.filter( 122 | (data: CovidData) => 123 | helper.removeAccentAndLowercase(data.lib_dep) === 124 | helper.removeAccentAndLowercase(name), 125 | ); 126 | return dataByDepepartment.length ? dataByDepepartment : 'No data found'; 127 | } 128 | 129 | async getDataByRegionName(name: string): Promise { 130 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 131 | 'covidDataDep.json', 132 | ); 133 | const dataByDepartement: CovidData[] = covidDataListDEP.filter( 134 | (data: CovidData) => 135 | helper.removeAccentAndLowercase(data.lib_reg) === 136 | helper.removeAccentAndLowercase(name), 137 | ); 138 | return dataByDepartement.length ? dataByDepartement : 'No data found'; 139 | } 140 | 141 | async getDataDepartementByDate(date: string): Promise { 142 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 143 | 'covidDataDep.json', 144 | ); 145 | const dataByDate: CovidData[] = covidDataListDEP.filter( 146 | (data: CovidData) => data.date === helper.formatDate(date), 147 | ); 148 | return dataByDate.length ? dataByDate : 'No data found'; 149 | } 150 | 151 | async getDataFRByDate(date: string): Promise { 152 | const covidDataListFR: CovidData[] = await this.s3Service.getFileS3( 153 | 'covidDataFR.json', 154 | ); 155 | const dataByDate: CovidData[] = covidDataListFR.filter( 156 | (data: CovidData) => data.date === helper.formatDate(date), 157 | ); 158 | return dataByDate.length ? dataByDate : 'No data found'; 159 | } 160 | 161 | async getDataByDepartementNameByDate( 162 | name: string, 163 | date: string, 164 | ): Promise { 165 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 166 | 'covidDataDep.json', 167 | ); 168 | const dataByDepartment: CovidData[] = covidDataListDEP.filter( 169 | (data: CovidData) => 170 | helper.removeAccentAndLowercase(data.lib_dep) === 171 | helper.removeAccentAndLowercase(name) && 172 | data.date === helper.formatDate(date), 173 | ); 174 | return dataByDepartment.length ? dataByDepartment : 'No data found'; 175 | } 176 | 177 | async getDataByRegionNameByDate( 178 | name: string, 179 | date: string, 180 | ): Promise { 181 | const covidDataListDEP: CovidData[] = await this.s3Service.getFileS3( 182 | 'covidDataDep.json', 183 | ); 184 | const dataByRegion: CovidData[] = covidDataListDEP.filter( 185 | (data: CovidData) => 186 | helper.removeAccentAndLowercase(data.lib_reg) === 187 | helper.removeAccentAndLowercase(name) && 188 | data.date === helper.formatDate(date), 189 | ); 190 | return dataByRegion.length ? dataByRegion : 'No data found'; 191 | } 192 | 193 | updateData(): void { 194 | this.taskService.getCovidDataFromFile(); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/data/interface.ts: -------------------------------------------------------------------------------- 1 | export interface CovidData { 2 | dep: number; 3 | date: string; 4 | reg: number; 5 | lib_dep: string; 6 | lib_reg: string; 7 | tx_pos: number; 8 | tx_incid: number; 9 | TO: number; 10 | R: any; 11 | hosp: number; 12 | rea: number; 13 | rad: number; 14 | dchosp: number; 15 | reg_rea: number; 16 | incid_hosp: number; 17 | incid_rea: number; 18 | incid_rad: number; 19 | incid_dchosp: number; 20 | reg_incid_rea: number; 21 | pos: number; 22 | pos_7j: number; 23 | cv_dose1: any; 24 | } 25 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | export const removeAccentAndLowercase = (name: string): string => { 3 | return name 4 | .toLowerCase() 5 | .normalize('NFD') 6 | .replace(/[\u0300-\u036f]/g, ''); 7 | }; 8 | 9 | export const formatDate = (date: string): string => { 10 | const splittedDate = date.split('-'); 11 | return format( 12 | new Date(+splittedDate[2], +splittedDate[1] - 1, +splittedDate[0]), 13 | 'yyyy-MM-dd', 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule, { cors: true }); 6 | const host = '0.0.0.0'; 7 | await app.listen(parseInt(process.env.PORT, 10) || 3000, host); 8 | } 9 | bootstrap(); 10 | -------------------------------------------------------------------------------- /src/s3/s3.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { S3Service } from './s3.service'; 3 | 4 | @Module({ 5 | providers: [S3Service], 6 | exports: [S3Service], 7 | }) 8 | export class S3Module {} 9 | -------------------------------------------------------------------------------- /src/s3/s3.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { S3Service } from './s3.service'; 3 | 4 | describe('S3Service', () => { 5 | let service: S3Service; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [S3Service], 10 | }).compile(); 11 | 12 | service = module.get(S3Service); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/s3/s3.service.ts: -------------------------------------------------------------------------------- 1 | import { ConfigService } from '@nestjs/config'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { CovidData } from 'src/data/interface'; 4 | import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; 5 | 6 | @Injectable() 7 | export class S3Service { 8 | constructor(private configService: ConfigService) {} 9 | 10 | getFileS3 = async (key: string): Promise => { 11 | const streamToJson = (stream): Promise => 12 | new Promise((resolve, reject): void => { 13 | const chunks = []; 14 | stream.on('data', (chunk): number => chunks.push(chunk)); 15 | stream.on('error', reject); 16 | stream.on('end', (): void => 17 | resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))), 18 | ); 19 | }); 20 | 21 | const s3client = new S3Client({ 22 | credentials: { 23 | accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'), 24 | secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'), 25 | }, 26 | region: this.configService.get('AWS_REGION'), 27 | }); 28 | 29 | const bucketParams = { 30 | Bucket: this.configService.get('AWS_BUCKET_NAME'), 31 | Key: key, 32 | ResponseContentType: 'application/json', 33 | }; 34 | 35 | const data = await s3client.send(new GetObjectCommand(bucketParams)); 36 | return (await streamToJson(data.Body)) as CovidData[]; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/task/task.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | import { TaskService } from './task.service'; 4 | 5 | @Module({ 6 | imports: [HttpModule], 7 | providers: [TaskService], 8 | exports: [TaskService], 9 | }) 10 | export class TaskModule {} 11 | -------------------------------------------------------------------------------- /src/task/task.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TaskService } from './task.service'; 3 | 4 | describe('TaskService', () => { 5 | let service: TaskService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TaskService], 10 | }).compile(); 11 | 12 | service = module.get(TaskService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/task/task.service.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { Cron, CronExpression } from '@nestjs/schedule'; 5 | import axios from 'axios'; 6 | import { 7 | PutObjectCommand, 8 | PutObjectCommandOutput, 9 | S3Client, 10 | } from '@aws-sdk/client-s3'; 11 | const { StringStream } = require('scramjet'); 12 | const Papa = require('papaparse'); 13 | 14 | @Injectable() 15 | export class TaskService { 16 | constructor(private configService: ConfigService) {} 17 | @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) 18 | getCovidDataFromFile(): void { 19 | const uploadFileS3 = async ( 20 | data: string, 21 | filename: string, 22 | ): Promise => { 23 | const jsonContent: string = JSON.stringify(data); 24 | const s3client = new S3Client({ 25 | credentials: { 26 | accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'), 27 | secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'), 28 | }, 29 | region: this.configService.get('AWS_REGION'), 30 | }); 31 | const bucketParams = { 32 | Bucket: this.configService.get('AWS_BUCKET_NAME'), 33 | Body: jsonContent, 34 | Key: filename, 35 | }; 36 | return await s3client.send(new PutObjectCommand(bucketParams)); 37 | }; 38 | 39 | const getDataByDep = async (): Promise => { 40 | const req = await axios({ 41 | method: 'GET', 42 | url: 'https://www.data.gouv.fr/fr/datasets/r/5c4e1452-3850-4b59-b11c-3dd51d7fb8b5', 43 | responseType: 'stream', 44 | }); 45 | const csv = req.data.pipe(new StringStream()); 46 | Papa.parse(csv, { 47 | dynamicTyping: true, 48 | header: true, 49 | complete: function (result) { 50 | uploadFileS3(result.data, 'covidDataDep.json'); 51 | console.log('File has been uploaded'); 52 | }, 53 | }); 54 | }; 55 | 56 | const getDataForFrance = async (): Promise => { 57 | const req = await axios({ 58 | method: 'GET', 59 | url: 'https://www.data.gouv.fr/fr/datasets/r/f335f9ea-86e3-4ffa-9684-93c009d5e617', 60 | responseType: 'stream', 61 | }); 62 | const csv = req.data.pipe(new StringStream()); 63 | Papa.parse(csv, { 64 | dynamicTyping: true, 65 | header: true, 66 | complete: function (result) { 67 | uploadFileS3(result.data, 'covidDataFR.json'); 68 | console.log('File has been uploaded'); 69 | }, 70 | }); 71 | }; 72 | getDataByDep(); 73 | getDataForFrance(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true, 15 | "skipLibCheck": true, 16 | "strictNullChecks": false, 17 | "noImplicitAny": false, 18 | "strictBindCallApply": false, 19 | "forceConsistentCasingInFileNames": false, 20 | "noFallthroughCasesInSwitch": false 21 | } 22 | } 23 | --------------------------------------------------------------------------------