Version 1.1 #43

Merged
dbroqua merged 25 commits from develop into master 2022-04-09 00:42:25 +02:00
19 changed files with 1022 additions and 669 deletions

View File

@ -62,7 +62,7 @@ Le site est accessible sur [http://localhost:PORT](http://localhost:PORT).
#### Standalone
Pour la version standalone je vous conseille de faire un script embarquant les variables d'environnement que vous souhaitez modifier :
Pour la version standalone je vous conseille de faire un script embarquant les variables d'environnement que vous souhaitez modifier ([voir à la fin pour la liste des variables](#env-file)) :
```bash
#! /bin/bash
@ -184,6 +184,26 @@ server {
Une fois le vhost activé (lien symbolique dans le dossier site-enable) et nginx rechargé votre site sera alors accessible en https.
### Jobs
Par défaut toute les images des albums sont affichées depuis Discogs. Cependant avec les temps les urls deviennent invalides. Pour éviter cela lors de l'ajout d'un album à votre collection un job est créé. Ce job a pour rôle de stocker les images sur un bucket s3.
Pour lancer les jobs il faut mettre en place une tâche cron qui sera éxécutée toute les heures (par exemple).
Exemple de crontab :
```crontab
0 * * * * curl 'http://localhost:3001/jobs' \
-H 'JOBS_HEADER_KEY: JOBS_HEADER_VALUE' \
-H 'Accept: application/json'
30 * * * * curl 'http://localhost:3001/jobs?state=ERROR' \
-H 'JOBS_HEADER_KEY: JOBS_HEADER_VALUE' \
-H 'Accept: application/json'
```
N'oubliez pas de remplacer `localhost:30001`, `JOBS_HEADER_KEY` et `JOBS_HEADER_VALUE` par les bonnes valeurs.
La première ligne permet de parcourir tous les nouveaux jobs alors que la seconde permet de relancer les jobs en erreurs (après 5 tentatives le job est marqué comme définitivement perdu).
### Fichier .env {#env-file}
Voici la liste des variables configurables :
@ -198,10 +218,17 @@ FORMSPREE_ID # Id du formulaire formspree pour la page "nous-contacter"
MATOMO_URL # Url vers l'instance matomo (exemple: https://analytics.darkou.fr/)
MATOMO_ID # Id du site sur votre instance matomo (exemple: 1)
SITE_NAME # Nom du site (utilisé dans le titre des pages)
AWS_ACCESS_KEY_ID # Clé d'accès AWS
AWS_SECRET_ACCESS_KEY # Clé secrète AWS
S3_ENDPOINT # Url de l'instance aws (s3.fr-par.scw.cloud pour scaleway france par exemple)
S3_SIGNATURE # Version de la signature AWS (s3v4 pour scaleway par exemple)
S3_BASEFOLDER # Nom du sous dossier dans lequel seront mis les pochettes des albums
S3_BUCKET # Nom du bucket
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (par exemple musictopus)
JOBS_HEADER_VALUE # Valeur de la clé
```
## Contributeurs
- Damien Broqua (développeur principal du projet)
- Brunus (Logo et fournisseur d'idées :wink: )

View File

@ -28,6 +28,14 @@ services:
MATOMO_URL: ${MATOMO_URL}
MATOMO_ID: ${MATOMO_ID}
SITE_NAME: ${SITE_NAME}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
S3_BASEFOLDER: ${S3_BASEFOLDER}
S3_BUCKET: ${S3_BUCKET}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_SIGNATURE: ${S3_SIGNATURE}
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
networks:
- musictopus
musictopus-db:

View File

@ -28,6 +28,14 @@ services:
MATOMO_URL: ${MATOMO_URL}
MATOMO_ID: ${MATOMO_ID}
SITE_NAME: ${SITE_NAME}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
S3_BASEFOLDER: ${S3_BASEFOLDER}
S3_BUCKET: ${S3_BUCKET}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_SIGNATURE: ${S3_SIGNATURE}
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
networks:
- musictopus
musictopus-db:

View File

@ -41,6 +41,7 @@
"@babel/cli": "^7.17.0",
"@babel/core": "^7.17.2",
"@babel/preset-env": "^7.16.11",
"aws-sdk": "^2.1110.0",
"axios": "^0.26.0",
"connect-ensure-login": "^0.1.1",
"connect-flash": "^0.1.1",
@ -60,10 +61,12 @@
"mongoose-unique-validator": "^3.0.0",
"npm-run-all": "^4.1.5",
"passport": "^0.5.2",
"passport-custom": "^1.1.1",
"passport-http": "^0.3.0",
"passport-local": "^1.0.0",
"rimraf": "^3.0.2",
"sass": "^1.49.7",
"uuid": "^8.3.2",
"vue": "^3.2.31"
},
"nodemonConfig": {

View File

@ -6,50 +6,50 @@
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 168.85766 133.4734"
version="1.1"
id="MusicTopus"
width="100%"
height="100%"
width="100%">
id="MusicTopus"
version="1.1"
viewBox="0 0 168.85766 133.4734">
<defs
id="defs2">
<linearGradient
id="linearGradient3016">
<stop
offset="0"
id="stop3018-4"
stop-color="#949494"
id="stop3018-4" />
offset="0" />
<stop
offset="1"
stop-opacity="0"
id="stop3020-0"
stop-color="#949494"
id="stop3020-0" />
stop-opacity="0"
offset="1" />
</linearGradient>
<linearGradient
x1="57.074001"
y1="27.309999"
gradientTransform="translate(-19.041285,-22.505715)"
x2="103.29"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient3016"
id="linearGradient1417"
y2="104.59"
id="linearGradient1417" />
<radialGradient
r="7.395"
gradientTransform="matrix(2.2777,1.8145,-1.5547,2.3139,262.42,-105.22857)"
cx="16.073999"
cy="98.385002"
xlink:href="#linearGradient3016"
gradientUnits="userSpaceOnUse"
id="radialGradient10835">
x2="103.29"
gradientTransform="translate(-19.041285,-22.505715)"
y1="27.309999"
x1="57.074001" />
<radialGradient
id="radialGradient10835"
gradientUnits="userSpaceOnUse"
cy="98.385002"
cx="16.073999"
gradientTransform="matrix(2.2777,1.8145,-1.5547,2.3139,262.42,-105.22857)"
r="7.395">
<stop
offset="0"
id="stop10767-64"
stop-color="#989898"
id="stop10767-64" />
offset="0" />
<stop
offset="1"
id="stop10769-6"
stop-color="#989898"
stop-opacity="0"
stop-color="#989898"
id="stop10769-6" />
offset="1" />
</radialGradient>
</defs>
<metadata
@ -65,279 +65,276 @@
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="translate(-4.0461145,-24.740973)">
transform="translate(-4.0461145,-24.740973)"
id="layer1">
<g
transform="matrix(-1,0,0,1,16.909353,13.841748)"
id="g4845">
id="g4845"
transform="matrix(-1,0,0,1,16.909353,13.841748)">
<path
style="fill:#301818;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -124.03737,64.054339 c -0.67869,5.653701 -1.79458,14.69263 1.57781,21.418531 0.16987,0.339747 -10.7216,2.427294 -11.91019,-4.293016 -1.11922,-6.328076 -2.52943,-10.954215 -1.33635,-13.964832 3.07095,-7.749247 11.86215,-3.651666 11.66873,-3.160683 z"
id="path4839" />
<path
style="fill:#7b2121;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -125.27588,63.26029 c 0.41675,0.233807 1.34252,0.316004 1.74941,0.697875 -0.40534,4.680582 -0.98591,7.419536 -0.0655,11.187673 1.56849,6.421216 1.99919,7.121733 2.51886,9.876016 -0.60349,0.201385 -1.88006,0.796452 -2.50566,0.776752 -0.78768,-7.838385 -2.58856,-15.620579 -1.69713,-22.538316 z"
id="path4841" />
<path
style="fill:#f3e8d4;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -135.88244,67.615699 c 0,0 3.35454,1.181174 3.82701,2.787573 0.47247,1.606399 0.70871,6.000372 0.37798,8.031993 -0.33073,2.031624 -2.74033,2.882069 -2.74033,2.882069 -0.81232,-5.382945 -2.2983,-10.046051 -1.46466,-13.701635 z"
id="path4843" />
</g>
<g
id="g4837"
transform="translate(195.33147,13.841748)">
<path
id="path4828"
id="path4839"
d="m -124.03737,64.054339 c -0.67869,5.653701 -1.79458,14.69263 1.57781,21.418531 0.16987,0.339747 -10.7216,2.427294 -11.91019,-4.293016 -1.11922,-6.328076 -2.52943,-10.954215 -1.33635,-13.964832 3.07095,-7.749247 11.86215,-3.651666 11.66873,-3.160683 z"
style="fill:#301818;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4830"
d="m -125.27588,63.26029 c 0.41675,0.233807 0.84139,0.349413 1.24828,0.731284 -0.40534,4.680582 -0.23721,7.396829 0.16838,11.25449 0.56623,5.385547 0.8967,7.522637 1.41637,10.27692 -0.60349,0.201385 -1.17847,0.395548 -1.80407,0.375848 -0.90324,-7.658049 -3.18063,-15.595778 -1.02896,-22.638542 z"
id="path4841"
d="m -125.27588,63.26029 c 0.41675,0.233807 1.34252,0.316004 1.74941,0.697875 -0.40534,4.680582 -0.98591,7.419536 -0.0655,11.187673 1.56849,6.421216 1.99919,7.121733 2.51886,9.876016 -0.60349,0.201385 -1.88006,0.796452 -2.50566,0.776752 -0.78768,-7.838385 -2.58856,-15.620579 -1.69713,-22.538316 z"
style="fill:#7b2121;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4832"
id="path4843"
d="m -135.88244,67.615699 c 0,0 3.35454,1.181174 3.82701,2.787573 0.47247,1.606399 0.70871,6.000372 0.37798,8.031993 -0.33073,2.031624 -2.74033,2.882069 -2.74033,2.882069 -0.81232,-5.382945 -2.2983,-10.046051 -1.46466,-13.701635 z"
style="fill:#f3e8d4;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
transform="translate(195.33147,13.841748)"
id="g4837">
<path
style="fill:#301818;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -124.03737,64.054339 c -0.67869,5.653701 -1.79458,14.69263 1.57781,21.418531 0.16987,0.339747 -10.7216,2.427294 -11.91019,-4.293016 -1.11922,-6.328076 -2.52943,-10.954215 -1.33635,-13.964832 3.07095,-7.749247 11.86215,-3.651666 11.66873,-3.160683 z"
id="path4828" />
<path
style="fill:#7b2121;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -125.27588,63.26029 c 0.41675,0.233807 0.84139,0.349413 1.24828,0.731284 -0.40534,4.680582 -0.23721,7.396829 0.16838,11.25449 0.56623,5.385547 0.8967,7.522637 1.41637,10.27692 -0.60349,0.201385 -1.17847,0.395548 -1.80407,0.375848 -0.90324,-7.658049 -3.18063,-15.595778 -1.02896,-22.638542 z"
id="path4830" />
<path
style="fill:#f3e8d4;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -135.88244,67.615699 c 0,0 3.35454,1.181174 3.82701,2.787573 0.47247,1.606399 0.70871,6.000372 0.37798,8.031993 -0.33073,2.031624 -2.74033,2.882069 -2.74033,2.882069 -0.81232,-5.382945 -2.2983,-10.046051 -1.46466,-13.701635 z"
id="path4832" />
</g>
<path
style="fill:#ec8479;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4668"
d="m 104.6994,52.638393 c -15.180178,-0.07451 -33.140948,8.951637 -33.639877,27.78125 -0.50786,19.166666 9.223449,34.250207 -6.425596,34.301337 -10.643849,0.0348 -6.80357,-16.158483 -6.80357,-16.158483 0.443803,-1.702342 -1.552627,-2.254759 -2.693082,-0.897692 -11.388255,13.593915 3.527003,26.318385 11.722934,37.071045 11.135404,14.60911 24.729611,20.33094 37.083241,-6.31323 16.89051,50.78843 72.75864,-14.34852 42.71131,-27.44716 -0.79429,-0.21911 -2.98222,-0.45948 -1.5119,1.74478 0,0 7.46421,11.18075 -3.02381,13.22916 -7.74851,0.37797 -6.46564,-8.79054 -1.88989,-25.702378 5.34881,-19.769046 -13.75388,-37.501741 -35.52976,-37.608629 z"
id="path4668" />
style="fill:#ec8479;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#fbb9b8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4723"
d="m 149.1438,102.30834 c 0,0 -0.30808,0.3199 -0.0742,0.7208 0.23386,0.4009 2.13815,1.20271 1.90429,2.03793 -0.23386,0.83522 -0.23386,1.46998 0.46773,2.03793 0.70158,0.56795 3.00678,1.43657 3.07359,2.23838 0.65095,0.94785 0.11036,4.73821 -1.10248,5.34539 -0.30068,0.83522 -0.26727,1.5368 -0.26727,1.5368 l 2.00452,4.47677 c 2.67736,-5.97229 4.5819,-11.72353 -6.00616,-18.394 z"
id="path4723" />
style="fill:#fbb9b8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4689"
d="m 54.790252,98.184879 c 0,0 -8.485807,6.113791 -6.147199,15.134141 2.338609,9.02034 7.015826,10.95805 7.015826,10.95805 -0.248403,-2.16366 -0.08184,-4.23511 0.801809,-6.1472 0,0 -0.200453,-2.07134 -1.336349,-3.00678 -1.135896,-0.93545 -3.073599,-1.67044 -2.672694,-3.07361 0.400905,-1.40316 1.403165,-3.07359 0.935442,-4.14267 -0.467722,-1.06908 -1.069078,-1.73725 -0.801809,-2.87315 0.267272,-1.1359 2.204974,-6.848781 2.204974,-6.848781 z"
id="path4689" />
<ellipse
ry="20.250488"
rx="22.683598"
cy="99.451027"
cx="39.641644"
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="circle4790"
d="m 62.325241,99.451027 c 0,11.184033 -10.15579,20.250493 -22.683597,20.250493 -7.402258,0 -0.829184,-8.01428 -4.969517,-12.91144 -0.612097,-0.72399 -1.770309,-5.66385 -3.213537,-6.11122 -5.316459,-1.647971 -14.500545,2.37098 -14.500544,-1.227833 3e-6,-11.184036 6.023828,-20.350467 18.551633,-20.350467 0.220732,3.973948 2.052425,13.960818 7.154907,14.623465 7.156786,-4.199604 19.410793,-0.618035 19.660655,5.727002 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8.20355606;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<path
id="path4"
transform="translate(4.0461145,24.740973)"
style="fill:#212121;fill-opacity:1;stroke-width:0.205;stroke:none;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 34.146484,51.925781 c -0.314077,0.0092 -0.628478,0.01217 -0.941406,0.02734 -11.059699,0.788242 -20.839327,8.72416 -22.753906,18.482422 -1.0222988,4.666022 -0.263288,9.571297 1.953125,13.871094 l 2.441406,-2.441407 4.525391,2.404297 2.121094,-3.251953 5.230468,1.13086 0.707032,-4.09961 1.414062,4.382813 0.566406,2.402343 -5.65625,-0.564453 -1.414062,4.351719 -4.013672,2.913906 c 0.239784,0.192385 0.473561,0.390466 0.722656,0.574219 7.713021,6.01634 19.591819,7.137217 28.550781,2.716797 C 57.055648,90.480286 62.646095,80.45375 60.816406,71.099609 59.52397,63.325904 53.292903,56.50211 45.197266,53.552734 c 1.309899,2.835554 1.897379,5.928925 2.693359,8.935547 l -10.398437,2 0.90039,3.699219 -3.90039,-4 6.199218,-3.498047 -8.498047,-4.099609 3.998047,-1.900391 z m -9.673828,6.271485 c 0.23821,0.241845 0.400025,0.530577 0.587891,0.802734 -0.185522,2.23358 -3.870804,4.435587 -3.755859,4.089844 -1.319654,1.186159 -2.291211,3.893629 -4.216797,1.74414 1.76144,-2.731738 4.374999,-5.003791 7.384765,-6.636718 z m 2.291016,3.501953 c 0.217609,0.03588 0.400611,0.22504 0.52539,0.664062 1.260066,1.740193 -2.311306,4.335474 -3.519531,4.785157 -0.880279,0.143799 -1.50846,0.89274 -2.666015,-0.138672 0.261264,-1.586273 5.126947,-5.38799 3.720703,-4.21875 0.526069,-0.238877 1.182742,-0.95689 1.710937,-1.080078 0.07923,-0.01848 0.15598,-0.02368 0.228516,-0.01172 z m 2.261719,3.511719 c 0.318835,0.04398 0.591476,0.29958 0.78125,0.880859 0.691211,2.140581 -1.931888,4.415457 -2.626953,4.083984 -1.770649,-0.510364 -3.306383,-1.512291 -1.222657,-2.802734 0.746208,-0.529496 2.111854,-2.294057 3.06836,-2.162109 z m 6.597656,6.009765 c 0.500623,0.0012 1.007104,0.09327 1.488281,0.292969 3.025279,0.986468 3.11205,5.198119 0.179688,6.324219 -2.297247,1.06592 -5.580236,-0.439849 -5.564454,-2.841797 -0.353584,-2.07507 1.72712,-3.780425 3.896485,-3.775391 z m 7.677734,8.976563 c 0.454001,-0.07081 0.897599,0.528293 1.34375,0.736328 1.285766,0.439342 -0.203248,1.889914 -0.646484,2.283203 -1.277789,1.157639 -3.468693,-2.140236 -1.15625,-2.685547 0.154247,-0.211427 0.307651,-0.310382 0.458984,-0.333984 z m 4.345703,2.757812 c 0.225859,0.03343 0.481027,0.198816 0.785157,0.564453 1.747014,0.72605 -1.303778,2.574316 -1.595703,2.697266 -1.103201,1.48933 -3.167305,-1.432728 -1.03125,-1.835938 0.74365,-0.437265 1.164221,-1.526065 1.841796,-1.425781 z m 3.269532,2.306641 c 0.03893,0.0024 0.06028,0.01926 0.0625,0.05078 0.307212,0.15856 0.582857,0.359282 0.86914,0.544922 1.634832,1.36857 -2.961621,4.630292 -3.164062,4.070312 -2.704111,-1.615931 1.64845,-4.702094 2.232422,-4.666015 z"
style="fill:#212121;fill-opacity:1;stroke-width:0.00521397" />
transform="translate(4.0461145,24.740973)"
id="path4" />
<ellipse
ry="5.6584005"
rx="6.7384396"
style="opacity:1;fill:#ffaf62;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.41468528;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
cy="99.451027"
cx="39.641644"
id="path4786"
cx="39.641644"
cy="99.451027" />
style="opacity:1;fill:#ffaf62;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.41468528;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
rx="6.7384396"
ry="5.6584005" />
<ellipse
ry="0.96285915"
rx="1.1466434"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.41468525;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="path4788"
cy="99.451027"
cx="39.641644"
cy="99.451027" />
id="path4788"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.41468525;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
rx="1.1466434"
ry="0.96285915" />
<path
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4693"
d="m 65.146946,109.76994 c -0.178541,-0.31245 -7.366014,3.88706 -8.686258,8.35993 -2.169132,7.34882 1.269529,10.75759 2.271789,11.89349 1.002261,1.1359 8.753078,5.41221 8.753078,5.41221 0,0 -2.53906,-8.01809 -2.472242,-12.22758 0.06682,-4.2095 1.336347,-12.2944 1.336347,-12.2944 0.214328,-1.45042 -0.351567,-1.51081 -1.202714,-1.14365 z"
id="path4693" />
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4695"
d="m 62.177083,111.27195 c 0,0 1.370163,-0.28348 1.700892,1.13393 0.330729,1.41741 -1.748139,2.92931 -1.559153,4.86644 0.18899,1.93713 0.944941,3.68527 0.803201,5.00818 -0.141742,1.32292 -1.228423,2.4096 -0.897694,4.15774 0.330729,1.74814 1.370163,3.73252 1.748139,4.96094 0.377979,1.22842 0.472472,2.22061 0.472472,2.22061 l -5.575149,-3.35454 c 0,0 -4.771948,-4.77195 -2.787573,-10.91406 1.984375,-6.14211 6.094865,-8.07924 6.094865,-8.07924 z"
id="path4695" />
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4697"
d="m 70.30357,122.75298 c -1.514956,0.94925 -3.443343,1.9309 -5.197172,1.98437 l 0.07087,-2.10249 c 1.584545,0.45556 3.723038,0.23999 5.126302,0.11812 z"
id="path4697" />
style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4699"
d="m 78.146576,107.06696 c 1.636436,10.62428 -0.93564,16.56205 -12.449591,21.04855 l -0.590587,-3.37816 c 7.160654,-1.083 13.649769,-7.8735 13.040178,-17.67039 z"
id="path4699" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4701"
d="m 81.637986,123.01159 c -12.670965,0.95958 -14.152431,12.42398 -10.076572,21.37751 7.724779,4.9815 14.580842,3.99696 17.238885,3.0736 0,0 -9.888972,-5.07812 -9.354434,-11.55941 0.53454,-6.48129 3.741774,-10.89123 3.741774,-10.89123 0.428014,-1.07406 -0.282581,-1.80534 -1.549653,-2.00047 z"
id="path4701" />
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4703"
d="m 79.448737,123.30205 c 0.992925,-0.19905 1.78017,0.29602 1.107439,1.0337 0,0 -1.819011,0.56696 -2.031622,2.52772 -0.212614,1.96075 0.354351,2.36235 0,3.47265 -0.354354,1.11031 -2.362353,3.85063 -2.315104,5.14993 0.04725,1.29929 2.244233,3.56715 2.433219,4.70108 0.18899,1.13393 -0.377975,2.81119 0.566965,4.11049 0.944941,1.29929 2.14974,3.73251 2.14974,3.73251 -4.734539,-0.4794 -8.074618,-2.55807 -9.79796,-3.64103 -1.841663,-3.17287 -5.803388,-17.66538 7.887323,-21.08705 z"
id="path4703" />
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4705"
d="m 96.150212,124.61115 c -4.01876,5.52151 -8.794257,9.92116 -16.711757,11.36671 -0.08036,0.88765 0.137909,1.87224 0.41237,2.79858 11.745993,-2.60459 14.877081,-9.93974 16.299387,-14.16529 z"
id="path4705" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#eea6a7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4707"
d="m 86.603794,146.14025 2.196505,1.32245 c 6.701159,-2.41232 11.511951,-7.21119 15.190401,-13.27595 1.66173,6.03294 8.2499,13.42904 14.22135,13.46541 l 1.87506,-1.76484 c 0,0 0.0148,0.77265 -2.15854,-0.59751 -2.17336,-1.37017 -0.75595,-2.74033 -2.17336,-2.83483 -1.41741,-0.0945 -1.9286,0.12534 -4.20499,-0.7087 -1.44801,-0.53053 -0.75609,-4.10885 -1.74814,-5.05543 -0.99131,-0.94586 -2.3151,-0.56697 -2.92931,-2.5041 -0.61422,-1.93712 0.33073,-2.64583 -0.56697,-3.96875 -0.89178,-1.83082 -2.3092,-2.24423 -2.3092,-2.24423 -0.1266,-0.0643 -1.51607,0.38315 -2.36825,1.96075 -0.37793,0.96857 -0.8032,4.48847 -1.748143,4.77195 -0.944941,0.28348 -1.700892,0.28348 -2.69308,2.03162 -0.992187,1.74814 -0.944941,3.77976 -2.173364,4.25224 -1.228421,0.47247 -2.83482,0.33073 -3.638021,1.46465 -0.803198,1.13393 -4.771948,3.68527 -4.771948,3.68527 z"
id="path4707" />
style="fill:#eea6a7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4709"
d="m 123.59821,125.20982 c -1.3987,-1.65054 -0.85807,-3.15445 2.88207,-2.78757 12.13782,1.66374 11.02212,16.63634 10.25261,17.85937 -2.85371,8.22299 -13.58843,8.19381 -18.52084,7.37054 0.96034,-1.98472 15.90292,-9.54748 5.38616,-22.44234 z"
id="path4709" />
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4711"
d="m 127.42522,122.75298 c 0,0 -1.93712,-0.23624 -1.65364,1.13392 0.28348,1.37017 2.3151,1.46466 2.26785,2.69308 -0.0472,1.22843 -0.51971,4.06325 0.33073,4.63021 0.85045,0.56697 2.55134,2.97656 1.55915,5.71689 -0.99218,2.74033 -1.98437,1.08668 -2.40959,3.54353 -0.42523,2.45684 -0.75596,3.77976 -1.46466,4.53571 -0.70871,0.75595 -1.22842,2.88207 -1.22842,2.88207 0,0 6.75631,0.37798 8.97693,-3.30729 9.56355,-9.24479 -1.05337,-21.95234 -6.37835,-21.82812 z"
id="path4711" />
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4713"
d="m 115.7277,126.3484 c 2.71026,4.74824 6.41201,7.84401 11.526,8.58604 0.0146,0.75716 -0.10568,1.5973 -0.4009,2.22168 -5.69579,-1.24493 -9.34848,-5.1092 -11.1251,-10.80772 z"
id="path4713" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4715"
d="m 143.05601,138.17508 c 8.45644,-12.2696 1.58622,-19.08645 -2.40542,-26.9274 -1.17246,-1.73452 0.56242,-1.73337 1.26953,-1.5368 4.83863,1.10659 18.77671,5.73353 11.35895,22.98518 -3.1826,2.72671 -6.38288,4.87014 -10.22306,5.47902 z"
id="path4715" />
style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4717"
d="m 145.33184,110.65773 c -1.44409,-0.17245 -1.87623,0.88971 -1.74814,1.55916 0.12809,0.66945 1.37019,0.51971 1.41742,2.07887 0.0472,1.55916 1.74813,4.91369 2.03162,5.57515 0.28348,0.66145 1.88988,1.65364 2.07887,4.06324 0.18898,2.4096 -0.36512,6.46405 -0.99219,7.46503 -0.73995,1.18117 -0.10965,4.58296 0.33073,4.58296 1.94348,-0.70588 3.22695,-2.18177 4.82892,-3.28608 3.76978,-8.80185 3.25139,-17.5402 -7.94723,-22.03833 z"
id="path4717" />
style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4719"
d="m 145.66189,120.68563 c 0.50608,1.04701 0.81356,2.13795 1.05237,3.27405 0,0 -1.26953,0.21716 -3.54132,0.15034 -2.27179,-0.0668 -5.028,-1.1526 -5.028,-1.1526 0,0 2.12145,0.33409 4.07585,-0.3842 1.95441,-0.71829 3.4411,-1.88759 3.4411,-1.88759 z"
id="path4719" />
style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4721"
d="m 131.34627,111.26438 c -0.46387,6.60804 -0.23431,15.14869 15.71879,15.3513 l -0.3508,-2.656 c -5.36818,0.47928 -14.93993,-0.59196 -15.36799,-12.6953 z"
id="path4721" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<g
id="g4740"
transform="rotate(19.617168,87.590538,52.720911)">
transform="rotate(19.617168,87.590538,52.720911)"
id="g4740">
<path
id="path4725"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.39299998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 108.72438,96.054369 c -1.67459,2.099573 -6.51237,1.558255 -9.762082,-0.889359 -3.249707,-2.447614 -5.286913,-7.54075 -1.925906,-10.583607 2.406544,-2.178749 7.101418,-0.79705 9.431368,1.36184 2.53306,2.34709 4.6065,7.164892 2.25662,10.111126 z"
id="path4725" />
<path
id="path4730"
d="m 108.84598,96.203312 c -1.50822,1.890992 -5.8654,1.403451 -8.79227,-0.801006 -2.926867,-2.204458 -4.761688,-6.79162 -1.734578,-9.532187 2.167468,-1.962302 6.395928,-0.717867 8.494418,1.226549 2.28141,2.11392 4.14887,6.453101 2.03243,9.106644 z"
style="opacity:1;fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35395768;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.46482563;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="path4732"
cx="101.24133"
cy="91.80127"
r="3.4367433" />
<circle
r="1.0470117"
cy="91.470535"
cx="100.24915"
id="circle4734"
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="circle4657"
cx="102.3987"
cy="93.070198"
r="0.6970861" />
</g>
<g
id="g4750"
transform="matrix(-0.84292907,0.5380247,0.5380247,0.84292907,153.98869,-37.720137)">
<path
id="path4742"
d="m 108.72438,96.054369 c -1.67459,2.099573 -6.51237,1.558255 -9.762082,-0.889359 -3.249707,-2.447614 -5.286913,-7.54075 -1.925906,-10.583607 2.406544,-2.178749 7.101418,-0.79705 9.431368,1.36184 2.53306,2.34709 4.6065,7.164892 2.25662,10.111126 z"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.39299998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<path
style="opacity:1;fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35395768;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 108.84598,96.203312 c -1.50822,1.890992 -5.8654,1.403451 -8.79227,-0.801006 -2.926867,-2.204458 -4.761688,-6.79162 -1.734578,-9.532187 2.167468,-1.962302 6.395928,-0.717867 8.494418,1.226549 2.28141,2.11392 4.14887,6.453101 2.03243,9.106644 z"
id="path4730" />
id="path4744" />
<circle
r="3.4367433"
cy="91.80127"
cx="101.24133"
id="path4732"
cy="90.845963"
cx="105.85611"
id="circle4746"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.46482563;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="circle4734"
cx="100.24915"
cy="91.470535"
id="circle4748"
cx="106.96864"
cy="89.537895"
r="1.0470117" />
<circle
r="0.6970861"
cy="93.070198"
cx="102.3987"
id="circle4657"
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
</g>
<g
transform="matrix(-0.84292907,0.5380247,0.5380247,0.84292907,153.98869,-37.720137)"
id="g4750">
<path
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.39299998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 108.72438,96.054369 c -1.67459,2.099573 -6.51237,1.558255 -9.762082,-0.889359 -3.249707,-2.447614 -5.286913,-7.54075 -1.925906,-10.583607 2.406544,-2.178749 7.101418,-0.79705 9.431368,1.36184 2.53306,2.34709 4.6065,7.164892 2.25662,10.111126 z"
id="path4742" />
<path
id="path4744"
d="m 108.84598,96.203312 c -1.50822,1.890992 -5.8654,1.403451 -8.79227,-0.801006 -2.926867,-2.204458 -4.761688,-6.79162 -1.734578,-9.532187 2.167468,-1.962302 6.395928,-0.717867 8.494418,1.226549 2.28141,2.11392 4.14887,6.453101 2.03243,9.106644 z"
style="opacity:1;fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35395768;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.46482563;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="circle4746"
cx="105.85611"
cy="90.845963"
r="3.4367433" />
<circle
r="1.0470117"
cy="89.537895"
cx="106.96864"
id="circle4748"
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="circle4655"
cx="105.70718"
r="0.59710735"
cy="92.300102"
r="0.59710735" />
cx="105.70718"
id="circle4655"
style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
</g>
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4752"
d="m 79.440842,83.663452 c 2.401026,-4.843277 7.683432,-2.978854 9.070822,-3.016481 -2.556226,0.763727 -4.708947,2.955988 -6.701589,4.398539 -0.740592,0.536143 -2.442986,-0.394305 -2.369233,-1.382058 z"
id="path4752" />
<path
id="path4754"
d="m 125.44005,85.24075 c -2.30199,-5.105822 -7.76722,-3.941999 -8.60332,-4.166742 2.43567,1.088609 4.28608,3.541392 6.07494,5.23005 0.66486,0.627613 2.47348,-0.07434 2.52838,-1.063308 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 125.44005,85.24075 c -2.30199,-5.105822 -7.76722,-3.941999 -8.60332,-4.166742 2.43567,1.088609 4.28608,3.541392 6.07494,5.23005 0.66486,0.627613 2.47348,-0.07434 2.52838,-1.063308 z"
id="path4754" />
<path
id="path4756"
d="m 70.96503,82.262275 c 0,0 6.331101,3.071056 7.606771,-0.519718 1.275667,-3.590772 -1.27567,-7.9375 -1.27567,-7.9375 2.892685,-1.049875 3.091067,-3.4469 2.220608,-6.378347 2.152776,-0.348696 4.444174,-0.539426 1.322917,-6.898066 -4.769498,4.415313 -9.420477,9.057506 -9.874626,21.733631 z"
id="path4756" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4758"
d="m 88.132125,56.156746 c 0,0 3.254408,16.386084 14.566195,13.79779 7.88444,-1.80407 6.28083,-7.617182 6.28083,-7.617182 3.56518,5.081814 12.40153,4.310975 11.29214,-2.539061 2.41655,0.835207 3.97125,0.06982 5.14493,-1.403165 -13.2372,-7.82638 -25.466402,-6.83272 -37.284095,-2.238382 z"
id="path4758" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4760"
d="m 131.36297,62.871892 c -1.12784,1.399585 -4.16412,7.545045 2.00452,7.650591 -0.56702,5.505473 1.00425,9.251589 7.45014,8.986938 -1.43088,-7.033989 -4.7288,-12.453251 -9.45466,-16.637529 z"
id="path4760" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4762"
d="m 140.95127,86.391611 c -1.79338,-0.414357 -3.99759,-0.910879 -6.68174,-1.503391 -2.67445,-0.05943 -3.63783,1.364072 -2.3052,4.777443 2.82115,3.939599 4.73867,3.511676 7.08264,5.144937 0.70035,-2.780098 1.61025,-5.476371 1.9043,-8.418989 z"
id="path4762" />
style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4764"
d="m 93.342168,119.42814 c 0.529236,-0.73806 0.604365,-0.3573 1.136405,-1.09204 0.364677,-0.50361 0.199924,-1.06153 0.644573,-1.46533 0.309321,-0.28091 0.977351,-0.124 1.362201,-0.31657 0.345479,-0.17287 0.405453,-0.70096 0.732931,-0.84737 0.789296,-0.35288 1.800707,0.26944 2.651021,0.0936 1.078571,-0.22307 2.076941,-1.09961 3.190181,-1.10946 0.97926,-0.009 1.7824,0.64931 2.75826,0.86661 0.76717,0.17085 1.97838,-0.33367 2.73467,-0.0767 0.43365,0.14737 1.07369,-0.0937 1.4783,0.11451 0.30157,0.15517 0.33276,0.75525 0.61169,0.95375 1.72336,1.22644 1.31075,0.33606 2.60238,2.06475 0,0 0.28975,-0.0348 -2.07105,-2.47226 -0.32347,-0.33398 -0.51448,-1.08044 -0.86627,-1.38846 -0.45582,-0.39911 -1.04178,-0.39938 -1.53149,-0.74298 -0.6807,-0.4776 -1.9258,0.26924 -2.60859,-0.0841 -1.25954,-0.65171 -2.00993,-2.31042 -3.18147,-2.02677 -1.01838,0.24657 -2.19748,1.18524 -3.446552,1.56394 -0.794617,0.24091 -1.705404,-0.22114 -2.337848,0.30385 -0.769592,0.63883 -0.5723,0.83835 -1.171115,1.59816 -0.306698,0.38915 -0.985411,0.37691 -1.287999,0.78707 -0.398797,0.54057 -0.388125,1.15748 -0.697902,1.70652 -0.81359,1.44197 -0.702326,1.56918 -0.702326,1.56918 z"
id="path4764" />
style="fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:none;stroke:#301818;stroke-width:4.46500015;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4847"
d="m 66.416477,77.838987 c 0,0 2.248303,-29.702875 39.355443,-29.132379 37.10713,0.570497 40.69178,29.399648 40.69178,29.399648"
id="path4847" />
<path
id="path4651"
d="m 82.283169,89.859621 c 1.901054,-0.826347 5.017206,-1.87303 7.190809,-1.209662 2.299291,0.701728 3.661648,3.157157 5.855653,3.784919 0,0 -0.675634,-7.540694 -7.907825,-7.911193 -3.749793,-0.192098 -5.138637,5.335936 -5.138637,5.335936 z"
style="opacity:1;vector-effect:none;fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="fill:none;stroke:#301818;stroke-width:4.46500015;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;vector-effect:none;fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 82.283169,89.859621 c 1.901054,-0.826347 5.017206,-1.87303 7.190809,-1.209662 2.299291,0.701728 3.661648,3.157157 5.855653,3.784919 0,0 -0.675634,-7.540694 -7.907825,-7.911193 -3.749793,-0.192098 -5.138637,5.335936 -5.138637,5.335936 z"
id="path4651" />
<path
id="path4653"
d="m 122.49651,94.411543 c -3.78678,-6.81696 -8.0759,-7.480996 -11.84594,-3.933836 0,0 3.20445,-7.27537 10.03419,-4.867774 4.12152,1.452904 1.81175,8.80161 1.81175,8.80161 z"
id="path4653" />
style="opacity:1;vector-effect:none;fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
style="stroke-width:42.36951447"
transform="matrix(0.02360188,0,0,0.02360188,113.18825,105.15104)"
id="g6"
transform="matrix(0.02360188,0,0,0.02360188,113.18825,105.15104)">
style="stroke-width:42.36951447">
<path
id="path2"
style="fill:#29abe2;stroke-width:42.36951447"
d="m 117.16,11.645 c 0,0 -31.496,38.028 -68.31,85.296 -47.195,60.599 -70.131,146.52 31.843,165.81 113.27,21.429 132.5,-118.17 65.274,-157.01 -49.223,-28.424 -28.81,-94.097 -28.81,-94.095 z"
style="fill:#29abe2;stroke-width:42.36951447" />
id="path2" />
<path
id="path4-9"
style="fill:#ffffff;stroke-width:42.36951447"
d="m 51.844,134.31 c 0,0 -56.287,70.6 25.797,108.12 0,0.01 -44.304,-31.59 -25.797,-108.12 z"
style="fill:#ffffff;stroke-width:42.36951447" />
id="path4-9" />
</g>
<g
transform="matrix(-0.0125514,0,0,0.0125514,96.697579,101.08859)"
style="stroke-width:79.67237854"
id="g4688"
style="stroke-width:79.67237854">
transform="matrix(-0.0125514,0,0,0.0125514,96.697579,101.08859)">
<path
style="fill:#29abe2;stroke-width:79.67237854"
id="path4684"
d="m 117.16,11.645 c 0,0 -31.496,38.028 -68.31,85.296 -47.195,60.599 -70.131,146.52 31.843,165.81 113.27,21.429 132.5,-118.17 65.274,-157.01 -49.223,-28.424 -28.81,-94.097 -28.81,-94.095 z"
id="path4684" />
style="fill:#29abe2;stroke-width:79.67237854" />
<path
style="fill:#ffffff;stroke-width:79.67237854"
id="path4686"
d="m 51.844,134.31 c 0,0 -56.287,70.6 25.797,108.12 0,0.01 -44.304,-31.59 -25.797,-108.12 z"
id="path4686" />
style="fill:#ffffff;stroke-width:79.67237854" />
</g>
<path
id="path4692"
style="opacity:1;vector-effect:none;fill:#29abe2;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 85.947033,99.042108 0.671609,-0.0707 c 0,0 0.49487,-0.636261 0.777652,-0.353479 0.282783,0.282783 0.424174,0.777652 1.343218,0.565565 0.919043,-0.212086 0.353478,0.212087 1.343217,0.49487 0.989739,0.282783 0.777653,0 1.484609,-0.282783 0.706957,-0.282782 0.212087,-0.777652 1.060435,-0.565565 0.848348,0.212087 0.282782,0.212087 1.272521,-0.212087 0.989739,-0.424174 1.334642,-0.665544 1.334642,-0.665544 0,0 -0.5002,2.353665 -3.049011,3.334305 -4.895674,0.33581 -6.238892,-2.244587 -6.238892,-2.244587 z"
id="path4692" />
<path
id="path4694"
d="m 90.215634,100.78237 c 0,0 0.47109,0.17648 1.084764,0.10067 0.31585,-0.039 0.649984,-0.31716 1.017863,-0.42775 0.326516,-0.0982 0.666764,-0.12133 0.976323,-0.31443 0.19834,-0.12373 0.30993,-0.330545 0.48732,-0.500317 0.276456,-0.264584 0.578906,-0.54704 0.757961,-0.950004 2.09e-4,-7.1e-5 -0.344817,0.269525 -1.022134,0.718841 -0.188018,0.124727 -0.295615,0.385643 -0.557188,0.510044 -0.286058,0.136046 -0.727267,0.150626 -1.096851,0.284556 -0.199876,0.0724 -0.248589,0.33662 -0.45906,0.42367 -0.308425,0.12756 -0.791836,0.0775 -1.188998,0.15472 z"
style="fill:#29ebe2;fill-opacity:1;stroke-width:1.00000024" />
<path
id="path4698"
d="m 119.99047,99.106433 -0.65394,0.265104 c 0,0 -0.42418,-0.459522 -0.70696,-0.17674 -0.28278,0.282783 -0.26511,0.848353 -1.18415,0.636261 -0.91904,-0.212086 -0.35348,0.212092 -1.34322,0.494872 -0.98974,0.28278 -0.77765,0 -1.48461,-0.28278 -0.70695,-0.282787 -0.21208,-0.777657 -1.06043,-0.56557 -0.84835,0.212087 -0.28278,0.212087 -1.27252,-0.212087 -0.98974,-0.424174 -1.51138,-0.479968 -1.51138,-0.479968 0,0 0.9067,2.486225 3.22575,3.148735 2.34542,0.68368 4.48756,-0.65364 5.92076,-2.54505 z"
style="opacity:1;vector-effect:none;fill:#29abe2;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="fill:#29ebe2;fill-opacity:1;stroke-width:1.00000024"
d="m 90.215634,100.78237 c 0,0 0.47109,0.17648 1.084764,0.10067 0.31585,-0.039 0.649984,-0.31716 1.017863,-0.42775 0.326516,-0.0982 0.666764,-0.12133 0.976323,-0.31443 0.19834,-0.12373 0.30993,-0.330545 0.48732,-0.500317 0.276456,-0.264584 0.578906,-0.54704 0.757961,-0.950004 2.09e-4,-7.1e-5 -0.344817,0.269525 -1.022134,0.718841 -0.188018,0.124727 -0.295615,0.385643 -0.557188,0.510044 -0.286058,0.136046 -0.727267,0.150626 -1.096851,0.284556 -0.199876,0.0724 -0.248589,0.33662 -0.45906,0.42367 -0.308425,0.12756 -0.791836,0.0775 -1.188998,0.15472 z"
id="path4694" />
<path
style="opacity:1;vector-effect:none;fill:#29abe2;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 119.99047,99.106433 -0.65394,0.265104 c 0,0 -0.42418,-0.459522 -0.70696,-0.17674 -0.28278,0.282783 -0.26511,0.848353 -1.18415,0.636261 -0.91904,-0.212086 -0.35348,0.212092 -1.34322,0.494872 -0.98974,0.28278 -0.77765,0 -1.48461,-0.28278 -0.70695,-0.282787 -0.21208,-0.777657 -1.06043,-0.56557 -0.84835,0.212087 -0.28278,0.212087 -1.27252,-0.212087 -0.98974,-0.424174 -1.51138,-0.479968 -1.51138,-0.479968 0,0 0.9067,2.486225 3.22575,3.148735 2.34542,0.68368 4.48756,-0.65364 5.92076,-2.54505 z"
id="path4698" />
<path
id="path4700"
d="m 115.9693,101.42994 c 0,0 -0.47109,0.17648 -1.08477,0.10067 -0.31585,-0.039 -0.64998,-0.31716 -1.01786,-0.42775 -0.32651,-0.0982 -0.66676,-0.12133 -0.97632,-0.31443 -0.19834,-0.12373 -0.30993,-0.33055 -0.48732,-0.50032 -0.27646,-0.26458 -0.57891,-0.547043 -0.75796,-0.950007 -2.1e-4,-7.1e-5 0.34481,0.269525 1.02213,0.718847 0.18802,0.12472 0.29562,0.38564 0.55719,0.51004 0.28606,0.13605 0.72727,0.15063 1.09685,0.28456 0.19988,0.0724 0.24859,0.33662 0.45906,0.42367 0.30843,0.12756 0.79184,0.0775 1.189,0.15472 z"
style="fill:#29ebe2;fill-opacity:1;stroke-width:1.00000024" />
id="path4700" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -105,7 +105,7 @@ function switchTheme(e) {
document.addEventListener('DOMContentLoaded', () => {
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
if ($navbarBurgers.length > 0) {
$navbarBurgers.forEach( el => {
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
const target = el.dataset.target;
const $target = document.getElementById(target);

View File

@ -46,4 +46,4 @@
@import './ajouter-un-album';
@import './collection';
@import './ma-collection-details';
@import './composants';
@import './composants';

View File

@ -23,6 +23,12 @@
background-color: var(--default-color);
}
&:nth-child(4n),
&:nth-child(4n-1)
{
background-color: var(--default-color);
}
&:first-child,
&:nth-child(2) {
border-top: 2px solid var(--border-color);

View File

@ -7,6 +7,8 @@ import flash from "connect-flash";
import session from "express-session";
import MongoStore from "connect-mongo";
import passportConfig from "./libs/passport";
import config, { env, mongoDbUri, secret } from "./config";
import { isXhr } from "./helpers";
@ -15,15 +17,13 @@ import indexRouter from "./routes";
import maCollectionRouter from "./routes/ma-collection";
import collectionRouter from "./routes/collection";
import importJobsRouter from "./routes/jobs";
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
import importSearchRouterApiV1 from "./routes/api/v1/search";
import importMeRouterApiV1 from "./routes/api/v1/me";
// Mongoose schema init
require("./models/users");
require("./models/albums");
require("./libs/passport")(passport);
passportConfig(passport);
mongoose
.connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true })
@ -46,10 +46,10 @@ const sess = {
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(flash());
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ extended: false, limit: "50mb" }));
app.use(session(sess));
@ -85,6 +85,7 @@ app.use(
app.use("/", indexRouter);
app.use("/ma-collection", maCollectionRouter);
app.use("/collection", collectionRouter);
app.use("/jobs", importJobsRouter);
app.use("/api/v1/albums", importAlbumRouterApiV1);
app.use("/api/v1/search", importSearchRouterApiV1);
app.use("/api/v1/me", importMeRouterApiV1);

View File

@ -8,4 +8,13 @@ module.exports = {
matomoUrl: process.env.MATOMO_URL || "",
matomoId: process.env.MATOMO_ID || "",
siteName: process.env.SITE_NAME || "MusicTopus",
awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
s3BaseFolder: process.env.S3_BASEFOLDER || "dev",
s3Bucket: process.env.S3_BUCKET || "musictopus",
s3Endpoint: process.env.S3_ENDPOINT || "s3.fr-par.scw.cloud",
s3Signature: process.env.S3_SIGNATURE || "s3v4",
jobsHeaderKey: process.env.JOBS_HEADER_KEY || "musictopus",
jobsHeaderValue:
process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew",
};

72
src/libs/aws.js Normal file
View File

@ -0,0 +1,72 @@
import AWS from "aws-sdk";
import fs from "fs";
import path from "path";
import axios from "axios";
import { v4 as uuid } from "uuid";
import {
awsAccessKeyId,
awsSecretAccessKey,
s3BaseFolder,
s3Endpoint,
s3Bucket,
s3Signature,
} from "../config";
AWS.config.update({
accessKeyId: awsAccessKeyId,
secretAccessKey: awsSecretAccessKey,
});
/**
* Fonction permettant de stocker un fichier local sur S3
* @param {String} filename
* @param {String} file
* @param {Boolean} deleteFile
*
* @return {String}
*/
export const uploadFromFile = async (filename, file, deleteFile = false) => {
const data = await fs.readFileSync(file);
const base64data = Buffer.from(data, "binary");
const dest = path.join(s3BaseFolder, filename);
const s3 = new AWS.S3({
endpoint: s3Endpoint,
signatureVersion: s3Signature,
});
await s3
.putObject({
Bucket: s3Bucket,
Key: dest,
Body: base64data,
ACL: "public-read",
})
.promise();
if (deleteFile) {
fs.unlinkSync(file);
}
return `https://${s3Bucket}.${s3Endpoint}/${dest}`;
};
/**
* Fonction permettant de stocker un fichier provenant d'une URL sur S3
* @param {String} url
*
* @return {String}
*/
export const uploadFromUrl = async (url) => {
const filename = `${uuid()}.jpg`;
const file = `/tmp/${filename}`;
const { data } = await axios.get(url, { responseType: "arraybuffer" });
fs.writeFileSync(file, data);
return uploadFromFile(filename, file, true);
// return s3Object;
};

View File

@ -1,11 +1,13 @@
/* eslint-disable func-names */
const mongoose = require("mongoose");
const LocalStrategy = require("passport-local").Strategy;
const { BasicStrategy } = require("passport-http");
import { Strategy as LocalStrategy } from "passport-local";
import { BasicStrategy } from "passport-http";
import { Strategy as CustomStrategy } from "passport-custom";
const Users = mongoose.model("Users");
import Users from "../models/users";
module.exports = function (passport) {
import { jobsHeaderKey, jobsHeaderValue } from "../config";
export default (passport) => {
passport.serializeUser((user, done) => {
done(null, user);
});
@ -55,4 +57,17 @@ module.exports = function (passport) {
.catch(done);
})
);
passport.use(
"jobs",
new CustomStrategy((req, next) => {
const apiKey = req.headers[jobsHeaderKey];
if (apiKey === jobsHeaderValue) {
return next(null, {
username: "jobs",
});
}
return next(null, false, "Oops! Identifiants incorrects");
})
);
};

View File

@ -1,468 +1,18 @@
import moment from "moment";
import momenttz from "moment-timezone";
import xl from "excel4node";
import Pages from "./Pages";
import Export from "./Export";
import AlbumsModel from "../models/albums";
import JobsModel from "../models/jobs";
import UsersModel from "../models/users";
import ErrorEvent from "../libs/error";
// import { uploadFromUrl } from "../libs/aws";
/**
* Classe permettant la gestion des albums d'un utilisateur
*/
class Albums extends Pages {
/**
* Méthode permettant de remplacer certains cartactères par leur équivalents html
* @param {String} str
*
* @return {String}
*/
static replaceSpecialChars(str) {
if (!str) {
return "";
}
let final = str.toString();
const find = ["&", "<", ">"];
const replace = ["&amp;", "&lt;", "&gt;"];
for (let i = 0; i < find.length; i += 1) {
final = final.replace(new RegExp(find[i], "g"), replace[i]);
}
return final;
}
/**
* Méthode permettant de convertir les rows en csv
* @param {Array} rows
*
* @return {string}
*/
static async convertToCsv(rows) {
let data =
"Artiste;Titre;Genre;Styles;Pays;Année;Date de sortie;Format\n\r";
for (let i = 0; i < rows.length; i += 1) {
const {
artists_sort,
title,
genres,
styles,
country,
year,
released,
formats,
} = rows[i];
let format = "";
for (let j = 0; j < formats.length; j += 1) {
format += `${format !== "" ? ", " : ""}${formats[j].name}`;
}
data += `${artists_sort};${title};${genres.join()};${styles.join()};${country};${year};${released};${format}\n\r`;
}
return data;
}
/**
* Méthode permettant de convertir les rows en fichier xls
* @param {Array} rows
*
* @return {Object}
*/
static async convertToXls(rows) {
const wb = new xl.Workbook();
const ws = wb.addWorksheet("MusicTopus");
const headerStyle = wb.createStyle({
font: {
color: "#FFFFFF",
size: 11,
},
fill: {
type: "pattern",
patternType: "solid",
bgColor: "#595959",
fgColor: "#595959",
},
});
const style = wb.createStyle({
font: {
color: "#000000",
size: 11,
},
numberFormat: "0000",
});
const header = [
"Artiste",
"Titre",
"Genre",
"Styles",
"Pays",
"Année",
"Date de sortie",
"Format",
];
for (let i = 0; i < header.length; i += 1) {
ws.cell(1, i + 1)
.string(header[i])
.style(headerStyle);
}
for (let i = 0; i < rows.length; i += 1) {
const currentRow = i + 2;
const {
artists_sort,
title,
genres,
styles,
country,
year,
released,
formats,
} = rows[i];
let format = "";
for (let j = 0; j < formats.length; j += 1) {
format += `${format !== "" ? ", " : ""}${formats[j].name}`;
}
ws.cell(currentRow, 1).string(artists_sort).style(style);
ws.cell(currentRow, 2).string(title).style(style);
ws.cell(currentRow, 3).string(genres.join()).style(style);
ws.cell(currentRow, 4).string(styles.join()).style(style);
if (country) {
ws.cell(currentRow, 5).string(country).style(style);
}
if (year) {
ws.cell(currentRow, 6).number(year).style(style);
}
if (released) {
ws.cell(currentRow, 7)
.date(momenttz.tz(released, "Europe/Paris").hour(12))
.style({ numberFormat: "dd/mm/yyyy" });
}
ws.cell(currentRow, 8).string(format).style(style);
}
return wb;
}
/**
* Méthode permettant de convertir les rows en csv pour importer dans MusicTopus
* @param {Array} rows
*
* @return {string}
*/
static async convertToXml(rows) {
let data = '<?xml version="1.0" encoding="UTF-8"?>\n\r<albums>';
for (let i = 0; i < rows.length; i += 1) {
const {
discogsId,
year,
released,
uri,
artists,
artists_sort,
labels,
series,
companies,
formats,
title,
country,
notes,
identifiers,
videos,
genres,
styles,
tracklist,
extraartists,
images,
thumb,
} = rows[i];
let artistsList = "";
let labelList = "";
let serieList = "";
let companiesList = "";
let formatsList = "";
let identifiersList = "";
let videosList = "";
let genresList = "";
let stylesList = "";
let tracklistList = "";
let extraartistsList = "";
let imagesList = "";
for (let j = 0; j < artists.length; j += 1) {
artistsList += `<artist>
<name>${Albums.replaceSpecialChars(artists[j].name)}</name>
<anv>${Albums.replaceSpecialChars(artists[j].anv)}</anv>
<join>${Albums.replaceSpecialChars(artists[j].join)}</join>
<role>${Albums.replaceSpecialChars(artists[j].role)}</role>
<tracks>${Albums.replaceSpecialChars(
artists[j].tracks
)}</tracks>
<id>${Albums.replaceSpecialChars(artists[j].id)}</id>
<resource_url>${Albums.replaceSpecialChars(
artists[j].resource_url
)}</resource_url>
<thumbnail_url>${Albums.replaceSpecialChars(
artists[j].thumbnail_url
)}</thumbnail_url>
</artist>`;
}
for (let j = 0; j < labels.length; j += 1) {
labelList += `<label>
<name>${Albums.replaceSpecialChars(labels[j].name)}</name>
<catno>${Albums.replaceSpecialChars(labels[j].catno)}</catno>
<entity_type>${Albums.replaceSpecialChars(
labels[j].entity_type
)}</entity_type>
<entity_type_name>${Albums.replaceSpecialChars(
labels[j].entity_type
)}</entity_type_name>
<id>${Albums.replaceSpecialChars(labels[j].id)}</id>
<resource_url>${Albums.replaceSpecialChars(
labels[j].resource_url
)}</resource_url>
<thumbnail_url>${Albums.replaceSpecialChars(
labels[j].thumbnail_url
)}</thumbnail_url>
</label>
`;
}
for (let j = 0; j < series.length; j += 1) {
serieList += `<serie>
<name>${Albums.replaceSpecialChars(series[j].name)}</name>
<catno>${Albums.replaceSpecialChars(series[j].catno)}</catno>
<entity_type>${Albums.replaceSpecialChars(
series[j].entity_type
)}</entity_type>
<entity_type_name>${Albums.replaceSpecialChars(
series[j].entity_type_name
)}</entity_type_name>
<id>${Albums.replaceSpecialChars(series[j].id)}</id>
<resource_url>${Albums.replaceSpecialChars(
series[j].resource_url
)}</resource_url>
<thumbnail_url>${Albums.replaceSpecialChars(
series[j].thumbnail_url
)}</thumbnail_url>
</serie>
`;
}
for (let j = 0; j < companies.length; j += 1) {
companiesList += `<company>
<name>${Albums.replaceSpecialChars(companies[j].name)}</name>
<catno>${Albums.replaceSpecialChars(companies[j].catno)}</catno>
<entity_type>${Albums.replaceSpecialChars(
companies[j].entity_type
)}</entity_type>
<entity_type_name>${Albums.replaceSpecialChars(
companies[j].entity_type_name
)}</entity_type_name>
<id>${Albums.replaceSpecialChars(companies[j].id)}</id>
<resource_url>${Albums.replaceSpecialChars(
companies[j].resource_url
)}</resource_url>
<thumbnail_url>${Albums.replaceSpecialChars(
companies[j].thumbnail_url
)}</thumbnail_url>
</company>
`;
}
for (let j = 0; j < formats.length; j += 1) {
let descriptions = "";
if (formats[j].descriptions) {
for (
let k = 0;
k < formats[j].descriptions.length;
k += 1
) {
descriptions += `<description>${formats[j].descriptions[k]}</description>
`;
}
}
formatsList += `<format>
<name>${Albums.replaceSpecialChars(formats[j].name)}</name>
<qte>${Albums.replaceSpecialChars(formats[j].qty)}</qte>
<text>${Albums.replaceSpecialChars(formats[j].text)}</text>
<descriptions>
${descriptions}
</descriptions>
</format>
`;
}
for (let j = 0; j < identifiers.length; j += 1) {
identifiersList += `<identifier>
<type>${Albums.replaceSpecialChars(identifiers[j].type)}</type>
<value>${Albums.replaceSpecialChars(
identifiers[j].value
)}</value>
<description>${Albums.replaceSpecialChars(
identifiers[j].description
)}</description>
</identifier>
`;
}
for (let j = 0; j < videos.length; j += 1) {
videosList += `<video embed="${videos[j].embed}">
<uri>${Albums.replaceSpecialChars(videos[j].uri)}</uri>
<title>${Albums.replaceSpecialChars(videos[j].title)}</title>
<description>${Albums.replaceSpecialChars(
videos[j].description
)}</description>
<duration>${Albums.replaceSpecialChars(
videos[j].duration
)}</duration>
</video>
`;
}
for (let j = 0; j < genres.length; j += 1) {
genresList += `<genre>${Albums.replaceSpecialChars(
genres[j]
)}</genre>
`;
}
for (let j = 0; j < styles.length; j += 1) {
stylesList += `<style>${Albums.replaceSpecialChars(
styles[j]
)}</style>
`;
}
for (let j = 0; j < tracklist.length; j += 1) {
tracklistList += `<tracklist position="${
tracklist[j].position
}" type="${tracklist[j].type_}" duration="${
tracklist[j].duration
}">
${Albums.replaceSpecialChars(tracklist[j].title)}
</tracklist>
`;
}
for (let j = 0; j < extraartists.length; j += 1) {
extraartistsList += `<extraartist>
<name>${Albums.replaceSpecialChars(extraartists[j].name)}</name>
<anv>${Albums.replaceSpecialChars(extraartists[j].anv)}</anv>
<join>${Albums.replaceSpecialChars(extraartists[j].join)}</join>
<role>${Albums.replaceSpecialChars(extraartists[j].role)}</role>
<tracks>${Albums.replaceSpecialChars(
extraartists[j].tracks
)}</tracks>
<id>${Albums.replaceSpecialChars(extraartists[j].id)}</id>
<resource_url>${Albums.replaceSpecialChars(
extraartists[j].resource_url
)}</resource_url>
<thumbnail_url>${Albums.replaceSpecialChars(
extraartists[j].thumbnail_url
)}</thumbnail_url>
</extraartist>
`;
}
for (let j = 0; j < images.length; j += 1) {
imagesList += `<image type="${images[j].type}" width="${
images[j].width
}" height="${images[j].height}">
<uri>${Albums.replaceSpecialChars(images[j].uri)}</uri>
<resource_url>${Albums.replaceSpecialChars(
images[j].resource_url
)}</resource_url>
<uri150>${Albums.replaceSpecialChars(
images[j].resource_url
)}</uri150>
</image>
`;
}
data += `
<album>
<discogId>${discogsId}</discogId>
<title>${Albums.replaceSpecialChars(title)}</title>
<artists_sort>${Albums.replaceSpecialChars(artists_sort)}</artists_sort>
<artists>
${artistsList}
</artists>
<year>${year}</year>
<country>${Albums.replaceSpecialChars(country)}</country>
<released>${released}</released>
<uri>${uri}</uri>
<thumb>${thumb}</thumb>
<labels>
${labelList}
</labels>
<series>
${serieList}
</series>
<companies>
${companiesList}
</companies>
<formats>
${formatsList}
</formats>
<notes>${Albums.replaceSpecialChars(notes)}</notes>
<identifiers>
${identifiersList}
</identifiers>
<videos>
${videosList}
</videos>
<genres>
${genresList}
</genres>
<styles>
${stylesList}
</styles>
<tracklist>
${tracklistList}
</tracklist>
<extraartists>
${extraartistsList}
</extraartists>
<images>
${imagesList}
</images>
</album>`;
}
return `${data}</albums>`;
}
/**
* Méthode permettant de convertir les rows en csv pour importer dans MusicTopus
* @param {Array} rows
*
* @return {string}
*/
static async convertToMusicTopus(rows) {
let data = "itemId;createdAt;updatedAt\n\r";
for (let i = 0; i < rows.length; i += 1) {
const { discogsId, createdAt, updatedAt } = rows[i];
data += `${discogsId};${createdAt};${updatedAt}\n\r`;
}
data += "v1.0";
return data;
}
/**
* Méthode permettant d'ajouter un album dans une collection
* @param {Object} req
@ -482,7 +32,18 @@ class Albums extends Pages {
const album = new AlbumsModel(data);
return album.save();
await album.save();
const jobData = {
model: "Albums",
id: album._id,
};
const job = new JobsModel(jobData);
job.save();
return album;
}
/**
@ -593,13 +154,13 @@ class Albums extends Pages {
switch (exportFormat) {
case "csv":
return Albums.convertToCsv(rows);
return Export.convertToCsv(rows);
case "xls":
return Albums.convertToXls(rows);
return Export.convertToXls(rows);
case "xml":
return Albums.convertToXml(rows);
return Export.convertToXml(rows);
case "musictopus":
return Albums.convertToMusicTopus(rows);
return Export.convertToMusicTopus(rows);
case "json":
default:
return {

453
src/middleware/Export.js Normal file
View File

@ -0,0 +1,453 @@
import momenttz from "moment-timezone";
import xl from "excel4node";
class Export {
/**
* Méthode permettant de remplacer certains cartactères par leur équivalents html
* @param {String} str
*
* @return {String}
*/
static replaceSpecialChars(str) {
if (!str) {
return "";
}
let final = str.toString();
const find = ["&", "<", ">"];
const replace = ["&amp;", "&lt;", "&gt;"];
for (let i = 0; i < find.length; i += 1) {
final = final.replace(new RegExp(find[i], "g"), replace[i]);
}
return final;
}
/**
* Méthode permettant de convertir les rows en csv
* @param {Array} rows
*
* @return {string}
*/
static async convertToCsv(rows) {
let data =
"Artiste;Titre;Genre;Styles;Pays;Année;Date de sortie;Format\n\r";
for (let i = 0; i < rows.length; i += 1) {
const {
artists_sort,
title,
genres,
styles,
country,
year,
released,
formats,
} = rows[i];
let format = "";
for (let j = 0; j < formats.length; j += 1) {
format += `${format !== "" ? ", " : ""}${formats[j].name}`;
}
data += `${artists_sort};${title};${genres.join()};${styles.join()};${country};${year};${released};${format}\n\r`;
}
return data;
}
/**
* Méthode permettant de convertir les rows en fichier xls
* @param {Array} rows
*
* @return {Object}
*/
static async convertToXls(rows) {
const wb = new xl.Workbook();
const ws = wb.addWorksheet("MusicTopus");
const headerStyle = wb.createStyle({
font: {
color: "#FFFFFF",
size: 11,
},
fill: {
type: "pattern",
patternType: "solid",
bgColor: "#595959",
fgColor: "#595959",
},
});
const style = wb.createStyle({
font: {
color: "#000000",
size: 11,
},
numberFormat: "0000",
});
const header = [
"Artiste",
"Titre",
"Genre",
"Styles",
"Pays",
"Année",
"Date de sortie",
"Format",
];
for (let i = 0; i < header.length; i += 1) {
ws.cell(1, i + 1)
.string(header[i])
.style(headerStyle);
}
for (let i = 0; i < rows.length; i += 1) {
const currentRow = i + 2;
const {
artists_sort,
title,
genres,
styles,
country,
year,
released,
formats,
} = rows[i];
let format = "";
for (let j = 0; j < formats.length; j += 1) {
format += `${format !== "" ? ", " : ""}${formats[j].name}`;
}
ws.cell(currentRow, 1).string(artists_sort).style(style);
ws.cell(currentRow, 2).string(title).style(style);
ws.cell(currentRow, 3).string(genres.join()).style(style);
ws.cell(currentRow, 4).string(styles.join()).style(style);
if (country) {
ws.cell(currentRow, 5).string(country).style(style);
}
if (year) {
ws.cell(currentRow, 6).number(year).style(style);
}
if (released) {
ws.cell(currentRow, 7)
.date(momenttz.tz(released, "Europe/Paris").hour(12))
.style({ numberFormat: "dd/mm/yyyy" });
}
ws.cell(currentRow, 8).string(format).style(style);
}
return wb;
}
/**
* Méthode permettant de convertir les rows en csv pour importer dans MusicTopus
* @param {Array} rows
*
* @return {string}
*/
static async convertToXml(rows) {
let data = '<?xml version="1.0" encoding="UTF-8"?>\n\r<albums>';
for (let i = 0; i < rows.length; i += 1) {
const {
discogsId,
year,
released,
uri,
artists,
artists_sort,
labels,
series,
companies,
formats,
title,
country,
notes,
identifiers,
videos,
genres,
styles,
tracklist,
extraartists,
images,
thumb,
} = rows[i];
let artistsList = "";
let labelList = "";
let serieList = "";
let companiesList = "";
let formatsList = "";
let identifiersList = "";
let videosList = "";
let genresList = "";
let stylesList = "";
let tracklistList = "";
let extraartistsList = "";
let imagesList = "";
for (let j = 0; j < artists.length; j += 1) {
artistsList += `<artist>
<name>${Export.replaceSpecialChars(artists[j].name)}</name>
<anv>${Export.replaceSpecialChars(artists[j].anv)}</anv>
<join>${Export.replaceSpecialChars(artists[j].join)}</join>
<role>${Export.replaceSpecialChars(artists[j].role)}</role>
<tracks>${Export.replaceSpecialChars(artists[j].tracks)}</tracks>
<id>${Export.replaceSpecialChars(artists[j].id)}</id>
<resource_url>${Export.replaceSpecialChars(
artists[j].resource_url
)}</resource_url>
<thumbnail_url>${Export.replaceSpecialChars(
artists[j].thumbnail_url
)}</thumbnail_url>
</artist>`;
}
for (let j = 0; j < labels.length; j += 1) {
labelList += `<label>
<name>${Export.replaceSpecialChars(labels[j].name)}</name>
<catno>${Export.replaceSpecialChars(labels[j].catno)}</catno>
<entity_type>${Export.replaceSpecialChars(
labels[j].entity_type
)}</entity_type>
<entity_type_name>${Export.replaceSpecialChars(
labels[j].entity_type
)}</entity_type_name>
<id>${Export.replaceSpecialChars(labels[j].id)}</id>
<resource_url>${Export.replaceSpecialChars(
labels[j].resource_url
)}</resource_url>
<thumbnail_url>${Export.replaceSpecialChars(
labels[j].thumbnail_url
)}</thumbnail_url>
</label>
`;
}
for (let j = 0; j < series.length; j += 1) {
serieList += `<serie>
<name>${Export.replaceSpecialChars(series[j].name)}</name>
<catno>${Export.replaceSpecialChars(series[j].catno)}</catno>
<entity_type>${Export.replaceSpecialChars(
series[j].entity_type
)}</entity_type>
<entity_type_name>${Export.replaceSpecialChars(
series[j].entity_type_name
)}</entity_type_name>
<id>${Export.replaceSpecialChars(series[j].id)}</id>
<resource_url>${Export.replaceSpecialChars(
series[j].resource_url
)}</resource_url>
<thumbnail_url>${Export.replaceSpecialChars(
series[j].thumbnail_url
)}</thumbnail_url>
</serie>
`;
}
for (let j = 0; j < companies.length; j += 1) {
companiesList += `<company>
<name>${Export.replaceSpecialChars(companies[j].name)}</name>
<catno>${Export.replaceSpecialChars(companies[j].catno)}</catno>
<entity_type>${Export.replaceSpecialChars(
companies[j].entity_type
)}</entity_type>
<entity_type_name>${Export.replaceSpecialChars(
companies[j].entity_type_name
)}</entity_type_name>
<id>${Export.replaceSpecialChars(companies[j].id)}</id>
<resource_url>${Export.replaceSpecialChars(
companies[j].resource_url
)}</resource_url>
<thumbnail_url>${Export.replaceSpecialChars(
companies[j].thumbnail_url
)}</thumbnail_url>
</company>
`;
}
for (let j = 0; j < formats.length; j += 1) {
let descriptions = "";
if (formats[j].descriptions) {
for (
let k = 0;
k < formats[j].descriptions.length;
k += 1
) {
descriptions += `<description>${formats[j].descriptions[k]}</description>
`;
}
}
formatsList += `<format>
<name>${Export.replaceSpecialChars(formats[j].name)}</name>
<qte>${Export.replaceSpecialChars(formats[j].qty)}</qte>
<text>${Export.replaceSpecialChars(formats[j].text)}</text>
<descriptions>
${descriptions}
</descriptions>
</format>
`;
}
for (let j = 0; j < identifiers.length; j += 1) {
identifiersList += `<identifier>
<type>${Export.replaceSpecialChars(identifiers[j].type)}</type>
<value>${Export.replaceSpecialChars(identifiers[j].value)}</value>
<description>${Export.replaceSpecialChars(
identifiers[j].description
)}</description>
</identifier>
`;
}
for (let j = 0; j < videos.length; j += 1) {
videosList += `<video embed="${videos[j].embed}">
<uri>${Export.replaceSpecialChars(videos[j].uri)}</uri>
<title>${Export.replaceSpecialChars(videos[j].title)}</title>
<description>${Export.replaceSpecialChars(
videos[j].description
)}</description>
<duration>${Export.replaceSpecialChars(
videos[j].duration
)}</duration>
</video>
`;
}
for (let j = 0; j < genres.length; j += 1) {
genresList += `<genre>${Export.replaceSpecialChars(
genres[j]
)}</genre>
`;
}
for (let j = 0; j < styles.length; j += 1) {
stylesList += `<style>${Export.replaceSpecialChars(
styles[j]
)}</style>
`;
}
for (let j = 0; j < tracklist.length; j += 1) {
tracklistList += `<tracklist position="${
tracklist[j].position
}" type="${tracklist[j].type_}" duration="${
tracklist[j].duration
}">
${Export.replaceSpecialChars(tracklist[j].title)}
</tracklist>
`;
}
for (let j = 0; j < extraartists.length; j += 1) {
extraartistsList += `<extraartist>
<name>${Export.replaceSpecialChars(extraartists[j].name)}</name>
<anv>${Export.replaceSpecialChars(extraartists[j].anv)}</anv>
<join>${Export.replaceSpecialChars(extraartists[j].join)}</join>
<role>${Export.replaceSpecialChars(extraartists[j].role)}</role>
<tracks>${Export.replaceSpecialChars(
extraartists[j].tracks
)}</tracks>
<id>${Export.replaceSpecialChars(extraartists[j].id)}</id>
<resource_url>${Export.replaceSpecialChars(
extraartists[j].resource_url
)}</resource_url>
<thumbnail_url>${Export.replaceSpecialChars(
extraartists[j].thumbnail_url
)}</thumbnail_url>
</extraartist>
`;
}
for (let j = 0; j < images.length; j += 1) {
imagesList += `<image type="${images[j].type}" width="${
images[j].width
}" height="${images[j].height}">
<uri>${Export.replaceSpecialChars(images[j].uri)}</uri>
<resource_url>${Export.replaceSpecialChars(
images[j].resource_url
)}</resource_url>
<uri150>${Export.replaceSpecialChars(
images[j].resource_url
)}</uri150>
</image>
`;
}
data += `
<album>
<discogId>${discogsId}</discogId>
<title>${Export.replaceSpecialChars(title)}</title>
<artists_sort>${Export.replaceSpecialChars(artists_sort)}</artists_sort>
<artists>
${artistsList}
</artists>
<year>${year}</year>
<country>${Export.replaceSpecialChars(country)}</country>
<released>${released}</released>
<uri>${uri}</uri>
<thumb>${thumb}</thumb>
<labels>
${labelList}
</labels>
<series>
${serieList}
</series>
<companies>
${companiesList}
</companies>
<formats>
${formatsList}
</formats>
<notes>${Export.replaceSpecialChars(notes)}</notes>
<identifiers>
${identifiersList}
</identifiers>
<videos>
${videosList}
</videos>
<genres>
${genresList}
</genres>
<styles>
${stylesList}
</styles>
<tracklist>
${tracklistList}
</tracklist>
<extraartists>
${extraartistsList}
</extraartists>
<images>
${imagesList}
</images>
</album>`;
}
return `${data}</albums>`;
}
/**
* Méthode permettant de convertir les rows en csv pour importer dans MusicTopus
* @param {Array} rows
*
* @return {string}
*/
static async convertToMusicTopus(rows) {
let data = "itemId;createdAt;updatedAt\n\r";
for (let i = 0; i < rows.length; i += 1) {
const { discogsId, createdAt, updatedAt } = rows[i];
data += `${discogsId};${createdAt};${updatedAt}\n\r`;
}
data += "v1.0";
return data;
}
}
export default Export;

128
src/middleware/Jobs.js Normal file
View File

@ -0,0 +1,128 @@
/* eslint-disable no-await-in-loop */
import ErrorEvent from "../libs/error";
import { uploadFromUrl } from "../libs/aws";
import { getAlbumDetails } from "../helpers";
import JobsModel from "../models/jobs";
import AlbumsModel from "../models/albums";
class Jobs {
/**
* Méthode permettant de télécharger toute les images d'un album
* @param {ObjectId} itemId
*/
static async importAlbumAssets(itemId) {
const album = await AlbumsModel.findById(itemId);
if (!album) {
throw new ErrorEvent(
404,
"Item non trouvé",
`L'album avant l'id ${itemId} n'existe plus dans la collection`
);
}
const item = await getAlbumDetails(album.discogsId);
if (!item) {
throw new ErrorEvent(
404,
"Erreur de communication",
"Erreur lors de la récupération des informations sur Discogs"
);
}
if (item.thumb) {
album.thumb = await uploadFromUrl(item.thumb);
album.thumbType = "local";
}
const { images } = item;
if (images && images.length > 0) {
for (let i = 0; i < images.length; i += 1) {
images[i].uri150 = await uploadFromUrl(images[i].uri150);
images[i].uri = await uploadFromUrl(images[i].uri);
}
}
album.images = images;
await album.save();
return true;
}
/**
* Point d'entrée
* @param {String} state
*
* @return {Object}
*/
async run(state = "NEW") {
const job = await JobsModel.findOne({
state,
tries: {
$lte: 5,
},
});
if (!job) {
return { message: "All jobs done" };
}
job.state = "IN-PROGRESS";
await job.save();
try {
switch (job.model) {
case "Albums":
await Jobs.importAlbumAssets(job.id);
break;
default:
throw new ErrorEvent(
500,
"Job inconnu",
`Le job avec l'id ${job._id} n'est pas un job valide`
);
}
job.state = "SUCCESS";
await job.save();
return this.run(state);
} catch (err) {
job.state = "ERROR";
job.lastTry = new Date();
job.lastErrorMessage = err.message;
job.tries += 1;
await job.save();
throw err;
}
}
/**
* Méthode permettant de créer tous les jobs
*
* @return {Object}
*/
static async populate() {
const albums = await AlbumsModel.find();
for (let i = 0; i < albums.length; i += 1) {
const jobData = {
model: "Albums",
id: albums[i]._id,
};
const job = new JobsModel(jobData);
await job.save();
}
return { message: `${albums.length} jobs ajouté à la file d'attente` };
}
}
export default Jobs;

View File

@ -29,6 +29,7 @@ const AlbumSchema = new mongoose.Schema(
extraartists: Array,
images: Array,
thumb: String,
thumbType: String,
},
{ timestamps: true }
);

24
src/models/jobs.js Normal file
View File

@ -0,0 +1,24 @@
import mongoose from "mongoose";
const { Schema } = mongoose;
const JobSchema = new mongoose.Schema(
{
model: String,
id: Schema.Types.ObjectId,
state: {
type: String,
enum: ["NEW", "IN-PROGRESS", "ERROR", "SUCCESS"],
default: "NEW",
},
lastTry: Date,
lastErrorMessage: String,
tries: {
type: Number,
default: 0,
},
},
{ timestamps: true }
);
export default mongoose.model("Jobs", JobSchema);

40
src/routes/jobs.js Normal file
View File

@ -0,0 +1,40 @@
import express from "express";
import passport from "passport";
import Jobs from "../middleware/Jobs";
// eslint-disable-next-line new-cap
const router = express.Router();
router.route("/").get(
passport.authenticate(["jobs"], {
session: false,
}),
async (req, res, next) => {
try {
const job = new Jobs();
const data = await job.run(req.query.state);
return res.status(200).json(data).end();
} catch (err) {
return next(err);
}
}
);
router.route("/populate").get(
passport.authenticate(["jobs"], {
session: false,
}),
async (req, res, next) => {
try {
const data = await Jobs.populate();
return res.status(200).json(data).end();
} catch (err) {
return next(err);
}
}
);
export default router;

View File

@ -170,7 +170,7 @@
}
},
created() {
this.setTrackList();
this.setTrackList();
this.setIdentifiers();
window.addEventListener("keydown", this.changeImage);
@ -231,7 +231,7 @@
},
showGallery(event) {
const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target;
const {
index,
} = item.dataset;