From 1251ca1e02d53d661bd6b0f0b522edad1e350231 Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Thu, 3 Mar 2022 17:03:18 +0100 Subject: [PATCH 01/28] #3 (#25) Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/25 Co-authored-by: Damien Broqua <dbroqua@noreply.localhost> Co-committed-by: Damien Broqua <dbroqua@noreply.localhost> --- public/font/icon.eot | Bin 9132 -> 9408 bytes public/font/icon.svg | 4 + public/font/icon.ttf | Bin 8980 -> 9256 bytes public/font/icon.woff | Bin 5688 -> 5844 bytes public/font/icon.woff2 | Bin 4716 -> 4860 bytes public/js/main.js | 7 +- sass/global.scss | 13 + sass/icons.scss | 2 + sass/index.scss | 3 +- sass/ma-collection-details.scss | 55 ++++ sass/modal.scss | 82 +++--- src/middleware/Albums.js | 14 + src/routes/index.js | 14 + views/pages/ajouter-un-album.ejs | 8 +- views/pages/composants.ejs | 2 + views/pages/mon-compte/ma-collection.ejs | 7 +- .../mon-compte/ma-collection/details.ejs | 260 ++++++++++++++++++ 17 files changed, 416 insertions(+), 55 deletions(-) create mode 100644 sass/ma-collection-details.scss create mode 100644 views/pages/mon-compte/ma-collection/details.ejs diff --git a/public/font/icon.eot b/public/font/icon.eot index 1609d7351cbe182bc43e2b003fee916409870f0a..8664abe7c3bb927c5503dbfc1ac1e0ec80045291 100644 GIT binary patch delta 806 zcmXw0O-NKx6h7y^H*e<Mr}Jjy>4Z$qWMU&Z6%}efkc9LDEu=+3Ipovk$8em7PBQ(V zE;53`vbz}~#}H}}EyMvAZF1pCXcs61QoC79p+Zm)W9QAw`}poT-#O==@11);TwJ-V zth4|`T4t3FIkZ%spY47yIR${d0MI{}Hmz3+?FHhC0IY9tJR7Dst$K?#Z^BBZ2af!? zPkf4KP0}2*<38dkvYzDdy+q`6hR(}10JUAIxOqGDH2N06-$VJ16b0PJq<-QLiMOQE z*<9!JwcRoW-cX=>I5TKAHa7GT|3<tyZRRXI;avo%lZl5%%yeAjcA~^-3N~kD#<CNi zV`~7$D&-ff(YQ6Sd}bNo;4|?8Z(>fAG1Ha@q%6Qokg8rgSx!UdL52jyz2E@`O35Q0 zv8dVv_7{e*_{jFVy`WR2BEGY~VvD(T8jO~$W-ncorRy;_K#4$Ip!6T0l7Gs9t+h4d z05pK@X$L^PwjVoyTC%?#(Eq^BoCDB{Ph_r40_?7^ywn2*1iB4GK)gb;Ahm+=K!7Rx z4y&xe&{1hVgsmq#vEz6M8w@s6S;sccV_HzF3}_k_coUCt_P(-FS+BYr=gDG0(_*~I zz8%uZ?&+v|I#(T`U|m&+M2^}bDsAUTDPSwzq5eTFMjDaIMztGt<v3I^9eHfSh*Mw} z0u-O~xDuQ%FNJRDXMce%mFx@Myl~gGXNAw-i9T`3A66JWF<TP}e>Bh~jYwbQ8}g>> zyz8g(nu_hm^%dXwzp?{9WGJDGE>zHs9#oN|hBa7=UewWteo-)5>($|SA{)(E@ew{c QluS9UST!QWAI2x|Kgq4GFaQ7m delta 503 zcmX@$xyGGsjWPp+i1I`>GnRX-8pabHs_SPmFfax%Ffb$}=Oz}+YVJ`4@;5LrFgYZb zl_&tkfj}e%Nbsc<q~|8-{j37=bAVi)^u*!<ptt~#9|EK~(sL@)&SdUmV_;zZz`($h zl98I2B3~w83{(P=H_HGDup2PP0r@>ZzDh=JNrmCZu>Ws>0zeBHEOPRb6Gig`p8@$V zfPA^!#EJq&Lq4FKAP11Ike8U7I=SMLE|9MQbl}{A{NfVnGTrwK48kXX@|^`msRec0 zthX^RC{;|Jz!=Rqaq=6+e9b8gj6hiihCT*nFiQtWa{)a$g@J`32`J9Tz`?-8!1jV| zato6#rxXJS$}q@G-o+#f)WEj*ArqqzP#F)%42Csel7WGD@(1Q{S4#$l7eEX&fZ;<Q z1T%0lFnnx<V30K*+aQ>M8%Y0vV31j#=0Y$7@8%^eTx^@0xRcmqj5S#A#q-;IW#DIi z0kY_x^)nwBJxR!%k#Dkvkiz5yA#LF-<_|0-EH_wN*o=S*Kpy1Vyh!La6HrN=sLbSf PqH2?GifU{UJHQ73hZ}Z7 diff --git a/public/font/icon.svg b/public/font/icon.svg index 67c8d09..9370b56 100644 --- a/public/font/icon.svg +++ b/public/font/icon.svg @@ -20,6 +20,10 @@ <glyph glyph-name="eye" unicode="" d="M929 314q-85 132-213 197 34-58 34-125 0-103-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 243 68 186 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" /> +<glyph glyph-name="left-open" unicode="" d="M654 682l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" /> + +<glyph glyph-name="right-open" unicode="" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" /> + <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" /> diff --git a/public/font/icon.ttf b/public/font/icon.ttf index cbdbe4d0dbb6c65bbc17942cfc0fe99d48b55f95..1f0de7e581f6d9e5e42b4de9768222fc8c0c1ce2 100644 GIT binary patch delta 777 zcmXw0T}TvB6h3EWcXnrH-JP}34O!Mj#dgW9s8IW%Bt&}XA;JpECC6=l4A&iWlPyAh zv6n)s=|Ljb5Q6AQTJW_VdJ^g>Pza>QB_vdc>}Bnnot(>e&;8E*&iU@#`*Psj<!9|P zvqXHAh=x;n!+f>eRR&)n5{6P!#VGRlnoLaojG4`k9Q*qad=}J~H74!62A)IoX2%|6 zVrL7ecln4KdUI*xZtIi8TcTh;_PcXPcut7J;E%vNa`|GZXYR)S77}le=o>4fjFy(> zA@CpIZF!?)@+tocQKJhyI&S3CR%tf@-hl%ynuW>Y^q1rsk^UL`%jQJdoL)V*O4R=J z`QB|VS%X}NFA!ss=mm-ORTrXye_S9JWsvuimjsfkUTcdJ@d()?iUA4oB2;dY$ODy( zI+c9XfSN$%g3!RS?Q4GlYf!wI^bl16NU8cCD5B~a2e$3@yaP0%ojC_+Njr}npgHaA zIe-oAE;@kWwx~!mMEjqF1+kxW3iavC#C!z@WwfHHP)LvtAC-k>J-|}i5$-(Q!`&xa zxmg$HYwH|Wl%yI~Ya^=4WhJ5{72$ntqqbgOD!hcqWmQco5&NRxQ#|mI4}7jUM&ZV~ z5loKTCNidra0=N@_s~DACgBmQZPdG=tHPr-!?EW$#~gtzB&z(8(^7b8>qG0E!1>=4 z5UciuZVudcA4IF9^{{5GXi-VPV+(86S1l3h701PIu3N56_a*l)={3amJ8Knx@Vfx@ m&qHAsYaFyBy|YOkOJ|CSf|(vyCPuS4XK8imvC4J*qyHZ<YNb8^ delta 537 zcmXv}OKTHR7(L%ia+6Lbla4hTtr{Pclp3)Xd{7s<P+YiBC@Lu0Nt$MqOf%RJq6=O4 z0HGAi%|@Z%(uE5l(51NS!XF?AU6vpPlY$UedZvT-a_)D}Ip5>nkJma^-yVPd4v-rF z<|~00zJIZDl>9SbT&lF11?pV;@9}UJ!&*>2_2(&hoz$**%OUkq@;SzIt^Q>3+ruvk zF#CYL;8(qcBdyUHrbPZ(pF--id7pfR{DdDg+tY(ve|IQwAkNnt6)(T!Zj<-O4+ma5 zlxc^an<g(Tc|ldTcT40&K5#v3EH@9fO22@yP5N)b$JOxJ#_R@gti3k8D{FdA8j~Fe zO<)z~-t8o##XokCK$*IOGz_So(mPU`Jb-AAb2Ejy$*d$4Qp_4*T?RI?w3NPS4E4N` z$=OkJeA+vKtiEI3zH$NR5v)OfH3q9Ocoid>KX@I39WfZjU~lZM$3V7gCoI+dX_eLZ zOfk9jAb0UMa%L|Yv^BeZ{eRTHdrlnv!7b=bcWUgB*-tbR-;*!Z8D>OZce>WTAI2d^ i3^64kNl}uLw2X))8L=fRj^wn;Pw30}V%N`qb^ZY-&v9J< diff --git a/public/font/icon.woff b/public/font/icon.woff index 4bed2250d5963c8db111617e457a68efddb52d4a..6474d86873d5600e7ce9e3cfcd9dd17133cfb3d1 100644 GIT binary patch delta 3408 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 delta 3275 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 diff --git a/public/font/icon.woff2 b/public/font/icon.woff2 index b39dcc983d5e8f168d5e1d77acf8205406d5bdf9..72207f36852f065f781663c05bf57befdcce483f 100644 GIT binary patch literal 4860 zcmV<Y5(DjbPew8T0RR91022HF4*&oF03;{?01~1A0RR9100000000000000000000 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 literal 4716 zcmV-y5|izBPew8T0RR9101|8f4*&oF03#Ft01_1d0RR9100000000000000000000 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 diff --git a/public/js/main.js b/public/js/main.js index 710d84b..0422e77 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,7 +1,7 @@ /** - * Fonction permettant d'afficher un message dans un toastr - * @param {String} message - */ + * Fonction permettant d'afficher un message dans un toastr + * @param {String} message + */ function showToastr(message) { let x = document.getElementById("toastr"); if ( message ) { @@ -124,7 +124,6 @@ document.addEventListener('DOMContentLoaded', () => { if ( currentThemeIsDark === 'false' && window.matchMedia ) { currentThemeIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } - console.log('currentThemeIsDark:', currentThemeIsDark); switchTheme({target: {checked: currentThemeIsDark === 'dark'}}); toggleSwitch.checked = currentThemeIsDark === 'dark'; }); \ No newline at end of file diff --git a/sass/global.scss b/sass/global.scss index 2165147..491b9ca 100644 --- a/sass/global.scss +++ b/sass/global.scss @@ -1,5 +1,6 @@ html { min-height: 100vh; + scroll-behavior: smooth; body { background-color: var(--bg-color); @@ -69,4 +70,16 @@ html { .is-hidden { display: none; +} + +.ml-4 { + margin-left: 1rem; +} + +.sm-hidden { + display: none; + + @include respond-to("small-up") { + display: initial; + } } \ No newline at end of file diff --git a/sass/icons.scss b/sass/icons.scss index ec219a8..05250a2 100644 --- a/sass/icons.scss +++ b/sass/icons.scss @@ -39,6 +39,8 @@ .icon-link:before { content: '\e804'; } /* '' */ .icon-heart:before { content: '\e805'; } /* '' */ .icon-eye:before { content: '\e806'; } /* '' */ +.icon-left-open:before { content: '\e807'; } /* '' */ +.icon-right-open:before { content: '\e808'; } /* '' */ .icon-spin:before { content: '\e839'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ .icon-sun:before { content: '\f185'; } /* '' */ diff --git a/sass/index.scss b/sass/index.scss index a2d268e..7ff8d31 100644 --- a/sass/index.scss +++ b/sass/index.scss @@ -43,4 +43,5 @@ @import './error'; @import './home'; @import './ajouter-un-album'; -@import './ma-collection'; \ No newline at end of file +@import './ma-collection'; +@import './ma-collection-details'; \ No newline at end of file diff --git a/sass/ma-collection-details.scss b/sass/ma-collection-details.scss new file mode 100644 index 0000000..6d4ba32 --- /dev/null +++ b/sass/ma-collection-details.scss @@ -0,0 +1,55 @@ +.ma-collection-details { + .galerie { + display: flex; + flex-wrap: wrap; + + div { + width: 80px; + height: 80px; + margin: 0.25rem; + padding: 0.25rem; + border: 2px solid var(--font-color); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + img { + max-width: 90%; + } + } + } + + .modal { + button.close { + height: 36px; + max-height: 36px; + max-width: 36px; + min-height: 36px; + min-width: 36px; + width: 36px; + position: absolute; + background-color: rgba(10,10,10,.6); + right: 12px; + top: 12px; + } + + .navigation { + position: absolute; + top: 50%; + cursor: pointer; + z-index: 10; + + &.previous { + left: 12px; + } + &.next { + right: 12px; + } + i { + font-size: 2rem; + color: $nord4; + } + } + } +} \ No newline at end of file diff --git a/sass/modal.scss b/sass/modal.scss index ffe0571..e44de67 100644 --- a/sass/modal.scss +++ b/sass/modal.scss @@ -24,6 +24,47 @@ top: 0; } + button.close { + user-select: none; + background-color: rgba(10,10,10,.2); + border: none; + border-radius: 9999px; + cursor: pointer; + pointer-events: auto; + display: inline-block; + flex-grow: 0; + flex-shrink: 0; + font-size: 0; + height: 20px; + max-height: 20px; + max-width: 20px; + min-height: 20px; + min-width: 20px; + outline: none; + position: relative; + width: 20px; + + &::before, + &::after { + background-color: var(--default-color); + content: ""; + display: block; + left: 50%; + position: absolute; + top: 50%; + transform: translateX(-50%) translateY(-50%) rotate(45deg); + transform-origin: center center; + } + &::before { + height: 2px; + width: 50%; + } + &::after { + height: 50%; + width: 2px; + } + } + .modal-card { position: relative; width: 300px; @@ -62,47 +103,6 @@ justify-content: space-between; font-size: 1.5rem; @include transition() {} - - button { - user-select: none; - background-color: rgba(10,10,10,.2); - border: none; - border-radius: 9999px; - cursor: pointer; - pointer-events: auto; - display: inline-block; - flex-grow: 0; - flex-shrink: 0; - font-size: 0; - height: 20px; - max-height: 20px; - max-width: 20px; - min-height: 20px; - min-width: 20px; - outline: none; - position: relative; - width: 20px; - - &::before, - &::after { - background-color: var(--default-color); - content: ""; - display: block; - left: 50%; - position: absolute; - top: 50%; - transform: translateX(-50%) translateY(-50%) rotate(45deg); - transform-origin: center center; - } - &::before { - height: 2px; - width: 50%; - } - &::after { - height: 50%; - width: 2px; - } - } } section { background-color: var(--default-color); diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index fe795c3..7e4d5d7 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -137,6 +137,20 @@ class Albums extends Pages { this.setPageContent("artists", artists); this.setPageContent("formats", formats); } + + /** + * Méthode permettant d'afficher le détails d'un album + */ + async loadItem() { + const { itemId: _id } = this.req.params; + const { _id: User } = this.req.user; + const item = await AlbumsModel.findOne({ + _id, + User, + }); + + this.setPageContent("item", item); + } } export default Albums; diff --git a/src/routes/index.js b/src/routes/index.js index 8a340a0..fca9847 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -107,6 +107,20 @@ router } }); +router + .route("/ma-collection/:itemId") + .get(ensureLoggedIn("/connexion"), async (req, res, next) => { + try { + const page = new Albums(req, "mon-compte/ma-collection/details"); + + await page.loadItem(); + + render(res, page); + } catch (err) { + next(err); + } + }); + router.route("/nous-contacter").get(async (req, res, next) => { try { const page = new Pages(req, "nous-contacter"); diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index 8a26488..bdebb4e 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -52,12 +52,12 @@ </div> </div> - <div class="modal" :class="{'is-visible': modalIsVisible}" id="addAlbum"> + <div class="modal" :class="{'is-visible': modalIsVisible}"> <div class="modal-background"></div> <div class="modal-card"> <header> <div>{{details.artists_sort}} - {{details.title}}</div> - <button aria-label="Fermer" @click="toggleModal"></button> + <button aria-label="Fermer" class="close" @click="toggleModal"></button> </header> <section> <div class="grid grid-cols-2 gap-16"> @@ -217,8 +217,6 @@ this.modalIsVisible = !this.modalIsVisible; }, loadDetails(discogsId) { - console.log('discogsId:', discogsId); - axios.get(`/api/v1/search/${discogsId}`) .then( response => { const { @@ -245,5 +243,5 @@ }); }, } - }).mount('#app') + }).mount('#app'); </script> diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 2362206..7c17b38 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -230,6 +230,8 @@ <i class="icon-moon">.icon-moon</i> <i class="icon-trash">.icon-trash</i> <i class="icon-blind">.icon-blind</i> + <i class="icon-left-open">.icon-left-open</i> + <i class="icon-right-open">.icon-right-open</i> <h2 id="listes">Les listes</h2> <div class="grid grid-cols-1 md:grid-cols-2 list"> diff --git a/views/pages/mon-compte/ma-collection.ejs b/views/pages/mon-compte/ma-collection.ejs index ed03634..b754ccf 100644 --- a/views/pages/mon-compte/ma-collection.ejs +++ b/views/pages/mon-compte/ma-collection.ejs @@ -40,12 +40,12 @@ <div class="grid grid-cols-1 md:grid-cols-2 list"> <div class="item" v-if="!loading" v-for="item in items"> <span class="title"> - {{ item.artists_sort}} - {{ item.title }} + <a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a> <i class="icon-trash" @click="showConfirmDelete(item._id)"></i> </span> <div class="grid grid-cols-2 md:grid-cols-4"> <div> - <img :src="item.thumb" :alt="item.title" /> + <a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a> </div> <div class="md:col-span-3"> <span><strong>Année :</strong> {{ item.year }}</span> @@ -175,7 +175,6 @@ this.fetch(); }, changeSort() { - console.log('TEST:', this.sortOrder); const [sort,order] = this.sortOrder.split('-'); this.sort = sort; this.order = order; @@ -208,5 +207,5 @@ }); } } - }).mount('#app') + }).mount('#app'); </script> diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs new file mode 100644 index 0000000..12d8750 --- /dev/null +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -0,0 +1,260 @@ +<main class="layout-maxed ma-collection-details" id="app"> + + <h1>{{item.artists_sort}} - {{item.title}}</h1> + <div class="grid md:grid-cols-3 gap-16"> + <div class="text-center"> + <img :src="item.thumb %>" :alt="`Miniature pour l'album ${item.title}`" /> + </div> + <div class="md:col-span-2 text-center galerie"> + <div v-for="(image, index) in item.images" :data-index="index" @click.stop.prevent="showGallery"> + <img :src="image.uri150" :alt="`Miniature de type ${image.type}`" /> + </div> + </div> + </div> + <hr /> + <div class="grid md:grid-cols-3 gap-16"> + <div> + <template v-for="album in tracklist"> + <strong v-if="album.title">{{album.title}}</strong> + <ol class="ml-4"> + <li v-for="track in album.tracks"> + {{ track.title }} ({{track.duration}}) + <ul v-if="track.extraartists && track.extraartists.length > 0" class="sm-hidden ml-4"> + <li v-for="extra in track.extraartists"> + <small>{{extra.role}} : {{extra.name}}</small> + </li> + </ul> + </li> + </ol> + </template> + </div> + <div class="md:col-span-2"> + <div class="grid grid-cols-2 gap-10"> + <div> + <strong>Genres</strong> + <br /> + <template v-for="(genre, index) in item.genres"> + {{genre}}<template v-if="index < item.genres.length - 1">, </template> + </template> + </div> + <div> + <strong>Styles</strong> + <br /> + <span v-for="(style, index) in item.styles"> + {{style}}<template v-if="index < item.styles.length - 1">, </template> + </span> + </div> + </div> + <hr /> + <div class="grid grid-cols-3 gap-10"> + <div> + <strong>Pays</strong> + <br /> + <span>{{item.country}}</span> + </div> + <div> + <strong>Année</strong> + <br /> + <span>{{item.year}}</span> + </div> + <div> + <strong>Date de sortie</strong> + <br /> + <span>{{item.released}}</span> + </div> + </div> + <hr /> + <div class="grid gap-10"> + <div> + <strong>Format</strong> + <ul class="ml-4"> + <li v-for="(format) in item.formats"> + {{format.name}} + (<span v-for="(description, index) in format.descriptions"> + {{description}}<template v-if="index < format.descriptions.length - 1">, </template> + </span>) + </li> + </ul> + </div> + </div> + <hr /> + <div class="grid grid-cols-2 gap-10"> + <div> + <strong id="identifiers">Codes barres</strong> + <ol class="ml-4"> + <li v-for="identifier in identifiers"> + {{identifier.value}} ({{identifier.type}}) + </li> + </ol> + <template v-if="item.identifiers.length > identifiersPreviewLength"> + <button type="button" class="button is-link" v-if="identifiersMode === 'preview'" @click="showAllIdentifiers"> + Voir la suite + </button> + <button type="button" class="button is-link" v-if="identifiersMode === 'all'" @click="showLessIdentifiers"> + Voir moins + </button> + </template> + </div> + <div> + <strong>Label</strong> + <br /> + <template v-for="label in item.labels"> + {{label.name}} {{label.catno}} + <br /> + </template> + <hr /> + <strong>Sociétés</strong> + <br /> + <template v-for="company in item.companies"> + <strong>{{company.entity_type_name}}</strong> : {{company.name}} + <br /> + </template> + </div> + </div> + <!-- <hr /> + <div class="grid gap-10"> + <div> + <strong>Note</strong> + <br /> + <span>{{item.notes}}</span> + </div> + </div> --> + <hr /> + <div class="grid gap-10"> + <div> + <strong>Vidéos</strong> + <dl> + <template v-for="video in item.videos"> + <dt> + <a :href="video.uri" target="_blank" rel="noopener noreferrer">{{video.title}} ({{video.duration}})</a> + </dt> + <dd> + {{video.description}} + </dd> + </template> + </dl> + </div> + </div> + </div> + </div> + + <div class="modal" :class="{'is-visible': modalIsVisible}"> + <div class="modal-background"></div> + <button type="button" aria-label="Fermer" class="close" @click="toggleModal"></button> + <button type="button" aria-label="Image précédente" class="navigation previous" @click="previous"> + <i class="icon-left-open"></i> + </button> + <button type="button" aria-label="Image suivante" class="navigation next" @click="next"> + <i class="icon-right-open"></i> + </button> + <div class="modal-card"> + <img :src="preview" /> + </div> + </div> +</main> + +<script> + Vue.createApp({ + data() { + return { + item: <%- JSON.stringify(page.item) %>, + tracklist: [], + identifiers: [], + modalIsVisible: false, + identifiersMode: 'preview', + identifiersPreviewLength: 16, + preview: null, + index: null, + } + }, + created() { + this.setTrackList(); + this.setIdentifiers(); + }, + methods: { + setIdentifiers() { + this.identifiers = []; + + let max = this.identifiersMode == 'preview' && this.item.identifiers.length > this.identifiersPreviewLength ? this.identifiersPreviewLength : this.item.identifiers.length; + + for ( let i = 0 ; i < max ; i += 1 ) { + this.identifiers.push(this.item.identifiers[i]); + } + }, + setTrackList() { + let subTrack = { + type: null, + title: null, + tracks: [], + }; + for (let i = 0 ; i < this.item.tracklist.length ; i += 1 ) { + const { + type_, + title, + position, + duration, + extraartists, + } = this.item.tracklist[i]; + + if ( type_ === 'heading' ) { + if ( subTrack.type ) { + this.tracklist.push(subTrack); + subTrack = { + type: null, + title: null, + tracks: [], + }; + } + + subTrack.type = type_; + subTrack.title = title; + } else { + subTrack.tracks.push({ + title, + position, + duration, + extraartists + }); + } + } + this.tracklist.push(subTrack); + }, + setImage() { + this.preview = this.item.images[this.index].uri; + }, + showGallery(event) { + const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target; + + const { + index, + } = item.dataset; + + this.index = Number(index); + this.modalIsVisible = true; + + this.setImage(); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + previous() { + this.index = this.index > 0 ? this.index - 1 : this.item.images.length -1; + this.setImage(); + }, + next() { + this.index = (this.index +1) === this.item.images.length ? 0 : this.index + 1; + this.setImage(); + }, + showAllIdentifiers() { + this.identifiersMode = 'all'; + this.setIdentifiers(); + }, + showLessIdentifiers() { + this.identifiersMode = 'preview'; + this.setIdentifiers(); + + document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' }); + } + }, + }).mount('#app'); +</script> \ No newline at end of file From 30d0713f7990e529cf8386dee5faab3ed36b9d89 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Fri, 4 Mar 2022 08:14:29 +0100 Subject: [PATCH 02/28] Corrections pour l'issue 3 --- public/js/main.js | 12 ++++-- .../mon-compte/ma-collection/details.ejs | 38 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index 0422e77..994b2ae 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -114,16 +114,22 @@ document.addEventListener('DOMContentLoaded', () => { } const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); - switchAriaThemeBtn.addEventListener("click", switchAriaTheme); + if ( switchAriaThemeBtn ) { + switchAriaThemeBtn.addEventListener("click", switchAriaTheme); + } setAriaTheme(getCookie('ariatheme')); const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); - toggleSwitch.addEventListener('change', switchTheme, false); + if ( toggleSwitch ) { + toggleSwitch.addEventListener('change', switchTheme, false); + } let currentThemeIsDark = getCookie('theme'); if ( currentThemeIsDark === 'false' && window.matchMedia ) { currentThemeIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } switchTheme({target: {checked: currentThemeIsDark === 'dark'}}); - toggleSwitch.checked = currentThemeIsDark === 'dark'; + if ( toggleSwitch) { + toggleSwitch.checked = currentThemeIsDark === 'dark'; + } }); \ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 12d8750..12490c0 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,11 +1,11 @@ -<main class="layout-maxed ma-collection-details" id="app"> +<main class="layout-maxed ma-collection-details" id="app" v-cloak @keyup="changeImage"> <h1>{{item.artists_sort}} - {{item.title}}</h1> - <div class="grid md:grid-cols-3 gap-16"> + <div class="grid sm:grid-cols-3 gap-16"> <div class="text-center"> <img :src="item.thumb %>" :alt="`Miniature pour l'album ${item.title}`" /> </div> - <div class="md:col-span-2 text-center galerie"> + <div class="sm:col-span-2 text-center galerie"> <div v-for="(image, index) in item.images" :data-index="index" @click.stop.prevent="showGallery"> <img :src="image.uri150" :alt="`Miniature de type ${image.type}`" /> </div> @@ -18,7 +18,7 @@ <strong v-if="album.title">{{album.title}}</strong> <ol class="ml-4"> <li v-for="track in album.tracks"> - {{ track.title }} ({{track.duration}}) + {{ track.title }} <template v-if="track.duration">({{track.duration}})</template> <ul v-if="track.extraartists && track.extraartists.length > 0" class="sm-hidden ml-4"> <li v-for="extra in track.extraartists"> <small>{{extra.role}} : {{extra.name}}</small> @@ -70,9 +70,11 @@ <ul class="ml-4"> <li v-for="(format) in item.formats"> {{format.name}} + <template v-if="format.descriptions && format.descriptions.length > 0"> (<span v-for="(description, index) in format.descriptions"> {{description}}<template v-if="index < format.descriptions.length - 1">, </template> </span>) + </template> </li> </ul> </div> @@ -126,7 +128,7 @@ <dl> <template v-for="video in item.videos"> <dt> - <a :href="video.uri" target="_blank" rel="noopener noreferrer">{{video.title}} ({{video.duration}})</a> + <a :href="video.uri" target="_blank" rel="noopener noreferrer">{{video.title}}</a> </dt> <dd> {{video.description}} @@ -141,10 +143,10 @@ <div class="modal" :class="{'is-visible': modalIsVisible}"> <div class="modal-background"></div> <button type="button" aria-label="Fermer" class="close" @click="toggleModal"></button> - <button type="button" aria-label="Image précédente" class="navigation previous" @click="previous"> + <button type="button" aria-label="Image précédente" class="navigation previous" @click="previous" v-if="index > 0"> <i class="icon-left-open"></i> </button> - <button type="button" aria-label="Image suivante" class="navigation next" @click="next"> + <button type="button" aria-label="Image suivante" class="navigation next" @click="next" v-if="index + 1 < item.images.length"> <i class="icon-right-open"></i> </button> <div class="modal-card"> @@ -170,6 +172,11 @@ created() { this.setTrackList(); this.setIdentifiers(); + + window.addEventListener("keydown", this.changeImage); + }, + destroyed() { + window.removeEventListener('keydown', this.changeImage); }, methods: { setIdentifiers() { @@ -245,6 +252,21 @@ this.index = (this.index +1) === this.item.images.length ? 0 : this.index + 1; this.setImage(); }, + changeImage(event) { + const direction = event.code; + + if ( this.modalIsVisible && ['ArrowRight', 'ArrowLeft', 'Escape'].indexOf(direction) !== -1 ) { + switch (direction) { + case 'ArrowRight': + return this.next(); + case 'ArrowLeft': + return this.previous(); + default: + this.modalIsVisible = false; + return true; + } + } + }, showAllIdentifiers() { this.identifiersMode = 'all'; this.setIdentifiers(); @@ -254,7 +276,7 @@ this.setIdentifiers(); document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' }); - } + }, }, }).mount('#app'); </script> \ No newline at end of file From 0b8b4b21bd42548767c0ef71368af1bda986dd51 Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Fri, 4 Mar 2022 08:23:23 +0100 Subject: [PATCH 03/28] Ajout du logo --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c787da0..fdc754d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # MusicTopus + + MusicTopus est une application Web (que vous pouvez auto-héberger) et un site Web (sur lequel vous pouvez créer un compte) permettant de gérer votre liste des CDs et Vinyles et de l'utiliser facilement n'importe où. Le code source est publié sous licence libre [GNU GPL-3.0-or-later](LICENSE) et est disponible sur [git.darkou.fr](https://git.darkou.fr/dbroqua/MusicTopus). From ac72c1c13c40f71ec0164297727a70255bfd34eb Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sat, 5 Mar 2022 19:43:26 +0100 Subject: [PATCH 04/28] Ajout du nom du fichier lors de l'export --- src/routes/api/v1/albums.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/api/v1/albums.js b/src/routes/api/v1/albums.js index 164ea40..c8f0f1a 100644 --- a/src/routes/api/v1/albums.js +++ b/src/routes/api/v1/albums.js @@ -19,9 +19,11 @@ router case "csv": case "musictopus": res.header("Content-Type", "text/csv"); + res.attachment("export-musictopus.csv"); return res.status(200).send(data); case "xml": res.header("Content-type", "text/xml"); + res.attachment("export-musictopus.xml"); return res.status(200).send(data); case "xls": return data.write("musictopus.xls", res); From aeb5df067c77692a7c529834a2761e6003313f88 Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Sun, 6 Mar 2022 14:38:26 +0100 Subject: [PATCH 05/28] issue/1 (#31) Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/31 --- package.json | 13 +- public/font/icon.eot | Bin 9408 -> 9976 bytes public/font/icon.svg | 2 + public/font/icon.ttf | Bin 9256 -> 9824 bytes public/font/icon.woff | Bin 5844 -> 6224 bytes public/font/icon.woff2 | Bin 4860 -> 5208 bytes public/js/main.js | 7 +- sass/{ma-collection.scss => collection.scss} | 7 +- sass/colors.scss | 17 ++ sass/composants.scss | 13 + sass/global.scss | 4 + sass/icons.scss | 1 + sass/index.scss | 5 +- sass/toast.scss | 5 + src/app.js | 9 +- src/libs/error.js | 4 +- src/middleware/Albums.js | 66 ++++- src/middleware/Me.js | 45 +++ src/models/users.js | 16 +- src/routes/api/v1/albums.js | 2 +- src/routes/api/v1/me.js | 24 ++ src/routes/collection.js | 22 ++ src/routes/ma-collection.js | 2 +- views/error.ejs | 2 + .../ma-collection.ejs => collection.ejs} | 57 +--- views/pages/composants.ejs | 90 +++++- .../mon-compte/ma-collection/exporter.ejs | 4 +- .../pages/mon-compte/ma-collection/index.ejs | 279 ++++++++++++++++++ 28 files changed, 630 insertions(+), 66 deletions(-) rename sass/{ma-collection.scss => collection.scss} (90%) create mode 100644 sass/composants.scss create mode 100644 src/middleware/Me.js create mode 100644 src/routes/api/v1/me.js create mode 100644 src/routes/collection.js rename views/pages/{mon-compte/ma-collection.ejs => collection.ejs} (78%) create mode 100644 views/pages/mon-compte/ma-collection/index.ejs diff --git a/package.json b/package.json index 8c53d5a..b5aea27 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,6 @@ }, "license": "GPL-3.0-or-later", "devDependencies": { - "@babel/cli": "^7.17.0", - "@babel/core": "^7.17.2", - "@babel/preset-env": "^7.16.11", "eslint": "^8.9.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.3.0", @@ -38,11 +35,12 @@ "husky": "^7.0.4", "lint-staged": "^12.3.3", "nodemon": "^2.0.15", - "npm-run-all": "^4.1.5", - "prettier": "^2.5.1", - "rimraf": "^3.0.2" + "prettier": "^2.5.1" }, "dependencies": { + "@babel/cli": "^7.17.0", + "@babel/core": "^7.17.2", + "@babel/preset-env": "^7.16.11", "axios": "^0.26.0", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", @@ -54,14 +52,17 @@ "excel4node": "^1.7.2", "express": "^4.17.2", "express-session": "^1.17.2", + "joi": "^17.6.0", "knacss": "^8.0.4", "moment": "^2.29.1", "moment-timezone": "^0.5.34", "mongoose": "^6.2.1", "mongoose-unique-validator": "^3.0.0", + "npm-run-all": "^4.1.5", "passport": "^0.5.2", "passport-http": "^0.3.0", "passport-local": "^1.0.0", + "rimraf": "^3.0.2", "sass": "^1.49.7", "vue": "^3.2.31" }, diff --git a/public/font/icon.eot b/public/font/icon.eot index 8664abe7c3bb927c5503dbfc1ac1e0ec80045291..a17c0f6c12920aacff321df497143f171c000437 100644 GIT binary patch delta 1165 zcmYLITTEP46y5utd(W8{bD7I<hbrYUaEBrkC(JM-(jo<|Qov`l0VC}I!(1N2OlHPP zeLzi|e(*tP$29)X_#kSGA2kyiqb3^lr+@u0{P@D8AH*~fNKEL5jC*EmyeI4Iz1G=h zpL2Ki`EKxFgdg|-&ik(Lki4>4xO1iNjperi@L3>rG9F2;{QlGT#BTsdmnJV{8c3(l z^C?Zv>0~TE(Q)r};$ISV#3Jb=>6OIaCu@n#Uz~n%tS$y1`vFwH8I4TUuZ{l&P<5Wx zLnaBr&nAe!N4(jLXBK;xUMk!r;Tsa3nomqd8b3a|0^pX3`{I$sBz8G(5N{{mun>ty zbBp`Oi4W5S*OQ5K#y|GhH2|G*Kt7p@CYNsZ+ypRw%&p<ecrkYu6R{0IS_QZctmL(m zwb4&*fD9?pD`_x*9qUf_?&{v<z3sh&{q;hD6eua#|CLgn`xDYWZ)r3GjoepKqo@QI zW#%9KA)!Wbj#BI||Lt()c3I*TYw;*j=n6aCWq=)EceRY-p1o`tP(k)`8Q>(lT?VLR z56YlR!2Ws};L2~vzw%`Xpm2|Uz@CRzI15ko^@9a1S&$X3$ovv;rf{}IwU7i#Gax{b zg%T^)fSm=#;2b&d%nQ$r3>ijkSE~`Q@}q57?dDC5USF3h7zzhEb>S9V@HX^v;d1Mq z#?H`542DDDkY2Scc$2rqcM_Ytf_rooy$@ptYYFy>aG;A~0-g1!yO}mVaOfWxA0Jp1 z4piIf6}i_4VyGE!8$ny8YVo|YWmARFM>qTWk4P25syKK<jfS#AYd+RDJl!*@3*oeH z+3}&ZVj8^NKaLKrvPab_DQFz}OW9ttE5d2Dp>4~~tTl6kBVSi=6fWzD&Zmz)=nr<Z zAFF-9BF)uUnSIMvmw(ItIa{WFDSW}km;(%Wyzf+fm87s{n<Rl1QDVa&GdT-PVOdZ> z$#N<rWI;kH3y2pG>S~YFXq6RqMFs;6!Sy!m)ZN_c3kSn3R2pB9O5h3mx`Mq}hrGqx zB6PR#grrZ-nC82)*p0UWr-w{)=yc!~n%AbK*}*5tBNaWfS7&Djg=5N3j(^G_&Y05t z+B}l$Br$KO-%7Q23Y#nE)DwS$%65va_uF8_XZP(@EsR?35v{fOX4y_*w-{2wXyK5J z0_2ifU5)IJQ}TiGrn1d@`JbZrT#Ii=%YUf-Or4!us&0@`!>Zi%>K;FjE+!MHjGRu+ qELi4e7Uqse7c(sV>Vh0kBo>sk8A(NzOe&H#l?hs&%KuaSgY#cdItO3? delta 634 zcmXv~OK1~O6g~6FB<eJiNVK686Euqotq5X2)<qR73JNYnP%Czt<|E`YG*RM*wwrWg zD7LdHVyqMtm!*Mj+!S;n0e9}yqPr}DWCB8jVmvcL@8i7t&bjZN_vu{UxvK2MfPvVG zGAu{7n(HeQPZt&e;Rb-|cvd%FZVbLA-vq>|_@iQ!I^&4J$)7Y**_l&+pO7z-swur- z>jCmKvoDo-oE$ipXTPihJ)`M_J{x&H^cDzSVtqJG!FyVoCVxgAOJ|Fvk@Br>lfoMc z6PbKm@9R4{MgEigcvdeN!WsW2a9Adf=JagBEVYNo`3kXW<O{|5FZb#|=o9N3#$3Xf z-yYuvPE^bn!d;<cHii7c8U$8>RY=Zikoh1Vxds_Y>VEhjLTUIKW36Rt$?CML-7e68 z!^`{J@o*=BJ;$68MU;}jeu-`#DDcwl0R0a^MGyCByt<&6mbkYnc{P3v!@jK@nz<+C zC&zdeVc)5>?1J5}$}Z>wYsm#2VRc;48||tKXtmGs52f}?eJ|A}gCE5H_2yROzIOf( zG^t@9wR`!YXFr?$p%Ecq-VB|*(CD`8x`W%@e^QWZ!ig)W;Jc6!uZa~&mPVzV^i{qi w?|LqJek-rI!~WGktseR&((GMt)U5Z8n`$_$7U%Rr+B1`RkefyAZ1|)9KOAYD0RR91 diff --git a/public/font/icon.svg b/public/font/icon.svg index 51f7015..d5ac2f6 100644 --- a/public/font/icon.svg +++ b/public/font/icon.svg @@ -34,6 +34,8 @@ <glyph glyph-name="moon" unicode="" d="M704 123q-30-5-61-5-102 0-188 50t-137 137-50 188q0 107 58 199-112-33-183-128t-72-214q0-72 29-139t76-113 114-77 139-28q80 0 152 34t123 96z m114 47q-53-113-159-181t-230-68q-87 0-167 34t-136 92-92 137-34 166q0 85 32 163t87 135 132 92 161 38q25 1 34-22 11-23-8-40-48-43-73-101t-26-122q0-83 41-152t111-111 152-41q66 0 127 29 23 10 40-7 8-8 10-19t-2-22z" horiz-adv-x="857.1" /> +<glyph glyph-name="share" unicode="" d="M679 279q74 0 126-53t52-126-52-126-126-53-127 53-52 126q0 7 1 19l-201 100q-51-48-121-48-75 0-127 53t-52 126 52 126 127 53q70 0 121-48l201 100q-1 12-1 19 0 74 52 126t127 53 126-53 52-126-52-126-126-53q-71 0-122 48l-201-100q1-12 1-19t-1-19l201-100q51 48 122 48z" horiz-adv-x="857.1" /> + <glyph glyph-name="trash" unicode="" d="M286 82v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m143 0v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m142 0v393q0 8-5 13t-12 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q7 0 12 5t5 13z m-303 554h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" /> <glyph glyph-name="blind" unicode="" d="M204 677q-35 0-61 25t-26 62q0 35 26 61t61 25 61-25 26-61q0-37-26-62t-61-25z m308-359q0-28-17-37t-35-4-27 19l-205 244q-4 7-8 9t-6 1l-1-2q-4-4 2-12l68-77 1-198-90-255q-38-107-52-130-8-15-15-18-28-15-58-1-16 7-23 24t-5 32q1 9 110 345l3 232-48-91 20-124q2-14-1-24t-8-15-10-9-10-4l-4-1q-10-2-19 1t-13 9-8 13-4 10-2 6l-25 167 118 212q12 19 63 19 41 0 59-22l237-291q4-3 8-9l1-2 0-1q4-7 4-16z m-225-83q24-64 49-126t39-94l13-31q21-51 24-69 6-39-20-54-20-13-37-9t-28 12-17 19h0q-4 9-5 14l-69 196z m460-331q17-27 17-32 0-3-2-4-5-2-8 1t-8 14-9 17q-64 96-236 369 1 0 4 1t3 2l2 1q6 5 6 10z" horiz-adv-x="785.7" /> diff --git a/public/font/icon.ttf b/public/font/icon.ttf index 1f0de7e581f6d9e5e42b4de9768222fc8c0c1ce2..4ccfa6dc9101474be2e0317776cd1611e5a2667a 100644 GIT binary patch delta 1160 zcmYLIZA@EL7=GV-?med;m)?GH+bN%gmLZ4@Ep3nu8MrwHQ|H$NV@H9)9fQ#nGMO=S zCU!qaBxudzAC{Rcx)^`VG+RtGF;V~Q-+mZk;uk;EG{!_qV$%JgzPEMpo;>Hg&vV}U zoR7Tc*2&%Rt+wpj0QfurOwBAM(;L5j{|)im0MeD2ORFuU)91TIontONzck(R;7#IR z5LM@sD{0c5#Bb1OnP0p-_wr=ZJb)Yq(85M4IorII_#418Me{L(1QBM_#6Kk7ZY-^? z4X(dZ`HO_FNI1E;Jd<qwxN!r(D-#bbCD+o}=eSM0n|RB`<WefXw*NTsaoXT^dU<6v zJo(f$0G&Lbkj|vi>vsq40tCN%ulyQb&cB4q*_(hg32+@)HR`6(MrXVUGGs`1Qeyx` zYq5WCvvjqzTPp8wS1P1HRmuIYRDB+HNdE<@tsTrpmLQRn3<_`%U;!(wV|RiIb>IMv z(p{9p^Jk=TeoC@yYSrnd|NLC}SK0FGR&yhy&<2bBHGmypZ?gvSf>N#qa)r{>8pt0? zyEV{CP%76zYr_6^4d5!M^3Oag0aPBakJt;)31{K>&@fogk^@=cip;M7X9{QQ=2-+v z=jj}ZER=ckhS*tP49-yi&%Jo&^jI+1(AOD^Sb1X?)_ZwdYarC;ipJuRUR`(v7rYAt zT)4ctueCRJ6r=H2Jf?fHg0}@aLPxPJAh=K0Fz^KSu#V`Uh)4P;C(_%Dx|g{VBM0G; zL}Fx9sHk-{EAn74im`UQAB@_Zn#K3dj!hFnpSUwLd`PMjRz>A4emBahyZ!Lc_}svR zE`-CrW5>s~OdGsEoIsUp>`BcdMS}<7s@ofOML4WBwC&iL)qi31^p|xUh0A)R_u0lr z!_l7Z!wrvEqzis4v+vlN3cuSvV_B-E%I9p7sSt#xhmJLSB!#uxBnhmD5*r7Z$vI#O z%TXODIc^q34kVOvfOrX^so{{{?X0saG6XRwxZZ`mx|at+@o2n*t|k<vEAYibebGT| zLf#SR5V}|RV$vt44C8|Z?8keN6Jv%kb|P{Qjcaq#!ss&;k&3>B_ZJpMg*qFiAUvyz zQ--v-wTKitNG!bT_*!cCt#W7MoOa|N&{)wdy<bM_K7Bam4Obl9`5UfI^F^~_WzWp1 zVxn@uCIIq3xSIU3Dre-f@|Lp82l=1Ee9Yz(a~EvxA0_(#4{wW%ZuI1N{ea-9we)gk fHGiz$?^-dEnUu1cNv;^m^y2x8vxQXsw~l`SI$-&N delta 571 zcmaFhv%+J7a(x2>17iRK14BY`Zeqc#MLP3<{0$5YOfku2B?>@sAP_A85`1X|>A6XI zKdXTJ1|XLwJ+T-hE&$|b0BMf&oXRu}t9+n-79Iu$UW<&>#1#2%-K7i+f-XRLvkagB zy8&|okY5Ajt7PPsRG7Dh{eJ@#SO64o$jMJml#&vQ0rDRJ`AWHo6$OmOd>a@Tgjj%l zg}lVv)X5c}bb)*opabU?<QJFJosB)kz#wu2D8HznD7Bz&oAovZ2IZcKKcX2cCWkQQ z$IoD31WGb6Ok!XLvvhzo7toJ07+4t6fZ}`%91KhhY%e%oSbpgH*!J<qr@8<CGcdeh z0P|a6e6Sf1f)S`k4h$6-6eepiDQ@;>;$h?jxd8<DC#NvyyIKN`0Ai3;ANnAeft!Kh zV=DwR0E6gb8w4}(0_h(R3^bbI(_9E<;NNV(a+hthC(jDz&1(gAGs(<<vq?Ul-{va= zKl2NaRrlOVSz+|#7GZNnfysM>HThrs{{WH)<H-O2CjSwZ6Jfr>62)?l)rs{T+Z>=4 zP?!j8HWInb1k}SPt}xk1+**aDxF9o+BPTO2TQ{|$gt@phk0m!hKaaJfD6u$$H3=w@ L!mxR+_+dT(LNb`{ diff --git a/public/font/icon.woff b/public/font/icon.woff index 6474d86873d5600e7ce9e3cfcd9dd17133cfb3d1..32621989e6e774018e5a3e688d923ab43ac1a6f8 100644 GIT binary patch delta 3695 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 LsXhR0!x8^K5d5_% delta 3379 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 diff --git a/public/font/icon.woff2 b/public/font/icon.woff2 index 72207f36852f065f781663c05bf57befdcce483f..f73baf47585f0d4dc4df930369809d57ee27d3b2 100644 GIT binary patch literal 5208 zcmV-e6sPNVPew8T0RR9102EjN4*&oF0487n02BcL0RR9100000000000000000000 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 SOZR}g=t0ffALdsEwoCy}C+chf literal 4860 zcmV<Y5(DjbPew8T0RR91022HF4*&oF03;{?01~1A0RR9100000000000000000000 z0000SR0d!Ggj@&?37iZO2nw1wmSGDb00A}vBm+PMAO(dT2Z16CfgBqrAu|ItW%C^( z`>zvB89TF>TC2jT8n>b1xO7HTv}4b8T$57D(!8H33>q~ZV{hX%R9@3(5dSC3($|fU zWEP&PpJgvIt4jY%+=Lo##u*I&$gBo30BUS6bKC*pxN&#vQOnr#yxaaa3%E%fm_?O6 zN}FhtFe;giSdHGO-H!N**8wYfX(!_rJfFIfiz;CSzVZWP2k5{d+1@<_DJb9iwy3Wt zGw!M<A0)~D_yJ38x%&(Ov}El)c#0}O6Cm*YC+d@?%&;77(;nK4UB}n^X+I+gD+0ct zKj5;g3fR6z0RG=x|Jr*enX|L-xg;?K77`^JS0QgEn=~`Y15Wa3;S*#(@j@AoFUxoQ zM_4Ez$VxK4&ehs~DunEDI1)E{Q7pXv{$D!l{XxMj3r6i_Cg6(c!T(4ENHW5H^vJO@ zSIFK|dE`FYZ~%b7a6`FgbG<^;t>SQiQGeh)u00{|z=J-e-vr(=`{U)8jEaKd4b;<x zwWBM1x5l48+NJGUZn01DQE_!36yXs@IH1^=e1NXrl?kLf%SaMkx&~x9Xb-jM%j_R# zzWG1@{86CBCa=~ZCH+Cy5Q^R%q<2sG|9C{iBuSAbLzWy!pva{u{E!tnsL>Y)uL+db z1jcIu$7=%5Yl6UQf&{M#B3={3h1#qjS@`4I1gXNGszan*coPJf!k_*r$QJ(mQK@bn z`p;!MK;XgGL1gtGjhj`%-8av~B0Vp0EZx;-)SCa1lw>;6BV#SR0JZ8-VRRV+igBEw zP^&h>AnmF3u1vU+dP<BgX|Hr?PnIb6PK2~aSvD>1`lQ!PhS9O(=axlT_SydA-Yr3? zuCLc_8J)tlKMsA9W*P%mV}V9`pnhm_iD@IP@Ldyhn0)Jdw9a}|6Arw^EbM&ujkTt` z;ka=9QI+&CGm8~7FF_KNo0cBxZcvSl=n*dXZc4_|y(zsRsbZvyc%59ZDVU042yI6} zjWkDa>*1`cZ0H-xy4LJEYyRbBL_NVjFOv}3CLkdM<sr056V-(b52*!bO)Df=puOZB z5yZyY_G-)swkCkEEF~sR2vXP3$S$uUh_Z|y+rm4ibjAPAYDgn_!zJ53Zkc8$cnvlP zr8^JWxWtuXe;El7^I1>E#}K7s?^M8Cvq2|gWJ*M0ERz_bXgbT$KPMBZfTR=yc_dcl zp_1t1hOl-|ZPY-jK8nJ$fD=nlAkL!H-L6zxOAoW?jnxzvDOoGRtQuBd{=sAp9b)$v zkfs%6r8e!KW@?f>*z(q>4Nq-G4oNcytn`#K@{ntYkmJh1wqNF5AisgPm@j7(pdE`G zR{lAecDqsvjMClZ92G$iTZh#>pWJs9?vwe^lL+b;Z2Q&S{*bZKsJ~<b5}k72uRFFt zQb!>KeU_=YlpA1v6J$mOGozF3*2J0X-sGCxFh916q^r=-K<&*LEeYf}^hE4T15dT$ zzhfrkl2!;|s~6Sbr`CvxMCM@6Q|F!3WW6KfAv%eFTjnOEJ#lHmIcGdHw2h>xp%i#? zSiBc9nvm!}%V>5?poJ;4GJ`hepq+W>U;#Q=0=ifP-7KEh4|RQt5Lfs7yOJXxx0GjR z3=lF+=I^x!GkeXyNh@N4TrhC~lAiY^)R~4m(BV#WxC<TbMu&UQ;a+sO4;}WP!(MdQ zhpsvVDWVmIYa=aDN__{VKgkU<l&*s#sQ5wp<7-kzAu^OeR<xPZJN4G7+87RF#a<<4 z3kS)zFGk17Yrpm{gMs0=ZCa;SCm`>Xo;!&Ncz}cU5+tb699LoH475e{N$1jp_RXA> zDM%qh)gvJP3uR0rzgA?-u#uyz-MZ;4lFG~j$yoyamhu@ccW*lKLv6$#85<Dq`We;! z<);~QfEk*-!x_$44xiGpM~csmyCE-9bTjCB=0_zLwTR#yXCmBnf3j)m#-BPj>&&q% znOz9(UUqqs((U4obP)OT5mj8?(-j|)_TU&>#j|PE7LJX<Gxi$t$Bjzo5fU9Vb4E#W zfZFM_@Ptt+t)MikqATOA*%~q=xI_YG$`1ElbeDD(g^UF^TuDUmNu<SGI<q)JlI9Pv zY)}TUe?Q*Qr08Udu5s^kDb$)f`|}0whnQf&eU_iM6g}89(cM64AMgDKrqc1=h|KdA z<GA+x8<n6v(t)%czL|c@>;x}G$kT}So>i`U@b)`q>wym)*E08OJ`CnUW9cJcK6FJb z)9XogGi+2VGiRh5j*c*snqlJfs3EP2ddu<pDuv`aC;D&v7*aB44rb!{UolKzX^y-P zdugF_#-_@c4JyL%d8|Eih&mCF9<y3YL+bR$lVSfRKiqTc_Alkc)cD4nT5=i*eGM@U z<uhRRgtG@@rdG*i(CtbcHGB@yOa=R{IgcJ0FucGNF6Kn=5>tJdjze}u0$k0B;5DYD z>o`SYscvXPp_~`NTMX5=PT3^8jVZgGDZ7KIzOy~7*<E<RDtN$_@IWuqafz+SIjrVH zaE)oOt`b&k!x_S6UIe!c4ekaBc#olQZ(aoNGj#R+NNjj7cwl1)b;WJApi<i`?i8r< zkz@$c#lcn%8f_ee_6Z<fAa<DX*lEUxT}xC!H-e}@yGw)~4uWF>NDzg+W&-w^3D|EY z;D8G-$Uzw5pc=**IgXfe95v-QX3BBg1(@I<OmeU~6>@m)v)a@_pMb+&9@ojU*~|cD zPL3-0AhzP5C&$$JK)rq_Ajr1+)uEABMFKn4wQ&G<5kP7pgd=4H)o6iqQ38dx<#D?y zIGmq915c$hsYC*SOpX!w5+o!V9`=ak;A8~6tDFojR7LxxDk6C)KfF|4C@xonO97xR z<_7KV<t|FgG<-43UVm@!iYfon`xO~tgK6dm4+<7J?hC2S{E4iieFQYK4E)DRI3^cQ z4!uy}@P!8-sv`O6QC&(0>V|NWOjM0)LxikG6QqWm*vMS$*XJfc4PGuK8M{1`hVdU$ zDxcM_;^E>qD5#$&1)^5w)EX1V{6dAa7S&zgF)=D(ja#bO5bLd|zRr@Rp}1VB`fPcO z%bWRB{6tQt&&0wdFJ^n2!JeyW<SC!G`1Ndy^%8AC_SGvRcgK|f`n2})^Y2-M-6O}F z^vRUI3|w01V2;DypU<`k3#AK;YHaqw@v*GuIquK7f3!v9XaE2ICCx`SZ8jWYEzuS$ zlC6y@CSmsACPw8RWUzUylMcI-o}J4wmRzp!PrGw8w8O*$(aP_P3R)$_VK$H@mF6~` z3<BfZu!N0OCJ0eq;zz*{{<9(dI~36fK+a!@D;N3@ZI~?qI(Qk>N*7OxbViru9<nsi z5C^&qu)K_H80r$T3DbTnSsIGQnEYc2*DwLZG($YRx6k8iFu|O|B4Vbp+lhiK6I-ys zt2Iwt3CA8`?qIEH-Z)g)D3Ur*0)U6YHn-dXs6~;6>kS)?hUvSaXm}*;xWrl?SGPnD zbIJY-H*4iD@!NmpKQ{9R_Yk!fLLC!=U5<u*RkkB(T*)voO<G4#4<KX(qO29D+T|vW z1A}k++}xYF?G=6Z22f}bU4M~7ZCg;fB54fS9&-q0o~Wa^7uCK}{x|tehJF14z4NJq zUQ51wp?rX__~1?ZHHEh{aoQ~SEqpqTi@;f-j5RMV8uYh~`XD8@Ai#>rn7NQ`cO30< zcpez&+XD{Sxq|~$ix)ot7calPc;y4{#Z%M@>ZusX`SWEOyJ>NJRgHWW+!X7qYj8$y z@pLNdDq@!d$#zZI`STi441VrjQra?Gw(&&txlJDauc+6ujLU6lq`jCkCK(0=%nPU- z4s4NO)_50M6tz|)PxLSKSNlPax#+XlcS<C$&iX=Z)=7%EVug&8;VFl>vgczIJ=_Di z5X2pJ2J3=14f@}Neez12!3cBNkRL)_W|Mn-J>^H6%^iYDW*6LT9sUpotg+U`<0v1B zaZk#3h&or}e{E4g^nUCEqkZW@;E!WJsKL?in>YM^^amUAZhZS|-TrIpf?auOKcLIW zera60bD{@e9fw{$;)sv@sr6$xoKwH|_2s6b%osvU`cR+3PhGWm=>u@_Y>nD4Aa&Tp zmJefIpT-P^x$cd+vNG+NrT`IYbp+q^<sH)Ilzt4#TZt^$%-fREN8Sb(#H>qkY|%M) z3HSm5N<;JWcLnZRw|*CGFRfcmTm7auhH9z>S$ayl19$U#6#pb*7wl>~fsnKrr_mI$ zav0aWb2tS&wc7mFIlZiIzaGE7^inbJ_(rAAJICw#agEWh$hUx;ODRyPjK*qG3E`SX zm7E;45=gVqlLW;Ud0O0EW;qjV6|@suiFw*0Ls%2b3}%Cwk=taN-<vQq`LMd6K>hF% zRBfn$2?oV-uqbANEB1QGwMIZy$~lbPZR6Q1>O$+GJtZY|by~dCjk6h^UZvfE84Ggl zNt4CqLM7ahWksYTo=9j`9^5p!(n+`d_N6IG?*`3*1~*Ig;J&dP>9Wa8qON?Bo@4t4 zw<k3S33X)?%Y<G*y@T>eEit2PSfQ_eCc3B#-B0P2d&(;I0p)ekc})!GIiTGTJyMW2 z60i3#&8oN7OPZgI^<-^OHzE3UU}lDVO+yQ+wR-f&GyZ{*g;w*#)V3+0x?2NuNW-W> z(X2k~?D1wWTg;ik90h}h!T<OxB#J{O$v2jL2bK}S#A@zCu2?yrBt337dwKc8XH18o zbwl3Yr%ngiceb)&gIEj76JdO1AR*d$_g5K$2gO<HWZ+tGS?%-Q((~{Bcobvn(>y+h z=aTGHE4tD<Vli3TBl98pZf|>XSS%!e#}Y|FQ1&se54L}}uVQAdkAsSGfhecB;yODA zi&thWm&%LwY-fH=A%De>xL*Nmf89P{C!P4N8b_dYv+bYs*>6l6F5R+Um>GAxtp7D@ zX)n%P6+v!#F|h0KofvnjV4UAIUKK|{hmQVdCtiHjM4VJ{>Z&MO1s<z!)--2l_<ap` zgOC*yJ%gIw#DO?A5i=<xViip6V)HK3(5cYg|17raV5aq`pl2{ejTb-^c@B@7*LB1> zxsHgt6DRKc<l^M@U2E^9Z4P38{kY=lQz{8NbFE+FV?h&o`mNe;vowXb*!hQ$gdz_S zVLq}=Vo1bn;z)=~Y@s1&8P!lBXt>y3nmMRqK7OHoK@y@b)L(zOlr7(AZVp?=^<9$> z-ThBj0T4N(B1qK)9ut}yr4${~;X+gYb2z2yG$WXv!C%AF*$WusJm&@o=eVGoBi`E) z{B$pV)27n2T$>4<e565|&{PUo+T4txC3QV<zN1d02lT1UQoV#``0P*ih=hWuOb?;c z9parkLy%C#saNM;z?*t|4ndvhI*FpGG}YR3?bUTxhZJ2-&K!sxS{&*MlNP#KQN_-1 z4~r#2UKN!5ZuH{RWbBw^XvH=CkvNm4xz4I-)0lI4Pfs~!u<xs<XEh4FJ3Nh((Ek+D zBtkl|o|K3Xl`|;~P8A7kdQ*vGDbHx=>P}*b&zEmpy>$NU@neTedv_W&8ZP8O^)z^X ze0P!C7NaLo^d<tWcR|Gj;i51-NjV~kJSR!=;!VB%x9ih)<`({jU1&9cH;z5(XUE<C zqcZ!AS%C-zZs!BT7^_v_|3>2k@*--i0Qz$me_~sg4+|(>6!hovXm7V-<!u+gEVD>8 z&t0f+Jd_R&@mAoGY#KDbjyKwbNq1!lj!8>NFl!l(5;}gw@}xLn1ss>HB>h}Zr>N&8 zjAvaucZ>IeB}8mnN>&n<5lJID-f4N7WRD6tK5r$tCWl{=Vy}Gge?^e|y~RJP-233% z&HLA-Z?u!Ae#p<n<wx7@ufdT6dEw^1C8P7YUiWPbYd-wg`q(3ntYxqD9TVaGbVE01 zs800T<}mm308jD|o2;|JBNXZ3FufERV}#l)F0xcr#V&_~@TItq3iren9_<b`u<Bu~ zN}E~>)O{JXT2=Qv@es>K-TQ@L8H<<Hx+SYB)Wdi)b*&=B1e@i?MMfE7q`>2ZB{irB zA6QW<ewfFY8p{w5))WXHYVrAs98+a>kq7W<D^kn3V8~$gh<R;u3^T0ysDf@nl20Qq zDke_61W8CFu_PrWX~{@da*~$<?(_rgvJS$Kd7xchg?1-81pYLMWw!d{YMfJ`2Y<b= zlPJzIat}Yypd+SeaP<i``0%5V%xT%oKk`uJ;SIl(JpKxB`m6l((v8oFyseD9DY@+f zpCfVTbI>=tZt|sH15#ZwM=?0^x!v&EMgQ5-pBtO7!g`XgM%0p6DYt*F>88FOy@0r$ i@#Vd}67BwTWc$7f+ttsyw|-9JHNW6j0!NMj0001JnnP*; diff --git a/public/js/main.js b/public/js/main.js index 994b2ae..895683a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -2,13 +2,16 @@ * Fonction permettant d'afficher un message dans un toastr * @param {String} message */ - function showToastr(message) { + function showToastr(message, success = false) { let x = document.getElementById("toastr"); if ( message ) { x.getElementsByTagName("SPAN")[0].innerHTML = message; } - x.className = `${x.className} show`; + x.className = `${x.className} show`.replace("sucess", ""); + if ( success ) { + x.className = `${x.className} success`; + } setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000); }; diff --git a/sass/ma-collection.scss b/sass/collection.scss similarity index 90% rename from sass/ma-collection.scss rename to sass/collection.scss index af22f2a..6412fe1 100644 --- a/sass/ma-collection.scss +++ b/sass/collection.scss @@ -1,4 +1,9 @@ -.ma-collection { +.collection { + h1 { + i { + cursor: pointer; + } + } .filters { display: flex; justify-content: end; diff --git a/sass/colors.scss b/sass/colors.scss index 5759f90..f100c6b 100644 --- a/sass/colors.scss +++ b/sass/colors.scss @@ -52,6 +52,23 @@ $pagination-hover-color: rgb(115, 151, 186); --box-shadow-color: #{rgba($nord4, 0.35)}; --border-color: #{$nord4}; + + --nord0: #{$nord0}; + --nord1: #{$nord1}; + --nord2: #{$nord2}; + --nord3: #{$nord3}; + --nord4: #{$nord4}; + --nord5: #{$nord5}; + --nord6: #{$nord6}; + --nord7: #{$nord7}; + --nord8: #{$nord8}; + --nord9: #{$nord9}; + --nord10: #{$nord10}; + --nord11: #{$nord11}; + --nord12: #{$nord12}; + --nord13: #{$nord13}; + --nord14: #{$nord14}; + --nord15: #{$nord15}; } [data-theme="dark"] { diff --git a/sass/composants.scss b/sass/composants.scss new file mode 100644 index 0000000..b9d4e65 --- /dev/null +++ b/sass/composants.scss @@ -0,0 +1,13 @@ +.composants { + .couleur { + margin-bottom: 1rem; + text-align: center; + + border: 1px solid var(--input-active-color); + box-shadow: var(--box-shadow-color) 0px 3px 6px 0px; + + div { + height: 56px; + } + } +} \ No newline at end of file diff --git a/sass/global.scss b/sass/global.scss index 491b9ca..454cd14 100644 --- a/sass/global.scss +++ b/sass/global.scss @@ -82,4 +82,8 @@ html { @include respond-to("small-up") { display: initial; } +} + +.is-danger { + color: $nord12; } \ No newline at end of file diff --git a/sass/icons.scss b/sass/icons.scss index 70ce584..4250307 100644 --- a/sass/icons.scss +++ b/sass/icons.scss @@ -46,6 +46,7 @@ .icon-link-ext:before { content: '\f08e'; } /* '' */ .icon-sun:before { content: '\f185'; } /* '' */ .icon-moon:before { content: '\f186'; } /* '' */ +.icon-share:before { content: '\f1e0'; } /* '' */ .icon-trash:before { content: '\f1f8'; } /* '' */ .icon-blind:before { content: '\f29d'; } /* '' */ diff --git a/sass/index.scss b/sass/index.scss index aa7557a..c54fc3c 100644 --- a/sass/index.scss +++ b/sass/index.scss @@ -43,5 +43,6 @@ @import './error'; @import './home'; @import './ajouter-un-album'; -@import './ma-collection'; -@import './ma-collection-details'; \ No newline at end of file +@import './collection'; +@import './ma-collection-details'; +@import './composants'; \ No newline at end of file diff --git a/sass/toast.scss b/sass/toast.scss index 21d1730..46777e7 100644 --- a/sass/toast.scss +++ b/sass/toast.scss @@ -13,6 +13,11 @@ color: $button-alternate-color; border-radius: 6px; + &.success { + background-color: $success-color; + color: $button-font-color; + } + &.show { visibility: visible; animation: toastrFadein 0.5s, toastrFadeout 0.5s 2.5s; diff --git a/src/app.js b/src/app.js index 0b32d82..6d1e507 100644 --- a/src/app.js +++ b/src/app.js @@ -13,9 +13,11 @@ import { isXhr } from "./helpers"; import indexRouter from "./routes"; import maCollectionRouter from "./routes/ma-collection"; +import collectionRouter from "./routes/collection"; 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"); @@ -82,8 +84,10 @@ app.use( app.use("/", indexRouter); app.use("/ma-collection", maCollectionRouter); +app.use("/collection", collectionRouter); app.use("/api/v1/albums", importAlbumRouterApiV1); app.use("/api/v1/search", importSearchRouterApiV1); +app.use("/api/v1/me", importMeRouterApiV1); // Handle 404 app.use((req, res) => { @@ -113,7 +117,10 @@ app.use((error, req, res, next) => { } else { res.status(error.errorCode || 500); res.render("index", { - page: { title: "500: Oups… le serveur a crashé !", error }, + page: { + title: error.title || "500: Oups… le serveur a crashé !", + error, + }, errorCode: error.errorCode || 500, viewname: "error", user: req.user || null, diff --git a/src/libs/error.js b/src/libs/error.js index 562265f..13470f2 100644 --- a/src/libs/error.js +++ b/src/libs/error.js @@ -4,9 +4,10 @@ class ErrorEvent extends Error { /** * @param {Number} errorCode + * @param {String} title * @param {Mixed} ...params */ - constructor(errorCode, ...params) { + constructor(errorCode, title, ...params) { super(...params); if (Error.captureStackTrace) { @@ -14,6 +15,7 @@ class ErrorEvent extends Error { } this.errorCode = parseInt(errorCode, 10); + this.title = title; this.date = new Date(); } } diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 4796207..8025973 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -5,12 +5,19 @@ import xl from "excel4node"; import Pages from "./Pages"; import AlbumsModel from "../models/albums"; +import UsersModel from "../models/users"; import ErrorEvent from "../libs/error"; /** * 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 ""; @@ -487,7 +494,7 @@ class Albums extends Pages { static async getAllDistincts(field, user) { const distincts = await AlbumsModel.find( { - user, + User: user, }, [], { @@ -513,8 +520,11 @@ class Albums extends Pages { order = "asc", artists_sort, format, + userId: collectionUserId, } = this.req.query; + let userId = this.req.user?._id; + const where = {}; if (artists_sort) { @@ -524,8 +534,35 @@ class Albums extends Pages { where["formats.name"] = format; } + if (!this.req.user && !collectionUserId) { + throw new ErrorEvent( + 401, + "Cette collection n'est pas publique", + "Cette collection n'est pas publique" + ); + } + + if (collectionUserId) { + const userIsSharingCollection = await UsersModel.findById( + collectionUserId + ); + + if ( + !userIsSharingCollection || + !userIsSharingCollection.isPublicCollection + ) { + throw new ErrorEvent( + 401, + "Cette collection n'est pas publique", + "Cette collection n'est pas publique" + ); + } + + userId = userIsSharingCollection._id; + } + const count = await AlbumsModel.count({ - user: this.req.user._id, + User: userId, ...where, }); @@ -547,7 +584,7 @@ class Albums extends Pages { const rows = await AlbumsModel.find( { - user: this.req.user._id, + User: userId, ...where, }, [], @@ -619,6 +656,29 @@ class Albums extends Pages { this.setPageContent("item", item); } + + /** + * Méthode permettant de créer la page "collection/:userId" + */ + async loadPublicCollection() { + const { userId } = this.req.params; + + const user = await UsersModel.findById(userId); + + if (!user || !user.isPublicCollection) { + throw new ErrorEvent( + 401, + "Cet utilisateur ne souhaite pas partager sa collection" + ); + } + + const artists = await Albums.getAllDistincts("artists_sort", userId); + const formats = await Albums.getAllDistincts("formats.name", userId); + + this.setPageContent("username", user.username); + this.setPageContent("artists", artists); + this.setPageContent("formats", formats); + } } export default Albums; diff --git a/src/middleware/Me.js b/src/middleware/Me.js new file mode 100644 index 0000000..ae2712c --- /dev/null +++ b/src/middleware/Me.js @@ -0,0 +1,45 @@ +import Joi from "joi"; + +import UsersModel from "../models/users"; + +/** + * Classe permettant la gestion de l'utilisateur connecté + */ +class Me { + constructor(req) { + this.req = req; + } + + /** + * Méthode permettant de modifier le profil d'un utilisateur + * @return {Object} + */ + async patchMe() { + const { body, user } = this.req; + + const schema = Joi.object({ + isPublicCollection: Joi.boolean(), + }); + + const value = await schema.validateAsync(body); + const update = await UsersModel.findByIdAndUpdate( + user._id, + { $set: value }, + { new: true } + ); + + await new Promise((resolve, reject) => { + this.req.login(update, (err) => { + if (err) { + return reject(err); + } + + return resolve(null); + }); + }); + + return update; + } +} + +export default Me; diff --git a/src/models/users.js b/src/models/users.js index 0807ccb..96629c0 100644 --- a/src/models/users.js +++ b/src/models/users.js @@ -1,5 +1,7 @@ /* eslint-disable func-names */ /* eslint-disable no-invalid-this */ +/* eslint-disable no-param-reassign */ + import mongoose from "mongoose"; import uniqueValidator from "mongoose-unique-validator"; import crypto from "crypto"; @@ -23,8 +25,20 @@ const UserSchema = new mongoose.Schema( }, hash: String, salt: String, + isPublicCollection: { + type: Boolean, + default: false, + }, }, - { timestamps: true } + { + timestamps: true, + toJSON: { + transform(doc, ret) { + delete ret.hash; + delete ret.salt; + }, + }, + } ); UserSchema.plugin(uniqueValidator, { message: "est déjà utilisé" }); diff --git a/src/routes/api/v1/albums.js b/src/routes/api/v1/albums.js index c8f0f1a..e1caa1f 100644 --- a/src/routes/api/v1/albums.js +++ b/src/routes/api/v1/albums.js @@ -9,7 +9,7 @@ const router = express.Router(); router .route("/") - .get(ensureLoggedIn("/connexion"), async (req, res, next) => { + .get(async (req, res, next) => { try { const albums = new Albums(req); const data = await albums.getAll(); diff --git a/src/routes/api/v1/me.js b/src/routes/api/v1/me.js new file mode 100644 index 0000000..42c46b9 --- /dev/null +++ b/src/routes/api/v1/me.js @@ -0,0 +1,24 @@ +import express from "express"; +import { ensureLoggedIn } from "connect-ensure-login"; + +import { sendResponse } from "../../../libs/format"; + +import Me from "../../../middleware/Me"; + +// eslint-disable-next-line new-cap +const router = express.Router(); + +router + .route("/") + .patch(ensureLoggedIn("/connexion"), async (req, res, next) => { + try { + const me = new Me(req); + const data = await me.patchMe(); + + return sendResponse(req, res, data); + } catch (err) { + return next(err); + } + }); + +export default router; diff --git a/src/routes/collection.js b/src/routes/collection.js new file mode 100644 index 0000000..6584992 --- /dev/null +++ b/src/routes/collection.js @@ -0,0 +1,22 @@ +import express from "express"; + +import Albums from "../middleware/Albums"; + +import render from "../libs/format"; + +// eslint-disable-next-line new-cap +const router = express.Router(); + +router.route("/:userId").get(async (req, res, next) => { + try { + const page = new Albums(req, "collection"); + + await page.loadPublicCollection(); + + render(res, page); + } catch (err) { + next(err); + } +}); + +export default router; diff --git a/src/routes/ma-collection.js b/src/routes/ma-collection.js index 51ff0ee..653c844 100644 --- a/src/routes/ma-collection.js +++ b/src/routes/ma-collection.js @@ -10,7 +10,7 @@ const router = express.Router(); router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => { try { - const page = new Albums(req, "mon-compte/ma-collection"); + const page = new Albums(req, "mon-compte/ma-collection/index"); await page.loadMyCollection(); diff --git a/views/error.ejs b/views/error.ejs index b0e5112..8f80430 100644 --- a/views/error.ejs +++ b/views/error.ejs @@ -5,7 +5,9 @@ <img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" /> </p> <% } %> + <% if ( process.env.NODE_ENV !== 'production' ) { %> <div> <pre><%= page.error %></pre> </div> + <% } %> </main> \ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection.ejs b/views/pages/collection.ejs similarity index 78% rename from views/pages/mon-compte/ma-collection.ejs rename to views/pages/collection.ejs index b754ccf..da22489 100644 --- a/views/pages/mon-compte/ma-collection.ejs +++ b/views/pages/collection.ejs @@ -1,5 +1,8 @@ -<main class="layout-maxed ma-collection" id="app"> - <h1>Ma collection</h1> +<main class="layout-maxed collection" id="app"> + <h1> + Collection de <%= page.username %> + </h1> + <div class="filters"> <div class="field"> <label for="artist">Artiste</label> @@ -40,12 +43,11 @@ <div class="grid grid-cols-1 md:grid-cols-2 list"> <div class="item" v-if="!loading" v-for="item in items"> <span class="title"> - <a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a> - <i class="icon-trash" @click="showConfirmDelete(item._id)"></i> + {{ item.artists_sort}} - {{ item.title }} </span> <div class="grid grid-cols-2 md:grid-cols-4"> <div> - <a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a> + <img :src="item.thumb" :alt="item.title" /> </div> <div class="md:col-span-3"> <span><strong>Année :</strong> {{ item.year }}</span> @@ -91,23 +93,14 @@ </template> </ul> </nav> - - <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> <script> + const { + protocol, + host + } = window.location; + Vue.createApp({ data() { return { @@ -122,8 +115,7 @@ sortOrder: 'artists_sort-asc', sort: 'artists_sort', order: 'asc', - itemId: null, - showModalDelete: false, + userId: "<%= params.userId %>", } }, created() { @@ -133,7 +125,7 @@ fetch() { this.loading = true; - let url = `/api/v1/albums?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}`; } @@ -149,7 +141,7 @@ }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger votre collection"); + showToastr(err.response?.data?.message || "Impossible de charger cette collection"); }) .finally(() => { this.loading = false; @@ -187,25 +179,6 @@ this.fetch(); }, - toggleModal() { - this.showModalDelete = !this.showModalDelete; - }, - showConfirmDelete(itemId) { - this.itemId = itemId; - this.toggleModal(); - }, - deleteItem() { - axios.delete(`/api/v1/albums/${this.itemId}`) - .then( () => { - this.fetch(); - }) - .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); - }) - .finally(() => { - this.toggleModal(); - }); - } } }).mount('#app'); </script> diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 7c17b38..dc4f8f2 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -1,8 +1,9 @@ -<main class="layout-maxed" id="app"> +<main class="layout-maxed composants" id="app"> <h1>Les composants</h1> <ul> <li><a href="#titres">Les titres</a></li> + <li><a href="#couleurs">Les couleurs</a></li> <li><a href="#grilles">Les grilles</a></li> <li><a href="#boutons">Les boutons</a></li> <li><a href="#formulaires">Les formulaires</a></li> @@ -24,6 +25,87 @@ <h5>Titre de niveau 5</h5> <h6>Titre de niveau 6</h6> + <h2 id="couleurs">Les couleurs</h2> + <h3>Polar Night</h3> + <div class="grid grid-cols-5 gap-5"> + <div class="couleur"> + <div style="background-color: var(--nord0);"> </div> + nord0 + </div> + <div class="couleur"> + <div style="background-color: var(--nord1);"> </div> + nord1 + </div> + <div class="couleur"> + <div style="background-color: var(--nord2);"> </div> + nord2 + </div> + <div class="couleur"> + <div style="background-color: var(--nord3);"> </div> + nord3 + </div> + </div> + <h3>Snow Storm</h3> + <div class="grid grid-cols-5 gap-5"> + <div class="couleur"> + <div style="background-color: var(--nord4);"> </div> + nord4 + </div> + <div class="couleur"> + <div style="background-color: var(--nord5);"> </div> + nord5 + </div> + <div class="couleur"> + <div style="background-color: var(--nord6);"> </div> + nord6 + </div> + </div> + <h3>Frost</h3> + <div class="grid grid-cols-5 gap-5"> + <div class="couleur"> + <div style="background-color: var(--nord7);"> </div> + nord7 + </div> + <div class="couleur"> + <div style="background-color: var(--nord8);"> </div> + nord8 + </div> + <div class="couleur"> + <div style="background-color: var(--nord9);"> </div> + nord9 + </div> + <div class="couleur"> + <div style="background-color: var(--nord10);"> </div> + nord10 + </div> + </div> + <h3>Aurora</h3> + <div class="grid grid-cols-5 gap-5"> + <div class="couleur"> + <div style="background-color: var(--nord11);"> </div> + nord11 + </div> + <div class="couleur"> + <div style="background-color: var(--nord12);"> </div> + nord12 + </div> + <div class="couleur"> + <div style="background-color: var(--nord13);"> </div> + nord13 + </div> + <div class="couleur"> + <div style="background-color: var(--nord14);"> </div> + nord14 + </div> + <div class="couleur"> + <div style="background-color: var(--nord15);"> </div> + nord15 + </div> + </div> + <p> + Vous pourrez trouver plus d'informations sur le <a href="https://www.nordtheme.com/" target="_blank" rel="noopener noreferrer">site offciel</a> du projet nord. + </p> + <h2 id="grilles">Les grilles</h2> <p> Se référer à la documentation de <a href="https://www.knacss.com/doc.html#grid" target="_blank" rel="noopener noreferrer">Knacss</a>. @@ -225,13 +307,15 @@ <i class="icon-link-ext">.icon-link-ext</i> <i class="icon-heart">.icon-heart</i> <i class="icon-eye">.icon-eye</i> + <i class="icon-left-open">.icon-left-open</i> + <i class="icon-right-open">.icon-right-open</i> + <i class="icon-export">.icon-export</i> + <i class="icon-share">.icon-share</i> <i class="icon-spin">.icon-spin</i> <i class="icon-sun">.icon-sun</i> <i class="icon-moon">.icon-moon</i> <i class="icon-trash">.icon-trash</i> <i class="icon-blind">.icon-blind</i> - <i class="icon-left-open">.icon-left-open</i> - <i class="icon-right-open">.icon-right-open</i> <h2 id="listes">Les listes</h2> <div class="grid grid-cols-1 md:grid-cols-2 list"> diff --git a/views/pages/mon-compte/ma-collection/exporter.ejs b/views/pages/mon-compte/ma-collection/exporter.ejs index dc1de77..13e3f98 100644 --- a/views/pages/mon-compte/ma-collection/exporter.ejs +++ b/views/pages/mon-compte/ma-collection/exporter.ejs @@ -1,4 +1,4 @@ -<main class="layout-maxed ma-collection-exporter" id="app"> +<main class="layout-maxed" id="app"> <h1>Exporter ma collection</h1> <p> Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir : @@ -38,7 +38,7 @@ <input type="radio" name="format" v-model="format" value="musictopus" id="musictopus"> <label for="musictopus">MusicTopus</label> </div> - + <button type="submit" class="button is-primary my-16"> <i class="icon-export"></i> Exporter diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs new file mode 100644 index 0000000..4f401b7 --- /dev/null +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -0,0 +1,279 @@ +<main class="layout-maxed collection" id="app"> + <h1> + Ma collection + <i class="icon-share" @click="toggleModalShare" aria-label="Partager ma collection" title="Votre collection sera visible en lecture aux personnes ayant le lien de partage"></i> + </h1> + <a :href="shareLink" v-if="isPublicCollection" target="_blank"> + <i class="icon-share"></i> Voir ma collection partagée + </a> + <div class="filters"> + <div class="field"> + <label for="artist">Artiste</label> + <select id="artist" v-model="artist" @change="changeFilter"> + <option value="">Tous</option> + <% + for (let i = 0; i < page.artists.length; i += 1 ) { + __append(`<option value="${page.artists[i]}">${page.artists[i]}</option>`); + } + %> + </select> + </div> + <div class="field"> + <label for="format">Format</label> + <select id="format" v-model="format" @change="changeFilter"> + <option value="">Tous</option> + <% + for (let i = 0; i < page.formats.length; i += 1 ) { + __append(`<option value="${page.formats[i]}">${page.formats[i]}</option>`); + } + %> + </select> + </div> + <div class="field"> + <label for="sortOrder">Trier par</label> + <select id="sortOrder" v-model="sortOrder" @change="changeSort"> + <option value="artists_sort-asc">Artiste (A-Z)</option> + <option value="artists_sort-desc">Artiste (Z-A)</option> + <option value="year-asc">Année (A-Z)</option> + <option value="year-desc">Année (Z-A)</option> + <option value="country-asc">Pays (A-Z)</option> + <option value="country-desc">Pays (Z-A)</option> + <option value="formats.name-asc">Format (A-Z)</option> + <option value="formats.name-desc">Format (Z-A)</option> + </select> + </div> + </div> + <div class="grid grid-cols-1 md:grid-cols-2 list"> + <div class="item" v-if="!loading" v-for="item in items"> + <span class="title"> + <a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a> + <i class="icon-trash" @click="showConfirmDelete(item._id)"></i> + </span> + <div class="grid grid-cols-2 md:grid-cols-4"> + <div> + <a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a> + </div> + <div class="md:col-span-3"> + <span><strong>Année :</strong> {{ item.year }}</span> + <br /> + <span><strong>Pays :</strong> {{ item.country }}</span> + <br /> + <span> + <strong>Format : </strong> + <span v-for="(format, index) in item.formats"> + {{ format.name }} + <template v-if="format.descriptions"> + (<template v-for="(description, j) in format.descriptions"> + {{description}}<template v-if="j < format.descriptions.length - 1">, </template> + </template>) + </template> + <template v-if="index < item.formats.length - 1">, </template> + </span> + </span> + <br /> + <span><strong>Genre :</strong> <template v-for="(genre, index) in item.genres">{{ genre }}<template v-if="index < item.genres.length - 1">, </template></template></span> + <br /> + <span><strong>Style :</strong> <template v-for="(style, index) in item.styles">{{ style }}<template v-if="index < item.styles.length - 1">, </template></template></span> + </div> + </div> + </div> + </div> + <div class="total"> + <strong>Nombre total d'éléments : </strong>{{total}} + </div> + <nav class="pagination" role="navigation" aria-label="Pagination"> + <ul class="pagination-list"> + <template v-for="p in Array.from({length: totalPages}, (v, i) => (i+1))"> + <template v-if="p < 2 || p > (totalPages - 1) || (page - 1) <= p && page + 1 >= p"> + <li> + <a class="pagination-link" :class="{'is-current': p === page}" @click="goTo(p)" aria-label="Aller à la page {{p}}">{{ p }}</a> + </li> + </template> + <template v-if="(page - 3 === p && page - 2 > 1) || (page + 2 === p && page + 2 < totalPages - 1)"> + <li> + <a class="pagination-link is-disabled">…</a> + </li> + </template> + </template> + </ul> + </nav> + + <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> + <div class="modal" :class="{'is-visible': showModalShare}"> + <div class="modal-background"></div> + <div class="modal-card"> + <header> + Partager ma collection + </header> + <section> + <template v-if="!isPublicCollection"> + Votre collection sera visible de toute personne disposant du lien suivant : + <br /> + <a :href="shareLink" target="_blank">{{shareLink}}</a> + <br /> + Ce lien permet uniquement de visualiser l'ensemble de votre collection mais ne perment <strong class="is-danger">en aucun cas</strong> de la modifier. + <br /> + Vous pourrez à tout moment supprimer le lien de partage en cliquant à nouveau sur l'icône <i class="icon-share"></i> sur votre collection. + </template> + <template v-if="isPublicCollection"> + Vous êtes sur le point de rendre votre collection privée. + <br /> + Toute les personnes ayant le lien partagé ne pourront plus accéder à votre collection. + <br /> + Vous pourrez à tout moment rendre à nouveau votre collection publique en cliquant sur l'icône <i class="icon-share"></i>. + </template> + </section> + <footer> + <button v-if="!isPublicCollection" class="button is-primary" @click="shareCollection">Partager</button> + <button v-if="isPublicCollection" class="button is-danger" @click="shareCollection">Supprimer</button> + <button class="button" @click="toggleModalShare">Annuler</button> + </footer> + </div> + </div> +</main> + +<script> + const { + protocol, + host + } = window.location; + + Vue.createApp({ + data() { + return { + loading: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: '', + format: '', + sortOrder: 'artists_sort-asc', + sort: 'artists_sort', + order: 'asc', + itemId: null, + showModalDelete: false, + showModalShare: false, + shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + isPublicCollection: <%= user.isPublicCollection ? 'true' : 'false' %>, + } + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + + 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}`; + } + + axios.get(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) { + event.preventDefault(); + + this.page += 1; + + this.fetch(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.fetch(); + }, + goTo(page) { + this.page = page; + + this.fetch(); + }, + changeSort() { + const [sort,order] = this.sortOrder.split('-'); + this.sort = sort; + this.order = order; + this.page = 1; + + this.fetch(); + }, + changeFilter() { + this.page = 1; + + this.fetch(); + }, + toggleModal() { + this.showModalDelete = !this.showModalDelete; + }, + toggleModalShare() { + this.showModalShare = !this.showModalShare; + }, + showConfirmDelete(itemId) { + this.itemId = itemId; + this.toggleModal(); + }, + deleteItem() { + axios.delete(`/api/v1/albums/${this.itemId}`) + .then( () => { + this.fetch(); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + }) + .finally(() => { + this.toggleModal(); + }); + }, + shareCollection() { + axios.patch(`/api/v1/me`, { + isPublicCollection: !this.isPublicCollection, + }) + .then( (res) => { + this.isPublicCollection = res.data.isPublicCollection; + + if ( this.isPublicCollection ) { + showToastr("Votre collection est désormais publique", true); + } else { + showToastr("Votre collection n'est plus partagée", true); + } + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + }) + .finally(() => { + this.toggleModalShare(); + }); + } + } + }).mount('#app'); +</script> From 73716335e25d97c87be747a9ba54f5d85f994633 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sun, 6 Mar 2022 14:39:08 +0100 Subject: [PATCH 06/28] Suppression des anciens logos --- public/img/logo.svg | 1 - public/mstile-310x150.png | Bin 7242 -> 0 bytes 2 files changed, 1 deletion(-) delete mode 100644 public/img/logo.svg delete mode 100644 public/mstile-310x150.png diff --git a/public/img/logo.svg b/public/img/logo.svg deleted file mode 100644 index 7ceed9f..0000000 --- a/public/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" ?><svg height="24" version="1.1" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="translate(0 -1028.4)"><path d="m12 1037.4c-2.2091 0-4 1.8-4 4s1.7909 4 4 4c2.209 0 4-1.8 4-4s-1.791-4-4-4zm0 3c0.552 0 1 0.4 1 1 0 0.5-0.448 1-1 1s-1-0.5-1-1c0-0.6 0.448-1 1-1z" fill="#e67e22"/><path d="m5.0503 1034.2c-3.9053 3.9-3.9053 10.2 0 14.1 3.9052 3.9 10.237 3.9 14.142 0 3.906-3.9 3.906-10.2 0-14.1-3.905-3.9-10.236-3.9-14.142 0zm4.9497 4.9c1.172-1.2 3.071-1.2 4.243 0 1.171 1.2 1.171 3.1 0 4.3-1.172 1.1-3.071 1.1-4.243 0-1.1716-1.2-1.1716-3.1 0-4.3z" fill="#bdc3c7"/><path d="m4.9289 1033.3c-3.9052 3.9-3.9052 10.2 0 14.1 3.9053 3.9 10.237 3.9 14.142 0s3.905-10.2 0-14.1-10.237-3.9-14.142 0zm4.9498 4.9c1.1713-1.1 3.0713-1.1 4.2423 0 1.172 1.2 1.172 3.1 0 4.3-1.171 1.2-3.071 1.2-4.2423 0-1.1716-1.2-1.1716-3.1 0-4.3z" fill="#34495e"/><path d="m3.3392 1035.4c-0.4005 0.7-0.6934 1.4-0.9126 2.1l6.9748 1.4-4.6623-5.4c-0.5275 0.6-0.9993 1.2-1.3999 1.9z" fill="#ecf0f1"/><path d="m20.66 1045.4c0.4-0.7 0.693-1.4 0.912-2.2l-6.974-1.3 4.662 5.3c0.527-0.5 0.999-1.1 1.4-1.8z" fill="#ecf0f1"/><path d="m12 1036.4c-2.2091 0-4 1.8-4 4s1.7909 4 4 4c2.209 0 4-1.8 4-4s-1.791-4-4-4zm0 3c0.552 0 1 0.4 1 1 0 0.5-0.448 1-1 1s-1-0.5-1-1c0-0.6 0.448-1 1-1z" fill="#f1c40f"/></g></svg> \ No newline at end of file diff --git a/public/mstile-310x150.png b/public/mstile-310x150.png deleted file mode 100644 index ed34b0c499418d137767f8a34d7a9aeba9893d3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7242 zcmdT}S6Gu-xBkEp1`yDpiZlgAiZp?sk)k3cR7HyP-n$S=sE#5kO@e@gs`M6gLg-CF zq<0c}C?X|5Xdw{V`S|b7#koI!a<QN6d~5BM_g!VLmA8-eHJKTC7y$rahG{)A1OQqo z0H9&{lMdWLvt$N<%O5Te^d11f*F>gcyNlpwK?f~EJpe%b4FFNE0DuZ^MXdk;Tm}Hv zYyklJ5dgS+GwYuyfnU(s>u5d#&d%StO-0Gz4t)SjPo4fZ?PbP0B0;<jr2xP~ggts- z6g;^x6O=hNmUXsGse>a5nAOsrynFEe6RQ?Bx3=&Z?jJ8Tq(Z%SpaUeMHkRs(`6^QV zD4tm%HFv_s^VaF3dO^k;;-Z%>dA<I0sjZd0a5{9K^@ECx?ma?JV|L{B!E9&?fpVzA zPuWS|RaU134f!vB^>x*<0D=&*Kxl~J!-w`HS9zpE*qUtx42do2I9Mof{r)28P+Y{q zt)Qu6{J9xshTA3rZsj;rO0h2YouLE1#(cln3S+Kc>DZ^SP&8M`a;dl~DyC^@!e(G0 z?3R^cLaq3X=Q;aq;G$eJ0=J+Ez81|C<#35#Nz!%FR1dkW_jQ<CXv$RbEYwb<0y0c1 z(Uf9@Jv<x8P^|1HHvw~!>{0*=l(ImiKk^DF#WqWbObz*E&isq``K)Pcd!6g|%6+>F zx$$|vG?TS<Tvo|{8QY>#PD=7O(YvD#$tmITQruzg+Zh9!{(N*%_t>TGUsISnCPH7) z{uXe8m*ro=hw!-O$nMug4NTYk+TdceilG^#idsfNoL63zq$hpZgqSGmFgEm$XdQ_t z^TpAf`FE|m1U9f-$R$6n1-yRjtu{|5GNMz~FCZry5=MCAjKuRA5W*(P5dYu3Z-`2O zIe#?MAl;?9t=HFaT;aMVYE~|8*Ci^(DtgW+(dy`zfU6n1RI8ik`uNuuJFmQ3Qpd88 zVd&i|8IQQ|s@r~N11*_%F&%JjK=S4Hi+A*xQDN|)7UsqqZm)>9D!R0Kgoaj(C7fG6 zkf2Oazk}+}F49%O>XIwZnhA6!rSTk+NXLS2Dmgws-p8U_I^di@-v9{;L^(S>m(HZ- z<`ZD={P{XQeaR;|EoCNGNuFUS=Lm!a8~u^_cSi!>HPJ-Uetv$2rOP&ZUfTM8ZAPke zWGN2L4(Msl(`M4qWky=rcltPM*LT7>6H#m|bj>}b4foeUKJ2Fl8J^1JUB9PuTMZPK z&vR;a*RzH!NB@O?0?6~f^nby<-<m94gRvuIuuC%AD#-KT8cu#%Xa9mSMs+y&N?p$L zZXzdb9VXK)2f3!rx{R&C(TnZx)@?=a$cN9RS8iA~p;7{0a6d|9x}4D)!nJ>vZ<Axq zhd!N+M#(#WBr1JvbGNq)nZ_(UxqCTz#gE2*P-viOfB(bo&tEX4tcDsr8IL-q<sjJ} zXsB_g(@fl5Zs(9S=6Q1$bd$I`%J$W>0J0O;H0PE8l}Zg$iSOmhb&_Zv&2G}objxil z&m@IaOGb{Fo^yDIkxTIP@$baSOVn?_WPOBh{>{7lQHcAJC?zujRpS`FZ))0*z{3`H zn=62*yn?Mbl%Ul1)8^W7FVeQZaT_VTsClV3L{&%<J4^B^Vo9%GP7n~2;(gAi`1doh zrv67Zd`iLQgCKj%YV)AL6*lRRqxI&yS8s;e=vR7Em%J3amlugM_uF)1PDZR^fI%50 zaa#M<rj}6Z0?7ut+TZpQo2ivn&hjpn$?amBB9FUKnjC$jMXk4S5>XJ)k5laeNOD4& zmol%gWKE95k!+M6q*=>!!1tfc$7=Mo*}s#Y3)!m$_$j|~&IoM|nk@YIj|169St{nH zU)0rq+_O?_JBq7!rl{O(5k12gqw|1b0cEMEn3G?Gh-8)H>C?0E@qwe0h?65P#Zps> zCtmL@1MVX|ok3>m8k9bY`UKPs3AQDeVldD@P|p(%YQJnA{xBF-k?Vu|+ed8_-yf5< zECL>@GIF)%ovzNR!j*gu=3sq(=-xe}8S<`xgo@O9y;-(PT%FULlVW~dKh4~Ms3njH ztueqoU3C-dgFLGO&plb&&sg%A>&=JU`eDO&l0>`=BqV;nxRvWRlB3&sqTkZh7d|`f zPhBfgw3)9D$#!u(4QXFW``G~Md!~m7WXW~P9Kp<}@!fN*?xgH~_7lQlU1Mg0L-msf zICeBgYjjHJ;Dmx~zMfHqyoXm|M;roio!XN&MEvH?9WOE1@av5d^SY8O9D8BO-wR}G zfvK-(rFkgKpiZB5POIDu2p&8x$l2T=6q!EP$s(AS5mZ#vb~&YeMwk7eDQ?|$*WlJI z4@IIq3|28m`}7Kt@akD=Rm@JTvhoX;rJERQ?qAS9qJ+-j+-+r()<+?hK2X96om#T7 z3fTI^Sxv07*Mpa;BV~)$C%)6}%*qa-$(248dru;6D$7+?Le{n1hN>Z{Z&4W4Y~@8A z{9S-|fqZJ7+jqDvS))wI^H|=L4CiYhR9>kg48;gk4ma;~vo$NSzj2*B7^5DF<dk|i zhhQuT+$B2BbyECQFa|#?HO#;8#uFdr2DyJ5Vs}jY;8_=1ug_9b09yok<#r=hDixKP z-*8IS)YjK6rYKLWE+LLzU!;EfLf$#@#JFr7m7}?<trM+V9%qe6n3t_G+sC6hl}DnA zzy!?y{HJoFJ`AR<gw=bCD)tD}oH@h@h;!cwytS2X7P{BT96MUJILkRO)bKR@)OV4V z+%r3a>QiMHk)Zrjq6WH@@1Ke90M?QOpEHUa0TH=g&Z;t6I(8^NU9Dz(RF?ALa%C;2 zr4riV=#o>Ad*u&-$iS^}vn(C*AXY&9?&c^R0iu3~d#E--otmHfQoDQ6e53xRecX)s zhZgr{A}_#_%f!C*emWMNA(8&|XQ-r{vT;*r;8Xnpboc6*aX5nV*j?KYV|`YjI6J0O zIbay55zSv0@oD(4(yZy2;dfuWGi1%x3*PFZBmZ#t9kH_+w~clH?nHqu`(~DFhDZOY zSxG*fbQo^>M;z9KRdu_uIb-)dB1qgHp$d{YWRP>uU<~e^<9ZY~VUzSMox0SUuAu)C zc;8MN5ap6T`S11H%+lt<CHYmlA_Y0bJdPXzr9fTT<Po)!E@lbmI9cj)%T#`=ca3_i zFnR5ZDL&CGuSjKuB(FI-S$nYa>N=9Mq4_2NM%sXkh68F*Nl(mKmu$-Tp?QUB!n^m< zjz~UDv%=anOu?<e49XGBG{?X$6=lkuRq;E=>sE=~4_!iLcK7&+4brRWTkXw7irGxS ztosR7x&oqhT#Ni`L9?!z3q;YErt!OYk%Dwkfb0lJWp=Gw_OeDZGpWg{3AzcNB-+RM z9W^qxJ}6(L4c08<l+tY2n~ry%x1!_u!R^+a8?P_7UWx8{9xedz-k+xpZjIWlBnoy- zB)q&H`DmFe5fFZDO#yB5>7UbL4AB%nN~dW}S!6+zE&yP{Vf+4IYHEpm6ey;=WR*=c zc43!f5;f<1f1__CI+@oduX&<?h4g;3@+pNu{{jdtHz;xgmo~ps`R2h)kisEv^<)tW z%TPMSkNix}$hnBw2`YcGQm&o;cm$zKw?AHDoFSJrlb7EPELsAr;7?U*<-)?G=s)hQ zjaR`fJmU?Fdkme*5^lA4juv}cJWEo3@Pkz{nL)8R_->wB4=F9+#)HT&MI!(pyj}d~ z)*lWZ)_15@w^7v+{(YtlKiMT?Fvj!<2UP0U>8_^3RKhZGO&$PXAP+a65j|%0V)UON zp6&~m%9T!Cfv!&7m{K#eS`1AYPM%)R*pqY^op1vHBuKHr7v#vyAB%19_*2iS?~%T` zJq%Oa3hzuH5o*+f$L(EBcgOWTrtUHW_x}QUX6{hy8OttJYq@cy>$uA$uPr=<1J>OA zPB=#z`mjSuQjDs1&#K6)>aieqU=|JV(x1jarX#hd{#vGmG2dgEPktE!8SQU$2XUpi zFh$}fnZ!%pF@)(bo#icOG{2vfc{^mACx?@7t|`3R{v-N3&fG!wlBrFrTNFQ6D^s~o zPESvo38Y!>+d=umaPi+xGfneZ)Y;_lpl=qRFyfp%Hj8Hi(vm?_^H*3d_D%Jq<qU7m z04M>_@QWIH8Mu*07J-UhZ}bGq5W1`1tG<$(tm{R+ECtJ;m9VQinF%%~qU=V+^^;zI z0w9ixk20kU|NV5QqNM20qqr=ri`|-Zpp<)%uX|9}aNGywSi{KG<SdcZUr!VDZBJNS z?%m*ZwH3{O9AD|?Ur!@{)xi>we&demu}U}fb}uT?Q`O6qrIOye7o|yRTM5BRWA{?n z@wcujRtE*W6fP+GyNgr_pOm9JPXn(?f35xV>fIk66&Y9R%a}&6K9P^R{Q7R!iRlmL z{_d@*xt*_|?k43OFWj}fD<&yo_9Du!fsr5J=b*9PI?o1TZyAiyXq#)>ZnJBi_?l1F z{&>eSsfEpDab*Rwh!*MF_U&&@(4>olxbv!#gv?wC$n3F@Ky)H}KUZQkNa0C~@@hxD z@?FK5C4*6+;Kf7fhJwf^#~uj$IsAk?^%UzFuE|Vnnju%*`A083pFlXP3QS9Nv--wa zG4(~A$XM+<g1f;%5_L<tl)hSm9OK7RXaSIitLf>hHd@z?8hW=z=}#1Dm-R0m#}j_k znWaa4&N?+len`qLwOcLW3EZ5Zw~?wWA1;)fs`75*T;lS?6;@1D`4J&wI7kH3%f;2C znI}j{Z8P7064jkD7s}mT)#E+ww5yO!U7U^TW`V@(<%VWHWIk#y!|8kYTDY><b?cLX zwSrR@GBmgxE%o{H0v~;-3xvaa<aK<s_!R!Y535`sn3`g>OpO4}LpCpnvhK8T+Ysv! zls>{c&_~RBpd2V(lsv`jQB$z`W1@sQwsGa~gxO|O_3~DkMcOSm5m~^H7sIa<agbNo z7d=(w=J6H6#o3eXXlvk%C^bZ~IXdB>P1gZo5cljS7FW~2@|(K*z2NT!yp_+Tp*+P~ zMhJUs@}&X}yBRY6y7=ramOp%7y8a|4lJLu)pY-IZ`cj0~v|)aqMJ(2h2mx^!nq;;x zdK&6VD3q&*rwSjIHx4mhtny<?Yozt9A}H<m_AAb&6Z+vSFeqk(UpaWgUgT9qP-AD8 zkqP}CCT*t*eK4q9r)Ui?K_553DxROz*S`U6_Bbi55Fa-x{_rZ8tc+<W4|CDuRgSaf zYLzWiyTCt8iR1UbKvUBo80q2VhdNy<cp_fm+SJFaBk>(fPRPAu4<EPw?@krS>mV}V zB~6b<PFK+q{<eQ5T@p5R^B1#I9Mc>P7-li~!XnL)xT`akKkvALs=UJ^_GF_U#aen; zpu9sOdi^S$D#l3J2(z4sQ+QdmYKv{`!*5Z-m?#BHz~`K#_Jceox_$qK@yVg;4cMsH zKqAgV2T{|xhJ|0ru|Aa1tm)&f*a%%Cw0!|N4~;yFgLuv0+`R}jl#GhEx&aT$tk_!_ z&g0;AVEdcOqpYEDP0L!?H8I5o)EgUv@If2ZkbNb|F4E{NiFI>{@Y?F+LS<zWgMp2r zNji4*r4(Z-<&63=Fm>H+8-mB-#Cf4x<5E0B$DA#KQFL~`QggEYoKlCoL{fhPbuq=m zQnSRgG9gsKX;34cXj=U10QHo*KHL5NH@XWqC4q2lOZcE+)uNf!kC;PGfp-;xz1MLV z8LBRXMH)5A_yq3<I4J!qE-+YqZnB)qJW}kQ&VJ+J+|$bJfcPtT+S6jH6KSZG|K4cX zi8Z|ImZ`HncA+Kxtr3`kVd~E;bdXp(mw#0xS8~A|(zny;wiTsPPh3_t(P4JB-k#vv zISNCrx3Os?Ha2niNJO#O@^Q5~7%sj%D~qc_Ml^*wM%1BO;hAiuu?JP|{;y5E`d!H$ z(l+E2{;&m+8NVK*t0Rq(FF8o#WWAvqU297wKD<!~*^Y}_AE&3M%^iA)iyB!(=)2`U ztK+w)$9S{8h}ngOEW)Y($-$72O5lt>5CdKUtsV0!12(1~C^tCQ=`VyxH>v1gmL=V) zcQ?eQZl2X-N#dG&<y~7Xz!Ju4E`w-)*cBH3EkrHjM7DEvWnQ(Zw4g`TC)M9;+Ol}% z#aHz!i#2kVHO#F^=g*_Ob&YT;gBS~!tdW));+<@+cn$K@PiNPYjtV{K02-;h!?$Le ze%Efan2esHo6`c_RMR*uty#2W1<4@9u|HlfWo?9QF%O!hys9Qm&aC)*621}#b*0LV ztZEoqzk-J(qLf0OW54cGy52INTJO)$O<0*1-a9)=oHTtB(w$?~;upKN#!?P*aB?=_ zma~s0?gMu?!31e{q}F^>1as4;6M(xDs816dhJJsypE;QuDXs82K;w?eJdEJMUmf-= z@Ed?{*m(fy@vDSY>h4K_H~2E?jC|Yo1!+7By9&gBGXd*^!1o)lrAk7+mi&#?`#wGG zjr0CiH8ic;=gzZSRuQ2)T$->UC-1Z%iubec_@^LeZNhX`Y4wN^!Xf_L)-4iNn$+us z##|^qucCg79r0zD4$4zl;COBX&nUs7K{IrM=+jhLGg}tvuxuq#P+BaidOUc-rpanS zgev~036!<9pZyZuu_lcSn22`xkUm@6$1wF-RpqRnQ`#b=cYEiZIER;niSS>C`-8ap z?pK7`ex|v0a9+dxWI6U&cNksktEFh8pbmS>fEN1ksu){O+UBZqp6Ph`bIqN&N3JKw z_}IHutQ$zx&Sf&<LufL&qnR!X^$+w&Acu@#5IJRaMQgvL@1Jss&tyN<7n+QaA`PRb z0*j^o8hST?7oAgGUmifk+)Us<tR`jx`U&4JzPZk}qtT00VAU}M8*CKfFk17JJ`Rzx zA%Txn*~(X;I;MPoa5gV3St&(0@j>s>xN=8qM{y^kF4w9JYa&M*Lb<FWwuATO+yRHU zo!6Hh8$LYPYFp}fgib$q3t||+^BO!wwyv%YyQ5y2Wc*f`S?Qi8KKzXK;)U{U!<;BW z5y)l0UfeB-iHwfrrSJ`?K-BBS0eL}<rk3FC%WULuK`|H-;wd%EB<E66THgVeH+ah$ z6L7+jJcjYWUHMtDC(Wjr7~Bb00gBGgvLi`b-X78-X)(fv_p=(vy=A0gPn!_7fiQ#? zOnWrm)VjQ%PcHF}cm7dFBL6lM{t(E7f)kLArWV&Og^o4#@ZE4J$*H2WQ<v&$&W!-R zz7llSy_HFlRc_UX{5%^(NZ9O_N4zZa6f5wbHm>N!-B^`IE1XikPxHu-tC2saG(J<k zht;MmUr(mP)o619r4;e%_*BT5wTtJdrmA*u=^T!DC1v0zNd8Fs5mTx)I50CA{`SLa zhpBpPOmW$jFn>=Vrvq#u0uDtM$BKd|4%Tuea=MOZs&Us8(eqOp7q)mAx#r||`aY($ zXltMDkI#muP;(}Bv4{-~{I<E#S!Q<q`sX2Su)Cb?Y2nLJJWf;Mr?m&Y+ZT1{Ts?FT zIVljK6zTBkbbt27#yEBg-tL@|o?WVQGal+K9h?6qsm|+oWj@k3i|zg{Sbp@?Fw2pn za&xiG;%+PRKZTNtQ_%+xF<fEgYuq^yK1MnP=YzXEiqCz+%jkX526VGz+z&$07+P1A z^9iB*=5*Z)SHVp@a&z=atlnl>@#sRLDjvCH;~E*qA+tUI{x$1nq%FrZ6@rrAfBEJ! zJ}W29^cPe*>bE*Ls{5_-Y-|2i@ldSB-^3MssfRnc%f&-Ja@8f<=14?L_CXt1qZ&Ug zi>mOJlDy!^O{<TJ$D6AikE~qi29M7NqPCmUPFbH{)E^1^S0wB<B|f@gP9CER-(P}O zNREHjYQJ{23=Vjf_SQeYIPsUyHK=qbR1|PZA2{&7nx?qUhOeWk0^!bOK6IYj%tO8& zqhxn?pHPp>n9b`DS&Y@59%Q&}ob1|!q|Sw-CD;2oa_j;ED%<qs+U2s8Tiw_8MVoH5 zu7a}~SBF9iVvj}Rub2*0XuxQ1UY;?2)Lmyjki&DPFy128_)AE}jU+A1Z`lsf28Vm( zl}Xr4gQHlHW?C|r1pR2|%C@WV78;iPyk~mm2~hDC)R;FLPMPtwd_I|C^4k>I@7d`B z;$z(CU!$!}DR!h-Z%kViB8P8plocC!=~a}LdL*oV0=28~8p^O6J-7d7(kZ{#3V{L_ zaUo^ntVtJ9UPj2T9fEkxFy<8Me^%?)R#V@c2$*w$v)~TB8k{0xXgJmhUKICQo*agS z!%Hjfe!1fXb`*khJ_}b_C$Fz2Vk3?^qlGlHtz(A(R~bewfIj?-t68LnP}()O(iB8} zzfg(xFmG4$V`LJwMuJL7tf-p;P<-dpbzSfP_n~3gi1f1Z^+zz5s)eN>v%JH1D16yp zv_AS>eO}kDNz%Ap29@4aSZ-s*c#X!9&EzJ#6H-6qwL$znX(ilZ^9xJ@Ul3fD6Z!7$ z=<Ku8J{ABUFY*MPkS4lK`NkD81o!J%-mFTQk%#B!Bp3!K+&%uj{BuWfZ}$hCYC{dD ze<vN|aO!z~t6slsrTF|;r#OCt=D+;UlK&s(%TJ%t;OSq+Q+50GL5oBJ)Sm@7+66d4 z9iBUZ3vgTJ_H9X-+mf;nBUu@!0t70jC?O*Qm5~v>&I<is0$x6jZq6b9w}4q#&1+D= z6k+r%z|by8(D%8Ivzw=rU_gkklc1Y#fCB&oXD;qs`qR%uRJ7Zmrx&(l2wV_il5${T z6JpazX1~BD_$W%g&GCW|#z?Sde6+K@x1G10z9&j6S6d2L<GK>C^H3q>99EdR{v-55 H+kgHC6DwZ$ From 4886e9cc487ae911cd2334a7791f5480fa026c95 Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Sun, 6 Mar 2022 15:00:24 +0100 Subject: [PATCH 07/28] #30 - Avoir des statistiques avec Matomo (#33) Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/33 --- README.md | 3 ++- docker-compose.yml.dev | 1 + docker-compose.yml.prod | 1 + src/config/index.js | 1 + views/index.ejs | 18 ++++++++++++++++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fdc754d..929ccbf 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Le code source est publié sous licence libre [GNU GPL-3.0-or-later](LICENSE) et Vous pouvez librement utiliser le service en vous inscrivant sur [https://www.musictopus.fr/](https://www.musictopus.fr/). -Une fois inscrit vous pourrez saisir vos CDs et Vinyles sur votre espace personnel le tout gratuitement, sans tracker et sans utilisation de vos données personnelles ! +Une fois inscrit vous pourrez saisir vos CDs et Vinyles sur votre espace personnel le tout gratuitement, sans tracker (en dehors de statistiques web via [Matomo](https://fr.matomo.org/)) et sans utilisation de vos données personnelles ! ## Auto hébergement @@ -195,6 +195,7 @@ MONGODB_URI # Url du serveur mongo (par défaut mongodb://musictopus-db/musictop SECRET # Hash utilisé pour pour sauvegardé les dessions (par défaut waemaeMe5ahc6ce1chaeKohKa6Io8Eik) DISCOGS_TOKEN # Token Discogs (vous devez créer un compte sur discogs afin d'en obtenir un gratuitement) FORMSPREE_ID # Id du formulaire formspree pour la page "nous-contacter" +MATOMO_URL # Url vers l'instance matomo (exemple: https://analytics.darkou.fr/) ``` ## Contributeurs diff --git a/docker-compose.yml.dev b/docker-compose.yml.dev index b14864f..9ae57ee 100644 --- a/docker-compose.yml.dev +++ b/docker-compose.yml.dev @@ -25,6 +25,7 @@ services: SECRET: ${SECRET} DISCOGS_TOKEN: ${DISCOGS_TOKEN} FORMSPREE_ID: ${FORMSPREE_ID} + MATOMO_URL: ${MATOMO_URL} networks: - musictopus musictopus-db: diff --git a/docker-compose.yml.prod b/docker-compose.yml.prod index d314748..ba4b3ef 100644 --- a/docker-compose.yml.prod +++ b/docker-compose.yml.prod @@ -25,6 +25,7 @@ services: SECRET: ${SECRET} DISCOGS_TOKEN: ${DISCOGS_TOKEN} FORMSPREE_ID: ${FORMSPREE_ID} + MATOMO_URL: ${MATOMO_URL} networks: - musictopus musictopus-db: diff --git a/src/config/index.js b/src/config/index.js index 43158f5..7c01f09 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -5,4 +5,5 @@ module.exports = { secret: process.env.SECRET || "waemaeMe5ahc6ce1chaeKohKa6Io8Eik", discogsToken: process.env.DISCOGS_TOKEN, formspreeId: process.env.FORMSPREE_ID, + matomoUrl: process.env.MATOMO_URL || "", }; diff --git a/views/index.ejs b/views/index.ejs index ddf0b8c..c45c557 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -19,6 +19,24 @@ <script src="/libs/axios/axios.min.js"></script> <script src="/libs/vue/vue.global.prod.js"></script> <script src="/js/main.js"></script> + + <% if ( config.matomoUrl ) { %> + <!-- Matomo --> + <script> + var _paq = window._paq = window._paq || []; + /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + (function() { + var u="<%= config.matomoUrl %>"; + _paq.push(['setTrackerUrl', u+'matomo.php']); + _paq.push(['setSiteId', '3']); + var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; + g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); + })(); + </script> + <!-- End Matomo Code --> + <% } %> </head> <body> <nav class="navbar" aria-label="Navigation principale"> From 9a088b9b5ff284b0cd4359ca4d98873187d000eb Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Sun, 6 Mar 2022 15:11:42 +0100 Subject: [PATCH 08/28] #29 - Avoir un titre distinct par page (#34) Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/34 --- README.md | 1 + docker-compose.yml.dev | 1 + docker-compose.yml.prod | 1 + src/config/index.js | 1 + src/middleware/Albums.js | 5 +++++ src/middleware/Pages.js | 4 ++++ src/routes/index.js | 12 ++++++++++++ src/routes/ma-collection.js | 2 ++ views/index.ejs | 2 +- 9 files changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 929ccbf..cf26bf1 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ SECRET # Hash utilisé pour pour sauvegardé les dessions (par défaut waemaeMe5 DISCOGS_TOKEN # Token Discogs (vous devez créer un compte sur discogs afin d'en obtenir un gratuitement) FORMSPREE_ID # Id du formulaire formspree pour la page "nous-contacter" MATOMO_URL # Url vers l'instance matomo (exemple: https://analytics.darkou.fr/) +SITE_NAME # Nom du site (utilisé dans le titre des pages) ``` ## Contributeurs diff --git a/docker-compose.yml.dev b/docker-compose.yml.dev index 9ae57ee..0d49feb 100644 --- a/docker-compose.yml.dev +++ b/docker-compose.yml.dev @@ -26,6 +26,7 @@ services: DISCOGS_TOKEN: ${DISCOGS_TOKEN} FORMSPREE_ID: ${FORMSPREE_ID} MATOMO_URL: ${MATOMO_URL} + SITE_NAME: ${SITE_NAME} networks: - musictopus musictopus-db: diff --git a/docker-compose.yml.prod b/docker-compose.yml.prod index ba4b3ef..90031d8 100644 --- a/docker-compose.yml.prod +++ b/docker-compose.yml.prod @@ -26,6 +26,7 @@ services: DISCOGS_TOKEN: ${DISCOGS_TOKEN} FORMSPREE_ID: ${FORMSPREE_ID} MATOMO_URL: ${MATOMO_URL} + SITE_NAME: ${SITE_NAME} networks: - musictopus musictopus-db: diff --git a/src/config/index.js b/src/config/index.js index 7c01f09..44d1402 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -6,4 +6,5 @@ module.exports = { discogsToken: process.env.DISCOGS_TOKEN, formspreeId: process.env.FORMSPREE_ID, matomoUrl: process.env.MATOMO_URL || "", + siteName: process.env.SITE_NAME || "MusicTopus", }; diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 8025973..6caa29b 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -641,6 +641,7 @@ class Albums extends Pages { this.setPageContent("artists", artists); this.setPageContent("formats", formats); + this.setPageTitle("Ma collection"); } /** @@ -655,6 +656,9 @@ class Albums extends Pages { }); this.setPageContent("item", item); + this.setPageTitle( + `Détails de l'album ${item.title} de ${item.artists_sort}` + ); } /** @@ -676,6 +680,7 @@ class Albums extends Pages { const formats = await Albums.getAllDistincts("formats.name", userId); this.setPageContent("username", user.username); + this.setPageTitle(`Collection publique de ${user.username}`); this.setPageContent("artists", artists); this.setPageContent("formats", formats); } diff --git a/src/middleware/Pages.js b/src/middleware/Pages.js index e5a59af..eb3ea75 100644 --- a/src/middleware/Pages.js +++ b/src/middleware/Pages.js @@ -34,6 +34,10 @@ class Pages { } } + setPageTitle(title) { + this.pageContent.page.title = title; + } + setPageContent(field, value) { this.pageContent.page[field] = value; } diff --git a/src/routes/index.js b/src/routes/index.js index 95c9a45..ac43a51 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -14,6 +14,8 @@ router.route("/").get((req, res, next) => { try { const page = new Pages(req, "home"); + page.setPageTitle("Présentation du projet"); + render(res, page); } catch (err) { next(err); @@ -26,6 +28,8 @@ router try { const page = new Pages(req, "connexion"); + page.setPageTitle("Connexion"); + render(res, page); } catch (err) { next(err); @@ -61,6 +65,8 @@ router try { const page = new Pages(req, "inscription"); + page.setPageTitle("Inscription"); + render(res, page); } catch (err) { next(err); @@ -82,6 +88,8 @@ router try { const page = new Pages(req, "ajouter-un-album"); + page.setPageTitle("Ajouter un album"); + render(res, page); } catch (err) { next(err); @@ -92,6 +100,8 @@ router.route("/nous-contacter").get(async (req, res, next) => { try { const page = new Pages(req, "nous-contacter"); + page.setPageTitle("Nous contacter"); + render(res, page); } catch (err) { next(err); @@ -102,6 +112,8 @@ router.route("/composants").get(async (req, res, next) => { try { const page = new Pages(req, "composants"); + page.setPageTitle("Les composants"); + render(res, page); } catch (err) { next(err); diff --git a/src/routes/ma-collection.js b/src/routes/ma-collection.js index 653c844..9c217bf 100644 --- a/src/routes/ma-collection.js +++ b/src/routes/ma-collection.js @@ -30,6 +30,8 @@ router try { const page = new Albums(req, "mon-compte/ma-collection/exporter"); + page.setPageTitle("Exporter ma collection"); + render(res, page); } catch (err) { next(err); diff --git a/views/index.ejs b/views/index.ejs index c45c557..2fd6eab 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title><% if (page.title) { %><%= page.title %> <% } else { %> DarKou - MusicTopus <% } %></title> + <title><%= config.siteName %> :: <%= page.title %></title> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> From 4068df3cba71fc280fafab6cac42d7414e109cce Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 7 Mar 2022 07:56:00 +0100 Subject: [PATCH 09/28] Correction d'un bug sur l'affichage des images verticales --- sass/ma-collection-details.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/sass/ma-collection-details.scss b/sass/ma-collection-details.scss index 6d4ba32..952a026 100644 --- a/sass/ma-collection-details.scss +++ b/sass/ma-collection-details.scss @@ -16,6 +16,7 @@ img { max-width: 90%; + max-height: 90%; } } } From 3aeb172dbf7c3ec7dd1f9d670eeb2686c0aa0c02 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 7 Mar 2022 10:33:35 +0100 Subject: [PATCH 10/28] Ajout d'un espace final (test webhook) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf26bf1..82c35ce 100644 --- a/README.md +++ b/README.md @@ -202,4 +202,5 @@ SITE_NAME # Nom du site (utilisé dans le titre des pages) ## Contributeurs - Damien Broqua (développeur principal du projet) -- Brunus (Logo et fournisseur d'idées :wink: ) \ No newline at end of file +- Brunus (Logo et fournisseur d'idées :wink: ) + From b05bed9a00edab0a9343d62fbaaa0aa9905ad7ed Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 7 Mar 2022 16:35:28 +0100 Subject: [PATCH 11/28] =?UTF-8?q?Am=C3=A9lioration=20de=20la=20visibilit?= =?UTF-8?q?=C3=A9=20des=20=C3=A9l=C3=A9ments=20d'une=20liste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sass/list.scss | 8 +++++++- views/pages/mon-compte/ma-collection/index.ejs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sass/list.scss b/sass/list.scss index 7c479ff..4f2b124 100644 --- a/sass/list.scss +++ b/sass/list.scss @@ -14,6 +14,12 @@ @include respond-to("medium-up") { border-left: 1px solid var(--border-color); + &:nth-child(4n), + &:nth-child(4n-1) + { + background-color: var(--default-color); + } + &:first-child, &:nth-child(2) { border-top: 1px solid var(--border-color); @@ -26,7 +32,7 @@ } &:hover { - background-color: var(--default-color); + background-color: var(--border-color); } } diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 4f401b7..970d584 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -44,7 +44,7 @@ </div> </div> <div class="grid grid-cols-1 md:grid-cols-2 list"> - <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"> <a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a> <i class="icon-trash" @click="showConfirmDelete(item._id)"></i> From b27cbd467b9f6e1e7cbead8bfc6046e8a266f84b Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 7 Mar 2022 16:58:02 +0100 Subject: [PATCH 12/28] =?UTF-8?q?Ajout=20d'une=20classe=20pour=20le=20hove?= =?UTF-8?q?r=20des=20=C3=A9l=C3=A9ments=20d'une=20liste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sass/colors.scss | 2 +- sass/list.scss | 10 +++-- views/pages/ajouter-un-album.ejs | 2 +- views/pages/composants.ejs | 42 +++++++++++++++---- .../pages/mon-compte/ma-collection/index.ejs | 2 +- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/sass/colors.scss b/sass/colors.scss index f100c6b..bc54734 100644 --- a/sass/colors.scss +++ b/sass/colors.scss @@ -45,7 +45,7 @@ $pagination-hover-color: rgb(115, 151, 186); --input-font-color: #{$nord3}; --input-color: #{$white}; --input-active-color: #{$nord5}; - + --navbar-color: #{darken($white, 5%)}; --box-bg-color: #F8F9FB; diff --git a/sass/list.scss b/sass/list.scss index 4f2b124..537a60d 100644 --- a/sass/list.scss +++ b/sass/list.scss @@ -30,10 +30,6 @@ border-right: 1px solid var(--border-color); margin-right: -1px; } - - &:hover { - background-color: var(--border-color); - } } .title { @@ -50,4 +46,10 @@ max-width: 90%; } } + + &.hover { + .item:hover { + background-color: var(--border-color); + } + } } \ No newline at end of file diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index bdebb4e..797bce6 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -15,7 +15,7 @@ </div> </div> - <div class="grid grid-cols-1 md:grid-cols-2 list"> + <div class="grid grid-cols-1 md:grid-cols-2 list hover"> <div class="item" v-if="!loading" v-for="item in items"> <a @click="loadDetails(item.id)" class="title">{{ item.artists_sort }} {{ item.title }}</a> <div class="grid grid-cols-2 md:grid-cols-4"> diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index dc4f8f2..592662b 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -110,7 +110,7 @@ <p> Se référer à la documentation de <a href="https://www.knacss.com/doc.html#grid" target="_blank" rel="noopener noreferrer">Knacss</a>. </p> - + <h2 id="boutons">Les boutons</h2> <div type="button" class="button">.button</div> <div type="button" class="button is-link">.button.is-link</div> @@ -170,28 +170,28 @@ </pre> <div class="field"> <label for="choix1">choix 1</label> - <input type="radio" id="choix1" name="choix" value="choix1" checked> + <input type="radio" id="choix1" name="choix" value="choix1" checked> <label for="choix2">choix 2</label> <input type="radio" id="choix2" name="choix" value="choix2"> </div> <pre> <div class="field"> <label for="choix1">choix 1</label> - <input type="radio" id="choix1" name="choix" value="choix1" checked> + <input type="radio" id="choix1" name="choix" value="choix1" checked> <label for="choix2">choix 2</label> <input type="radio" id="choix2" name="choix" value="choix2"> </div> </pre> <div class="field"> <label for="checkbox1">choix 1</label> - <input type="checkbox" id="checkbox1" name="checkbox" value="checkbox1" checked> + <input type="checkbox" id="checkbox1" name="checkbox" value="checkbox1" checked> <label for="checkbox2">choix 2</label> <input type="checkbox" id="checkbox2" name="checkbox" value="checkbox2"> </div> <pre> <div class="field"> <label for="checkbox1">choix 1</label> - <input type="checkbox" id="checkbox1" name="checkbox" value="checkbox1" checked> + <input type="checkbox" id="checkbox1" name="checkbox" value="checkbox1" checked> <label for="checkbox2">choix 2</label> <input type="checkbox" id="checkbox2" name="checkbox" value="checkbox2"> </div> @@ -243,11 +243,11 @@ <label for="password">Mot de passe</label> <input type="password" name="password" id="password" placeholder="********"> </div> - + <div class="text-right mt-10"> <p>Pas encore inscrit ? <a href="/inscription">Inscrivez-vous</a></p> </div> - + <button type="submit" class="button is-primary">Connexion</button> </form> </div> @@ -284,9 +284,9 @@ <span></span> </div> <pre> -<button +<button type="button" - class="button is-primary" + class="button is-primary" onclick="showToastr('Ceci est une notification');" > Afficher une notification @@ -318,6 +318,7 @@ <i class="icon-blind">.icon-blind</i> <h2 id="listes">Les listes</h2> + <h3>Liste normale</h3> <div class="grid grid-cols-1 md:grid-cols-2 list"> <div class="item" v-for="item in items"> <span class="title"> @@ -340,6 +341,29 @@ </div> </div> </pre> + <h3>Liste avec effet au hover</h3> + <div class="grid grid-cols-1 md:grid-cols-2 list hover"> + <div class="item" v-for="item in items"> + <span class="title"> + {{ item.title }} + </span> + <div class="grid grid-cols-2 md:grid-cols-4"> + <div> + <img :src="item.thumb" :alt="item.title" /> + </div> + <div class="md:col-span-3"> + {{ item.lorem }} + </div> + </div> + </div> + </div> + <pre> +<div class="grid grid-cols-1 md:grid-cols-2 list hover"> + <div class="item"> + Contenu + </div> +</div> + </pre> <h2 id="paginations">Les paginations</h2> <nav class="pagination" role="navigation" aria-label="Pagination"> diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 970d584..9dd4bbd 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -43,7 +43,7 @@ </select> </div> </div> - <div class="grid grid-cols-1 md:grid-cols-2 list"> + <div class="grid grid-cols-1 md:grid-cols-2 list hover"> <div class="item" v-if="!loading" v-for="item in items"> <span class="title"> <a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a> From b0e6964205851b14b0ddb0db9a808c52dbb32de6 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 7 Mar 2022 17:00:48 +0100 Subject: [PATCH 13/28] =?UTF-8?q?Ajout=20du=20composant=20toastr=20succ?= =?UTF-8?q?=C3=A8s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- views/pages/composants.ejs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 592662b..bcf5c68 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -278,6 +278,7 @@ </pre> <h2 id="notifications">Les notifications</h2> + <h3>Erreur</h3> <button type="button" class="button is-primary" onclick="showToastr('Ceci est une notification');">Afficher une notification</button> <div id="toastr"> <button class="delete" onclick="hideToastr()" aria-label="Masquer la notification"></button> @@ -292,6 +293,26 @@ Afficher une notification </button> +<div id="toastr"> + <button class="delete" onclick="hideToastr()" aria-label="Masquer la notification"></button> + <span></span> +</div> + </pre> + <h3>Succès</h3> + <button type="button" class="button is-primary" onclick="showToastr('Ceci est une notification', true);">Afficher une notification</button> + <div id="toastr"> + <button class="delete" onclick="hideToastr()" aria-label="Masquer la notification"></button> + <span></span> + </div> + <pre> +<button + type="button" + class="button is-primary" + onclick="showToastr('Ceci est une notification', true);" +> + Afficher une notification +</button> + <div id="toastr"> <button class="delete" onclick="hideToastr()" aria-label="Masquer la notification"></button> <span></span> From e0a8fa42c2b1d8c03785abb2e4cadf8e48690bf4 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Tue, 8 Mar 2022 09:42:20 +0100 Subject: [PATCH 14/28] Ajout d'une nouvelle couleur pour les listes --- sass/colors.scss | 2 ++ sass/list.scss | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/sass/colors.scss b/sass/colors.scss index bc54734..8fdcfdd 100644 --- a/sass/colors.scss +++ b/sass/colors.scss @@ -38,6 +38,7 @@ $pagination-hover-color: rgb(115, 151, 186); :root { --default-color: #{$white}; --bg-color: #{darken($white, 5%)}; + --bg-alternate-color: #{darken($white, 8%)}; --font-color: #{$nord3}; --footer-color: #{$darken-white}; --link-color: #{$nord1}; @@ -74,6 +75,7 @@ $pagination-hover-color: rgb(115, 151, 186); [data-theme="dark"] { --default-color: #{$nord3}; --bg-color: #{lighten($nord0, 2%)}; + --bg-alternate-color: #{lighten($nord0, 4%)}; --font-color: #{$nord6}; --footer-color: #{$nord1}; --link-color: #{$nord4}; diff --git a/sass/list.scss b/sass/list.scss index 537a60d..9dbd03a 100644 --- a/sass/list.scss +++ b/sass/list.scss @@ -3,16 +3,19 @@ .item{ padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--border-color); + border-bottom: 2px solid var(--border-color); + background-color: var(--bg-alternate-color); @include transition() {} @include respond-to("medium") { &:nth-child(2n) { background-color: var(--default-color); } + border: none; } + @include respond-to("medium-up") { - border-left: 1px solid var(--border-color); + border-left: 2px solid var(--border-color); &:nth-child(4n), &:nth-child(4n-1) @@ -22,13 +25,13 @@ &:first-child, &:nth-child(2) { - border-top: 1px solid var(--border-color); + border-top: 2px solid var(--border-color); } &:nth-child(2n), &:last-child { - border-right: 1px solid var(--border-color); - margin-right: -1px; + border-right: 2px solid var(--border-color); + margin-right: -2px; } } From 9e7743e16d3b937d7a444f3ea7ac8ba00d0a07d1 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Tue, 8 Mar 2022 09:56:23 +0100 Subject: [PATCH 15/28] =?UTF-8?q?Correction=20d'un=20bug=20sur=20le=20th?= =?UTF-8?q?=C3=A8me=20sombre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sass/colors.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sass/colors.scss b/sass/colors.scss index 8fdcfdd..bd46fcb 100644 --- a/sass/colors.scss +++ b/sass/colors.scss @@ -75,7 +75,7 @@ $pagination-hover-color: rgb(115, 151, 186); [data-theme="dark"] { --default-color: #{$nord3}; --bg-color: #{lighten($nord0, 2%)}; - --bg-alternate-color: #{lighten($nord0, 4%)}; + --bg-alternate-color: #{lighten($nord3, 8%)}; --font-color: #{$nord6}; --footer-color: #{$nord1}; --link-color: #{$nord4}; From 7578d9b63f2ef73dcb969a41449290df98d29682 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 14 Mar 2022 16:27:50 +0100 Subject: [PATCH 16/28] Correction des couleurs sur les boutons --- sass/button.scss | 2 +- sass/colors.scss | 6 +++++- views/pages/composants.ejs | 20 ++++++++++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/sass/button.scss b/sass/button.scss index 1d8c946..1fd0e5d 100644 --- a/sass/button.scss +++ b/sass/button.scss @@ -54,7 +54,7 @@ &.is-link { background-color: transparent; border-color: $nord9; - color: $nord9; + color: var(--button-link-text-color); &:hover { border-color: darken($nord9, $hoverAmount); diff --git a/sass/colors.scss b/sass/colors.scss index bd46fcb..441062c 100644 --- a/sass/colors.scss +++ b/sass/colors.scss @@ -29,7 +29,7 @@ $danger-color-hl: darken($danger-color, $hoverAmount); $warning-color-hl: darken($warning-color, $hoverAmount); $success-color-hl: darken($success-color, $hoverAmount); -$button-font-color: $nord1; +$button-font-color: #2C364A; $button-alternate-color: #01103C; $pagination-border-color: $nord3; @@ -54,6 +54,8 @@ $pagination-hover-color: rgb(115, 151, 186); --border-color: #{$nord4}; + --button-link-text-color: #2C364A; + --nord0: #{$nord0}; --nord1: #{$nord1}; --nord2: #{$nord2}; @@ -90,4 +92,6 @@ $pagination-hover-color: rgb(115, 151, 186); --box-shadow-color: #{rgba($nord4, 0.2)}; --border-color: #{$nord1}; + + --button-link-text-color: #{$white}; } \ No newline at end of file diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index bcf5c68..79b8ee6 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -121,13 +121,13 @@ <h2 id="formulaires">Les formulaires</h2> <div class="field"> - <label for="email">Adresse e-mail</label> - <input type="email" name="email" id="email" placeholder="ex : damien@darkou.fr"> + <label for="demo-email">Adresse e-mail</label> + <input type="email" name="email" id="demo-email" placeholder="ex : damien@darkou.fr"> </div> <pre> <div class="field"> - <label for="email">Adresse e-mail</label> - <input type="email" name="email" id="email" placeholder="ex : damien@darkou.fr"> + <label for="demo-email">Adresse e-mail</label> + <input type="email" name="email" id="demo-email" placeholder="ex : damien@darkou.fr"> </div> </pre> <div class="field"> @@ -168,16 +168,24 @@ </select> </div> </pre> - <div class="field"> + <form> + <fieldset> + <div class="field inline"> <label for="choix1">choix 1</label> <input type="radio" id="choix1" name="choix" value="choix1" checked> + </div> + <div class="field inline"> <label for="choix2">choix 2</label> <input type="radio" id="choix2" name="choix" value="choix2"> </div> + </fieldset> +</form> <pre> -<div class="field"> +<div class="field inline"> <label for="choix1">choix 1</label> <input type="radio" id="choix1" name="choix" value="choix1" checked> +</div> +<div class="field inline"> <label for="choix2">choix 2</label> <input type="radio" id="choix2" name="choix" value="choix2"> </div> From 5b9d6c94b834f8e1d7deb4b7383506057ec84b69 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Mon, 14 Mar 2022 16:28:08 +0100 Subject: [PATCH 17/28] Correction d'un bug sur Matomo --- README.md | 1 + docker-compose.yml.dev | 1 + docker-compose.yml.prod | 1 + src/config/index.js | 1 + views/index.ejs | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 82c35ce..1da497b 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ SECRET # Hash utilisé pour pour sauvegardé les dessions (par défaut waemaeMe5 DISCOGS_TOKEN # Token Discogs (vous devez créer un compte sur discogs afin d'en obtenir un gratuitement) 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) ``` diff --git a/docker-compose.yml.dev b/docker-compose.yml.dev index 0d49feb..9391624 100644 --- a/docker-compose.yml.dev +++ b/docker-compose.yml.dev @@ -26,6 +26,7 @@ services: DISCOGS_TOKEN: ${DISCOGS_TOKEN} FORMSPREE_ID: ${FORMSPREE_ID} MATOMO_URL: ${MATOMO_URL} + MATOMO_ID: ${MATOMO_ID} SITE_NAME: ${SITE_NAME} networks: - musictopus diff --git a/docker-compose.yml.prod b/docker-compose.yml.prod index 90031d8..9077877 100644 --- a/docker-compose.yml.prod +++ b/docker-compose.yml.prod @@ -26,6 +26,7 @@ services: DISCOGS_TOKEN: ${DISCOGS_TOKEN} FORMSPREE_ID: ${FORMSPREE_ID} MATOMO_URL: ${MATOMO_URL} + MATOMO_ID: ${MATOMO_ID} SITE_NAME: ${SITE_NAME} networks: - musictopus diff --git a/src/config/index.js b/src/config/index.js index 44d1402..19f16ea 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -6,5 +6,6 @@ module.exports = { discogsToken: process.env.DISCOGS_TOKEN, formspreeId: process.env.FORMSPREE_ID, matomoUrl: process.env.MATOMO_URL || "", + matomoId: process.env.MATOMO_ID || "", siteName: process.env.SITE_NAME || "MusicTopus", }; diff --git a/views/index.ejs b/views/index.ejs index 2fd6eab..a2a98b5 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -30,7 +30,7 @@ (function() { var u="<%= config.matomoUrl %>"; _paq.push(['setTrackerUrl', u+'matomo.php']); - _paq.push(['setSiteId', '3']); + _paq.push(['setSiteId', <%= config.matomoId %>]); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); From 7d7ee080ed4ce56daa3f87d7295091111247240b Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Fri, 8 Apr 2022 15:01:09 +0200 Subject: [PATCH 18/28] #32 - Avoir un logo pour les pages d'erreurs --- public/500.html | 39 +---- public/img/404.svg | 344 ++++++++++++++++++++++++++++++++++++++++++++- sass/500.scss | 22 +++ sass/index.scss | 1 + 4 files changed, 372 insertions(+), 34 deletions(-) create mode 100644 sass/500.scss diff --git a/public/500.html b/public/500.html index 072dfd0..76eb123 100644 --- a/public/500.html +++ b/public/500.html @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>Site en maintenance</title> + <title>MusicTopus - Erreur applicative</title> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> @@ -17,37 +17,10 @@ <link href="/css/main.css" rel="stylesheet" /> <script src="/js/main.js"></script> </head> - <body> - <nav class="navbar" aria-label="Navigation principale"> - <div class="navbar-brand"> - <a class="navbar-item" href="/"> - <img src="/img/logo.png" alt="Logo MusicTopus"> - <span>MusicTopus</span> - </a> - - <a role="button" class="navbar-burger" aria-label="Afficher le menu" aria-expanded="false" data-target="navbar"> - <span aria-hidden="true"></span> - <span aria-hidden="true"></span> - <span aria-hidden="true"></span> - </a> - </div> - </nav> - - <main class="layout-maxed home"> - <div class="header layout-hero"></div> - <h1>Site inaccessible</h1> - - <p> - Pas de panique on revient très vite ! - </p> - </main> - - <footer class="footer layout-hero"> - <p> - <strong title="Merci Brunus ! 😜">MusicTopus</strong> par <a href="https://www.darkou.fr" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>. - Fait avec ❤️ à Bordeaux. - Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a>. - </p> - </footer> + <body class="body-500"> + <img src="/img/404.svg" alt="Image représentant la mascotte tenant un vinyle cassé" /> + Nous sommes désolé mais quelque chose a mal tourné de notre côté. + <br /> + <small>On devrait revenir très vite ! 😅</small> </body> </html> diff --git a/public/img/404.svg b/public/img/404.svg index c963bb6..b4dbe78 100644 --- a/public/img/404.svg +++ b/public/img/404.svg @@ -1 +1,343 @@ -<?xml version="1.0" ?><svg data-name="Layer 1" id="Layer_1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#515570;}.cls-2,.cls-3,.cls-4,.cls-5{fill:none;stroke:#00adfe;stroke-miterlimit:10;}.cls-2,.cls-3,.cls-4,.cls-5,.cls-6{stroke-linecap:round;}.cls-2,.cls-5{stroke-width:4px;}.cls-3,.cls-4{stroke-width:3px;}.cls-4{stroke-dasharray:0.09 8.71;}.cls-5{stroke-dasharray:0.09 8.92;}.cls-6{fill:#fff;stroke:#393c54;stroke-linejoin:round;stroke-width:6px;}.cls-7{fill:#f8dc25;}</style></defs><title/><path class="cls-1" d="M41.48,93.8l-1.25,0-.6,14.28a2.07,2.07,0,0,1-1.89,2l-12.65,1.12A2.07,2.07,0,0,1,22.83,109L23.6,94.2,6.21,94.6a2.07,2.07,0,0,1-2.09-2.42L8.93,63.66a2.07,2.07,0,0,1,2-1.73h9.85a2.07,2.07,0,0,1,2,2.44L19.21,84.74l4.92-.15,1.32-26.28a2.07,2.07,0,0,1,2.07-2h12a2.07,2.07,0,0,1,2.07,2.16L40.59,84l.73,0A2.07,2.07,0,0,1,43.5,86v5.71A2.07,2.07,0,0,1,41.48,93.8Z"/><path class="cls-1" d="M121.48,50.8l-1.25,0-.6,14.28a2.07,2.07,0,0,1-1.89,2l-12.65,1.12A2.07,2.07,0,0,1,102.83,66l.77-14.83-17.39.39a2.07,2.07,0,0,1-2.09-2.42l4.81-28.51a2.07,2.07,0,0,1,2-1.73h9.85a2.07,2.07,0,0,1,2,2.44L99.21,41.74l4.92-.15,1.32-26.28a2.07,2.07,0,0,1,2.07-2h12a2.07,2.07,0,0,1,2.07,2.16l-1,25.49.73,0A2.07,2.07,0,0,1,123.5,43v5.71A2.07,2.07,0,0,1,121.48,50.8Z"/><path class="cls-2" d="M75.83,54.64l.69.48a14.84,14.84,0,0,1,4.26,19,11.87,11.87,0,0,1-16.07,4.85,9.5,9.5,0,0,1-3.88-12.86A7.6,7.6,0,0,1,71.12,63a6.08,6.08,0,0,1,2.48,8.23,4.86,4.86,0,0,1-6.58,2,3.89,3.89,0,0,1-1.59-5.27,3.11,3.11,0,0,1,4.21-1.27,2.49,2.49,0,0,1,1,3.37,2,2,0,0,1-2.7.81,1.59,1.59,0,0,1-.65-2.16A1.27,1.27,0,0,1,69,68.14a1,1,0,0,1,.42,1.38"/><path class="cls-3" d="M56,91a23.19,23.19,0,0,1-6.42-29.44,18.55,18.55,0,0,1,21.64-9"/><path class="cls-3" d="M87,92.64l0,0"/><path class="cls-4" d="M78.88,95.74a28.9,28.9,0,0,1-13.12-.21"/><path class="cls-3" d="M61.62,94.16l0,0"/><path class="cls-3" d="M98.32,81.11a28.9,28.9,0,0,1-6.83,8.44"/><path class="cls-2" d="M62.52,85.08A14.84,14.84,0,0,1,56.46,65a11.87,11.87,0,0,1,16.07-4.85A9.5,9.5,0,0,1,76.41,73a7.6,7.6,0,0,1-10.29,3.1,6.08,6.08,0,0,1-2.48-8.23,4.86,4.86,0,0,1,6.58-2,3.89,3.89,0,0,1,1.59,5.27,3.11,3.11,0,0,1-4.21,1.27,2.49,2.49,0,0,1-1-3.37,2,2,0,0,1,2.7-.81,1.59,1.59,0,0,1,.65,2.16,1.27,1.27,0,0,1-1.73.52,1,1,0,0,1-.42-1.38"/><path class="cls-3" d="M61,42.87a28.85,28.85,0,0,1,17.13,3.24A23.18,23.18,0,0,1,87.63,77.5,18.55,18.55,0,0,1,68.4,87.06"/><path class="cls-2" d="M41.49,54l0,0"/><path class="cls-5" d="M48,47.81a29,29,0,0,1,3.93-2.28"/><path class="cls-2" d="M56,43.92h0"/><ellipse class="cls-6" cx="68.42" cy="69.5" rx="7.58" ry="7.43"/><circle class="cls-7" cx="55.22" cy="55.5" r="4"/><path class="cls-7" d="M39.64,81.33l.47-1.2a.4.4,0,0,1,.75,0l.47,1.2a3.63,3.63,0,0,0,2,2l1.2.47a.4.4,0,0,1,0,.75l-1.2.47a3.63,3.63,0,0,0-2,2l-.47,1.2a.4.4,0,0,1-.75,0l-.47-1.2a3.63,3.63,0,0,0-2-2l-1.2-.47a.4.4,0,0,1,0-.75l1.2-.47A3.63,3.63,0,0,0,39.64,81.33Z"/><path class="cls-7" d="M94,48.27l.47-1.2a.4.4,0,0,1,.75,0l.47,1.2a3.63,3.63,0,0,0,2,2l1.2.47a.4.4,0,0,1,0,.75l-1.2.47a3.63,3.63,0,0,0-2,2l-.47,1.2a.4.4,0,0,1-.75,0L94,54.07a3.63,3.63,0,0,0-2-2l-1.2-.47a.4.4,0,0,1,0-.75l1.2-.47A3.63,3.63,0,0,0,94,48.27Z"/><circle class="cls-7" cx="95.61" cy="95.63" r="1.6"/><circle class="cls-7" cx="42.02" cy="63.64" r="1.6"/><circle class="cls-7" cx="85.48" cy="42.04" r="1.6"/></svg> \ No newline at end of file +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + 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" + height="100%" + width="100%"> + <defs + id="defs2"> + <linearGradient + id="linearGradient3016"> + <stop + offset="0" + stop-color="#949494" + id="stop3018-4" /> + <stop + offset="1" + stop-opacity="0" + stop-color="#949494" + id="stop3020-0" /> + </linearGradient> + <linearGradient + x1="57.074001" + y1="27.309999" + gradientTransform="translate(-19.041285,-22.505715)" + x2="103.29" + gradientUnits="userSpaceOnUse" + xlink:href="#linearGradient3016" + 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" + gradientUnits="userSpaceOnUse" + id="radialGradient10835"> + <stop + offset="0" + stop-color="#989898" + id="stop10767-64" /> + <stop + offset="1" + stop-opacity="0" + stop-color="#989898" + id="stop10769-6" /> + </radialGradient> + </defs> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + transform="translate(-4.0461145,-24.740973)"> + <g + transform="matrix(-1,0,0,1,16.909353,13.841748)" + id="g4845"> + <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" + 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" + style="fill:#7b2121;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + id="path4832" + 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> + <path + style="fill:#ec8479;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#fbb9b8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" + id="circle4790" + 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)" + 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" /> + <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" + id="path4786" + cx="39.641644" + cy="99.451027" /> + <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" + cx="39.641644" + cy="99.451027" /> + <path + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#eea6a7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <g + id="g4740" + transform="rotate(19.617168,87.590538,52.720911)"> + <path + id="path4725" + 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" /> + <circle + r="3.4367433" + cy="91.80127" + cx="101.24133" + id="path4732" + 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" + 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" + cy="92.300102" + r="0.59710735" /> + </g> + <path + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <path + style="fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + 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" /> + <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" + 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" /> + <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 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" /> + <g + style="stroke-width:42.36951447" + id="g6" + transform="matrix(0.02360188,0,0,0.02360188,113.18825,105.15104)"> + <path + id="path2" + 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" /> + <path + id="path4-9" + 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" /> + </g> + <g + transform="matrix(-0.0125514,0,0,0.0125514,96.697579,101.08859)" + id="g4688" + style="stroke-width:79.67237854"> + <path + style="fill:#29abe2;stroke-width:79.67237854" + 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" /> + <path + style="fill:#ffffff;stroke-width:79.67237854" + 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" /> + </g> + <path + id="path4692" + 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" + 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" /> + </g> +</svg> diff --git a/sass/500.scss b/sass/500.scss new file mode 100644 index 0000000..910f8dd --- /dev/null +++ b/sass/500.scss @@ -0,0 +1,22 @@ +.body-500 { + padding: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + font-weight: 400; + font-size: larger; + line-height: 200%; + + text-align: center; + + img { + max-width: 60%; + margin-bottom: 32px; + + @include respond-to("small-up") { + max-width: 30%; + } + } +} \ No newline at end of file diff --git a/sass/index.scss b/sass/index.scss index c54fc3c..3a53fd2 100644 --- a/sass/index.scss +++ b/sass/index.scss @@ -41,6 +41,7 @@ @import './box'; @import './error'; +@import './500'; @import './home'; @import './ajouter-un-album'; @import './collection'; From 182aa7a6a6006c3102d7105788ae4eb803a83876 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Fri, 8 Apr 2022 15:16:33 +0200 Subject: [PATCH 19/28] Modification du logo d'un erreur #32 --- public/img/404.svg | 383 ++++++++++++++++++++++----------------------- 1 file changed, 190 insertions(+), 193 deletions(-) diff --git a/public/img/404.svg b/public/img/404.svg index b4dbe78..4cd6c0c 100644 --- a/public/img/404.svg +++ b/public/img/404.svg @@ -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> From ae4b7b6de0f6bd77020fef45768b918cc7279445 Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Sat, 9 Apr 2022 00:07:22 +0200 Subject: [PATCH 20/28] issue/37 (#42) Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/42 --- README.md | 31 ++- docker-compose.yml.dev | 8 + docker-compose.yml.prod | 8 + package.json | 3 + src/app.js | 15 +- src/config/index.js | 9 + src/libs/aws.js | 72 ++++++ src/libs/passport.js | 25 +- src/middleware/Albums.js | 477 ++------------------------------------- src/middleware/Export.js | 453 +++++++++++++++++++++++++++++++++++++ src/middleware/Jobs.js | 128 +++++++++++ src/models/albums.js | 1 + src/models/jobs.js | 24 ++ src/routes/jobs.js | 40 ++++ 14 files changed, 822 insertions(+), 472 deletions(-) create mode 100644 src/libs/aws.js create mode 100644 src/middleware/Export.js create mode 100644 src/middleware/Jobs.js create mode 100644 src/models/jobs.js create mode 100644 src/routes/jobs.js diff --git a/README.md b/README.md index 1da497b..e05aa15 100644 --- a/README.md +++ b/README.md @@ -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: ) - diff --git a/docker-compose.yml.dev b/docker-compose.yml.dev index 9391624..dbe38b9 100644 --- a/docker-compose.yml.dev +++ b/docker-compose.yml.dev @@ -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: diff --git a/docker-compose.yml.prod b/docker-compose.yml.prod index 9077877..c343531 100644 --- a/docker-compose.yml.prod +++ b/docker-compose.yml.prod @@ -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: diff --git a/package.json b/package.json index b5aea27..d574778 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/app.js b/src/app.js index 6d1e507..c8a9574 100644 --- a/src/app.js +++ b/src/app.js @@ -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); diff --git a/src/config/index.js b/src/config/index.js index 19f16ea..17b341d 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -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", }; diff --git a/src/libs/aws.js b/src/libs/aws.js new file mode 100644 index 0000000..f65521b --- /dev/null +++ b/src/libs/aws.js @@ -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; +}; diff --git a/src/libs/passport.js b/src/libs/passport.js index af0ab0b..a3ed7fe 100644 --- a/src/libs/passport.js +++ b/src/libs/passport.js @@ -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"); + }) + ); }; diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 6caa29b..84f5c5d 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -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 = ["&", "<", ">"]; - - 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 { diff --git a/src/middleware/Export.js b/src/middleware/Export.js new file mode 100644 index 0000000..380f025 --- /dev/null +++ b/src/middleware/Export.js @@ -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 = ["&", "<", ">"]; + + 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; diff --git a/src/middleware/Jobs.js b/src/middleware/Jobs.js new file mode 100644 index 0000000..ccb2cf9 --- /dev/null +++ b/src/middleware/Jobs.js @@ -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; diff --git a/src/models/albums.js b/src/models/albums.js index 0180dad..e08b7ad 100644 --- a/src/models/albums.js +++ b/src/models/albums.js @@ -29,6 +29,7 @@ const AlbumSchema = new mongoose.Schema( extraartists: Array, images: Array, thumb: String, + thumbType: String, }, { timestamps: true } ); diff --git a/src/models/jobs.js b/src/models/jobs.js new file mode 100644 index 0000000..ba3727d --- /dev/null +++ b/src/models/jobs.js @@ -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); diff --git a/src/routes/jobs.js b/src/routes/jobs.js new file mode 100644 index 0000000..6bac899 --- /dev/null +++ b/src/routes/jobs.js @@ -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; From 62a3bcd8ee678c31985cb5f7fc84f236b4f868a6 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sat, 9 Apr 2022 00:28:12 +0200 Subject: [PATCH 21/28] Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/25 Co-authored-by: Damien Broqua <dbroqua@noreply.localhost> Co-committed-by: Damien Broqua <dbroqua@noreply.localhost> --- public/js/main.js | 2 +- sass/index.scss | 2 +- views/pages/collection.ejs | 2 +- views/pages/composants.ejs | 2 ++ views/pages/mon-compte/ma-collection/details.ejs | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index 895683a..f203a63 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -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); diff --git a/sass/index.scss b/sass/index.scss index 3a53fd2..373b302 100644 --- a/sass/index.scss +++ b/sass/index.scss @@ -46,4 +46,4 @@ @import './ajouter-un-album'; @import './collection'; @import './ma-collection-details'; -@import './composants'; \ No newline at end of file +@import './composants'; diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index da22489..d4fc28b 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -47,7 +47,7 @@ </span> <div class="grid grid-cols-2 md:grid-cols-4"> <div> - <img :src="item.thumb" :alt="item.title" /> + <a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a> </div> <div class="md:col-span-3"> <span><strong>Année :</strong> {{ item.year }}</span> diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 79b8ee6..a63d3a3 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -345,6 +345,8 @@ <i class="icon-moon">.icon-moon</i> <i class="icon-trash">.icon-trash</i> <i class="icon-blind">.icon-blind</i> + <i class="icon-left-open">.icon-left-open</i> + <i class="icon-right-open">.icon-right-open</i> <h2 id="listes">Les listes</h2> <h3>Liste normale</h3> diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 12490c0..bd97f90 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -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; From 0d90e0da205d9a42f42d7a4be0397276c7fffd31 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sat, 9 Apr 2022 00:29:50 +0200 Subject: [PATCH 22/28] issue/1 (#31) Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/31 --- views/pages/collection.ejs | 2 +- views/pages/composants.ejs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index d4fc28b..da22489 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -47,7 +47,7 @@ </span> <div class="grid grid-cols-2 md:grid-cols-4"> <div> - <a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a> + <img :src="item.thumb" :alt="item.title" /> </div> <div class="md:col-span-3"> <span><strong>Année :</strong> {{ item.year }}</span> diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index a63d3a3..79b8ee6 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -345,8 +345,6 @@ <i class="icon-moon">.icon-moon</i> <i class="icon-trash">.icon-trash</i> <i class="icon-blind">.icon-blind</i> - <i class="icon-left-open">.icon-left-open</i> - <i class="icon-right-open">.icon-right-open</i> <h2 id="listes">Les listes</h2> <h3>Liste normale</h3> From 36b33124bcc670c42bd5ef8317d0e4cf6955ee20 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sat, 9 Apr 2022 00:33:16 +0200 Subject: [PATCH 23/28] =?UTF-8?q?Am=C3=A9lioration=20de=20la=20visibilit?= =?UTF-8?q?=C3=A9=20des=20=C3=A9l=C3=A9ments=20d'une=20liste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sass/list.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sass/list.scss b/sass/list.scss index 9dbd03a..fbdc7c2 100644 --- a/sass/list.scss +++ b/sass/list.scss @@ -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); From 48c997ae10956a849310586c14f4267efe18a3ac Mon Sep 17 00:00:00 2001 From: Damien Broqua <dbroqua@noreply.localhost> Date: Sun, 10 Apr 2022 15:21:15 +0200 Subject: [PATCH 24/28] =?UTF-8?q?#35=20-=20Je=20peux=20acc=C3=A9der=20?= =?UTF-8?q?=C3=A0=20mon=20compte=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dbroqua <contact@darkou.fr> Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/44 --- public/robots.txt | 0 sass/flash.scss | 8 +++ sass/navbar.scss | 9 +++ src/app.js | 44 +++++++++----- src/middleware/Albums.js | 5 +- src/middleware/Me.js | 34 +++++++++-- src/middleware/Pages.js | 17 +++--- src/routes/mon-compte.js | 35 +++++++++++ views/error.ejs | 2 - views/index.ejs | 70 ++++++++++++++-------- views/pages/ajouter-un-album.ejs | 2 +- views/pages/composants.ejs | 16 ++++++ views/pages/mon-compte/index.ejs | 99 ++++++++++++++++++++++++++++++++ 13 files changed, 284 insertions(+), 57 deletions(-) create mode 100644 public/robots.txt create mode 100644 src/routes/mon-compte.js create mode 100644 views/pages/mon-compte/index.ejs diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e69de29 diff --git a/sass/flash.scss b/sass/flash.scss index db28cb0..fc804a6 100644 --- a/sass/flash.scss +++ b/sass/flash.scss @@ -11,4 +11,12 @@ .header { font-weight: 800; } + + &.info { + background-color: $warning-color; + } + + &.success { + background-color: $success-color; + } } \ No newline at end of file diff --git a/sass/navbar.scss b/sass/navbar.scss index 8e4f3ec..f9583b9 100644 --- a/sass/navbar.scss +++ b/sass/navbar.scss @@ -132,6 +132,8 @@ } &:hover { + background-color: var(--default-color); + .navbar-link { background-color: var(--default-hl-color); color: rgba(0,0,0,.7); @@ -252,6 +254,13 @@ padding-bottom: .5rem; padding-top: .5rem; + hr { + background-color: var(--font-color); + border: none; + height: 2px; + margin: .5rem 0; + } + .navbar-item { cursor: pointer; padding-left: 1.5rem; diff --git a/src/app.js b/src/app.js index c8a9574..85650e8 100644 --- a/src/app.js +++ b/src/app.js @@ -15,6 +15,7 @@ import { isXhr } from "./helpers"; import indexRouter from "./routes"; import maCollectionRouter from "./routes/ma-collection"; +import monCompteRouter from "./routes/mon-compte"; import collectionRouter from "./routes/collection"; import importJobsRouter from "./routes/jobs"; @@ -83,6 +84,7 @@ app.use( ); app.use("/", indexRouter); +app.use("/mon-compte", monCompteRouter); app.use("/ma-collection", maCollectionRouter); app.use("/collection", collectionRouter); app.use("/jobs", importJobsRouter); @@ -97,15 +99,22 @@ app.use((req, res) => { } else { res.status(404).render("index", { page: { title: `404: Cette page n'existe pas.` }, - errorCode: 404, viewname: "error", - user: req.user || null, - config, session: req.session || null, - flashInfo: null, - query: null, - params: null, - error: null, + flash: { + info: req.flash("info"), + error: [ + ...req.flash("error"), + ...(req.session?.flash?.error || []), + ], + success: req.flash("success"), + }, + query: req.query, + params: req.params, + user: req.user, + config, + getBaseUrl: null, + errorCode: 404, }); } }); @@ -122,15 +131,22 @@ app.use((error, req, res, next) => { title: error.title || "500: Oups… le serveur a crashé !", error, }, - errorCode: error.errorCode || 500, viewname: "error", - user: req.user || null, - config, session: req.session || null, - flashInfo: null, - query: null, - params: null, - error: null, + flash: { + info: req.flash("info"), + error: [ + ...req.flash("error"), + ...(req.session?.flash?.error || []), + ], + success: req.flash("success"), + }, + query: req.query, + params: req.params, + user: req.user, + config, + getBaseUrl: null, + errorCode: error.errorCode || 500, }); next(); diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 84f5c5d..05cc60a 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -98,7 +98,7 @@ class Albums extends Pages { if (!this.req.user && !collectionUserId) { throw new ErrorEvent( 401, - "Cette collection n'est pas publique", + "Collection", "Cette collection n'est pas publique" ); } @@ -114,7 +114,7 @@ class Albums extends Pages { ) { throw new ErrorEvent( 401, - "Cette collection n'est pas publique", + "Collection", "Cette collection n'est pas publique" ); } @@ -233,6 +233,7 @@ class Albums extends Pages { if (!user || !user.isPublicCollection) { throw new ErrorEvent( 401, + "Collection non partagée", "Cet utilisateur ne souhaite pas partager sa collection" ); } diff --git a/src/middleware/Me.js b/src/middleware/Me.js index ae2712c..27a1b46 100644 --- a/src/middleware/Me.js +++ b/src/middleware/Me.js @@ -1,15 +1,12 @@ import Joi from "joi"; import UsersModel from "../models/users"; +import Pages from "./Pages"; /** * Classe permettant la gestion de l'utilisateur connecté */ -class Me { - constructor(req) { - this.req = req; - } - +class Me extends Pages { /** * Méthode permettant de modifier le profil d'un utilisateur * @return {Object} @@ -40,6 +37,33 @@ class Me { return update; } + + /** + * Méthode permettant de modifier le mot de passe d'un utilisateur + */ + async updatePassword() { + const { body } = this.req; + const { _id } = this.req.user; + + const schema = Joi.object({ + oldPassword: Joi.string().required(), + password: Joi.string().required(), + passwordConfirm: Joi.ref("password"), + }); + + const value = await schema.validateAsync(body); + const user = await UsersModel.findById(_id); + + if (!user.validPassword(value.oldPassword)) { + throw new Error("Votre ancien mot de passe n'est pas valide"); + } + + user.salt = value.password; + + await user.save(); + + this.req.flash("success", "Profil correctement mis à jour"); + } } export default Me; diff --git a/src/middleware/Pages.js b/src/middleware/Pages.js index eb3ea75..04c2e2e 100644 --- a/src/middleware/Pages.js +++ b/src/middleware/Pages.js @@ -52,21 +52,20 @@ class Pages { */ render() { this.pageContent.session = this.req.session; - this.pageContent.flashInfo = this.req.flash("info"); - this.pageContent.error = this.req.flash("error") || null; + this.pageContent.flash = { + info: this.req.flash("info"), + error: [ + ...this.req.flash("error"), + ...(this.req.session?.flash?.error || []), + ], + success: this.req.flash("success"), + }; this.pageContent.query = this.req.query; this.pageContent.params = this.req.params; this.pageContent.user = this.user; this.pageContent.config = config; this.pageContent.getBaseUrl = getBaseUrl(this.req); - if (this.req.session.flash && this.req.session.flash.error) { - // eslint-disable-next-line prefer-destructuring - this.pageContent.page.failureFlash = - this.req.session.flash.error[0]; - this.req.session.flash = null; - } - return this.pageContent; } } diff --git a/src/routes/mon-compte.js b/src/routes/mon-compte.js new file mode 100644 index 0000000..813d0e5 --- /dev/null +++ b/src/routes/mon-compte.js @@ -0,0 +1,35 @@ +import express from "express"; +import { ensureLoggedIn } from "connect-ensure-login"; + +import Me from "../middleware/Me"; + +import render from "../libs/format"; + +// eslint-disable-next-line new-cap +const router = express.Router(); + +router + .route("/") + .get(ensureLoggedIn("/connexion"), async (req, res, next) => { + try { + const page = new Me(req, "mon-compte/index"); + + page.setPageTitle("Mon compte"); + + render(res, page); + } catch (err) { + next(err); + } + }) + .post(ensureLoggedIn("/connexion"), async (req, res) => { + try { + const page = new Me(req, "mon-compte/index"); + + await page.updatePassword(); + } catch (err) { + req.flash("error", err.toString()); + } + res.redirect("/mon-compte"); + }); + +export default router; diff --git a/views/error.ejs b/views/error.ejs index 8f80430..520a37e 100644 --- a/views/error.ejs +++ b/views/error.ejs @@ -1,10 +1,8 @@ <main class="layout-maxed error"> <h1><%= page.title %></h1> - <% if ( errorCode && errorCode === 404 ) { %> <p class="text-center"> <img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" /> </p> - <% } %> <% if ( process.env.NODE_ENV !== 'production' ) { %> <div> <pre><%= page.error %></pre> diff --git a/views/index.ejs b/views/index.ejs index a2a98b5..ef76ac1 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -83,6 +83,10 @@ </a> <div class="navbar-dropdown"> + <a class="navbar-item" href="/mon-compte"> + Mon compte + </a> + <hr /> <a class="navbar-item" href="/ma-collection"> Ma collection </a> @@ -125,32 +129,50 @@ <span></span> </div> - <% if ( page.failureFlash || (error && error.length > 0 ) ) {%> - <div class="flash"> - <% if ( page.failureFlash ) {%> - <div class="header"> - Erreur + <% + if ( flash.error.length > 0 ) { + for ( let i = 0 ; i < flash.error.length ; i += 1 ) { + %> + <div class="flash"> + <div class="header"> + Erreur + </div> + <div class="body"> + <%= flash.error[i].replace('Error: ', '') %> + </div> </div> - <div class="body"> - <%= page.failureFlash %> + <% + } + } + if ( flash.info.length > 0 ) { + for ( let i = 0 ; i < flash.info.length ; i += 1 ) { + %> + <div class="flash info"> + <div class="header"> + Information + </div> + <div class="body"> + <%= flash.info[i] %> + </div> </div> - <% } %> - <% - if (error && error.length > 0) { - for( let i = 0 ; i < error.length ; i += 1 ) { - %> - <div class="header"> - Erreur - </div> - <div class="body"> - <%= error %> - </div> - <% - } - } - %> - </div> - <% } %> + <% + } + } + if ( flash.success.length > 0 ) { + for ( let i = 0 ; i < flash.success.length ; i += 1 ) { + %> + <div class="flash success"> + <div class="header"> + Succès + </div> + <div class="body"> + <%= flash.success[i] %> + </div> + </div> + <% + } + } + %> <%- include(viewname) %> diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index 797bce6..0ab0b49 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -244,4 +244,4 @@ }, } }).mount('#app'); - </script> +</script> diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 79b8ee6..1cba4e5 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -274,6 +274,22 @@ Ceci est une erreur </div> </div> + <div class="flash info"> + <div class="header"> + Information + </div> + <div class="body"> + Ceci est une information + </div> + </div> + <div class="flash success"> + <div class="header"> + Succès + </div> + <div class="body"> + Ceci est un succès + </div> + </div> <pre> <div class="flash"> <div class="header"> diff --git a/views/pages/mon-compte/index.ejs b/views/pages/mon-compte/index.ejs new file mode 100644 index 0000000..d8f7f6a --- /dev/null +++ b/views/pages/mon-compte/index.ejs @@ -0,0 +1,99 @@ +<main class="layout-maxed collection" id="app"> + <h1> + Mon compte + </h1> + + <div class="grid grid-cols-1 md:grid-cols-2 gap-10"> + <form method="POST" action="/mon-compte" @submit="updateProfil"> + + <div class="field"> + <label for="email">Adresse e-mail</label> + <input + type="email" + readonly + disabled + name="email" + id="email" + v-model="email" + /> + </div> + <div class="field"> + <label for="username">Nom d'utilisateur</label> + <input + type="string" + readonly + disabled + name="username" + id="username" + v-model="username" + /> + </div> + <div class="field"> + <label for="oldPassword">Mot de passe actuel</label> + <input + type="password" + name="oldPassword" + id="oldPassword" + required + placeholder="Saisisssez votre mot de passe actuel" + v-model="oldPassword" + /> + </div> + <div></div> + <div class="field"> + <label for="password">Nouveau mot de passe</label> + <input + type="password" + name="password" + id="password" + required + placeholder="Saisisssez votre nouveau mot de passe" + v-model="password" + /> + </div> + <div class="field"> + <label for="passwordConfirm">Nouveau mot de passe (confirmation)</label> + <input + type="password" + name="passwordConfirm" + id="passwordConfirm" + required + placeholder="Confirmez votre nouveau mot de passe" + v-model="passwordConfirm" + /> + </div> + + <button type="submit" class="button is-primary mt-10" :disabled="loading"> + <span v-if="!loading">Mettre à jour</span> + <i class="icon-spin animate-spin" v-if="loading"></i> + </button> + </form> + </div> +</main> + +<script> + Vue.createApp({ + data() { + return { + email: '<%= user.email %>', + username: '<%= user.username %>', + oldPassword: '', + password: '', + passwordConfirm: '', + loading: false, + } + }, + methods: { + async updateProfil(event) { + // try { + // if ( this.password !== this.passwordConfirm ) { + // throw "La confirnation du mot de passe ne correspond pas"; + // } + // } catch(err) { + // event.preventDefault(); + // showToastr(err); + // } + }, + } + }).mount('#app'); + </script> From e8f91288fc498d3a5d9f150aadb96e07178ebd7a Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sun, 10 Apr 2022 15:27:51 +0200 Subject: [PATCH 25/28] Correction orthographique --- views/pages/ajouter-un-album.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index 0ab0b49..2cd4ab4 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -239,7 +239,7 @@ window.location.href = '/ma-collection'; }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible d'ajouter ce album pour le moment…"); + showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); }); }, } From f5196edfb8c04c354151011afac834a7481a02b1 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sun, 10 Apr 2022 16:04:50 +0200 Subject: [PATCH 26/28] #38 - Remplacer momentjs --- package.json | 4 ++-- src/middleware/Albums.js | 11 ++++++++--- src/middleware/Export.js | 7 +++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d574778..49c965d 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "connect-flash": "^0.1.1", "connect-mongo": "^4.6.0", "cookie-parser": "^1.4.6", + "date-fns": "^2.28.0", + "date-fns-tz": "^1.3.3", "debug": "^4.3.3", "disconnect": "^1.2.2", "ejs": "^3.1.6", @@ -55,8 +57,6 @@ "express-session": "^1.17.2", "joi": "^17.6.0", "knacss": "^8.0.4", - "moment": "^2.29.1", - "moment-timezone": "^0.5.34", "mongoose": "^6.2.1", "mongoose-unique-validator": "^3.0.0", "npm-run-all": "^4.1.5", diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 05cc60a..7dd5ae6 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -1,4 +1,4 @@ -import moment from "moment"; +import { format as formatDate } from "date-fns"; import Pages from "./Pages"; import Export from "./Export"; @@ -26,7 +26,7 @@ class Albums extends Pages { User: user._id, }; data.released = data.released - ? moment(data.released.replace("-00", "-01")) + ? new Date(data.released.replace("-00", "-01")) : null; delete data.id; @@ -211,11 +211,16 @@ class Albums extends Pages { async loadItem() { const { itemId: _id } = this.req.params; const { _id: User } = this.req.user; - const item = await AlbumsModel.findOne({ + const album = await AlbumsModel.findOne({ _id, User, }); + const item = { + ...album.toJSON(), + released: formatDate(album.released, "MM/dd/yyyy"), + }; + this.setPageContent("item", item); this.setPageTitle( `Détails de l'album ${item.title} de ${item.artists_sort}` diff --git a/src/middleware/Export.js b/src/middleware/Export.js index 380f025..513e440 100644 --- a/src/middleware/Export.js +++ b/src/middleware/Export.js @@ -1,4 +1,5 @@ -import momenttz from "moment-timezone"; +import { utcToZonedTime } from "date-fns-tz"; +import setHours from "date-fns/setHours"; import xl from "excel4node"; class Export { @@ -132,7 +133,9 @@ class Export { } if (released) { ws.cell(currentRow, 7) - .date(momenttz.tz(released, "Europe/Paris").hour(12)) + .date( + setHours(utcToZonedTime(released, "Europe/Paris"), 12) + ) .style({ numberFormat: "dd/mm/yyyy" }); } ws.cell(currentRow, 8).string(format).style(style); From 9dd7a35f22738ba7330984a1fff1b5beca038ce2 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sun, 10 Apr 2022 16:42:21 +0200 Subject: [PATCH 27/28] #45 - Ajouter de nouveaux filtres sur la liste --- sass/collection.scss | 12 ++++ src/middleware/Albums.js | 30 +++++++++ views/pages/collection.ejs | 60 ++++++++++++++++++ .../pages/mon-compte/ma-collection/index.ejs | 63 ++++++++++++++++++- 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/sass/collection.scss b/sass/collection.scss index 6412fe1..88f94b5 100644 --- a/sass/collection.scss +++ b/sass/collection.scss @@ -17,6 +17,7 @@ } @include respond-to("small-up") { + width: 33%; &:last-child { padding-right: 0; } @@ -28,6 +29,17 @@ } } + .showMoreFilters { + cursor: pointer; + + .up::before { + transform: rotate(90deg); + } + .down::before { + transform: rotate(270deg); + } + } + .list{ .title { .icon-trash { diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 7dd5ae6..2076b32 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -81,6 +81,9 @@ class Albums extends Pages { order = "asc", artists_sort, format, + year, + genre, + style, userId: collectionUserId, } = this.req.query; @@ -94,6 +97,15 @@ class Albums extends Pages { if (format) { where["formats.name"] = format; } + if (year) { + where.year = year; + } + if (genre) { + where.genres = genre; + } + if (style) { + where.styles = style; + } if (!this.req.user && !collectionUserId) { throw new ErrorEvent( @@ -199,9 +211,21 @@ class Albums extends Pages { "formats.name", this.req.user._id ); + const years = await Albums.getAllDistincts("year", this.req.user._id); + const genres = await Albums.getAllDistincts( + "genres", + this.req.user._id + ); + const styles = await Albums.getAllDistincts( + "styles", + this.req.user._id + ); this.setPageContent("artists", artists); this.setPageContent("formats", formats); + this.setPageContent("years", years); + this.setPageContent("genres", genres); + this.setPageContent("styles", styles); this.setPageTitle("Ma collection"); } @@ -245,11 +269,17 @@ class Albums extends Pages { const artists = await Albums.getAllDistincts("artists_sort", userId); const formats = await Albums.getAllDistincts("formats.name", userId); + const years = await Albums.getAllDistincts("year", userId); + const genres = await Albums.getAllDistincts("genres", userId); + const styles = await Albums.getAllDistincts("styles", userId); this.setPageContent("username", user.username); this.setPageTitle(`Collection publique de ${user.username}`); this.setPageContent("artists", artists); this.setPageContent("formats", formats); + this.setPageContent("years", years); + this.setPageContent("genres", genres); + this.setPageContent("styles", styles); } } diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index da22489..a0530f7 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -40,6 +40,50 @@ </select> </div> </div> + + <div class="filters" v-if="moreFilters"> + <div class="field"> + <label for="format">Année</label> + <select id="format" v-model="year" @change="changeFilter"> + <option value="">Toutes</option> + <% + for (let i = 0; i < page.years.length; i += 1 ) { + __append(`<option value="${page.years[i]}">${page.years[i]}</option>`); + } + %> + </select> + </div> + <div class="field"> + <label for="genre">Genre</label> + <select id="genre" v-model="genre" @change="changeFilter"> + <option value="">Tous</option> + <% + for (let i = 0; i < page.genres.length; i += 1 ) { + __append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`); + } + %> + </select> + </div> + <div class="field"> + <label for="style">Style</label> + <select id="style" v-model="style" @change="changeFilter"> + <option value="">Tous</option> + <% + for (let i = 0; i < page.styles.length; i += 1 ) { + __append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`); + } + %> + </select> + </div> + </div> + + <span @click="showMoreFilters" class="showMoreFilters"> + <template v-if="!moreFilters">Voir plus de filtres</template> + <template v-if="moreFilters">Voir moins de filtres</template> + <i class="icon-left-open down" v-if="!moreFilters"></i> + <i class="icon-left-open up" v-if="moreFilters"></i> + </span> + <div class="grid grid-cols-1 md:grid-cols-2 list"> <div class="item" v-if="!loading" v-for="item in items"> <span class="title"> @@ -105,6 +149,7 @@ data() { return { loading: false, + moreFilters: false, items: [], total: 0, page: 1, @@ -112,6 +157,9 @@ limit: 16, artist: '', format: '', + year: '', + genre: '', + style: '', sortOrder: 'artists_sort-asc', sort: 'artists_sort', order: 'asc', @@ -132,6 +180,15 @@ if ( this.format ) { url += `&format=${this.format}`; } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre.replace('&', '%26')}`; + } + if ( this.style ) { + url += `&style=${this.style.replace('&', '%26')}`; + } axios.get(url) .then( response => { @@ -179,6 +236,9 @@ this.fetch(); }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + } } }).mount('#app'); </script> diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 9dd4bbd..eb21c37 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -6,6 +6,7 @@ <a :href="shareLink" v-if="isPublicCollection" target="_blank"> <i class="icon-share"></i> Voir ma collection partagée </a> + <div class="filters"> <div class="field"> <label for="artist">Artiste</label> @@ -43,6 +44,50 @@ </select> </div> </div> + + <div class="filters" v-if="moreFilters"> + <div class="field"> + <label for="format">Année</label> + <select id="format" v-model="year" @change="changeFilter"> + <option value="">Toutes</option> + <% + for (let i = 0; i < page.years.length; i += 1 ) { + __append(`<option value="${page.years[i]}">${page.years[i]}</option>`); + } + %> + </select> + </div> + <div class="field"> + <label for="genre">Genre</label> + <select id="genre" v-model="genre" @change="changeFilter"> + <option value="">Tous</option> + <% + for (let i = 0; i < page.genres.length; i += 1 ) { + __append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`); + } + %> + </select> + </div> + <div class="field"> + <label for="style">Style</label> + <select id="style" v-model="style" @change="changeFilter"> + <option value="">Tous</option> + <% + for (let i = 0; i < page.styles.length; i += 1 ) { + __append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`); + } + %> + </select> + </div> + </div> + + <span @click="showMoreFilters" class="showMoreFilters"> + <template v-if="!moreFilters">Voir plus de filtres</template> + <template v-if="moreFilters">Voir moins de filtres</template> + <i class="icon-left-open down" v-if="!moreFilters"></i> + <i class="icon-left-open up" v-if="moreFilters"></i> + </span> + <div class="grid grid-cols-1 md:grid-cols-2 list hover"> <div class="item" v-if="!loading" v-for="item in items"> <span class="title"> @@ -154,6 +199,7 @@ data() { return { loading: false, + moreFilters: false, items: [], total: 0, page: 1, @@ -161,6 +207,9 @@ limit: 16, artist: '', format: '', + year: '', + genre: '', + style: '', sortOrder: 'artists_sort-asc', sort: 'artists_sort', order: 'asc', @@ -185,6 +234,15 @@ if ( this.format ) { url += `&format=${this.format}`; } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre.replace('&', '%26')}`; + } + if ( this.style ) { + url += `&style=${this.style.replace('&', '%26')}`; + } axios.get(url) .then( response => { @@ -232,6 +290,9 @@ this.fetch(); }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, toggleModal() { this.showModalDelete = !this.showModalDelete; }, @@ -273,7 +334,7 @@ .finally(() => { this.toggleModalShare(); }); - } + }, } }).mount('#app'); </script> From 12ca71e6434c492c415b7c6907a9625f620a9026 Mon Sep 17 00:00:00 2001 From: dbroqua <contact@darkou.fr> Date: Sun, 10 Apr 2022 17:27:26 +0200 Subject: [PATCH 28/28] =?UTF-8?q?#39=20-=20=C3=8Atre=20capable=20de=20d?= =?UTF-8?q?=C3=A9sactiver=20les=20inscriptions=20+=20maj=20footer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +++++----- docker-compose.yml.dev | 1 + docker-compose.yml.prod | 1 + src/config/index.js | 2 + src/routes/index.js | 42 +++++++++++++------ views/index.ejs | 5 ++- views/pages/connexion.ejs | 2 + views/pages/inscription/desactivee.ejs | 19 +++++++++ .../index.ejs} | 0 9 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 views/pages/inscription/desactivee.ejs rename views/pages/{inscription.ejs => inscription/index.ejs} (100%) diff --git a/README.md b/README.md index e05aa15..03e3247 100644 --- a/README.md +++ b/README.md @@ -210,22 +210,23 @@ Voici la liste des variables configurables : ``` NODE_ENV # Environnement dans lequel exécuter le projet (development ou production) -PORT # Port sur lequel éxécuter le serveur (par défaut 3001) -MONGODB_URI # Url du serveur mongo (par défaut mongodb://musictopus-db/musictopus) -SECRET # Hash utilisé pour pour sauvegardé les dessions (par défaut waemaeMe5ahc6ce1chaeKohKa6Io8Eik) +PORT # Port sur lequel éxécuter le serveur (3001 par défaut) +MONGODB_URI # Url du serveur mongo (mongodb://musictopus-db/musictopus par défaut) +SECRET # Hash utilisé pour pour sauvegardé les dessions (waemaeMe5ahc6ce1chaeKohKa6Io8Eik par défault) DISCOGS_TOKEN # Token Discogs (vous devez créer un compte sur discogs afin d'en obtenir un gratuitement) 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) +SITE_NAME # Nom du site utilisé dans le titre des pages (MusicTopus par défaut) 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é +S3_ENDPOINT # Url de l'instance aws (s3.fr-par.scw.cloud par défaut) +S3_SIGNATURE # Version de la signature AWS (s3v4 par défaut) +S3_BASEFOLDER # Nom du sous dossier dans lequel seront mis les pochettes des albums (dev par défaut) +S3_BUCKET # Nom du bucket (musictopus par défaut, à changer impérativement si vous voulez que cela fonctionne) +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) +REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut) ``` ## Contributeurs diff --git a/docker-compose.yml.dev b/docker-compose.yml.dev index dbe38b9..ca86d9e 100644 --- a/docker-compose.yml.dev +++ b/docker-compose.yml.dev @@ -36,6 +36,7 @@ services: S3_SIGNATURE: ${S3_SIGNATURE} JOBS_HEADER_KEY: ${JOBS_HEADER_KEY} JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE} + REGISTRATION_OPEN: ${REGISTRATION_OPEN} networks: - musictopus musictopus-db: diff --git a/docker-compose.yml.prod b/docker-compose.yml.prod index c343531..6beffa3 100644 --- a/docker-compose.yml.prod +++ b/docker-compose.yml.prod @@ -36,6 +36,7 @@ services: S3_SIGNATURE: ${S3_SIGNATURE} JOBS_HEADER_KEY: ${JOBS_HEADER_KEY} JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE} + REGISTRATION_OPEN: ${REGISTRATION_OPEN} networks: - musictopus musictopus-db: diff --git a/src/config/index.js b/src/config/index.js index 17b341d..7906240 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -17,4 +17,6 @@ module.exports = { jobsHeaderKey: process.env.JOBS_HEADER_KEY || "musictopus", jobsHeaderValue: process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew", + registrationOpen: + (process.env.REGISTRATION_OPEN || "true").toLowerCase() === "true", }; diff --git a/src/routes/index.js b/src/routes/index.js index ac43a51..faa4142 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -7,6 +7,8 @@ import Auth from "../middleware/Auth"; import render from "../libs/format"; +import { registrationOpen } from "../config"; + // eslint-disable-next-line new-cap const router = express.Router(); @@ -59,11 +61,33 @@ router } ); -router - .route("/inscription") - .get((req, res, next) => { +if (registrationOpen) { + router + .route("/inscription") + .get((req, res, next) => { + try { + const page = new Pages(req, "inscription/index"); + + page.setPageTitle("Inscription"); + + render(res, page); + } catch (err) { + next(err); + } + }) + .post(async (req, res) => { + try { + await Auth.register(req); + + res.redirect("/"); + } catch (err) { + res.redirect("/inscription"); + } + }); +} else { + router.route("/inscription").get((req, res, next) => { try { - const page = new Pages(req, "inscription"); + const page = new Pages(req, "inscription/desactivee"); page.setPageTitle("Inscription"); @@ -71,16 +95,8 @@ router } catch (err) { next(err); } - }) - .post(async (req, res) => { - try { - await Auth.register(req); - - res.redirect("/"); - } catch (err) { - res.redirect("/inscription"); - } }); +} router .route("/ajouter-un-album") diff --git a/views/index.ejs b/views/index.ejs index ef76ac1..2386a83 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -179,8 +179,11 @@ <footer class="footer layout-hero"> <p> <strong title="Merci Brunus ! 😜">MusicTopus</strong> par <a href="https://www.darkou.fr" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>. + Logo réalisé par Brunus avec <a href="https://inkscape.org/fr/" target="_blank" rel="noopener noreferrer">Inkscape <i class="icon-link"></i></a>. + <br /> + Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a> et disponible sur <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>. + <br /> Fait avec ❤️ à Bordeaux. - Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a>. </p> </footer> </body> diff --git a/views/pages/connexion.ejs b/views/pages/connexion.ejs index ae162fa..2e86f72 100644 --- a/views/pages/connexion.ejs +++ b/views/pages/connexion.ejs @@ -12,9 +12,11 @@ <input type="password" name="password" id="password" placeholder="********"> </div> + <% if ( config.registrationOpen === true ) { %> <div class="text-right mt-10"> <p>Pas encore inscrit ? <a href="/inscription">Inscrivez-vous</a></p> </div> + <% } %> <button type="submit" class="button is-primary">Connexion</button> </form> diff --git a/views/pages/inscription/desactivee.ejs b/views/pages/inscription/desactivee.ejs new file mode 100644 index 0000000..b8140f1 --- /dev/null +++ b/views/pages/inscription/desactivee.ejs @@ -0,0 +1,19 @@ +<main class="layout-maxed"> + <div class="header layout-hero"></div> + <h1> + Inscription + </h1> + <div class="container"> + <div class="text"> + <p class="text-justify"> + Les inscriptions sur ce site sont fermées. + </p> + <p class="text-justify"> + Vous avez cependant la possibilité d'héberger vous même une version de <a href="https://www.musictopus.fr" target="_blank">MusicTopus</a> en vous rendant directement sur le <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">dépot du projet</a>. + </p> + </div> + <p class="text-center"> + <img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" /> + </p> + </div> +</main> \ No newline at end of file diff --git a/views/pages/inscription.ejs b/views/pages/inscription/index.ejs similarity index 100% rename from views/pages/inscription.ejs rename to views/pages/inscription/index.ejs