Merge branch 'develop' of git.darkou.fr:dbroqua/MusicTopus
This commit is contained in:
commit
bc3bb3b554
31 changed files with 684 additions and 67 deletions
13
README.md
13
README.md
|
@ -18,7 +18,9 @@ Vous pouvez, si vous le souhaitez héberger l'application sur votre propre serve
|
||||||
|
|
||||||
### Prérequis
|
### Prérequis
|
||||||
|
|
||||||
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone. Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle.
|
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone.
|
||||||
|
|
||||||
|
Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle ou configurer le SMTP tel que défini dans la section [variables d'environnements](#env-file).
|
||||||
|
|
||||||
Pour la méthode docker il ne vous faudra rien de plus que `docker` et `docker-compose`.
|
Pour la méthode docker il ne vous faudra rien de plus que `docker` et `docker-compose`.
|
||||||
|
|
||||||
|
@ -90,7 +92,7 @@ C'est terminé !
|
||||||
|
|
||||||
Le site est accessible sur [http://localhost:3001](http://localhost:3001).
|
Le site est accessible sur [http://localhost:3001](http://localhost:3001).
|
||||||
|
|
||||||
:information_source: Information : Vous pouvez, et vous dreviez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur.
|
:information_source: Information : Vous pouvez, et vous devriez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur.
|
||||||
|
|
||||||
### Aller plus loin
|
### Aller plus loin
|
||||||
|
|
||||||
|
@ -227,6 +229,13 @@ S3_BUCKET # Nom du bucket (musictopus par défaut, à changer impérativement si
|
||||||
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (musictopus par défaut)
|
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (musictopus par défaut)
|
||||||
JOBS_HEADER_VALUE # Valeur de la clé (ooYee9xok7eigo2shiePohyoGh1eepew par défaut)
|
JOBS_HEADER_VALUE # Valeur de la clé (ooYee9xok7eigo2shiePohyoGh1eepew par défaut)
|
||||||
REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut)
|
REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut)
|
||||||
|
MAIL_METHOD # permet de définir la façon dont les mails de la page contact sont envoyés (formspree ou smtp)
|
||||||
|
MAIL_HOST # Adresse du server mail (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||||
|
MAIL_PORT # Port d'écoute du serveur smtp (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||||
|
MAIL_USER # Adresse mail du compte permettant d'envoyer les mails (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||||
|
MAIL_PASSWORD # Mot de passe du compte email (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||||
|
MAIL_TO # Adresse mail du contact qui recevra les messages de la page "nous contacter" (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributeurs
|
## Contributeurs
|
||||||
|
|
|
@ -37,6 +37,12 @@ services:
|
||||||
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
|
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
|
||||||
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
|
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
|
||||||
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
|
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
|
||||||
|
MAIL_METHOD: ${MAIL_METHOD}
|
||||||
|
MAIL_HOST: ${MAIL_HOST}
|
||||||
|
MAIL_PORT: ${MAIL_PORT}
|
||||||
|
MAIL_USER: ${MAIL_USER}
|
||||||
|
MAIL_PASSWORD: ${MAIL_PASSWORD}
|
||||||
|
MAIL_TO: ${MAIL_TO}
|
||||||
networks:
|
networks:
|
||||||
- musictopus
|
- musictopus
|
||||||
musictopus-db:
|
musictopus-db:
|
||||||
|
|
118
fontello.json
Normal file
118
fontello.json
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
{
|
||||||
|
"name": "icon",
|
||||||
|
"css_prefix_text": "icon-",
|
||||||
|
"css_use_suffix": false,
|
||||||
|
"hinting": true,
|
||||||
|
"units_per_em": 1000,
|
||||||
|
"ascent": 850,
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"uid": "44e04715aecbca7f266a17d5a7863c68",
|
||||||
|
"css": "plus",
|
||||||
|
"code": 59392,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "8b80d36d4ef43889db10bc1f0dc9a862",
|
||||||
|
"css": "user",
|
||||||
|
"code": 59393,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "9dd9e835aebe1060ba7190ad2b2ed951",
|
||||||
|
"css": "search",
|
||||||
|
"code": 59394,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "bf882b30900da12fca090d9796bc3030",
|
||||||
|
"css": "mail",
|
||||||
|
"code": 59395,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
|
||||||
|
"css": "link",
|
||||||
|
"code": 59396,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "e15f0d620a7897e2035c18c80142f6d9",
|
||||||
|
"css": "link-ext",
|
||||||
|
"code": 61582,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "9bc2902722abb366a213a052ade360bc",
|
||||||
|
"css": "spin",
|
||||||
|
"code": 59449,
|
||||||
|
"src": "fontelico"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "bbfb51903f40597f0b70fd75bc7b5cac",
|
||||||
|
"css": "trash",
|
||||||
|
"code": 61944,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "d73eceadda1f594cec0536087539afbf",
|
||||||
|
"css": "heart",
|
||||||
|
"code": 59397,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "cce5e05853d0798a4d10077ef613387c",
|
||||||
|
"css": "blind",
|
||||||
|
"code": 62109,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "567e3e257f2cc8fba2c12bf691c9f2d8",
|
||||||
|
"css": "moon",
|
||||||
|
"code": 61830,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "aa035df0908c4665c269b7b09a5596f3",
|
||||||
|
"css": "sun",
|
||||||
|
"code": 61829,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "c5fd349cbd3d23e4ade333789c29c729",
|
||||||
|
"css": "eye",
|
||||||
|
"code": 59398,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "d870630ff8f81e6de3958ecaeac532f2",
|
||||||
|
"css": "left-open",
|
||||||
|
"code": 59399,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "399ef63b1e23ab1b761dfbb5591fa4da",
|
||||||
|
"css": "right-open",
|
||||||
|
"code": 59400,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "895405dfac8a3b7b2f23b183c6608ee6",
|
||||||
|
"css": "export",
|
||||||
|
"code": 59401,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "4aad6bb50b02c18508aae9cbe14e784e",
|
||||||
|
"css": "share",
|
||||||
|
"code": 61920,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "a73c5deb486c8d66249811642e5d719a",
|
||||||
|
"css": "refresh",
|
||||||
|
"code": 59402,
|
||||||
|
"src": "fontawesome"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -59,6 +59,7 @@
|
||||||
"knacss": "^8.0.4",
|
"knacss": "^8.0.4",
|
||||||
"mongoose": "^6.2.1",
|
"mongoose": "^6.2.1",
|
||||||
"mongoose-unique-validator": "^3.0.0",
|
"mongoose-unique-validator": "^3.0.0",
|
||||||
|
"nodemailer": "^6.7.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"passport": "^0.5.2",
|
"passport": "^0.5.2",
|
||||||
"passport-custom": "^1.1.1",
|
"passport-custom": "^1.1.1",
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.49.7",
|
"sass": "^1.49.7",
|
||||||
|
"svg-captcha": "^1.4.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"vue": "^3.2.31"
|
"vue": "^3.2.31"
|
||||||
},
|
},
|
||||||
|
|
Binary file not shown.
|
@ -26,6 +26,8 @@
|
||||||
|
|
||||||
<glyph glyph-name="export" unicode="" d="M786 298v-144q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h142q7 0 13-6t5-12q0-15-15-18-43-15-74-34-5-2-9-2h-62q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v119q0 11 10 16 16 7 31 21 8 9 19 4 12-5 12-16z m132 277l-214-214q-10-11-25-11-7 0-14 3-22 9-22 33v107h-89q-181 0-245-73-66-77-41-264 2-13-11-19-5-1-7-1-9 0-14 7-6 8-12 17t-22 39-28 55-21 64-10 68q0 27 2 51t8 50 15 49 27 45 38 42 52 34 70 27 89 17 110 6h89v107q0 24 22 33 7 3 14 3 14 0 25-11l214-214q11-10 11-25t-11-25z" horiz-adv-x="928.6" />
|
<glyph glyph-name="export" unicode="" d="M786 298v-144q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h142q7 0 13-6t5-12q0-15-15-18-43-15-74-34-5-2-9-2h-62q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v119q0 11 10 16 16 7 31 21 8 9 19 4 12-5 12-16z m132 277l-214-214q-10-11-25-11-7 0-14 3-22 9-22 33v107h-89q-181 0-245-73-66-77-41-264 2-13-11-19-5-1-7-1-9 0-14 7-6 8-12 17t-22 39-28 55-21 64-10 68q0 27 2 51t8 50 15 49 27 45 38 42 52 34 70 27 89 17 110 6h89v107q0 24 22 33 7 3 14 3 14 0 25-11l214-214q11-10 11-25t-11-25z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="refresh" unicode="" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
<glyph glyph-name="spin" unicode="" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
|
<glyph glyph-name="spin" unicode="" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/img/loading-dark.gif
Normal file
BIN
public/img/loading-dark.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
BIN
public/img/loading-light.gif
Normal file
BIN
public/img/loading-light.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
|
@ -56,6 +56,8 @@ $pagination-hover-color: rgb(115, 151, 186);
|
||||||
|
|
||||||
--button-link-text-color: #2C364A;
|
--button-link-text-color: #2C364A;
|
||||||
|
|
||||||
|
--loader-img: url('/img/loading-light.gif');
|
||||||
|
|
||||||
--nord0: #{$nord0};
|
--nord0: #{$nord0};
|
||||||
--nord1: #{$nord1};
|
--nord1: #{$nord1};
|
||||||
--nord2: #{$nord2};
|
--nord2: #{$nord2};
|
||||||
|
@ -94,4 +96,6 @@ $pagination-hover-color: rgb(115, 151, 186);
|
||||||
--border-color: #{$nord1};
|
--border-color: #{$nord1};
|
||||||
|
|
||||||
--button-link-text-color: #{$white};
|
--button-link-text-color: #{$white};
|
||||||
|
|
||||||
|
--loader-img: url('/img/loading-dark.gif');
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icon';
|
font-family: 'icon';
|
||||||
src: url('/font/icon.eot?80770511');
|
src: url('/font/icon.eot?41426785');
|
||||||
src: url('/font/icon.eot?80770511#iefix') format('embedded-opentype'),
|
src: url('/font/icon.eot?41426785#iefix') format('embedded-opentype'),
|
||||||
url('/font/icon.woff2?80770511') format('woff2'),
|
url('/font/icon.woff2?41426785') format('woff2'),
|
||||||
url('/font/icon.woff?80770511') format('woff'),
|
url('/font/icon.woff?41426785') format('woff'),
|
||||||
url('/font/icon.ttf?80770511') format('truetype'),
|
url('/font/icon.ttf?41426785') format('truetype'),
|
||||||
url('/font/icon.svg?80770511#icon') format('svg');
|
url('/font/icon.svg?41426785#icon') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@
|
||||||
.icon-left-open:before { content: '\e807'; } /* '' */
|
.icon-left-open:before { content: '\e807'; } /* '' */
|
||||||
.icon-right-open:before { content: '\e808'; } /* '' */
|
.icon-right-open:before { content: '\e808'; } /* '' */
|
||||||
.icon-export:before { content: '\e809'; } /* '' */
|
.icon-export:before { content: '\e809'; } /* '' */
|
||||||
|
.icon-refresh:before { content: '\e80a'; } /* '' */
|
||||||
.icon-spin:before { content: '\e839'; } /* '' */
|
.icon-spin:before { content: '\e839'; } /* '' */
|
||||||
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
.icon-sun:before { content: '\f185'; } /* '' */
|
.icon-sun:before { content: '\f185'; } /* '' */
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
@import './icons';
|
@import './icons';
|
||||||
@import './list';
|
@import './list';
|
||||||
@import './box';
|
@import './box';
|
||||||
|
@import './loader';
|
||||||
|
|
||||||
@import './error';
|
@import './error';
|
||||||
@import './500';
|
@import './500';
|
||||||
|
|
13
sass/loader.scss
Normal file
13
sass/loader.scss
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
.loader {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.animation {
|
||||||
|
background-image: var(--loader-img);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,26 @@
|
||||||
.ma-collection-details {
|
.ma-collection-details {
|
||||||
|
h1 {
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.icon-trash {
|
||||||
|
color: $danger-color;
|
||||||
|
@include transition() {}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $danger-color-hl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.icon-refresh {
|
||||||
|
color: $primary-color;
|
||||||
|
@include transition() {}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-color-hl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.galerie {
|
.galerie {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 3.25rem;
|
width: 3.25rem;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
color: rgba(0,0,0,.7);
|
color: var(--font-color);
|
||||||
|
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import importJobsRouter from "./routes/jobs";
|
||||||
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
||||||
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
||||||
import importMeRouterApiV1 from "./routes/api/v1/me";
|
import importMeRouterApiV1 from "./routes/api/v1/me";
|
||||||
|
import importContactRouterApiV1 from "./routes/api/v1/contact";
|
||||||
|
|
||||||
passportConfig(passport);
|
passportConfig(passport);
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@ app.use("/jobs", importJobsRouter);
|
||||||
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
||||||
app.use("/api/v1/search", importSearchRouterApiV1);
|
app.use("/api/v1/search", importSearchRouterApiV1);
|
||||||
app.use("/api/v1/me", importMeRouterApiV1);
|
app.use("/api/v1/me", importMeRouterApiV1);
|
||||||
|
app.use("/api/v1/contact", importContactRouterApiV1);
|
||||||
|
|
||||||
// Handle 404
|
// Handle 404
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
|
|
|
@ -19,4 +19,14 @@ module.exports = {
|
||||||
process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew",
|
process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew",
|
||||||
registrationOpen:
|
registrationOpen:
|
||||||
(process.env.REGISTRATION_OPEN || "true").toLowerCase() === "true",
|
(process.env.REGISTRATION_OPEN || "true").toLowerCase() === "true",
|
||||||
|
mailMethod: process.env.MAIL_METHOD || "formspree",
|
||||||
|
smtpConfig: {
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
port: process.env.MAIL_PORT,
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
pass: process.env.MAIL_PASSWORD,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mailTo: process.env.MAIL_TO,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,13 +5,25 @@ import { discogsToken } from "../config";
|
||||||
|
|
||||||
export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`;
|
export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`;
|
||||||
|
|
||||||
export const searchSong = async (q) => {
|
export const searchSong = async (q, format, year, country) => {
|
||||||
const dis = new Discogs({ userToken: discogsToken }).database();
|
const dis = new Discogs({ userToken: discogsToken }).database();
|
||||||
|
|
||||||
const res = await dis.search({
|
const params = {
|
||||||
q,
|
q,
|
||||||
type: "release",
|
type: "release",
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (format) {
|
||||||
|
params.format = format;
|
||||||
|
}
|
||||||
|
if (year) {
|
||||||
|
params.year = year;
|
||||||
|
}
|
||||||
|
if (country) {
|
||||||
|
params.country = country;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await dis.search(params);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,8 @@ import AlbumsModel from "../models/albums";
|
||||||
import JobsModel from "../models/jobs";
|
import JobsModel from "../models/jobs";
|
||||||
import UsersModel from "../models/users";
|
import UsersModel from "../models/users";
|
||||||
import ErrorEvent from "../libs/error";
|
import ErrorEvent from "../libs/error";
|
||||||
// import { uploadFromUrl } from "../libs/aws";
|
|
||||||
|
import { getAlbumDetails } from "../helpers";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classe permettant la gestion des albums d'un utilisateur
|
* Classe permettant la gestion des albums d'un utilisateur
|
||||||
|
@ -182,6 +183,34 @@ class Albums extends Pages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de mettre à jour un album
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
async patchOne() {
|
||||||
|
const { itemId: _id } = this.req.params;
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const album = await AlbumsModel.findOne({
|
||||||
|
_id,
|
||||||
|
User,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!album) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
404,
|
||||||
|
"Mise à jour",
|
||||||
|
"Impossible de trouver cet album"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = await getAlbumDetails(album.discogsId);
|
||||||
|
|
||||||
|
await album.updateOne(values);
|
||||||
|
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode permettant de supprimer un élément d'une collection
|
* Méthode permettant de supprimer un élément d'une collection
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
|
@ -196,7 +225,11 @@ class Albums extends Pages {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ErrorEvent(404, "Impossible de trouver cet album");
|
throw new ErrorEvent(
|
||||||
|
404,
|
||||||
|
"Suppression",
|
||||||
|
"Impossible de trouver cet album"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Jobs {
|
||||||
throw new ErrorEvent(
|
throw new ErrorEvent(
|
||||||
404,
|
404,
|
||||||
"Item non trouvé",
|
"Item non trouvé",
|
||||||
`L'album avant l'id ${itemId} n'existe plus dans la collection`
|
`L'album avec l'id ${itemId} n'existe plus dans la collection`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,16 @@ router
|
||||||
|
|
||||||
router
|
router
|
||||||
.route("/:itemId")
|
.route("/:itemId")
|
||||||
|
.patch(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const albums = new Albums(req);
|
||||||
|
const data = await albums.patchOne();
|
||||||
|
|
||||||
|
sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
.delete(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
.delete(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const albums = new Albums(req);
|
const albums = new Albums(req);
|
||||||
|
|
77
src/routes/api/v1/contact.js
Normal file
77
src/routes/api/v1/contact.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import express from "express";
|
||||||
|
import nodemailer from "nodemailer";
|
||||||
|
import svgCaptcha from "svg-captcha";
|
||||||
|
|
||||||
|
import { sendResponse } from "../../../libs/format";
|
||||||
|
|
||||||
|
import { mailMethod, smtpConfig, mailTo, siteName } from "../../../config";
|
||||||
|
import ErrorEvent from "../../../libs/error";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/")
|
||||||
|
.get(async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const captcha = svgCaptcha.create({
|
||||||
|
size: 4,
|
||||||
|
noise: 2,
|
||||||
|
color: true,
|
||||||
|
});
|
||||||
|
req.session.captcha = captcha.text;
|
||||||
|
|
||||||
|
res.type("svg");
|
||||||
|
return res.status(200).send(captcha.data);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post(async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
if (mailMethod === "smtp") {
|
||||||
|
const { email, name, message, captcha } = req.body;
|
||||||
|
|
||||||
|
if (!captcha || captcha !== req.session.captcha) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
406,
|
||||||
|
"Captcha",
|
||||||
|
"Le captcha n'est pas valide"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!email || !message) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
406,
|
||||||
|
"Erreur de saisie",
|
||||||
|
"Le formulaire n'est pas correctement saisi"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport(smtpConfig);
|
||||||
|
|
||||||
|
const text = `Bonjour,
|
||||||
|
Vous venez de recevoir un nouveau message de ${name} (${email}) :
|
||||||
|
|
||||||
|
${message}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await transporter.sendMail({
|
||||||
|
from: smtpConfig.auth.user,
|
||||||
|
to: mailTo,
|
||||||
|
subject: `${siteName} : Nouveau message`,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { messageId, response } = data;
|
||||||
|
|
||||||
|
return sendResponse(req, res, { messageId, response });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ErrorEvent(500, "Routeur", "Méthode non configurée");
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -9,7 +9,12 @@ const router = express.Router();
|
||||||
|
|
||||||
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const data = await searchSong(req.query.q);
|
const data = await searchSong(
|
||||||
|
req.query.q,
|
||||||
|
req.query.format || null,
|
||||||
|
req.query.year || null,
|
||||||
|
req.query.country || null
|
||||||
|
);
|
||||||
|
|
||||||
sendResponse(req, res, data);
|
sendResponse(req, res, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,19 +1,37 @@
|
||||||
<main class="layout-maxed ajouter-un-album" id="app">
|
<main class="layout-maxed ajouter-un-album" id="app">
|
||||||
<h1>Ajouter un album</h1>
|
<h1>Ajouter un album</h1>
|
||||||
<div class="grid sm:grid-cols-2">
|
<form @submit="search">
|
||||||
<div>
|
<div class="grid sm:grid-cols-2">
|
||||||
<form @submit="search">
|
<div>
|
||||||
<label for="q">Nom de l'album ou code barre</label>
|
<label for="q">Nom de l'album ou code barre</label>
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<input type="text" name="q" id="q" v-model="q" placeholder="ex : Hybrid Theory" autofocus>
|
<input type="text" name="q" id="q" v-model="q" placeholder="ex : Iron Maiden - Powerslave" autofocus>
|
||||||
<button class="button is-primary" :disabled="loading" aria-label="Chercher">
|
<button class="button is-primary" :disabled="loading" aria-label="Chercher">
|
||||||
<i class="icon-search" v-if="!loading"></i>
|
<i class="icon-search" v-if="!loading"></i>
|
||||||
<i class="icon-spin animate-spin" v-if="loading"></i>
|
<i class="icon-spin animate-spin" v-if="loading"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="grid sm:grid-cols-3 gap-5">
|
||||||
|
<div class="field">
|
||||||
|
<label for="format">Trier par</label>
|
||||||
|
<select id="format" v-model="format">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<option v-for="format in orderedItems(formats)" :value="format">{{format}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="year">Année</label>
|
||||||
|
<input type="number" name="year" v-model="year" id="year" placeholder="1984">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="country">Pays</label>
|
||||||
|
<input type="string" name="country" v-model="country" id="country" placeholder="France">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
||||||
<div class="item" v-if="!loading" v-for="item in items">
|
<div class="item" v-if="!loading" v-for="item in items">
|
||||||
|
@ -158,10 +176,77 @@
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
q: '',
|
q: '',
|
||||||
|
year: '',
|
||||||
|
country: '',
|
||||||
|
format: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
items: [],
|
items: [],
|
||||||
details: {},
|
details: {},
|
||||||
modalIsVisible: false,
|
modalIsVisible: false,
|
||||||
|
formats: [
|
||||||
|
'Vinyl',
|
||||||
|
'Acetate',
|
||||||
|
'Flexi-disc',
|
||||||
|
'Lathe Cut',
|
||||||
|
'Mighty Tiny',
|
||||||
|
'Shellac',
|
||||||
|
'Sopic',
|
||||||
|
'Pathé Disc',
|
||||||
|
'Edison Disc',
|
||||||
|
'Cylinder',
|
||||||
|
'CD',
|
||||||
|
'CDr',
|
||||||
|
'CDV',
|
||||||
|
'DVD',
|
||||||
|
'DVDr',
|
||||||
|
'HD DVD',
|
||||||
|
'HD DVD-R',
|
||||||
|
'Blu-ray',
|
||||||
|
'Blu-ray-R',
|
||||||
|
'Ultra HD Blu-ray',
|
||||||
|
'SACD',
|
||||||
|
'4-Track Cartridge',
|
||||||
|
'8-Track Cartridge',
|
||||||
|
'Cassette',
|
||||||
|
'DC-International',
|
||||||
|
'Elcaset',
|
||||||
|
'PlayTape',
|
||||||
|
'RCA Tape Cartridge',
|
||||||
|
'DAT',
|
||||||
|
'DCC',
|
||||||
|
'Microcassette',
|
||||||
|
'NT Cassette',
|
||||||
|
'Pocket Rocker',
|
||||||
|
'Revere Magnetic Stereo Tape Ca',
|
||||||
|
'Tefifon',
|
||||||
|
'Reel-To-Reel',
|
||||||
|
'Sabamobil',
|
||||||
|
'Betacam',
|
||||||
|
'Betacam SP',
|
||||||
|
'Betamax',
|
||||||
|
'Cartrivision',
|
||||||
|
'MiniDV',
|
||||||
|
'Super VHS',
|
||||||
|
'U-matic',
|
||||||
|
'VHS',
|
||||||
|
'Video 2000',
|
||||||
|
'Video8',
|
||||||
|
'Film Reel',
|
||||||
|
'HitClips',
|
||||||
|
'Laserdisc',
|
||||||
|
'SelectaVision',
|
||||||
|
'VHD',
|
||||||
|
'Wire Recording',
|
||||||
|
'Minidisc',
|
||||||
|
'MVD',
|
||||||
|
'UMD',
|
||||||
|
'Floppy Disk',
|
||||||
|
'File',
|
||||||
|
'Memory Stick',
|
||||||
|
'Hybrid',
|
||||||
|
'All Media',
|
||||||
|
'Box Set',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -173,8 +258,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
let url = `/api/v1/search?q=${this.q}`;
|
||||||
|
|
||||||
axios.get(`/api/v1/search?q=${this.q}`)
|
if ( this.year ) {
|
||||||
|
url += `&year=${this.year}`;
|
||||||
|
}
|
||||||
|
if ( this.country ) {
|
||||||
|
url += `&country=${this.country}`;
|
||||||
|
}
|
||||||
|
if ( this.format ) {
|
||||||
|
url += `&format=${this.format}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get(url)
|
||||||
.then( response => {
|
.then( response => {
|
||||||
const {
|
const {
|
||||||
results,
|
results,
|
||||||
|
@ -242,6 +338,9 @@
|
||||||
showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…");
|
showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
orderedItems(items) {
|
||||||
|
return items.sort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -85,6 +85,12 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
||||||
|
<div class="loader" v-if="loading">
|
||||||
|
<div class="animation"></div>
|
||||||
|
<div>
|
||||||
|
Chargement des données en cours…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="item" v-if="!loading" v-for="item in items">
|
<div class="item" v-if="!loading" v-for="item in items">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
{{ item.artists_sort}} - {{ item.title }}
|
{{ item.artists_sort}} - {{ item.title }}
|
||||||
|
@ -172,8 +178,55 @@
|
||||||
methods: {
|
methods: {
|
||||||
fetch() {
|
fetch() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.total = 0;
|
||||||
|
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
const entries = urlParams.entries();
|
||||||
|
|
||||||
|
for(const entry of entries) {
|
||||||
|
switch(entry[0]) {
|
||||||
|
case 'artists_sort':
|
||||||
|
this.artist = entry[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this[entry[0]] = entry[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
|
if ( this.artist ) {
|
||||||
|
url += `&artists_sort=${this.artist}`;
|
||||||
|
}
|
||||||
|
if ( this.format ) {
|
||||||
|
url += `&format=${this.format}`;
|
||||||
|
}
|
||||||
|
if ( this.year ) {
|
||||||
|
url += `&year=${this.year}`;
|
||||||
|
}
|
||||||
|
if ( this.genre ) {
|
||||||
|
url += `&genre=${this.genre}`;
|
||||||
|
}
|
||||||
|
if ( this.style ) {
|
||||||
|
url += `&style=${this.style}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get(url)
|
||||||
|
.then( response => {
|
||||||
|
this.items = response.data.rows;
|
||||||
|
this.total = response.data.count || 0;
|
||||||
|
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de charger cette collection");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeUrl() {
|
||||||
|
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
if ( this.artist ) {
|
if ( this.artist ) {
|
||||||
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
|
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
|
||||||
}
|
}
|
||||||
|
@ -190,38 +243,26 @@
|
||||||
url += `&style=${this.style.replace('&', '%26')}`;
|
url += `&style=${this.style.replace('&', '%26')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get(url)
|
location.href = url;
|
||||||
.then( response => {
|
|
||||||
this.items = response.data.rows;
|
|
||||||
this.total = response.data.count;
|
|
||||||
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
showToastr(err.response?.data?.message || "Impossible de charger cette collection");
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
next(event) {
|
next(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.page += 1;
|
this.page += 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
previous(event) {
|
previous(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.page -= 1;
|
this.page -= 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
goTo(page) {
|
goTo(page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
changeSort() {
|
changeSort() {
|
||||||
const [sort,order] = this.sortOrder.split('-');
|
const [sort,order] = this.sortOrder.split('-');
|
||||||
|
@ -229,12 +270,12 @@
|
||||||
this.order = order;
|
this.order = order;
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
changeFilter() {
|
changeFilter() {
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
showMoreFilters() {
|
showMoreFilters() {
|
||||||
this.moreFilters = !this.moreFilters;
|
this.moreFilters = !this.moreFilters;
|
||||||
|
|
|
@ -355,6 +355,7 @@
|
||||||
<i class="icon-left-open">.icon-left-open</i>
|
<i class="icon-left-open">.icon-left-open</i>
|
||||||
<i class="icon-right-open">.icon-right-open</i>
|
<i class="icon-right-open">.icon-right-open</i>
|
||||||
<i class="icon-export">.icon-export</i>
|
<i class="icon-export">.icon-export</i>
|
||||||
|
<i class="icon-refresh">.icon-refresh</i>
|
||||||
<i class="icon-share">.icon-share</i>
|
<i class="icon-share">.icon-share</i>
|
||||||
<i class="icon-spin">.icon-spin</i>
|
<i class="icon-spin">.icon-spin</i>
|
||||||
<i class="icon-sun">.icon-sun</i>
|
<i class="icon-sun">.icon-sun</i>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<main class="layout-maxed ma-collection-details" id="app" v-cloak @keyup="changeImage">
|
<main class="layout-maxed ma-collection-details" id="app" v-cloak @keyup="changeImage">
|
||||||
|
|
||||||
<h1>{{item.artists_sort}} - {{item.title}}</h1>
|
<h1>
|
||||||
|
{{item.artists_sort}} - {{item.title}}
|
||||||
|
<i class="icon-trash" title="Supprimer cette fiche" @click="showConfirmDelete()"></i>
|
||||||
|
<i class="icon-refresh" title="Mettre à jour les données de cette fiche" @click="updateItem()"></i>
|
||||||
|
</h1>
|
||||||
<div class="grid sm:grid-cols-3 gap-16">
|
<div class="grid sm:grid-cols-3 gap-16">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img :src="item.thumb %>" :alt="`Miniature pour l'album ${item.title}`" />
|
<img :src="item.thumb %>" :alt="`Miniature pour l'album ${item.title}`" />
|
||||||
|
@ -70,6 +74,9 @@
|
||||||
<ul class="ml-4">
|
<ul class="ml-4">
|
||||||
<li v-for="(format) in item.formats">
|
<li v-for="(format) in item.formats">
|
||||||
{{format.name}}
|
{{format.name}}
|
||||||
|
<template v-if="format.text">
|
||||||
|
- <i>{{format.text}}</i>
|
||||||
|
</template>
|
||||||
<template v-if="format.descriptions && format.descriptions.length > 0">
|
<template v-if="format.descriptions && format.descriptions.length > 0">
|
||||||
(<span v-for="(description, index) in format.descriptions">
|
(<span v-for="(description, index) in format.descriptions">
|
||||||
{{description}}<template v-if="index < format.descriptions.length - 1">, </template>
|
{{description}}<template v-if="index < format.descriptions.length - 1">, </template>
|
||||||
|
@ -153,6 +160,20 @@
|
||||||
<img :src="preview" />
|
<img :src="preview" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" :class="{'is-visible': showModalDelete}">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header></header>
|
||||||
|
<section>
|
||||||
|
Êtes-vous sûr de vouloir supprimer cet album ?
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<button class="button is-primary" @click="deleteItem">Supprimer</button>
|
||||||
|
<button class="button" @click="toggleModal">Annuler</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -167,6 +188,7 @@
|
||||||
identifiersPreviewLength: 16,
|
identifiersPreviewLength: 16,
|
||||||
preview: null,
|
preview: null,
|
||||||
index: null,
|
index: null,
|
||||||
|
showModalDelete: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -277,6 +299,35 @@
|
||||||
|
|
||||||
document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' });
|
document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' });
|
||||||
},
|
},
|
||||||
|
showConfirmDelete() {
|
||||||
|
this.toggleModal();
|
||||||
|
},
|
||||||
|
toggleModal() {
|
||||||
|
this.showModalDelete = !this.showModalDelete;
|
||||||
|
},
|
||||||
|
updateItem() {
|
||||||
|
showToastr("Mise à jour en cours…", true);
|
||||||
|
axios.patch(`/api/v1/albums/${this.item._id}`)
|
||||||
|
.then( (res) => {
|
||||||
|
showToastr("Mise à jour réalisée avec succès", true);
|
||||||
|
this.item = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteItem() {
|
||||||
|
axios.delete(`/api/v1/albums/${this.item._id}`)
|
||||||
|
.then( () => {
|
||||||
|
return locatiom.href = "/ma-collection";
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.toggleModal();
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
</script>
|
</script>
|
|
@ -89,6 +89,12 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
||||||
|
<div class="loader" v-if="loading">
|
||||||
|
<div class="animation"></div>
|
||||||
|
<div>
|
||||||
|
Chargement des données en cours…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="item" v-if="!loading" v-for="item in items">
|
<div class="item" v-if="!loading" v-for="item in items">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
<a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a>
|
<a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a>
|
||||||
|
@ -226,8 +232,54 @@
|
||||||
methods: {
|
methods: {
|
||||||
fetch() {
|
fetch() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.total = 0;
|
||||||
|
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
const entries = urlParams.entries();
|
||||||
|
|
||||||
|
for(const entry of entries) {
|
||||||
|
switch(entry[0]) {
|
||||||
|
case 'artists_sort':
|
||||||
|
this.artist = entry[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this[entry[0]] = entry[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
|
if ( this.artist ) {
|
||||||
|
url += `&artists_sort=${this.artist}`;
|
||||||
|
}
|
||||||
|
if ( this.format ) {
|
||||||
|
url += `&format=${this.format}`;
|
||||||
|
}
|
||||||
|
if ( this.year ) {
|
||||||
|
url += `&year=${this.year}`;
|
||||||
|
}
|
||||||
|
if ( this.genre ) {
|
||||||
|
url += `&genre=${this.genre}`;
|
||||||
|
}
|
||||||
|
if ( this.style ) {
|
||||||
|
url += `&style=${this.style}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get(url)
|
||||||
|
.then( response => {
|
||||||
|
this.items = response.data.rows;
|
||||||
|
this.total = response.data.count || 0;
|
||||||
|
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de charger votre collection");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeUrl() {
|
||||||
|
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
if ( this.artist ) {
|
if ( this.artist ) {
|
||||||
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
|
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
|
||||||
}
|
}
|
||||||
|
@ -244,38 +296,26 @@
|
||||||
url += `&style=${this.style.replace('&', '%26')}`;
|
url += `&style=${this.style.replace('&', '%26')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get(url)
|
location.href = url;
|
||||||
.then( response => {
|
|
||||||
this.items = response.data.rows;
|
|
||||||
this.total = response.data.count;
|
|
||||||
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
showToastr(err.response?.data?.message || "Impossible de charger votre collection");
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
next(event) {
|
next(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.page += 1;
|
this.page += 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
previous(event) {
|
previous(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.page -= 1;
|
this.page -= 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
goTo(page) {
|
goTo(page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
changeSort() {
|
changeSort() {
|
||||||
const [sort,order] = this.sortOrder.split('-');
|
const [sort,order] = this.sortOrder.split('-');
|
||||||
|
@ -283,12 +323,12 @@
|
||||||
this.order = order;
|
this.order = order;
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
changeFilter() {
|
changeFilter() {
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
|
|
||||||
this.fetch();
|
this.changeUrl();
|
||||||
},
|
},
|
||||||
showMoreFilters() {
|
showMoreFilters() {
|
||||||
this.moreFilters = !this.moreFilters;
|
this.moreFilters = !this.moreFilters;
|
||||||
|
|
|
@ -1,22 +1,80 @@
|
||||||
<section class="box">
|
<section class="box" id="app">
|
||||||
<h1>Nous contacter</h1>
|
<h1>Nous contacter</h1>
|
||||||
<form action="https://formspree.io/f/<%= config.formspreeId %>" method="POST">
|
<form @submit="send" <% if (config.mailMethod === 'formspree' ) { %> id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="email">Addresse e-mail*</label>
|
<label for="email">Addresse e-mail*</label>
|
||||||
<input type="email" name="email" id="email" required />
|
<input type="email" name="email" id="email" v-model="email" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="name">Prénom, nom</label>
|
<label for="name">Prénom, nom</label>
|
||||||
<input type="text" name="name" id="name" />
|
<input type="text" name="name" id="name" v-model="name" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="message">Message*</label>
|
<label for="message">Message*</label>
|
||||||
<textarea name="message" id="message" rows="6" required ></textarea>
|
<textarea name="message" id="message" rows="6" required v-model="message" ></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="button is-primary">Envoyer</button>
|
<% if (config.mailMethod !== 'formspree' ) { %>
|
||||||
|
<img src="/api/v1/contact" alt="Captcha" />
|
||||||
|
<div class="field">
|
||||||
|
<label for="captcha">Captcha</label>
|
||||||
|
<input type="text" name="captcha" id="captcha" v-model="captcha" required />
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<button type="submit" class="button is-primary" :disabled="loading">
|
||||||
|
<% if (config.mailMethod !== 'formspree' ) { %>
|
||||||
|
<i class="icon-spin animate-spin" v-if="loading"></i>
|
||||||
|
<% } %>
|
||||||
|
Envoyer
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<% if (config.mailMethod === 'smtp' ) { %>
|
||||||
|
<script>
|
||||||
|
Vue.createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
name: '',
|
||||||
|
message: '',
|
||||||
|
captcha: '',
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
send(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if ( this.loading ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const {
|
||||||
|
email,
|
||||||
|
message,
|
||||||
|
name,
|
||||||
|
captcha,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
axios.post('/api/v1/contact', {email, name, message, captcha})
|
||||||
|
.then( () => {
|
||||||
|
showToastr("Message correctement envoyé", true);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).mount('#app');
|
||||||
|
</script>
|
||||||
|
<% } %>
|
Loading…
Reference in a new issue