From 6d0405d129eee2faa38483c31028b62ec484f34a Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Sat, 9 Apr 2022 00:42:24 +0200 Subject: [PATCH 01/65] Version 1.1 Correction de bugs : * Avoir un logo pour les pages d'erreurs #32 * Stocker localement les assets d'un album #37 Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/43 --- README.md | 31 +- docker-compose.yml.dev | 8 + docker-compose.yml.prod | 8 + package.json | 3 + public/img/404.svg | 383 +++++++------- public/js/main.js | 2 +- sass/index.scss | 2 +- sass/list.scss | 6 + 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 ++ .../mon-compte/ma-collection/details.ejs | 4 +- 19 files changed, 1022 insertions(+), 669 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/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"> + offset="0" /> + stop-opacity="0" + offset="1" /> - + x2="103.29" + gradientTransform="translate(-19.041285,-22.505715)" + y1="27.309999" + x1="57.074001" /> + + offset="0" /> + offset="1" /> + transform="translate(-4.0461145,-24.740973)" + id="layer1"> + id="g4845" + transform="matrix(-1,0,0,1,16.909353,13.841748)"> - - - - - + + + + + + style="fill:#ec8479;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#fbb9b8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - + + transform="translate(4.0461145,24.740973)" + id="path4" /> + 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" /> + 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" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#eea6a7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + transform="rotate(19.617168,87.590538,52.720911)" + id="g4740"> + + + + + + + + id="path4744" /> - - - - - - - + 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" /> - + + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;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" /> + + 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="stroke-width:42.36951447"> + id="path2" /> + id="path4-9" /> + transform="matrix(-0.0125514,0,0,0.0125514,96.697579,101.08859)"> + style="fill:#29abe2;stroke-width:79.67237854" /> + style="fill:#ffffff;stroke-width:79.67237854" /> + + - - + id="path4700" /> 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/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); 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 = '\n\r'; - - 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 += ` - ${Albums.replaceSpecialChars(artists[j].name)} - ${Albums.replaceSpecialChars(artists[j].anv)} - ${Albums.replaceSpecialChars(artists[j].join)} - ${Albums.replaceSpecialChars(artists[j].role)} - ${Albums.replaceSpecialChars( - artists[j].tracks - )} - ${Albums.replaceSpecialChars(artists[j].id)} - ${Albums.replaceSpecialChars( - artists[j].resource_url - )} - ${Albums.replaceSpecialChars( - artists[j].thumbnail_url - )} - `; - } - - for (let j = 0; j < labels.length; j += 1) { - labelList += ` - `; - } - - for (let j = 0; j < series.length; j += 1) { - serieList += ` - ${Albums.replaceSpecialChars(series[j].name)} - ${Albums.replaceSpecialChars(series[j].catno)} - ${Albums.replaceSpecialChars( - series[j].entity_type - )} - ${Albums.replaceSpecialChars( - series[j].entity_type_name - )} - ${Albums.replaceSpecialChars(series[j].id)} - ${Albums.replaceSpecialChars( - series[j].resource_url - )} - ${Albums.replaceSpecialChars( - series[j].thumbnail_url - )} - - `; - } - - for (let j = 0; j < companies.length; j += 1) { - companiesList += ` - ${Albums.replaceSpecialChars(companies[j].name)} - ${Albums.replaceSpecialChars(companies[j].catno)} - ${Albums.replaceSpecialChars( - companies[j].entity_type - )} - ${Albums.replaceSpecialChars( - companies[j].entity_type_name - )} - ${Albums.replaceSpecialChars(companies[j].id)} - ${Albums.replaceSpecialChars( - companies[j].resource_url - )} - ${Albums.replaceSpecialChars( - companies[j].thumbnail_url - )} - - `; - } - - 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 += `${formats[j].descriptions[k]} - `; - } - } - formatsList += ` - ${Albums.replaceSpecialChars(formats[j].name)} - ${Albums.replaceSpecialChars(formats[j].qty)} - ${Albums.replaceSpecialChars(formats[j].text)} - - ${descriptions} - - - `; - } - - for (let j = 0; j < identifiers.length; j += 1) { - identifiersList += ` - ${Albums.replaceSpecialChars(identifiers[j].type)} - ${Albums.replaceSpecialChars( - identifiers[j].value - )} - ${Albums.replaceSpecialChars( - identifiers[j].description - )} - - `; - } - - for (let j = 0; j < videos.length; j += 1) { - videosList += ` - `; - } - - for (let j = 0; j < genres.length; j += 1) { - genresList += `${Albums.replaceSpecialChars( - genres[j] - )} - `; - } - - for (let j = 0; j < styles.length; j += 1) { - stylesList += ` - `; - } - - for (let j = 0; j < tracklist.length; j += 1) { - tracklistList += ` - ${Albums.replaceSpecialChars(tracklist[j].title)} - - `; - } - - for (let j = 0; j < extraartists.length; j += 1) { - extraartistsList += ` - ${Albums.replaceSpecialChars(extraartists[j].name)} - ${Albums.replaceSpecialChars(extraartists[j].anv)} - ${Albums.replaceSpecialChars(extraartists[j].join)} - ${Albums.replaceSpecialChars(extraartists[j].role)} - ${Albums.replaceSpecialChars( - extraartists[j].tracks - )} - ${Albums.replaceSpecialChars(extraartists[j].id)} - ${Albums.replaceSpecialChars( - extraartists[j].resource_url - )} - ${Albums.replaceSpecialChars( - extraartists[j].thumbnail_url - )} - - `; - } - - for (let j = 0; j < images.length; j += 1) { - imagesList += ` - ${Albums.replaceSpecialChars(images[j].uri)} - ${Albums.replaceSpecialChars( - images[j].resource_url - )} - ${Albums.replaceSpecialChars( - images[j].resource_url - )} - - `; - } - - data += ` - - ${discogsId} - ${Albums.replaceSpecialChars(title)} - ${Albums.replaceSpecialChars(artists_sort)} - - ${artistsList} - - ${year} - ${Albums.replaceSpecialChars(country)} - ${released} - ${uri} - ${thumb} - - ${labelList} - - - ${serieList} - - - ${companiesList} - - - ${formatsList} - - ${Albums.replaceSpecialChars(notes)} - - ${identifiersList} - - - ${videosList} - - - ${genresList} - - - ${stylesList} - - - ${tracklistList} - - - ${extraartistsList} - - - ${imagesList} - - `; - } - - return `${data}`; - } - - /** - * 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 = '\n\r'; + + 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 += ` + ${Export.replaceSpecialChars(artists[j].name)} + ${Export.replaceSpecialChars(artists[j].anv)} + ${Export.replaceSpecialChars(artists[j].join)} + ${Export.replaceSpecialChars(artists[j].role)} + ${Export.replaceSpecialChars(artists[j].tracks)} + ${Export.replaceSpecialChars(artists[j].id)} + ${Export.replaceSpecialChars( + artists[j].resource_url + )} + ${Export.replaceSpecialChars( + artists[j].thumbnail_url + )} + `; + } + + for (let j = 0; j < labels.length; j += 1) { + labelList += ` + `; + } + + for (let j = 0; j < series.length; j += 1) { + serieList += ` + ${Export.replaceSpecialChars(series[j].name)} + ${Export.replaceSpecialChars(series[j].catno)} + ${Export.replaceSpecialChars( + series[j].entity_type + )} + ${Export.replaceSpecialChars( + series[j].entity_type_name + )} + ${Export.replaceSpecialChars(series[j].id)} + ${Export.replaceSpecialChars( + series[j].resource_url + )} + ${Export.replaceSpecialChars( + series[j].thumbnail_url + )} + + `; + } + + for (let j = 0; j < companies.length; j += 1) { + companiesList += ` + ${Export.replaceSpecialChars(companies[j].name)} + ${Export.replaceSpecialChars(companies[j].catno)} + ${Export.replaceSpecialChars( + companies[j].entity_type + )} + ${Export.replaceSpecialChars( + companies[j].entity_type_name + )} + ${Export.replaceSpecialChars(companies[j].id)} + ${Export.replaceSpecialChars( + companies[j].resource_url + )} + ${Export.replaceSpecialChars( + companies[j].thumbnail_url + )} + + `; + } + + 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 += `${formats[j].descriptions[k]} + `; + } + } + formatsList += ` + ${Export.replaceSpecialChars(formats[j].name)} + ${Export.replaceSpecialChars(formats[j].qty)} + ${Export.replaceSpecialChars(formats[j].text)} + + ${descriptions} + + + `; + } + + for (let j = 0; j < identifiers.length; j += 1) { + identifiersList += ` + ${Export.replaceSpecialChars(identifiers[j].type)} + ${Export.replaceSpecialChars(identifiers[j].value)} + ${Export.replaceSpecialChars( + identifiers[j].description + )} + + `; + } + + for (let j = 0; j < videos.length; j += 1) { + videosList += ` + `; + } + + for (let j = 0; j < genres.length; j += 1) { + genresList += `${Export.replaceSpecialChars( + genres[j] + )} + `; + } + + for (let j = 0; j < styles.length; j += 1) { + stylesList += ` + `; + } + + for (let j = 0; j < tracklist.length; j += 1) { + tracklistList += ` + ${Export.replaceSpecialChars(tracklist[j].title)} + + `; + } + + for (let j = 0; j < extraartists.length; j += 1) { + extraartistsList += ` + ${Export.replaceSpecialChars(extraartists[j].name)} + ${Export.replaceSpecialChars(extraartists[j].anv)} + ${Export.replaceSpecialChars(extraartists[j].join)} + ${Export.replaceSpecialChars(extraartists[j].role)} + ${Export.replaceSpecialChars( + extraartists[j].tracks + )} + ${Export.replaceSpecialChars(extraartists[j].id)} + ${Export.replaceSpecialChars( + extraartists[j].resource_url + )} + ${Export.replaceSpecialChars( + extraartists[j].thumbnail_url + )} + + `; + } + + for (let j = 0; j < images.length; j += 1) { + imagesList += ` + ${Export.replaceSpecialChars(images[j].uri)} + ${Export.replaceSpecialChars( + images[j].resource_url + )} + ${Export.replaceSpecialChars( + images[j].resource_url + )} + + `; + } + + data += ` + + ${discogsId} + ${Export.replaceSpecialChars(title)} + ${Export.replaceSpecialChars(artists_sort)} + + ${artistsList} + + ${year} + ${Export.replaceSpecialChars(country)} + ${released} + ${uri} + ${thumb} + + ${labelList} + + + ${serieList} + + + ${companiesList} + + + ${formatsList} + + ${Export.replaceSpecialChars(notes)} + + ${identifiersList} + + + ${videosList} + + + ${genresList} + + + ${stylesList} + + + ${tracklistList} + + + ${extraartistsList} + + + ${imagesList} + +`; + } + + return `${data}`; + } + + /** + * 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; 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; -- 2.39.5 From e0a0a63d84c70608fc2df4bc4f6b780e46206682 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Mon, 29 Aug 2022 23:19:47 +0200 Subject: [PATCH 02/65] #56 --- src/helpers/index.js | 18 ++++- src/routes/api/v1/search.js | 7 +- views/pages/ajouter-un-album.ejs | 113 +++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/helpers/index.js b/src/helpers/index.js index 128f594..3a81cc7 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -5,13 +5,25 @@ import { discogsToken } from "../config"; export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`; -export const searchSong = async (q) => { +export const searchSong = async (q, format, year, country) => { const dis = new Discogs({ userToken: discogsToken }).database(); - const res = await dis.search({ + const params = { q, type: "release", - }); + }; + + if (format) { + params.format = format; + } + if (year) { + params.year = year; + } + if (country) { + params.country = country; + } + + const res = await dis.search(params); return res; }; diff --git a/src/routes/api/v1/search.js b/src/routes/api/v1/search.js index 76e1c7e..267ec0f 100644 --- a/src/routes/api/v1/search.js +++ b/src/routes/api/v1/search.js @@ -9,7 +9,12 @@ const router = express.Router(); router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => { try { - const data = await searchSong(req.query.q); + const data = await searchSong( + req.query.q, + req.query.format || null, + req.query.year || null, + req.query.country || null + ); sendResponse(req, res, data); } catch (err) { diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index 2cd4ab4..549140e 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,19 +1,37 @@

Ajouter un album

-
-
-
+ +
+
- +
- +
+
+ + +
+
+ + +
+
+ + +
+
+
-
+ +
@@ -158,10 +176,77 @@ data() { return { q: '', + year: '', + country: '', + format: '', loading: false, items: [], details: {}, modalIsVisible: false, + formats: [ + 'Vinyl', + 'Acetate', + 'Flexi-disc', + 'Lathe Cut', + 'Mighty Tiny', + 'Shellac', + 'Sopic', + 'Pathé Disc', + 'Edison Disc', + 'Cylinder', + 'CD', + 'CDr', + 'CDV', + 'DVD', + 'DVDr', + 'HD DVD', + 'HD DVD-R', + 'Blu-ray', + 'Blu-ray-R', + 'Ultra HD Blu-ray', + 'SACD', + '4-Track Cartridge', + '8-Track Cartridge', + 'Cassette', + 'DC-International', + 'Elcaset', + 'PlayTape', + 'RCA Tape Cartridge', + 'DAT', + 'DCC', + 'Microcassette', + 'NT Cassette', + 'Pocket Rocker', + 'Revere Magnetic Stereo Tape Ca', + 'Tefifon', + 'Reel-To-Reel', + 'Sabamobil', + 'Betacam', + 'Betacam SP', + 'Betamax', + 'Cartrivision', + 'MiniDV', + 'Super VHS', + 'U-matic', + 'VHS', + 'Video 2000', + 'Video8', + 'Film Reel', + 'HitClips', + 'Laserdisc', + 'SelectaVision', + 'VHD', + 'Wire Recording', + 'Minidisc', + 'MVD', + 'UMD', + 'Floppy Disk', + 'File', + 'Memory Stick', + 'Hybrid', + 'All Media', + 'Box Set', + ] } }, methods: { @@ -173,8 +258,19 @@ } this.loading = true; + let url = `/api/v1/search?q=${this.q}`; - axios.get(`/api/v1/search?q=${this.q}`) + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.country ) { + url += `&country=${this.country}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + + axios.get(url) .then( response => { const { results, @@ -242,6 +338,9 @@ showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); }); }, + orderedItems(items) { + return items.sort(); + } } }).mount('#app'); -- 2.39.5 From 63207647438075bcba13679ed51933946e78cc86 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Mon, 29 Aug 2022 23:22:28 +0200 Subject: [PATCH 03/65] #56 (#57) Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/57 --- src/helpers/index.js | 18 ++++- src/routes/api/v1/search.js | 7 +- views/pages/ajouter-un-album.ejs | 113 +++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/helpers/index.js b/src/helpers/index.js index 128f594..3a81cc7 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -5,13 +5,25 @@ import { discogsToken } from "../config"; export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`; -export const searchSong = async (q) => { +export const searchSong = async (q, format, year, country) => { const dis = new Discogs({ userToken: discogsToken }).database(); - const res = await dis.search({ + const params = { q, type: "release", - }); + }; + + if (format) { + params.format = format; + } + if (year) { + params.year = year; + } + if (country) { + params.country = country; + } + + const res = await dis.search(params); return res; }; diff --git a/src/routes/api/v1/search.js b/src/routes/api/v1/search.js index 76e1c7e..267ec0f 100644 --- a/src/routes/api/v1/search.js +++ b/src/routes/api/v1/search.js @@ -9,7 +9,12 @@ const router = express.Router(); router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => { try { - const data = await searchSong(req.query.q); + const data = await searchSong( + req.query.q, + req.query.format || null, + req.query.year || null, + req.query.country || null + ); sendResponse(req, res, data); } catch (err) { diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index 2cd4ab4..549140e 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,19 +1,37 @@

Ajouter un album

-
-
-
+ +
+
- +
- +
+
+ + +
+
+ + +
+
+ + +
+
+
-
+ +
@@ -158,10 +176,77 @@ data() { return { q: '', + year: '', + country: '', + format: '', loading: false, items: [], details: {}, modalIsVisible: false, + formats: [ + 'Vinyl', + 'Acetate', + 'Flexi-disc', + 'Lathe Cut', + 'Mighty Tiny', + 'Shellac', + 'Sopic', + 'Pathé Disc', + 'Edison Disc', + 'Cylinder', + 'CD', + 'CDr', + 'CDV', + 'DVD', + 'DVDr', + 'HD DVD', + 'HD DVD-R', + 'Blu-ray', + 'Blu-ray-R', + 'Ultra HD Blu-ray', + 'SACD', + '4-Track Cartridge', + '8-Track Cartridge', + 'Cassette', + 'DC-International', + 'Elcaset', + 'PlayTape', + 'RCA Tape Cartridge', + 'DAT', + 'DCC', + 'Microcassette', + 'NT Cassette', + 'Pocket Rocker', + 'Revere Magnetic Stereo Tape Ca', + 'Tefifon', + 'Reel-To-Reel', + 'Sabamobil', + 'Betacam', + 'Betacam SP', + 'Betamax', + 'Cartrivision', + 'MiniDV', + 'Super VHS', + 'U-matic', + 'VHS', + 'Video 2000', + 'Video8', + 'Film Reel', + 'HitClips', + 'Laserdisc', + 'SelectaVision', + 'VHD', + 'Wire Recording', + 'Minidisc', + 'MVD', + 'UMD', + 'Floppy Disk', + 'File', + 'Memory Stick', + 'Hybrid', + 'All Media', + 'Box Set', + ] } }, methods: { @@ -173,8 +258,19 @@ } this.loading = true; + let url = `/api/v1/search?q=${this.q}`; - axios.get(`/api/v1/search?q=${this.q}`) + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.country ) { + url += `&country=${this.country}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + + axios.get(url) .then( response => { const { results, @@ -242,6 +338,9 @@ showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); }); }, + orderedItems(items) { + return items.sort(); + } } }).mount('#app'); -- 2.39.5 From adea857666cab0c11e9a80a9ad3ab6eb486564c0 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 30 Aug 2022 15:17:14 +0200 Subject: [PATCH 04/65] =?UTF-8?q?#49=20-=20Mise=20=C3=A0=20jour=20d'une=20?= =?UTF-8?q?fiche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fontello.json | 118 ++++++++++++++++++ public/font/icon.eot | Bin 9976 -> 10308 bytes public/font/icon.svg | 2 + public/font/icon.ttf | Bin 9824 -> 10156 bytes public/font/icon.woff | Bin 6224 -> 6444 bytes public/font/icon.woff2 | Bin 5208 -> 5400 bytes sass/icons.scss | 13 +- sass/ma-collection-details.scss | 22 ++++ src/middleware/Albums.js | 27 +++- src/routes/api/v1/albums.js | 10 ++ views/pages/composants.ejs | 1 + .../mon-compte/ma-collection/details.ejs | 50 +++++++- 12 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 fontello.json diff --git a/fontello.json b/fontello.json new file mode 100644 index 0000000..11e03d9 --- /dev/null +++ b/fontello.json @@ -0,0 +1,118 @@ +{ + "name": "icon", + "css_prefix_text": "icon-", + "css_use_suffix": false, + "hinting": true, + "units_per_em": 1000, + "ascent": 850, + "glyphs": [ + { + "uid": "44e04715aecbca7f266a17d5a7863c68", + "css": "plus", + "code": 59392, + "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59393, + "src": "fontawesome" + }, + { + "uid": "9dd9e835aebe1060ba7190ad2b2ed951", + "css": "search", + "code": 59394, + "src": "fontawesome" + }, + { + "uid": "bf882b30900da12fca090d9796bc3030", + "css": "mail", + "code": 59395, + "src": "fontawesome" + }, + { + "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1", + "css": "link", + "code": 59396, + "src": "fontawesome" + }, + { + "uid": "e15f0d620a7897e2035c18c80142f6d9", + "css": "link-ext", + "code": 61582, + "src": "fontawesome" + }, + { + "uid": "9bc2902722abb366a213a052ade360bc", + "css": "spin", + "code": 59449, + "src": "fontelico" + }, + { + "uid": "bbfb51903f40597f0b70fd75bc7b5cac", + "css": "trash", + "code": 61944, + "src": "fontawesome" + }, + { + "uid": "d73eceadda1f594cec0536087539afbf", + "css": "heart", + "code": 59397, + "src": "fontawesome" + }, + { + "uid": "cce5e05853d0798a4d10077ef613387c", + "css": "blind", + "code": 62109, + "src": "fontawesome" + }, + { + "uid": "567e3e257f2cc8fba2c12bf691c9f2d8", + "css": "moon", + "code": 61830, + "src": "fontawesome" + }, + { + "uid": "aa035df0908c4665c269b7b09a5596f3", + "css": "sun", + "code": 61829, + "src": "fontawesome" + }, + { + "uid": "c5fd349cbd3d23e4ade333789c29c729", + "css": "eye", + "code": 59398, + "src": "fontawesome" + }, + { + "uid": "d870630ff8f81e6de3958ecaeac532f2", + "css": "left-open", + "code": 59399, + "src": "fontawesome" + }, + { + "uid": "399ef63b1e23ab1b761dfbb5591fa4da", + "css": "right-open", + "code": 59400, + "src": "fontawesome" + }, + { + "uid": "895405dfac8a3b7b2f23b183c6608ee6", + "css": "export", + "code": 59401, + "src": "fontawesome" + }, + { + "uid": "4aad6bb50b02c18508aae9cbe14e784e", + "css": "share", + "code": 61920, + "src": "fontawesome" + }, + { + "uid": "a73c5deb486c8d66249811642e5d719a", + "css": "refresh", + "code": 59402, + "src": "fontawesome" + } + ] +} \ No newline at end of file diff --git a/public/font/icon.eot b/public/font/icon.eot index a17c0f6c12920aacff321df497143f171c000437..424368fa06b91335d76d8252af48d4c7c53d3c37 100644 GIT binary patch delta 852 zcmX9+T}TvB6h3$E?48*kXE&XlU12F(_s8<5SaT=wr>(3+UC^k&ENoj>H*(uW7qbL+ ziGE(fvZ2UGkO*Jup;!2gu(6a8vQIYz6B0xPRj!4Q7T?SL^=X z1}Nu|+J<7I8PpvZ-#{-84c{7k-Lkp|fRCVjDVc~3>`xTF0?02h9!{bl9Hw#P%gBAn zRCfIM_Y1pNEN`RGHJpyeyrO>%z}bepDis^gkg$Z!IERr}+=!(T+W1y2@`u>qY$iRL zbw7RM#0F|G{xLI>$V@IYEdT@^+GEm3ZfYAOePA94Mgiu47T;>nTkw$cz(W>w2O0%f zw`g~{daDm~3+SeO=KQ8o%w*aoxATMwc3W|y_2LRps_oE;e%xvu0ao;l1_1MZO&Nd^ zfX#abKtg}t0NBy58qjCBi6hVfIKQn~1AredeB;Z-90#!bp7v2I_I3s?M=qZ9bIg2* zn3#)DP9%i#U0@cOXl8i{XqsZBY>X`nd9ki|P09*O h@(&Kv=3PFH$l8+YgeZ(821gR3NzLZ2EvW8K(tkx8vfTgx delta 519 zcmX>S@WYqwhZ+Mzg4#qjGnTHc|DQ~BsIG5hU|J0`V0CB3-Yhf cW-hVYOh84?BvdBzNIFdplhobZD!HEz05P+K=l}o! diff --git a/public/font/icon.svg b/public/font/icon.svg index d5ac2f6..52ad914 100644 --- a/public/font/icon.svg +++ b/public/font/icon.svg @@ -26,6 +26,8 @@ + + diff --git a/public/font/icon.ttf b/public/font/icon.ttf index 4ccfa6dc9101474be2e0317776cd1611e5a2667a..6688a7902b56ba7925888de4b4596ec0eea25cde 100644 GIT binary patch delta 823 zcmX9+OHUI~6h3F}^v?9bwDvJANuyBOg1jU}S_u#F3E`n))CAFBErlAXoj`-A2_?qI z#-L`R5hIZp!$udHCWIJWxbYW|xNsp0LRhd6q6xaeGwqyw^Ue32dnV`HdslnkpMFx2 z%K_jS0JxG!$F+s?j~|l$03ggJCbMPa)8qS1FJ(j8f;mO2eo^&3ML0m)(e`lls(l9qC8Z;JlWZ$oihW^H2k|l>efQC$;IN zmL-5n$J3obys4kV%>G3{N&+kZD}2=wTWH2b5FtyxgMtBUSoBS7v z^F@FYAm1RC$~ggG_ahr*R_dn{E_Yl!781Dm0GhZLnII!Fu@}rTm(6@eB3IxhVMauO zae++(GGGWZAOkna{F))y05Aq|plSP0o(P9R(NMU_9klR$HR$#zSP=+UN<0v5!-LTl z%tcgg$GsSfL}L^!7*SmwH?P4!IC>bv6#*_$7&6w>j*Su38;eF%))QQt>wnSOW%aA7 z@=s9(b6?ePtov>y_ldUavPE$_itbshhfJ@@mXT~2T%YTIX%zX;#PIp2_U5iyYoT8C zTl<35*xhb9RAR$rsx_RxqUK^@sa4H)+R7z=N8axkvbX*LJ1d;m&&FlC8vw+re$N#{ zm;TvRhXeY$E9QA)l1;PR$tU@WG$Yp+u7)cgasLo#nc@u!=+-0Nqp~!f92rker1Y$} KF8|v5RrwF_-KxR> delta 492 zcmXw#%PT}t9LK+R?qxjYg&9V98;Xpkyb=vrQSvA(C|qM63)kGnr18jO!D1GoQ?eOZ zNRoxRk%f@_33kLREF|(MHCFttoAc{?zUT8jr}H}Oj(4t?E-nLH4}ejRB+Ai;n=9fY zK)2+X4i`~QEoYxPlTY?bW7XfY#HWM?zc?jRZXwbX}&%{f!@WB5pNMY0#Z2QUKsd!q2P>y_KBcJEZ)nH0#<>zToNNPS7$mRt|Bg) z6eTZ4GsiI2Cpcwrm2~vy5+^H^ch4V8t#tngq7N|Bb51 z)98)65D=o=Ld645sqBTTNiTs~4z*aNu1x9G8MxHDzYTy4=Husj8)-2K>A?G%CK!N^ z6-_XbuWEvcJgy05@`NV!(>`OGkfF2+xhcwuu~47)_;VZ`F*kp~%s;7z-gg)e{+(S~ z8@NoCusT_Vt=1M25=rw(@nm=MvtIpA^_7`Q$abrvMO}6W3)|b+x!tL}+AmDM=308~ diff --git a/public/font/icon.woff b/public/font/icon.woff index 32621989e6e774018e5a3e688d923ab43ac1a6f8..9543cf5a12ebb323fdf0bf25648a423d72c14c39 100644 GIT binary patch delta 3937 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 delta 3716 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 literal 5208 zcmV-e6sPNVPew8T0RR9102EjN4*&oF0487n02BcL0RR9100000000000000000000 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 SOZR}g=t0ffALdsEwoCy}C+chf diff --git a/sass/icons.scss b/sass/icons.scss index 4250307..7b2150b 100644 --- a/sass/icons.scss +++ b/sass/icons.scss @@ -1,11 +1,11 @@ @font-face { font-family: 'icon'; - src: url('/font/icon.eot?80770511'); - src: url('/font/icon.eot?80770511#iefix') format('embedded-opentype'), - url('/font/icon.woff2?80770511') format('woff2'), - url('/font/icon.woff?80770511') format('woff'), - url('/font/icon.ttf?80770511') format('truetype'), - url('/font/icon.svg?80770511#icon') format('svg'); + src: url('/font/icon.eot?41426785'); + src: url('/font/icon.eot?41426785#iefix') format('embedded-opentype'), + url('/font/icon.woff2?41426785') format('woff2'), + url('/font/icon.woff?41426785') format('woff'), + url('/font/icon.ttf?41426785') format('truetype'), + url('/font/icon.svg?41426785#icon') format('svg'); font-weight: normal; font-style: normal; } @@ -42,6 +42,7 @@ .icon-left-open:before { content: '\e807'; } /* '' */ .icon-right-open:before { content: '\e808'; } /* '' */ .icon-export:before { content: '\e809'; } /* '' */ +.icon-refresh:before { content: '\e80a'; } /* '' */ .icon-spin:before { content: '\e839'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ .icon-sun:before { content: '\f185'; } /* '' */ diff --git a/sass/ma-collection-details.scss b/sass/ma-collection-details.scss index 952a026..00a8946 100644 --- a/sass/ma-collection-details.scss +++ b/sass/ma-collection-details.scss @@ -1,4 +1,26 @@ .ma-collection-details { + h1 { + i { + cursor: pointer; + + &.icon-trash { + color: $danger-color; + @include transition() {} + + &:hover { + color: $danger-color-hl; + } + } + &.icon-refresh { + color: $primary-color; + @include transition() {} + + &:hover { + color: $primary-color-hl; + } + } + } + } .galerie { display: flex; flex-wrap: wrap; diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 312675c..5363c2c 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -7,7 +7,8 @@ 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"; + +import { getAlbumDetails } from "../helpers"; /** * Classe permettant la gestion des albums d'un utilisateur @@ -182,6 +183,30 @@ class Albums extends Pages { } } + /** + * Méthode permettant de mettre à jour un album + * + * @return {Object} + */ + async patchOne() { + const { itemId: _id } = this.req.params; + const { _id: User } = this.req.user; + const album = await AlbumsModel.findOne({ + _id, + User, + }); + + if (!album) { + throw new ErrorEvent(404, "Impossible de trouver cet album"); + } + + const values = await getAlbumDetails(album.discogsId); + + await album.updateOne(values); + + return album; + } + /** * Méthode permettant de supprimer un élément d'une collection * @return {Boolean} diff --git a/src/routes/api/v1/albums.js b/src/routes/api/v1/albums.js index e1caa1f..596b502 100644 --- a/src/routes/api/v1/albums.js +++ b/src/routes/api/v1/albums.js @@ -47,6 +47,16 @@ router router .route("/:itemId") + .patch(ensureLoggedIn("/connexion"), async (req, res, next) => { + try { + const albums = new Albums(req); + const data = await albums.patchOne(); + + sendResponse(req, res, data); + } catch (err) { + next(err); + } + }) .delete(ensureLoggedIn("/connexion"), async (req, res, next) => { try { const albums = new Albums(req); diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 1cba4e5..cdd1776 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -355,6 +355,7 @@ .icon-left-open .icon-right-open .icon-export + .icon-refresh .icon-share .icon-spin .icon-sun diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index bd97f90..9385544 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,6 +1,10 @@
-

{{item.artists_sort}} - {{item.title}}

+

+ {{item.artists_sort}} - {{item.title}} + + +

@@ -153,6 +157,20 @@
+ +
\ No newline at end of file -- 2.39.5 From b8b3df2932af40b4cfc09dbc83c731f8b342e10f Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 30 Aug 2022 15:30:16 +0200 Subject: [PATCH 05/65] #52 - Afficher toute les infos du format d'un album --- views/pages/mon-compte/ma-collection/details.ejs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 9385544..41c0b24 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -74,6 +74,9 @@
  • {{format.name}} +
- +

-- 2.39.5 From a74c67e241597eee0b3dcd041974201d712d32df Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:06:56 +0200 Subject: [PATCH 13/65] #64 - Depuis un album pouvoir voir tous les albums de cet artiste --- views/pages/mon-compte/ma-collection/details.ejs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index a505509..8f5b0ef 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,7 +1,7 @@

- {{item.artists_sort}} - {{item.title}} + {{item.artists_sort}} - {{item.title}}

@@ -327,6 +327,9 @@ this.toggleModal(); }); }, + goToArtist() { + return ""; + }, }, }).mount('#app'); \ No newline at end of file -- 2.39.5 From 8f9e9025872e33077ed918cc74258a4193f6e6f8 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:40:02 +0200 Subject: [PATCH 14/65] #66 - Compiler le JS avant de l'envoyer au client --- .gitignore | 2 +- gulpfile.js | 46 +++++ javascripts/ajouter-un-album.js | 171 ++++++++++++++++ javascripts/collection.js | 133 +++++++++++++ javascripts/conctact.js | 42 ++++ {public/js => javascripts}/main.js | 9 +- javascripts/mon-compte/index.js | 26 +++ .../mon-compte/ma-collection/details.js | 158 +++++++++++++++ .../mon-compte/ma-collection/exporter.js | 18 ++ javascripts/mon-compte/ma-collection/index.js | 178 +++++++++++++++++ package.json | 11 +- src/app.js | 8 - views/index.ejs | 5 +- views/pages/ajouter-un-album.ejs | 178 +---------------- views/pages/collection.ejs | 139 +------------ views/pages/mon-compte/index.ejs | 28 +-- .../mon-compte/ma-collection/details.ejs | 159 +-------------- .../mon-compte/ma-collection/exporter.ejs | 25 +-- .../pages/mon-compte/ma-collection/index.ejs | 186 +----------------- views/pages/nous-contacter.ejs | 49 +---- 20 files changed, 809 insertions(+), 762 deletions(-) create mode 100644 gulpfile.js create mode 100644 javascripts/ajouter-un-album.js create mode 100644 javascripts/collection.js create mode 100644 javascripts/conctact.js rename {public/js => javascripts}/main.js (97%) create mode 100644 javascripts/mon-compte/index.js create mode 100644 javascripts/mon-compte/ma-collection/details.js create mode 100644 javascripts/mon-compte/ma-collection/exporter.js create mode 100644 javascripts/mon-compte/ma-collection/index.js diff --git a/.gitignore b/.gitignore index a8dcba6..3054b59 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,6 @@ dist dist yarn.lock public/css -public/css +public/js docker-compose.yml dump diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8e76f38 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,46 @@ +const { parallel, src, dest } = require("gulp"); + +const sourcemaps = require("gulp-sourcemaps"); +const concat = require("gulp-concat"); + +const gulp = require("gulp"); +const uglify = require("gulp-uglify"); +const babel = require("gulp-babel"); + +const sourceJs = "javascripts/**/*.js"; +const sourceRemoteJS = [ + "./node_modules/vue/dist/vue.global.prod.js", + "./node_modules/axios/dist/axios.min.js", +]; + +const destination = "public/js"; + +// TASKS ---------------------------------------------------------------------- + +const compileJs = function () { + return gulp + .src(sourceJs) + .pipe(sourcemaps.init()) + .pipe(concat("main.js")) + .pipe( + babel({ + presets: ["@babel/env"], + }) + ) + .pipe(uglify()) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +const compileRemoteJs = function () { + return gulp + .src(sourceRemoteJS) + .pipe(sourcemaps.init()) + .pipe(concat("libs.js")) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +// ---------------------------------------------------------------------------- + +// COMMANDS ------------------------------------------------------------------- +exports.default = parallel(compileJs, compileRemoteJs); +// ---------------------------------------------------------------------------- diff --git a/javascripts/ajouter-un-album.js b/javascripts/ajouter-un-album.js new file mode 100644 index 0000000..d46d5d4 --- /dev/null +++ b/javascripts/ajouter-un-album.js @@ -0,0 +1,171 @@ +Vue.createApp({ + data() { + return { + q: '', + year: '', + country: '', + format: '', + loading: false, + items: [], + details: {}, + modalIsVisible: false, + formats: [ + 'Vinyl', + 'Acetate', + 'Flexi-disc', + 'Lathe Cut', + 'Mighty Tiny', + 'Shellac', + 'Sopic', + 'Pathé Disc', + 'Edison Disc', + 'Cylinder', + 'CD', + 'CDr', + 'CDV', + 'DVD', + 'DVDr', + 'HD DVD', + 'HD DVD-R', + 'Blu-ray', + 'Blu-ray-R', + 'Ultra HD Blu-ray', + 'SACD', + '4-Track Cartridge', + '8-Track Cartridge', + 'Cassette', + 'DC-International', + 'Elcaset', + 'PlayTape', + 'RCA Tape Cartridge', + 'DAT', + 'DCC', + 'Microcassette', + 'NT Cassette', + 'Pocket Rocker', + 'Revere Magnetic Stereo Tape Ca', + 'Tefifon', + 'Reel-To-Reel', + 'Sabamobil', + 'Betacam', + 'Betacam SP', + 'Betamax', + 'Cartrivision', + 'MiniDV', + 'Super VHS', + 'U-matic', + 'VHS', + 'Video 2000', + 'Video8', + 'Film Reel', + 'HitClips', + 'Laserdisc', + 'SelectaVision', + 'VHD', + 'Wire Recording', + 'Minidisc', + 'MVD', + 'UMD', + 'Floppy Disk', + 'File', + 'Memory Stick', + 'Hybrid', + 'All Media', + 'Box Set', + ] + } + }, + methods: { + search(event) { + event.preventDefault(); + + if ( this.loading ) { + return false; + } + + this.loading = true; + let url = `/api/v1/search?q=${this.q}`; + + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.country ) { + url += `&country=${this.country}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + + axios.get(url) + .then( response => { + const { + results, + } = response.data; + let items = []; + + for (let i = 0 ; i < results.length ; i += 1 ) { + const { + id, + title, + thumb, + year, + country, + format, + genre, + style, + } = results[i]; + items.push({ + id, + title, + thumb, + year, + country, + format, + genre, + style, + }); + } + + this.items = items; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Aucun résultat trouvé :/"); + }) + .finally(() => { + this.loading = false; + }); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + loadDetails(discogsId) { + axios.get(`/api/v1/search/${discogsId}`) + .then( response => { + const { + data, + } = response; + + this.details = data; + this.toggleModal(); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album"); + }) + .finally(() => { + this.loading = false; + }); + }, + add() { + axios.post('/api/v1/albums', this.details) + .then(() => { + window.location.href = '/ma-collection'; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); + }); + }, + orderedItems(items) { + return items.sort(); + } + } +}).mount('#ajouter-album'); \ No newline at end of file diff --git a/javascripts/collection.js b/javascripts/collection.js new file mode 100644 index 0000000..c3b3561 --- /dev/null +++ b/javascripts/collection.js @@ -0,0 +1,133 @@ +if ( typeof userId !== 'undefined' ) { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: '', + format: '', + year: '', + genre: '', + style: '', + sortOrder: 'artists_sort-asc', + sort: 'artists_sort', + order: 'asc', + userId, + } + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + for(const entry of entries) { + switch(entry[0]) { + case 'artists_sort': + this.artist = entry[1]; + break; + default: + this[entry[0]] = entry[1]; + } + } + + let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + 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 => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger cette collection"); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + 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')}`; + } + + location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort,order] = this.sortOrder.split('-'); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + } + } + }).mount('#collection-publique'); +} \ No newline at end of file diff --git a/javascripts/conctact.js b/javascripts/conctact.js new file mode 100644 index 0000000..51ed1cb --- /dev/null +++ b/javascripts/conctact.js @@ -0,0 +1,42 @@ +if ( typeof contactMethod !== 'undefined' && contactMethod === 'smtp' ) { + Vue.createApp({ + data() { + return { + email: '', + name: '', + message: '', + captcha: '', + loading: false, + } + }, + methods: { + send(event) { + event.preventDefault(); + + if ( this.loading ) { + return false; + } + + this.loading = true; + + const { + email, + message, + name, + captcha, + } = this; + + axios.post('/api/v1/contact', {email, name, message, captcha}) + .then( () => { + showToastr("Message correctement envoyé", true); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false); + }) + .finally(() => { + this.loading = false; + }) + }, + }, + }).mount('#contact'); +} \ No newline at end of file diff --git a/public/js/main.js b/javascripts/main.js similarity index 97% rename from public/js/main.js rename to javascripts/main.js index f203a63..0b0592e 100644 --- a/public/js/main.js +++ b/javascripts/main.js @@ -1,8 +1,13 @@ -/** +const { + protocol, + host +} = window.location; + + /** * Fonction permettant d'afficher un message dans un toastr * @param {String} message */ - function showToastr(message, success = false) { +function showToastr(message, success = false) { let x = document.getElementById("toastr"); if ( message ) { x.getElementsByTagName("SPAN")[0].innerHTML = message; diff --git a/javascripts/mon-compte/index.js b/javascripts/mon-compte/index.js new file mode 100644 index 0000000..ccc1d02 --- /dev/null +++ b/javascripts/mon-compte/index.js @@ -0,0 +1,26 @@ +if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) { + Vue.createApp({ + data() { + return { + email, + 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('#mon-compte'); +} \ No newline at end of file diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js new file mode 100644 index 0000000..26f069f --- /dev/null +++ b/javascripts/mon-compte/ma-collection/details.js @@ -0,0 +1,158 @@ +if ( typeof item !== 'undefined' ) { + Vue.createApp({ + data() { + return { + item, + tracklist: [], + identifiers: [], + modalIsVisible: false, + identifiersMode: 'preview', + identifiersPreviewLength: 16, + preview: null, + index: null, + showModalDelete: false, + } + }, + created() { + this.setTrackList(); + this.setIdentifiers(); + + window.addEventListener("keydown", this.changeImage); + }, + destroyed() { + window.removeEventListener('keydown', this.changeImage); + }, + 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(); + }, + 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(); + }, + showLessIdentifiers() { + this.identifiersMode = 'preview'; + this.setIdentifiers(); + + document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' }); + }, + showConfirmDelete() { + this.toggleModal(); + }, + toggleModal() { + this.showModalDelete = !this.showModalDelete; + }, + updateItem() { + showToastr("Mise à jour en cours…", true); + axios.patch(`/api/v1/albums/${this.item._id}`) + .then( (res) => { + showToastr("Mise à jour réalisée avec succès", true); + this.item = res.data; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false); + }); + }, + deleteItem() { + axios.delete(`/api/v1/albums/${this.item._id}`) + .then( () => { + window.location.href = "/ma-collection"; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + }) + .finally(() => { + this.toggleModal(); + }); + }, + goToArtist() { + return ""; + }, + }, + }).mount('#ma-collection-details'); +} \ No newline at end of file diff --git a/javascripts/mon-compte/ma-collection/exporter.js b/javascripts/mon-compte/ma-collection/exporter.js new file mode 100644 index 0000000..f5dcbcb --- /dev/null +++ b/javascripts/mon-compte/ma-collection/exporter.js @@ -0,0 +1,18 @@ +Vue.createApp({ + data() { + return { + format: 'xml', + } + }, + created() { + }, + destroyed() { + }, + methods: { + exportCollection(event) { + event.preventDefault(); + + window.open(`/api/v1/albums?exportFormat=${this.format}`, '_blank'); + } + }, +}).mount('#exporter'); \ No newline at end of file diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js new file mode 100644 index 0000000..7e2bdf5 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/index.js @@ -0,0 +1,178 @@ +if ( typeof isPublicCollection !== 'undefined' ) { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: '', + format: '', + year: '', + genre: '', + style: '', + sortOrder: 'artists_sort-asc', + sort: 'artists_sort', + order: 'asc', + itemId: null, + showModalDelete: false, + showModalShare: false, + shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + isPublicCollection, + } + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + for(const entry of entries) { + switch(entry[0]) { + case 'artists_sort': + this.artist = entry[1]; + break; + default: + this[entry[0]] = entry[1]; + } + } + + let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + 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 => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger votre collection"); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + 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')}`; + } + + location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort,order] = this.sortOrder.split('-'); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, + 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('#ma-collection'); +} \ No newline at end of file diff --git a/package.json b/package.json index 2ee4492..b3dffed 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Simple application to manage your CD/Vinyl collection", "scripts": { "start": "node ./dist/bin/www", - "run:all": "npm-run-all build sass start", + "run:all": "npm-run-all build sass uglify start", "watch": "nodemon -e js,scss", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", + "uglify": "npx gulp", "prebuild": "rimraf dist", "build": "babel ./src --out-dir dist --copy-files", "test": "jest", @@ -55,6 +56,11 @@ "excel4node": "^1.7.2", "express": "^4.17.2", "express-session": "^1.17.2", + "gulp": "^4.0.2", + "gulp-babel": "^8.0.0", + "gulp-concat": "^2.6.1", + "gulp-sourcemaps": "^3.0.0", + "gulp-uglify": "^3.0.2", "joi": "^17.6.0", "knacss": "^8.0.4", "mongoose": "^6.2.1", @@ -75,7 +81,8 @@ "exec": "yarn run:all", "watch": [ "src/*", - "sass/*" + "sass/*", + "javascripts/*" ], "ignore": [ "**/__tests__/**", diff --git a/src/app.js b/src/app.js index 7c40a27..6fa4688 100644 --- a/src/app.js +++ b/src/app.js @@ -75,14 +75,6 @@ app.set("views", path.join(__dirname, "../views")); app.set("view engine", "ejs"); app.use(express.static(path.join(__dirname, "../public"))); -app.use( - "/libs/vue", - express.static(path.join(__dirname, "../node_modules/vue/dist")) -); -app.use( - "/libs/axios", - express.static(path.join(__dirname, "../node_modules/axios/dist")) -); app.use("/", indexRouter); app.use("/mon-compte", monCompteRouter); diff --git a/views/index.ejs b/views/index.ejs index 2386a83..a310984 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -16,9 +16,8 @@ - - - + + <% if ( config.matomoUrl ) { %> diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index a7306ff..f32ea67 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,4 +1,4 @@ -
+

Ajouter un album

@@ -169,178 +169,4 @@
-
- - +
\ No newline at end of file diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index 9960983..3272d8b 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -1,4 +1,4 @@ -
+

Collection de <%= page.username %>

@@ -146,140 +146,5 @@
diff --git a/views/pages/mon-compte/index.ejs b/views/pages/mon-compte/index.ejs index d8f7f6a..af7b634 100644 --- a/views/pages/mon-compte/index.ejs +++ b/views/pages/mon-compte/index.ejs @@ -1,4 +1,4 @@ -
+

Mon compte

@@ -72,28 +72,6 @@
diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 8f5b0ef..e8615b4 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,4 +1,4 @@ -
+

{{item.artists_sort}} - {{item.title}} @@ -176,160 +176,5 @@

\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/exporter.ejs b/views/pages/mon-compte/ma-collection/exporter.ejs index 13e3f98..28cc559 100644 --- a/views/pages/mon-compte/ma-collection/exporter.ejs +++ b/views/pages/mon-compte/ma-collection/exporter.ejs @@ -1,4 +1,4 @@ -
+

Exporter ma collection

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 : @@ -44,25 +44,4 @@ Exporter -

- - \ No newline at end of file +
\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index fb67cd3..38993c8 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -1,4 +1,4 @@ -
+

Ma collection @@ -196,185 +196,5 @@

+ const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>; + \ No newline at end of file diff --git a/views/pages/nous-contacter.ejs b/views/pages/nous-contacter.ejs index 276b6f4..f179135 100644 --- a/views/pages/nous-contacter.ejs +++ b/views/pages/nous-contacter.ejs @@ -1,4 +1,4 @@ -
+

Nous contacter

id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
@@ -34,47 +34,6 @@
-<% if (config.mailMethod === 'smtp' ) { %> - -<% } %> \ No newline at end of file + \ No newline at end of file -- 2.39.5 From 980586d8ebc308d5a373f80788559861391cc580 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:45:38 +0200 Subject: [PATCH 15/65] Correction mineure sur le refresh d'un album --- javascripts/mon-compte/ma-collection/details.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js index 26f069f..3f63290 100644 --- a/javascripts/mon-compte/ma-collection/details.js +++ b/javascripts/mon-compte/ma-collection/details.js @@ -133,6 +133,10 @@ if ( typeof item !== 'undefined' ) { .then( (res) => { showToastr("Mise à jour réalisée avec succès", true); this.item = res.data; + + this.setTrackList(); + this.setIdentifiers(); + this.showLessIdentifiers(); }) .catch((err) => { showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false); -- 2.39.5 From e01f01337c615a448b192281abc26365fe21c05d Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:56:04 +0200 Subject: [PATCH 16/65] Lint --- .eslintrc.js | 73 ++++--- javascripts/ajouter-un-album.js | 192 +++++++++--------- javascripts/collection.js | 96 +++++---- javascripts/conctact.js | 39 ++-- javascripts/main.js | 108 +++++----- javascripts/mon-compte/index.js | 19 +- .../mon-compte/ma-collection/details.js | 146 +++++++------ .../mon-compte/ma-collection/exporter.js | 16 +- javascripts/mon-compte/ma-collection/index.js | 129 +++++++----- .../mon-compte/ma-collection/details.ejs | 2 +- 10 files changed, 450 insertions(+), 370 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 143bf10..1f95ab5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,36 +1,43 @@ module.exports = { - env: { - browser: true, - es2020: true, - node: true, - jquery: true, - }, - extends: ['airbnb-base', 'prettier'], - plugins: ['prettier'], - parserOptions: { - ecmaVersion: 11, - sourceType: 'module', - }, - rules: { - 'prettier/prettier': ['error'], - 'no-underscore-dangle': [ - 'error', - { - allow: ['_id', 'artists_sort', 'type_'], - }, - ], - 'camelcase': [ - 'error', - { - allow: ['artists_sort',] - }, - ], - }, - ignorePatterns: ['public/libs/**/*.js', 'public/js/main.js', 'dist/**'], - overrides: [ - { - files: ['**/*.js'], - excludedFiles: '*.ejs', + env: { + browser: true, + es2020: true, + node: true, + jquery: true, + }, + extends: ["airbnb-base", "prettier"], + plugins: ["prettier"], + parserOptions: { + ecmaVersion: 11, + sourceType: "module", + }, + rules: { + "prettier/prettier": ["error"], + "no-underscore-dangle": [ + "error", + { + allow: ["_id", "artists_sort", "type_"], + }, + ], + camelcase: [ + "error", + { + allow: ["artists_sort"], + }, + ], + }, + ignorePatterns: ["public/libs/**/*.js", "public/js/main.js", "dist/**"], + overrides: [ + { + files: ["**/*.js"], + excludedFiles: "*.ejs", + }, + ], + globals: { + Vue: true, + axios: true, + showToastr: true, + protocol: true, + host: true, }, - ], }; diff --git a/javascripts/ajouter-un-album.js b/javascripts/ajouter-un-album.js index d46d5d4..eb55697 100644 --- a/javascripts/ajouter-un-album.js +++ b/javascripts/ajouter-un-album.js @@ -1,109 +1,108 @@ Vue.createApp({ data() { return { - q: '', - year: '', - country: '', - format: '', + q: "", + year: "", + country: "", + format: "", loading: false, items: [], details: {}, modalIsVisible: false, formats: [ - 'Vinyl', - 'Acetate', - 'Flexi-disc', - 'Lathe Cut', - 'Mighty Tiny', - 'Shellac', - 'Sopic', - 'Pathé Disc', - 'Edison Disc', - 'Cylinder', - 'CD', - 'CDr', - 'CDV', - 'DVD', - 'DVDr', - 'HD DVD', - 'HD DVD-R', - 'Blu-ray', - 'Blu-ray-R', - 'Ultra HD Blu-ray', - 'SACD', - '4-Track Cartridge', - '8-Track Cartridge', - 'Cassette', - 'DC-International', - 'Elcaset', - 'PlayTape', - 'RCA Tape Cartridge', - 'DAT', - 'DCC', - 'Microcassette', - 'NT Cassette', - 'Pocket Rocker', - 'Revere Magnetic Stereo Tape Ca', - 'Tefifon', - 'Reel-To-Reel', - 'Sabamobil', - 'Betacam', - 'Betacam SP', - 'Betamax', - 'Cartrivision', - 'MiniDV', - 'Super VHS', - 'U-matic', - 'VHS', - 'Video 2000', - 'Video8', - 'Film Reel', - 'HitClips', - 'Laserdisc', - 'SelectaVision', - 'VHD', - 'Wire Recording', - 'Minidisc', - 'MVD', - 'UMD', - 'Floppy Disk', - 'File', - 'Memory Stick', - 'Hybrid', - 'All Media', - 'Box Set', - ] - } + "Vinyl", + "Acetate", + "Flexi-disc", + "Lathe Cut", + "Mighty Tiny", + "Shellac", + "Sopic", + "Pathé Disc", + "Edison Disc", + "Cylinder", + "CD", + "CDr", + "CDV", + "DVD", + "DVDr", + "HD DVD", + "HD DVD-R", + "Blu-ray", + "Blu-ray-R", + "Ultra HD Blu-ray", + "SACD", + "4-Track Cartridge", + "8-Track Cartridge", + "Cassette", + "DC-International", + "Elcaset", + "PlayTape", + "RCA Tape Cartridge", + "DAT", + "DCC", + "Microcassette", + "NT Cassette", + "Pocket Rocker", + "Revere Magnetic Stereo Tape Ca", + "Tefifon", + "Reel-To-Reel", + "Sabamobil", + "Betacam", + "Betacam SP", + "Betamax", + "Cartrivision", + "MiniDV", + "Super VHS", + "U-matic", + "VHS", + "Video 2000", + "Video8", + "Film Reel", + "HitClips", + "Laserdisc", + "SelectaVision", + "VHD", + "Wire Recording", + "Minidisc", + "MVD", + "UMD", + "Floppy Disk", + "File", + "Memory Stick", + "Hybrid", + "All Media", + "Box Set", + ], + }; }, methods: { search(event) { event.preventDefault(); - if ( this.loading ) { + if (this.loading) { return false; } this.loading = true; let url = `/api/v1/search?q=${this.q}`; - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.country ) { + if (this.country) { url += `&country=${this.country}`; } - if ( this.format ) { + if (this.format) { url += `&format=${this.format}`; } - axios.get(url) - .then( response => { - const { - results, - } = response.data; - let items = []; + return axios + .get(url) + .then((response) => { + const { results } = response.data; + const items = []; - for (let i = 0 ; i < results.length ; i += 1 ) { + for (let i = 0; i < results.length; i += 1) { const { id, title, @@ -129,7 +128,10 @@ Vue.createApp({ this.items = items; }) .catch((err) => { - showToastr(err.response?.data?.message || "Aucun résultat trouvé :/"); + showToastr( + err.response?.data?.message || + "Aucun résultat trouvé :/" + ); }) .finally(() => { this.loading = false; @@ -139,33 +141,39 @@ Vue.createApp({ this.modalIsVisible = !this.modalIsVisible; }, loadDetails(discogsId) { - axios.get(`/api/v1/search/${discogsId}`) - .then( response => { - const { - data, - } = response; + axios + .get(`/api/v1/search/${discogsId}`) + .then((response) => { + const { data } = response; this.details = data; this.toggleModal(); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album"); + showToastr( + err.response?.data?.message || + "Impossible de charger les détails de cet album" + ); }) .finally(() => { this.loading = false; }); }, add() { - axios.post('/api/v1/albums', this.details) + axios + .post("/api/v1/albums", this.details) .then(() => { - window.location.href = '/ma-collection'; + window.location.href = "/ma-collection"; }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); + showToastr( + err.response?.data?.message || + "Impossible d'ajouter cet album pour le moment…" + ); }); }, orderedItems(items) { return items.sort(); - } - } -}).mount('#ajouter-album'); \ No newline at end of file + }, + }, +}).mount("#ajouter-album"); diff --git a/javascripts/collection.js b/javascripts/collection.js index c3b3561..beed2c6 100644 --- a/javascripts/collection.js +++ b/javascripts/collection.js @@ -1,4 +1,4 @@ -if ( typeof userId !== 'undefined' ) { +if (typeof userId !== "undefined") { Vue.createApp({ data() { return { @@ -9,16 +9,17 @@ if ( typeof userId !== 'undefined' ) { page: 1, totalPages: 1, limit: 16, - artist: '', - format: '', - year: '', - genre: '', - style: '', - sortOrder: 'artists_sort-asc', - sort: 'artists_sort', - order: 'asc', + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", + // eslint-disable-next-line no-undef userId, - } + }; }, created() { this.fetch(); @@ -32,42 +33,49 @@ if ( typeof userId !== 'undefined' ) { const urlParams = new URLSearchParams(queryString); const entries = urlParams.entries(); - for(const entry of entries) { - switch(entry[0]) { - case 'artists_sort': - this.artist = entry[1]; + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; break; default: - this[entry[0]] = entry[1]; + this[key] = value; } } 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.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - axios.get(url) - .then( response => { + axios + .get(url) + .then((response) => { this.items = response.data.rows; this.total = response.data.count || 0; - this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); - + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger cette collection"); + showToastr( + err.response?.data?.message || + "Impossible de charger cette collection" + ); }) .finally(() => { this.loading = false; @@ -75,23 +83,23 @@ if ( typeof userId !== 'undefined' ) { }, changeUrl() { let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; - if ( this.artist ) { - url += `&artists_sort=${this.artist.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - location.href = url; + window.location.href = url; }, next(event) { event.preventDefault(); @@ -113,7 +121,7 @@ if ( typeof userId !== 'undefined' ) { this.changeUrl(); }, changeSort() { - const [sort,order] = this.sortOrder.split('-'); + const [sort, order] = this.sortOrder.split("-"); this.sort = sort; this.order = order; this.page = 1; @@ -127,7 +135,7 @@ if ( typeof userId !== 'undefined' ) { }, showMoreFilters() { this.moreFilters = !this.moreFilters; - } - } - }).mount('#collection-publique'); -} \ No newline at end of file + }, + }, + }).mount("#collection-publique"); +} diff --git a/javascripts/conctact.js b/javascripts/conctact.js index 51ed1cb..1fbde34 100644 --- a/javascripts/conctact.js +++ b/javascripts/conctact.js @@ -1,42 +1,43 @@ -if ( typeof contactMethod !== 'undefined' && contactMethod === 'smtp' ) { +// eslint-disable-next-line no-undef +if (typeof contactMethod !== "undefined" && contactMethod === "smtp") { Vue.createApp({ data() { return { - email: '', - name: '', - message: '', - captcha: '', + email: "", + name: "", + message: "", + captcha: "", loading: false, - } + }; }, methods: { send(event) { event.preventDefault(); - if ( this.loading ) { + if (this.loading) { return false; } this.loading = true; - const { - email, - message, - name, - captcha, - } = this; + const { email, message, name, captcha } = this; - axios.post('/api/v1/contact', {email, name, message, captcha}) - .then( () => { + return axios + .post("/api/v1/contact", { email, name, message, captcha }) + .then(() => { showToastr("Message correctement envoyé", true); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false); + showToastr( + err.response?.data?.message || + "Impossible d'envoyer votre message", + false + ); }) .finally(() => { this.loading = false; - }) + }); }, }, - }).mount('#contact'); -} \ No newline at end of file + }).mount("#contact"); +} diff --git a/javascripts/main.js b/javascripts/main.js index 0b0592e..388ab22 100644 --- a/javascripts/main.js +++ b/javascripts/main.js @@ -1,30 +1,30 @@ -const { - protocol, - host -} = window.location; +/* eslint-disable no-unused-vars */ +const { protocol, host } = window.location; - /** +/** * Fonction permettant d'afficher un message dans un toastr * @param {String} message */ function showToastr(message, success = false) { - let x = document.getElementById("toastr"); - if ( message ) { + const x = document.getElementById("toastr"); + if (message) { x.getElementsByTagName("SPAN")[0].innerHTML = message; } x.className = `${x.className} show`.replace("sucess", ""); - if ( success ) { + if (success) { x.className = `${x.className} success`; } - setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000); -}; + setTimeout(() => { + x.className = x.className.replace("show", ""); + }, 3000); +} /** * Fonction permettant de masquer le toastr */ function hideToastr() { - let x = document.getElementById("toastr"); + const x = document.getElementById("toastr"); x.className = x.className.replace("show", ""); x.getElementsByTagName("SPAN")[0].innerHTML = ""; @@ -37,17 +37,17 @@ function hideToastr() { * * @return {String} */ -function getCookie(cname, defaultValue = 'false') { - let name = cname + "="; - let decodedCookie = decodeURIComponent(document.cookie); - let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i+=1) { +function getCookie(cname, defaultValue = "false") { + const name = `${cname}=`; + const decodedCookie = decodeURIComponent(document.cookie); + const ca = decodedCookie.split(";"); + for (let i = 0; i < ca.length; i += 1) { let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); + while (c.charAt(0) === " ") { + c = c.substring(1); } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); } } return defaultValue; @@ -61,9 +61,9 @@ function getCookie(cname, defaultValue = 'false') { */ function setCookie(cname, cvalue, exdays = 30) { const d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - let expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + const expires = `expires=${d.toUTCString()}`; + document.cookie = `${cname}=${cvalue};${expires};path=/`; } /** @@ -71,9 +71,9 @@ function setCookie(cname, cvalue, exdays = 30) { * @param {String} value */ function setAriaTheme(value) { - let body = document.body; - if ( value === 'true' ) { - let classesString = body.className || ""; + const { body } = document; + if (value === "true") { + const classesString = body.className || ""; if (classesString.indexOf("is-accessible") === -1) { body.classList.add("is-accessible"); } @@ -86,11 +86,11 @@ function setAriaTheme(value) { * Fonction de (dé)charger le thème accessible */ function switchAriaTheme() { - let body = document.body; + const { body } = document; body.classList.toggle("is-accessible"); - setCookie('ariatheme', body.classList.contains("is-accessible")); + setCookie("ariatheme", body.classList.contains("is-accessible")); } /** @@ -98,46 +98,54 @@ function switchAriaTheme() { * @param {Object} e */ function switchTheme(e) { - const theme = e.target.checked ? 'dark' : 'light'; + const theme = e.target.checked ? "dark" : "light"; - document.documentElement.setAttribute('data-theme', theme); - setCookie('theme', theme); + document.documentElement.setAttribute("data-theme", theme); + setCookie("theme", theme); } /** * Ensemble d'actions effectuées au chargement de la page */ -document.addEventListener('DOMContentLoaded', () => { - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); +document.addEventListener("DOMContentLoaded", () => { + const $navbarBurgers = Array.prototype.slice.call( + document.querySelectorAll(".navbar-burger"), + 0 + ); if ($navbarBurgers.length > 0) { - $navbarBurgers.forEach( el => { - el.addEventListener('click', () => { - const target = el.dataset.target; + $navbarBurgers.forEach((el) => { + el.addEventListener("click", () => { + const { target } = el.dataset; const $target = document.getElementById(target); - el.classList.toggle('is-active'); - $target.classList.toggle('is-active'); + el.classList.toggle("is-active"); + $target.classList.toggle("is-active"); }); }); } const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); - if ( switchAriaThemeBtn ) { + if (switchAriaThemeBtn) { switchAriaThemeBtn.addEventListener("click", switchAriaTheme); } - setAriaTheme(getCookie('ariatheme')); + setAriaTheme(getCookie("ariatheme")); - const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); - if ( toggleSwitch ) { - toggleSwitch.addEventListener('change', switchTheme, false); + const toggleSwitch = document.querySelector( + '.theme-switch input[type="checkbox"]' + ); + 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'; + let currentThemeIsDark = getCookie("theme"); + if (currentThemeIsDark === "false" && window.matchMedia) { + currentThemeIsDark = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; } - switchTheme({target: {checked: currentThemeIsDark === 'dark'}}); - if ( toggleSwitch) { - toggleSwitch.checked = currentThemeIsDark === 'dark'; + switchTheme({ target: { checked: currentThemeIsDark === "dark" } }); + if (toggleSwitch) { + toggleSwitch.checked = currentThemeIsDark === "dark"; } -}); \ No newline at end of file +}); diff --git a/javascripts/mon-compte/index.js b/javascripts/mon-compte/index.js index ccc1d02..ed40f03 100644 --- a/javascripts/mon-compte/index.js +++ b/javascripts/mon-compte/index.js @@ -1,16 +1,19 @@ -if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) { +if (typeof email !== "undefined" && typeof username !== "undefined") { Vue.createApp({ data() { return { + // eslint-disable-next-line no-undef email, + // eslint-disable-next-line no-undef username, - oldPassword: '', - password: '', - passwordConfirm: '', + oldPassword: "", + password: "", + passwordConfirm: "", loading: false, - } + }; }, methods: { + // eslint-disable-next-line no-unused-vars async updateProfil(event) { // try { // if ( this.password !== this.passwordConfirm ) { @@ -21,6 +24,6 @@ if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) { // showToastr(err); // } }, - } - }).mount('#mon-compte'); -} \ No newline at end of file + }, + }).mount("#mon-compte"); +} diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js index 3f63290..fe8d079 100644 --- a/javascripts/mon-compte/ma-collection/details.js +++ b/javascripts/mon-compte/ma-collection/details.js @@ -1,17 +1,18 @@ -if ( typeof item !== 'undefined' ) { +if (typeof item !== "undefined") { Vue.createApp({ data() { return { + // eslint-disable-next-line no-undef item, tracklist: [], identifiers: [], modalIsVisible: false, - identifiersMode: 'preview', + identifiersMode: "preview", identifiersPreviewLength: 16, preview: null, index: null, showModalDelete: false, - } + }; }, created() { this.setTrackList(); @@ -20,65 +21,65 @@ if ( typeof item !== 'undefined' ) { window.addEventListener("keydown", this.changeImage); }, destroyed() { - window.removeEventListener('keydown', this.changeImage); + window.removeEventListener("keydown", this.changeImage); }, methods: { setIdentifiers() { this.identifiers = []; - let max = this.identifiersMode == 'preview' && this.item.identifiers.length > this.identifiersPreviewLength ? this.identifiersPreviewLength : this.item.identifiers.length; + const max = + this.identifiersMode === "preview" && + this.item.identifiers.length > this.identifiersPreviewLength + ? this.identifiersPreviewLength + : this.item.identifiers.length; - for ( let i = 0 ; i < max ; i += 1 ) { + 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]; + 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: [], - }; + 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, + }); } - - subTrack.type = type_; - subTrack.title = title; - } else { - subTrack.tracks.push({ - title, - position, - duration, - extraartists - }); } - } - this.tracklist.push(subTrack); + 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 item = + event.target.tagName === "IMG" + ? event.target.parentElement + : event.target; - const { - index, - } = item.dataset; + const { index } = item.dataset; this.index = Number(index); this.modalIsVisible = true; @@ -89,48 +90,63 @@ if ( typeof item !== 'undefined' ) { this.modalIsVisible = !this.modalIsVisible; }, previous() { - this.index = this.index > 0 ? this.index - 1 : this.item.images.length -1; + 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.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 ) { + if ( + this.modalIsVisible && + ["ArrowRight", "ArrowLeft", "Escape"].indexOf(direction) !== + -1 + ) { switch (direction) { - case 'ArrowRight': + case "ArrowRight": return this.next(); - case 'ArrowLeft': + case "ArrowLeft": return this.previous(); default: this.modalIsVisible = false; return true; } } + + return true; }, showAllIdentifiers() { - this.identifiersMode = 'all'; + this.identifiersMode = "all"; this.setIdentifiers(); }, showLessIdentifiers() { - this.identifiersMode = 'preview'; + this.identifiersMode = "preview"; this.setIdentifiers(); - document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' }); + document + .querySelector("#identifiers") + .scrollIntoView({ behavior: "smooth" }); }, showConfirmDelete() { - this.toggleModal(); + this.toggleModalDelete(); }, - toggleModal() { + toggleModalDelete() { this.showModalDelete = !this.showModalDelete; }, updateItem() { showToastr("Mise à jour en cours…", true); - axios.patch(`/api/v1/albums/${this.item._id}`) - .then( (res) => { + axios + .patch(`/api/v1/albums/${this.item._id}`) + .then((res) => { showToastr("Mise à jour réalisée avec succès", true); this.item = res.data; @@ -139,24 +155,32 @@ if ( typeof item !== 'undefined' ) { this.showLessIdentifiers(); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false); + showToastr( + err.response?.data?.message || + "Impossible de mettre à jour cet album", + false + ); }); }, deleteItem() { - axios.delete(`/api/v1/albums/${this.item._id}`) - .then( () => { + axios + .delete(`/api/v1/albums/${this.item._id}`) + .then(() => { window.location.href = "/ma-collection"; }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); }) .finally(() => { - this.toggleModal(); + this.toggleModalDelete(); }); }, goToArtist() { return ""; }, }, - }).mount('#ma-collection-details'); -} \ No newline at end of file + }).mount("#ma-collection-details"); +} diff --git a/javascripts/mon-compte/ma-collection/exporter.js b/javascripts/mon-compte/ma-collection/exporter.js index f5dcbcb..b391436 100644 --- a/javascripts/mon-compte/ma-collection/exporter.js +++ b/javascripts/mon-compte/ma-collection/exporter.js @@ -1,18 +1,16 @@ Vue.createApp({ data() { return { - format: 'xml', - } - }, - created() { - }, - destroyed() { + format: "xml", + }; }, + created() {}, + destroyed() {}, methods: { exportCollection(event) { event.preventDefault(); - window.open(`/api/v1/albums?exportFormat=${this.format}`, '_blank'); - } + window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank"); + }, }, -}).mount('#exporter'); \ No newline at end of file +}).mount("#exporter"); diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 7e2bdf5..967e3f2 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -1,4 +1,4 @@ -if ( typeof isPublicCollection !== 'undefined' ) { +if (typeof isPublicCollection !== "undefined") { Vue.createApp({ data() { return { @@ -9,20 +9,21 @@ if ( typeof isPublicCollection !== 'undefined' ) { page: 1, totalPages: 1, limit: 16, - artist: '', - format: '', - year: '', - genre: '', - style: '', - sortOrder: 'artists_sort-asc', - sort: 'artists_sort', - order: 'asc', + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", itemId: null, showModalDelete: false, showModalShare: false, shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + // eslint-disable-next-line no-undef isPublicCollection, - } + }; }, created() { this.fetch(); @@ -36,41 +37,49 @@ if ( typeof isPublicCollection !== 'undefined' ) { const urlParams = new URLSearchParams(queryString); const entries = urlParams.entries(); - for(const entry of entries) { - switch(entry[0]) { - case 'artists_sort': - this.artist = entry[1]; + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; break; default: - this[entry[0]] = entry[1]; + this[key] = value; } } 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.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - axios.get(url) - .then( response => { + axios + .get(url) + .then((response) => { this.items = response.data.rows; this.total = response.data.count || 0; - this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger votre collection"); + showToastr( + err.response?.data?.message || + "Impossible de charger votre collection" + ); }) .finally(() => { this.loading = false; @@ -78,23 +87,23 @@ if ( typeof isPublicCollection !== 'undefined' ) { }, changeUrl() { let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; - if ( this.artist ) { - url += `&artists_sort=${this.artist.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - location.href = url; + window.location.href = url; }, next(event) { event.preventDefault(); @@ -116,7 +125,7 @@ if ( typeof isPublicCollection !== 'undefined' ) { this.changeUrl(); }, changeSort() { - const [sort,order] = this.sortOrder.split('-'); + const [sort, order] = this.sortOrder.split("-"); this.sort = sort; this.order = order; this.page = 1; @@ -142,37 +151,51 @@ if ( typeof isPublicCollection !== 'undefined' ) { this.toggleModal(); }, deleteItem() { - axios.delete(`/api/v1/albums/${this.itemId}`) - .then( () => { + axios + .delete(`/api/v1/albums/${this.itemId}`) + .then(() => { this.fetch(); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); }) .finally(() => { this.toggleModal(); }); }, shareCollection() { - axios.patch(`/api/v1/me`, { - isPublicCollection: !this.isPublicCollection, - }) - .then( (res) => { + 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); + if (this.isPublicCollection) { + showToastr( + "Votre collection est désormais publique", + true + ); } else { - showToastr("Votre collection n'est plus partagée", true); + showToastr( + "Votre collection n'est plus partagée", + true + ); } }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); }) .finally(() => { this.toggleModalShare(); }); }, - } - }).mount('#ma-collection'); -} \ No newline at end of file + }, + }).mount("#ma-collection"); +} diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index e8615b4..475ee79 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -169,7 +169,7 @@
- +
-- 2.39.5 From 1d59ee3b7136321af40218b5529abedc4730da88 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Sun, 30 Oct 2022 21:48:49 +0100 Subject: [PATCH 17/65] Version 1.4 (#67) Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/67 --- .eslintrc.js | 73 ++++--- .gitignore | 2 +- gulpfile.js | 46 ++++ javascripts/ajouter-un-album.js | 179 ++++++++++++++++ javascripts/collection.js | 141 ++++++++++++ javascripts/conctact.js | 43 ++++ javascripts/main.js | 151 +++++++++++++ javascripts/mon-compte/index.js | 29 +++ .../mon-compte/ma-collection/details.js | 186 ++++++++++++++++ .../mon-compte/ma-collection/exporter.js | 16 ++ javascripts/mon-compte/ma-collection/index.js | 201 ++++++++++++++++++ package.json | 11 +- public/js/main.js | 138 ------------ src/app.js | 8 - views/index.ejs | 5 +- views/pages/ajouter-un-album.ejs | 178 +--------------- views/pages/collection.ejs | 139 +----------- views/pages/mon-compte/index.ejs | 28 +-- .../mon-compte/ma-collection/details.ejs | 167 +-------------- .../mon-compte/ma-collection/exporter.ejs | 25 +-- .../pages/mon-compte/ma-collection/index.ejs | 186 +--------------- views/pages/nous-contacter.ejs | 49 +---- 22 files changed, 1067 insertions(+), 934 deletions(-) create mode 100644 gulpfile.js create mode 100644 javascripts/ajouter-un-album.js create mode 100644 javascripts/collection.js create mode 100644 javascripts/conctact.js create mode 100644 javascripts/main.js create mode 100644 javascripts/mon-compte/index.js create mode 100644 javascripts/mon-compte/ma-collection/details.js create mode 100644 javascripts/mon-compte/ma-collection/exporter.js create mode 100644 javascripts/mon-compte/ma-collection/index.js delete mode 100644 public/js/main.js diff --git a/.eslintrc.js b/.eslintrc.js index 143bf10..1f95ab5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,36 +1,43 @@ module.exports = { - env: { - browser: true, - es2020: true, - node: true, - jquery: true, - }, - extends: ['airbnb-base', 'prettier'], - plugins: ['prettier'], - parserOptions: { - ecmaVersion: 11, - sourceType: 'module', - }, - rules: { - 'prettier/prettier': ['error'], - 'no-underscore-dangle': [ - 'error', - { - allow: ['_id', 'artists_sort', 'type_'], - }, - ], - 'camelcase': [ - 'error', - { - allow: ['artists_sort',] - }, - ], - }, - ignorePatterns: ['public/libs/**/*.js', 'public/js/main.js', 'dist/**'], - overrides: [ - { - files: ['**/*.js'], - excludedFiles: '*.ejs', + env: { + browser: true, + es2020: true, + node: true, + jquery: true, + }, + extends: ["airbnb-base", "prettier"], + plugins: ["prettier"], + parserOptions: { + ecmaVersion: 11, + sourceType: "module", + }, + rules: { + "prettier/prettier": ["error"], + "no-underscore-dangle": [ + "error", + { + allow: ["_id", "artists_sort", "type_"], + }, + ], + camelcase: [ + "error", + { + allow: ["artists_sort"], + }, + ], + }, + ignorePatterns: ["public/libs/**/*.js", "public/js/main.js", "dist/**"], + overrides: [ + { + files: ["**/*.js"], + excludedFiles: "*.ejs", + }, + ], + globals: { + Vue: true, + axios: true, + showToastr: true, + protocol: true, + host: true, }, - ], }; diff --git a/.gitignore b/.gitignore index a8dcba6..3054b59 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,6 @@ dist dist yarn.lock public/css -public/css +public/js docker-compose.yml dump diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8e76f38 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,46 @@ +const { parallel, src, dest } = require("gulp"); + +const sourcemaps = require("gulp-sourcemaps"); +const concat = require("gulp-concat"); + +const gulp = require("gulp"); +const uglify = require("gulp-uglify"); +const babel = require("gulp-babel"); + +const sourceJs = "javascripts/**/*.js"; +const sourceRemoteJS = [ + "./node_modules/vue/dist/vue.global.prod.js", + "./node_modules/axios/dist/axios.min.js", +]; + +const destination = "public/js"; + +// TASKS ---------------------------------------------------------------------- + +const compileJs = function () { + return gulp + .src(sourceJs) + .pipe(sourcemaps.init()) + .pipe(concat("main.js")) + .pipe( + babel({ + presets: ["@babel/env"], + }) + ) + .pipe(uglify()) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +const compileRemoteJs = function () { + return gulp + .src(sourceRemoteJS) + .pipe(sourcemaps.init()) + .pipe(concat("libs.js")) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +// ---------------------------------------------------------------------------- + +// COMMANDS ------------------------------------------------------------------- +exports.default = parallel(compileJs, compileRemoteJs); +// ---------------------------------------------------------------------------- diff --git a/javascripts/ajouter-un-album.js b/javascripts/ajouter-un-album.js new file mode 100644 index 0000000..eb55697 --- /dev/null +++ b/javascripts/ajouter-un-album.js @@ -0,0 +1,179 @@ +Vue.createApp({ + data() { + return { + q: "", + year: "", + country: "", + format: "", + loading: false, + items: [], + details: {}, + modalIsVisible: false, + formats: [ + "Vinyl", + "Acetate", + "Flexi-disc", + "Lathe Cut", + "Mighty Tiny", + "Shellac", + "Sopic", + "Pathé Disc", + "Edison Disc", + "Cylinder", + "CD", + "CDr", + "CDV", + "DVD", + "DVDr", + "HD DVD", + "HD DVD-R", + "Blu-ray", + "Blu-ray-R", + "Ultra HD Blu-ray", + "SACD", + "4-Track Cartridge", + "8-Track Cartridge", + "Cassette", + "DC-International", + "Elcaset", + "PlayTape", + "RCA Tape Cartridge", + "DAT", + "DCC", + "Microcassette", + "NT Cassette", + "Pocket Rocker", + "Revere Magnetic Stereo Tape Ca", + "Tefifon", + "Reel-To-Reel", + "Sabamobil", + "Betacam", + "Betacam SP", + "Betamax", + "Cartrivision", + "MiniDV", + "Super VHS", + "U-matic", + "VHS", + "Video 2000", + "Video8", + "Film Reel", + "HitClips", + "Laserdisc", + "SelectaVision", + "VHD", + "Wire Recording", + "Minidisc", + "MVD", + "UMD", + "Floppy Disk", + "File", + "Memory Stick", + "Hybrid", + "All Media", + "Box Set", + ], + }; + }, + methods: { + search(event) { + event.preventDefault(); + + if (this.loading) { + return false; + } + + this.loading = true; + let url = `/api/v1/search?q=${this.q}`; + + if (this.year) { + url += `&year=${this.year}`; + } + if (this.country) { + url += `&country=${this.country}`; + } + if (this.format) { + url += `&format=${this.format}`; + } + + return axios + .get(url) + .then((response) => { + const { results } = response.data; + const items = []; + + for (let i = 0; i < results.length; i += 1) { + const { + id, + title, + thumb, + year, + country, + format, + genre, + style, + } = results[i]; + items.push({ + id, + title, + thumb, + year, + country, + format, + genre, + style, + }); + } + + this.items = items; + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Aucun résultat trouvé :/" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + loadDetails(discogsId) { + axios + .get(`/api/v1/search/${discogsId}`) + .then((response) => { + const { data } = response; + + this.details = data; + this.toggleModal(); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de charger les détails de cet album" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + add() { + axios + .post("/api/v1/albums", this.details) + .then(() => { + window.location.href = "/ma-collection"; + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible d'ajouter cet album pour le moment…" + ); + }); + }, + orderedItems(items) { + return items.sort(); + }, + }, +}).mount("#ajouter-album"); diff --git a/javascripts/collection.js b/javascripts/collection.js new file mode 100644 index 0000000..beed2c6 --- /dev/null +++ b/javascripts/collection.js @@ -0,0 +1,141 @@ +if (typeof userId !== "undefined") { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", + // eslint-disable-next-line no-undef + userId, + }; + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; + break; + default: + this[key] = value; + } + } + + 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.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + 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) => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de charger cette collection" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + 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")}`; + } + + window.location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort, order] = this.sortOrder.split("-"); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, + }, + }).mount("#collection-publique"); +} diff --git a/javascripts/conctact.js b/javascripts/conctact.js new file mode 100644 index 0000000..1fbde34 --- /dev/null +++ b/javascripts/conctact.js @@ -0,0 +1,43 @@ +// eslint-disable-next-line no-undef +if (typeof contactMethod !== "undefined" && contactMethod === "smtp") { + Vue.createApp({ + data() { + return { + email: "", + name: "", + message: "", + captcha: "", + loading: false, + }; + }, + methods: { + send(event) { + event.preventDefault(); + + if (this.loading) { + return false; + } + + this.loading = true; + + const { email, message, name, captcha } = this; + + return axios + .post("/api/v1/contact", { email, name, message, captcha }) + .then(() => { + showToastr("Message correctement envoyé", true); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible d'envoyer votre message", + false + ); + }) + .finally(() => { + this.loading = false; + }); + }, + }, + }).mount("#contact"); +} diff --git a/javascripts/main.js b/javascripts/main.js new file mode 100644 index 0000000..388ab22 --- /dev/null +++ b/javascripts/main.js @@ -0,0 +1,151 @@ +/* eslint-disable no-unused-vars */ +const { protocol, host } = window.location; + +/** + * Fonction permettant d'afficher un message dans un toastr + * @param {String} message + */ +function showToastr(message, success = false) { + const x = document.getElementById("toastr"); + if (message) { + x.getElementsByTagName("SPAN")[0].innerHTML = message; + } + + x.className = `${x.className} show`.replace("sucess", ""); + if (success) { + x.className = `${x.className} success`; + } + setTimeout(() => { + x.className = x.className.replace("show", ""); + }, 3000); +} + +/** + * Fonction permettant de masquer le toastr + */ +function hideToastr() { + const x = document.getElementById("toastr"); + + x.className = x.className.replace("show", ""); + x.getElementsByTagName("SPAN")[0].innerHTML = ""; +} + +/** + * Fonction permettant de récupérer la valeur d'un cookie + * @param {String} cname + * @param {String} defaultValue + * + * @return {String} + */ +function getCookie(cname, defaultValue = "false") { + const name = `${cname}=`; + const decodedCookie = decodeURIComponent(document.cookie); + const ca = decodedCookie.split(";"); + for (let i = 0; i < ca.length; i += 1) { + let c = ca[i]; + while (c.charAt(0) === " ") { + c = c.substring(1); + } + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); + } + } + return defaultValue; +} + +/** + * Fonction permettant de créer un cookie + * @param {String} cname + * @param {String} cvalue + * @param {Number} exdays + */ +function setCookie(cname, cvalue, exdays = 30) { + const d = new Date(); + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + const expires = `expires=${d.toUTCString()}`; + document.cookie = `${cname}=${cvalue};${expires};path=/`; +} + +/** + * Fonction de (dé)charger le thème accessible + * @param {String} value + */ +function setAriaTheme(value) { + const { body } = document; + if (value === "true") { + const classesString = body.className || ""; + if (classesString.indexOf("is-accessible") === -1) { + body.classList.add("is-accessible"); + } + } else { + body.classList.remove("is-accessible"); + } +} + +/** + * Fonction de (dé)charger le thème accessible + */ +function switchAriaTheme() { + const { body } = document; + + body.classList.toggle("is-accessible"); + + setCookie("ariatheme", body.classList.contains("is-accessible")); +} + +/** + * Fonction permettant de switcher de thème clair/sombre + * @param {Object} e + */ +function switchTheme(e) { + const theme = e.target.checked ? "dark" : "light"; + + document.documentElement.setAttribute("data-theme", theme); + setCookie("theme", theme); +} + +/** + * Ensemble d'actions effectuées au chargement de la page + */ +document.addEventListener("DOMContentLoaded", () => { + const $navbarBurgers = Array.prototype.slice.call( + document.querySelectorAll(".navbar-burger"), + 0 + ); + if ($navbarBurgers.length > 0) { + $navbarBurgers.forEach((el) => { + el.addEventListener("click", () => { + const { target } = el.dataset; + const $target = document.getElementById(target); + + el.classList.toggle("is-active"); + $target.classList.toggle("is-active"); + }); + }); + } + + const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); + if (switchAriaThemeBtn) { + switchAriaThemeBtn.addEventListener("click", switchAriaTheme); + } + setAriaTheme(getCookie("ariatheme")); + + const toggleSwitch = document.querySelector( + '.theme-switch input[type="checkbox"]' + ); + 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" } }); + if (toggleSwitch) { + toggleSwitch.checked = currentThemeIsDark === "dark"; + } +}); diff --git a/javascripts/mon-compte/index.js b/javascripts/mon-compte/index.js new file mode 100644 index 0000000..ed40f03 --- /dev/null +++ b/javascripts/mon-compte/index.js @@ -0,0 +1,29 @@ +if (typeof email !== "undefined" && typeof username !== "undefined") { + Vue.createApp({ + data() { + return { + // eslint-disable-next-line no-undef + email, + // eslint-disable-next-line no-undef + username, + oldPassword: "", + password: "", + passwordConfirm: "", + loading: false, + }; + }, + methods: { + // eslint-disable-next-line no-unused-vars + 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("#mon-compte"); +} diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js new file mode 100644 index 0000000..fe8d079 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/details.js @@ -0,0 +1,186 @@ +if (typeof item !== "undefined") { + Vue.createApp({ + data() { + return { + // eslint-disable-next-line no-undef + item, + tracklist: [], + identifiers: [], + modalIsVisible: false, + identifiersMode: "preview", + identifiersPreviewLength: 16, + preview: null, + index: null, + showModalDelete: false, + }; + }, + created() { + this.setTrackList(); + this.setIdentifiers(); + + window.addEventListener("keydown", this.changeImage); + }, + destroyed() { + window.removeEventListener("keydown", this.changeImage); + }, + methods: { + setIdentifiers() { + this.identifiers = []; + + const 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(); + }, + 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; + } + } + + return true; + }, + showAllIdentifiers() { + this.identifiersMode = "all"; + this.setIdentifiers(); + }, + showLessIdentifiers() { + this.identifiersMode = "preview"; + this.setIdentifiers(); + + document + .querySelector("#identifiers") + .scrollIntoView({ behavior: "smooth" }); + }, + showConfirmDelete() { + this.toggleModalDelete(); + }, + toggleModalDelete() { + this.showModalDelete = !this.showModalDelete; + }, + updateItem() { + showToastr("Mise à jour en cours…", true); + axios + .patch(`/api/v1/albums/${this.item._id}`) + .then((res) => { + showToastr("Mise à jour réalisée avec succès", true); + this.item = res.data; + + this.setTrackList(); + this.setIdentifiers(); + this.showLessIdentifiers(); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de mettre à jour cet album", + false + ); + }); + }, + deleteItem() { + axios + .delete(`/api/v1/albums/${this.item._id}`) + .then(() => { + window.location.href = "/ma-collection"; + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); + }) + .finally(() => { + this.toggleModalDelete(); + }); + }, + goToArtist() { + return ""; + }, + }, + }).mount("#ma-collection-details"); +} diff --git a/javascripts/mon-compte/ma-collection/exporter.js b/javascripts/mon-compte/ma-collection/exporter.js new file mode 100644 index 0000000..b391436 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/exporter.js @@ -0,0 +1,16 @@ +Vue.createApp({ + data() { + return { + format: "xml", + }; + }, + created() {}, + destroyed() {}, + methods: { + exportCollection(event) { + event.preventDefault(); + + window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank"); + }, + }, +}).mount("#exporter"); diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js new file mode 100644 index 0000000..967e3f2 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/index.js @@ -0,0 +1,201 @@ +if (typeof isPublicCollection !== "undefined") { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", + itemId: null, + showModalDelete: false, + showModalShare: false, + shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + // eslint-disable-next-line no-undef + isPublicCollection, + }; + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; + break; + default: + this[key] = value; + } + } + + 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.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + 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) => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de charger votre collection" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + 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")}`; + } + + window.location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort, order] = this.sortOrder.split("-"); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, + 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("#ma-collection"); +} diff --git a/package.json b/package.json index 2ee4492..b3dffed 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Simple application to manage your CD/Vinyl collection", "scripts": { "start": "node ./dist/bin/www", - "run:all": "npm-run-all build sass start", + "run:all": "npm-run-all build sass uglify start", "watch": "nodemon -e js,scss", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", + "uglify": "npx gulp", "prebuild": "rimraf dist", "build": "babel ./src --out-dir dist --copy-files", "test": "jest", @@ -55,6 +56,11 @@ "excel4node": "^1.7.2", "express": "^4.17.2", "express-session": "^1.17.2", + "gulp": "^4.0.2", + "gulp-babel": "^8.0.0", + "gulp-concat": "^2.6.1", + "gulp-sourcemaps": "^3.0.0", + "gulp-uglify": "^3.0.2", "joi": "^17.6.0", "knacss": "^8.0.4", "mongoose": "^6.2.1", @@ -75,7 +81,8 @@ "exec": "yarn run:all", "watch": [ "src/*", - "sass/*" + "sass/*", + "javascripts/*" ], "ignore": [ "**/__tests__/**", diff --git a/public/js/main.js b/public/js/main.js deleted file mode 100644 index f203a63..0000000 --- a/public/js/main.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Fonction permettant d'afficher un message dans un toastr - * @param {String} message - */ - function showToastr(message, success = false) { - let x = document.getElementById("toastr"); - if ( message ) { - x.getElementsByTagName("SPAN")[0].innerHTML = message; - } - - x.className = `${x.className} show`.replace("sucess", ""); - if ( success ) { - x.className = `${x.className} success`; - } - setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000); -}; - -/** - * Fonction permettant de masquer le toastr - */ -function hideToastr() { - let x = document.getElementById("toastr"); - - x.className = x.className.replace("show", ""); - x.getElementsByTagName("SPAN")[0].innerHTML = ""; -} - -/** - * Fonction permettant de récupérer la valeur d'un cookie - * @param {String} cname - * @param {String} defaultValue - * - * @return {String} - */ -function getCookie(cname, defaultValue = 'false') { - let name = cname + "="; - let decodedCookie = decodeURIComponent(document.cookie); - let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i+=1) { - let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return defaultValue; -} - -/** - * Fonction permettant de créer un cookie - * @param {String} cname - * @param {String} cvalue - * @param {Number} exdays - */ -function setCookie(cname, cvalue, exdays = 30) { - const d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - let expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; -} - -/** - * Fonction de (dé)charger le thème accessible - * @param {String} value - */ -function setAriaTheme(value) { - let body = document.body; - if ( value === 'true' ) { - let classesString = body.className || ""; - if (classesString.indexOf("is-accessible") === -1) { - body.classList.add("is-accessible"); - } - } else { - body.classList.remove("is-accessible"); - } -} - -/** - * Fonction de (dé)charger le thème accessible - */ -function switchAriaTheme() { - let body = document.body; - - body.classList.toggle("is-accessible"); - - setCookie('ariatheme', body.classList.contains("is-accessible")); -} - -/** - * Fonction permettant de switcher de thème clair/sombre - * @param {Object} e - */ -function switchTheme(e) { - const theme = e.target.checked ? 'dark' : 'light'; - - document.documentElement.setAttribute('data-theme', theme); - setCookie('theme', theme); -} - -/** - * Ensemble d'actions effectuées au chargement de la page - */ -document.addEventListener('DOMContentLoaded', () => { - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); - if ($navbarBurgers.length > 0) { - $navbarBurgers.forEach( el => { - el.addEventListener('click', () => { - const target = el.dataset.target; - const $target = document.getElementById(target); - - el.classList.toggle('is-active'); - $target.classList.toggle('is-active'); - }); - }); - } - - const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); - if ( switchAriaThemeBtn ) { - switchAriaThemeBtn.addEventListener("click", switchAriaTheme); - } - setAriaTheme(getCookie('ariatheme')); - - const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); - 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'}}); - if ( toggleSwitch) { - toggleSwitch.checked = currentThemeIsDark === 'dark'; - } -}); \ No newline at end of file diff --git a/src/app.js b/src/app.js index 7c40a27..6fa4688 100644 --- a/src/app.js +++ b/src/app.js @@ -75,14 +75,6 @@ app.set("views", path.join(__dirname, "../views")); app.set("view engine", "ejs"); app.use(express.static(path.join(__dirname, "../public"))); -app.use( - "/libs/vue", - express.static(path.join(__dirname, "../node_modules/vue/dist")) -); -app.use( - "/libs/axios", - express.static(path.join(__dirname, "../node_modules/axios/dist")) -); app.use("/", indexRouter); app.use("/mon-compte", monCompteRouter); diff --git a/views/index.ejs b/views/index.ejs index 2386a83..a310984 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -16,9 +16,8 @@ - - - + + <% if ( config.matomoUrl ) { %> diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index a7306ff..f32ea67 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,4 +1,4 @@ -
+

Ajouter un album

@@ -169,178 +169,4 @@
-
- - +
\ No newline at end of file diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index 9960983..3272d8b 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -1,4 +1,4 @@ -
+

Collection de <%= page.username %>

@@ -146,140 +146,5 @@
diff --git a/views/pages/mon-compte/index.ejs b/views/pages/mon-compte/index.ejs index d8f7f6a..af7b634 100644 --- a/views/pages/mon-compte/index.ejs +++ b/views/pages/mon-compte/index.ejs @@ -1,4 +1,4 @@ -
+

Mon compte

@@ -72,28 +72,6 @@
diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 41c0b24..475ee79 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,7 +1,7 @@ -
+

- {{item.artists_sort}} - {{item.title}} + {{item.artists_sort}} - {{item.title}}

@@ -120,14 +120,13 @@ - +
@@ -170,164 +169,12 @@
- +
\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/exporter.ejs b/views/pages/mon-compte/ma-collection/exporter.ejs index 13e3f98..28cc559 100644 --- a/views/pages/mon-compte/ma-collection/exporter.ejs +++ b/views/pages/mon-compte/ma-collection/exporter.ejs @@ -1,4 +1,4 @@ -
+

Exporter ma collection

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 : @@ -44,25 +44,4 @@ Exporter -

- - \ No newline at end of file +
\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index fb67cd3..38993c8 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -1,4 +1,4 @@ -
+

Ma collection @@ -196,185 +196,5 @@

+ const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>; + \ No newline at end of file diff --git a/views/pages/nous-contacter.ejs b/views/pages/nous-contacter.ejs index 276b6f4..f179135 100644 --- a/views/pages/nous-contacter.ejs +++ b/views/pages/nous-contacter.ejs @@ -1,4 +1,4 @@ -
+

Nous contacter

id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
@@ -34,47 +34,6 @@
-<% if (config.mailMethod === 'smtp' ) { %> - -<% } %> \ No newline at end of file + \ No newline at end of file -- 2.39.5 From a56db99a8160cc52ec46e548df34699dd3647eb9 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Wed, 2 Nov 2022 09:48:05 +0100 Subject: [PATCH 18/65] #69 - Partager ma collection --- javascripts/mon-compte/ma-collection/index.js | 2 +- views/pages/mon-compte/ma-collection/index.ejs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 967e3f2..1cfb05e 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -20,7 +20,7 @@ if (typeof isPublicCollection !== "undefined") { itemId: null, showModalDelete: false, showModalShare: false, - shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + shareLink: `${protocol}//${host}/collection/${userId}`, // eslint-disable-next-line no-undef isPublicCollection, }; diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 38993c8..5dffd9f 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -197,4 +197,5 @@ \ No newline at end of file -- 2.39.5 From 9fe49eca270e9d46221d038c301693f58108aba9 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Wed, 2 Nov 2022 09:56:59 +0100 Subject: [PATCH 19/65] Version 1.4.1 (#70) - #69 Partager ma collection Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/70 --- javascripts/mon-compte/ma-collection/index.js | 2 +- views/pages/mon-compte/ma-collection/index.ejs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 967e3f2..1cfb05e 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -20,7 +20,7 @@ if (typeof isPublicCollection !== "undefined") { itemId: null, showModalDelete: false, showModalShare: false, - shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + shareLink: `${protocol}//${host}/collection/${userId}`, // eslint-disable-next-line no-undef isPublicCollection, }; diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 38993c8..5dffd9f 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -197,4 +197,5 @@ \ No newline at end of file -- 2.39.5 From d446735450e3f15cb9b175cfdfa1c65a904e1779 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 16:24:54 +0100 Subject: [PATCH 20/65] Utilisation de NPX --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b3dffed..b5607af 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,13 @@ "scripts": { "start": "node ./dist/bin/www", "run:all": "npm-run-all build sass uglify start", - "watch": "nodemon -e js,scss", + "watch": "npx nodemon -e js,scss", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", "uglify": "npx gulp", "prebuild": "rimraf dist", - "build": "babel ./src --out-dir dist --copy-files", + "build": "npx babel ./src --out-dir dist --copy-files", "test": "jest", - "prepare": "husky install" + "prepare": "npx husky install" }, "engines": { "node": "16.x", @@ -78,7 +78,7 @@ "vue": "^3.2.31" }, "nodemonConfig": { - "exec": "yarn run:all", + "exec": "npm run run:all", "watch": [ "src/*", "sass/*", -- 2.39.5 From dff1d2baf0d057e5542da66014b614052c268908 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 16:37:13 +0100 Subject: [PATCH 21/65] =?UTF-8?q?#75=20-=20Num=C3=A9rotation=20de=20la=20t?= =?UTF-8?q?racklist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- views/pages/mon-compte/ma-collection/details.ejs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 475ee79..c868db0 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -20,16 +20,16 @@
-- 2.39.5 From 8822056c1f40f6d3e96b4c2f7a37aa996a3e2890 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 16:54:58 +0100 Subject: [PATCH 22/65] =?UTF-8?q?#76=20-=20Avoir=20plus=20de=20d=C3=A9tail?= =?UTF-8?q?s=20sur=20le=20support=20physique=20sur=20la=20modale=20d'ajout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mon-compte/ma-collection/details.js | 12 ++++++-- views/pages/ajouter-un-album.ejs | 28 +++++++++++++++---- .../mon-compte/ma-collection/details.ejs | 5 ++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js index fe8d079..5486244 100644 --- a/javascripts/mon-compte/ma-collection/details.js +++ b/javascripts/mon-compte/ma-collection/details.js @@ -38,14 +38,21 @@ if (typeof item !== "undefined") { } }, setTrackList() { + this.tracklist = []; 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]; + const { + type_, + title, + position, + duration, + artists, + extraartists, + } = this.item.tracklist[i]; if (type_ === "heading") { if (subTrack.type) { @@ -65,6 +72,7 @@ if (typeof item !== "undefined") { position, duration, extraartists, + artists, }); } } diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index f32ea67..2141963 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,7 +1,7 @@

Ajouter un album

-
+
@@ -87,7 +87,14 @@
    -
  1. {{ track.title }} ({{track.duration}})
  2. +
  3. + {{ track.title }} ({{track.duration}}) +
      +
    • + {{extra.role}} : {{extra.name}} +
    • +
    +
@@ -129,10 +136,19 @@
Format -
- - {{format.name}} - +
    +
  • + {{format.name}} + + +
  • +

diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index c868db0..dc56b3a 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -23,6 +23,11 @@
  • {{track.position || (index+1)}} - {{ track.title }} +
      +
    • + {{extra.name}} +
    • +
    • {{extra.role}} : {{extra.name}} -- 2.39.5 From fe3ed3e91f68d9de7be1c57b44ff0a93ec6fd58f Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 17:08:41 +0100 Subject: [PATCH 23/65] #73 - Savoir sur quelle page on est --- javascripts/mon-compte/ma-collection/index.js | 6 +++++- views/pages/mon-compte/ma-collection/index.ejs | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 1cfb05e..a2d93c5 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -6,7 +6,8 @@ if (typeof isPublicCollection !== "undefined") { moreFilters: false, items: [], total: 0, - page: 1, + // eslint-disable-next-line no-undef + page: query.page || 1, totalPages: 1, limit: 16, artist: "", @@ -20,9 +21,12 @@ if (typeof isPublicCollection !== "undefined") { itemId: null, showModalDelete: false, showModalShare: false, + // eslint-disable-next-line no-undef shareLink: `${protocol}//${host}/collection/${userId}`, // eslint-disable-next-line no-undef isPublicCollection, + // eslint-disable-next-line no-undef + query, }; }, created() { diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 5dffd9f..c061d9a 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -135,12 +135,12 @@