Compare commits
63 Commits
Author | SHA1 | Date |
---|---|---|
|
1931bd9eda | |
|
81c61a0529 | |
|
205474a701 | |
|
e28f382c6c | |
|
4ea7b42d52 | |
|
fd0a9df724 | |
|
2f988798df | |
|
6862afda5c | |
|
4109186a47 | |
|
e0f227af08 | |
|
fbeb1a67c5 | |
|
2389d7d731 | |
|
9fe49eca27 | |
|
1d59ee3b71 | |
|
d03394bee7 | |
|
4da4dd9423 | |
|
6454f5f8d6 | |
|
bc3bb3b554 | |
|
da08aa0222 | |
|
2da6afa06d | |
|
b8b3df2932 | |
|
adea857666 | |
|
6320764743 | |
|
d473899b20 | |
|
827dcb9ccc | |
|
1377b4c0c1 | |
|
080471eb37 | |
|
befdfa35a6 | |
|
cc25b83b2e | |
|
fe3bdafb63 | |
|
a3c03a1569 | |
|
06752ebcec | |
|
12ca71e643 | |
|
9dd7a35f22 | |
|
f5196edfb8 | |
|
e8f91288fc | |
|
48c997ae10 | |
|
6d0405d129 | |
|
226a9ef1d1 | |
|
36b33124bc | |
|
0d90e0da20 | |
|
62a3bcd8ee | |
|
ae4b7b6de0 | |
|
182aa7a6a6 | |
|
7d7ee080ed | |
|
5b9d6c94b8 | |
|
7578d9b63f | |
|
9e7743e16d | |
|
e0a8fa42c2 | |
|
b0e6964205 | |
|
b27cbd467b | |
|
b05bed9a00 | |
|
3aeb172dbf | |
|
4068df3cba | |
|
9a088b9b5f | |
|
4886e9cc48 | |
|
73716335e2 | |
|
aeb5df067c | |
|
ac72c1c13c | |
|
95ab6f5f42 | |
|
0b8b4b21bd | |
|
30d0713f79 | |
|
1251ca1e02 |
73
.eslintrc.js
73
.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", "access_token", "api_url", "media_ids"],
|
||||
},
|
||||
],
|
||||
},
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -121,6 +121,6 @@ dist
|
|||
dist
|
||||
yarn.lock
|
||||
public/css
|
||||
public/css
|
||||
public/js
|
||||
docker-compose.yml
|
||||
dump
|
||||
|
|
53
README.md
53
README.md
|
@ -18,7 +18,9 @@ Vous pouvez, si vous le souhaitez héberger l'application sur votre propre serve
|
|||
|
||||
### Prérequis
|
||||
|
||||
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone. Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle.
|
||||
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone.
|
||||
|
||||
Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle ou configurer le SMTP tel que défini dans la section [variables d'environnements](#env-file).
|
||||
|
||||
Pour la méthode docker il ne vous faudra rien de plus que `docker` et `docker-compose`.
|
||||
|
||||
|
@ -62,7 +64,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
|
||||
|
@ -90,7 +92,7 @@ C'est terminé !
|
|||
|
||||
Le site est accessible sur [http://localhost:3001](http://localhost:3001).
|
||||
|
||||
:information_source: Information : Vous pouvez, et vous dreviez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur.
|
||||
:information_source: Information : Vous pouvez, et vous devriez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur.
|
||||
|
||||
### Aller plus loin
|
||||
|
||||
|
@ -184,24 +186,59 @@ 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 :
|
||||
|
||||
```
|
||||
NODE_ENV # Environnement dans lequel exécuter le projet (development ou production)
|
||||
PORT # Port sur lequel éxécuter le serveur (par défaut 3001)
|
||||
MONGODB_URI # Url du serveur mongo (par défaut mongodb://musictopus-db/musictopus)
|
||||
SECRET # Hash utilisé pour pour sauvegardé les dessions (par défaut waemaeMe5ahc6ce1chaeKohKa6Io8Eik)
|
||||
PORT # Port sur lequel éxécuter le serveur (3001 par défaut)
|
||||
MONGODB_URI # Url du serveur mongo (mongodb://musictopus-db/musictopus par défaut)
|
||||
SECRET # Hash utilisé pour pour sauvegardé les dessions (waemaeMe5ahc6ce1chaeKohKa6Io8Eik par défault)
|
||||
DISCOGS_TOKEN # Token Discogs (vous devez créer un compte sur discogs afin d'en obtenir un gratuitement)
|
||||
FORMSPREE_ID # Id du formulaire formspree pour la page "nous-contacter"
|
||||
MATOMO_URL # Url vers l'instance matomo (exemple: https://analytics.darkou.fr/)
|
||||
MATOMO_ID # Id du site sur votre instance matomo (exemple: 1)
|
||||
SITE_NAME # Nom du site (utilisé dans le titre des pages)
|
||||
SITE_NAME # Nom du site utilisé dans le titre des pages (MusicTopus par défaut)
|
||||
AWS_ACCESS_KEY_ID # Clé d'accès AWS
|
||||
AWS_SECRET_ACCESS_KEY # Clé secrète AWS
|
||||
S3_ENDPOINT # Url de l'instance aws (s3.fr-par.scw.cloud par défaut)
|
||||
S3_SIGNATURE # Version de la signature AWS (s3v4 par défaut)
|
||||
S3_BASEFOLDER # Nom du sous dossier dans lequel seront mis les pochettes des albums (dev par défaut)
|
||||
S3_BUCKET # Nom du bucket (musictopus par défaut, à changer impérativement si vous voulez que cela fonctionne)
|
||||
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (musictopus par défaut)
|
||||
JOBS_HEADER_VALUE # Valeur de la clé (ooYee9xok7eigo2shiePohyoGh1eepew par défaut)
|
||||
REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut)
|
||||
MAIL_METHOD # permet de définir la façon dont les mails de la page contact sont envoyés (formspree ou smtp)
|
||||
MAIL_HOST # Adresse du server mail (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||
MAIL_PORT # Port d'écoute du serveur smtp (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||
MAIL_USER # Adresse mail du compte permettant d'envoyer les mails (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||
MAIL_PASSWORD # Mot de passe du compte email (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||
MAIL_TO # Adresse mail du contact qui recevra les messages de la page "nous contacter" (dams le cas ou MAIL_METHOD est défini sur smtp)
|
||||
|
||||
```
|
||||
|
||||
## Contributeurs
|
||||
|
||||
- Damien Broqua (développeur principal du projet)
|
||||
- Brunus (Logo et fournisseur d'idées :wink: )
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
|||
services:
|
||||
musictopus-www:
|
||||
container_name: musictopus-www
|
||||
image: "node:16"
|
||||
image: "node:18"
|
||||
restart: always
|
||||
user: "node"
|
||||
working_dir: /home/node/app
|
||||
|
@ -28,6 +28,21 @@ 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}
|
||||
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
|
||||
MAIL_METHOD: ${MAIL_METHOD}
|
||||
MAIL_HOST: ${MAIL_HOST}
|
||||
MAIL_PORT: ${MAIL_PORT}
|
||||
MAIL_USER: ${MAIL_USER}
|
||||
MAIL_PASSWORD: ${MAIL_PASSWORD}
|
||||
MAIL_TO: ${MAIL_TO}
|
||||
networks:
|
||||
- musictopus
|
||||
musictopus-db:
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
|||
services:
|
||||
musictopus-www:
|
||||
container_name: musictopus-www
|
||||
image: "node:16"
|
||||
image: "node:18"
|
||||
restart: always
|
||||
user: "node"
|
||||
working_dir: /home/node/app
|
||||
|
@ -28,6 +28,15 @@ 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}
|
||||
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
|
||||
networks:
|
||||
- musictopus
|
||||
musictopus-db:
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
// ----------------------------------------------------------------------------
|
|
@ -0,0 +1,188 @@
|
|||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
q: "",
|
||||
year: "",
|
||||
country: "",
|
||||
format: "",
|
||||
loading: false,
|
||||
items: [],
|
||||
details: {},
|
||||
modalIsVisible: false,
|
||||
submitting: 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,
|
||||
inCollection,
|
||||
} = results[i];
|
||||
items.push({
|
||||
id,
|
||||
title,
|
||||
thumb,
|
||||
year,
|
||||
country,
|
||||
format,
|
||||
genre,
|
||||
style,
|
||||
inCollection,
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
if (this.submitting) {
|
||||
return true;
|
||||
}
|
||||
this.submitting = true;
|
||||
|
||||
return axios
|
||||
.post("/api/v1/albums", this.details)
|
||||
.then(() => {
|
||||
window.location.href = "/ma-collection";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.submitting = false;
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible d'ajouter cet album pour le moment…"
|
||||
);
|
||||
});
|
||||
},
|
||||
orderedItems(items) {
|
||||
return items.sort();
|
||||
},
|
||||
},
|
||||
}).mount("#ajouter-album");
|
|
@ -0,0 +1,236 @@
|
|||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
moreFilters: false,
|
||||
items: [],
|
||||
total: 0,
|
||||
// eslint-disable-next-line no-undef
|
||||
page: query.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,
|
||||
// eslint-disable-next-line no-undef
|
||||
shareLink: `/collection/${userId}`,
|
||||
// eslint-disable-next-line no-undef
|
||||
isPublicCollection,
|
||||
// eslint-disable-next-line no-undef
|
||||
query,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
formatParams(param) {
|
||||
return param.replace("&", "%26").replace("+", "%2B");
|
||||
},
|
||||
fetch() {
|
||||
this.loading = true;
|
||||
this.total = 0;
|
||||
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const entries = urlParams.entries();
|
||||
|
||||
const sortOrder = {
|
||||
sort: "artists_sort",
|
||||
order: "asc",
|
||||
};
|
||||
|
||||
// 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:
|
||||
if (["order", "sort"].indexOf(key) !== -1) {
|
||||
sortOrder[key] = value;
|
||||
}
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.sortOrder = `${sortOrder.sort}-${sortOrder.order}`;
|
||||
|
||||
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||
if (this.artist) {
|
||||
url += `&artist=${this.formatParams(this.artist)}`;
|
||||
}
|
||||
if (this.format) {
|
||||
url += `&format=${this.formatParams(this.format)}`;
|
||||
}
|
||||
if (this.year) {
|
||||
url += `&year=${this.year}`;
|
||||
}
|
||||
if (this.genre) {
|
||||
url += `&genre=${this.formatParams(this.genre)}`;
|
||||
}
|
||||
if (this.style) {
|
||||
url += `&style=${this.formatParams(this.style)}`;
|
||||
}
|
||||
|
||||
axios
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
this.items = response.data.rows;
|
||||
this.total = response.data.count || 0;
|
||||
this.totalPages =
|
||||
parseInt(response.data.count / this.limit, 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.formatParams(this.artist)}`;
|
||||
}
|
||||
if (this.format) {
|
||||
url += `&format=${this.formatParams(this.format)}`;
|
||||
}
|
||||
if (this.year) {
|
||||
url += `&year=${this.year}`;
|
||||
}
|
||||
if (this.genre) {
|
||||
url += `&genre=${this.formatParams(this.genre)}`;
|
||||
}
|
||||
if (this.style) {
|
||||
url += `&style=${this.formatParams(this.style)}`;
|
||||
}
|
||||
|
||||
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() {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (vueType !== "private") {
|
||||
return false;
|
||||
}
|
||||
return 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() {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (vueType !== "private") {
|
||||
return false;
|
||||
}
|
||||
return 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();
|
||||
});
|
||||
},
|
||||
renderAlbumTitle(item) {
|
||||
let render = "";
|
||||
|
||||
for (let i = 0; i < item.artists.length; i += 1) {
|
||||
const { name, join } = item.artists[i];
|
||||
render += `${name} ${join ? `${join} ` : ""}`;
|
||||
}
|
||||
|
||||
render += `- ${item.title}`;
|
||||
|
||||
return render;
|
||||
},
|
||||
},
|
||||
}).mount("#collection");
|
|
@ -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");
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
const { protocol, host } = window.location;
|
||||
|
||||
let timeout = null;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
x.classList.remove("show");
|
||||
}
|
||||
|
||||
x.classList.remove("success");
|
||||
x.classList.remove("error");
|
||||
if (success) {
|
||||
x.classList.add("success");
|
||||
} else {
|
||||
x.classList.add("error");
|
||||
}
|
||||
|
||||
x.classList.add("show");
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
x.classList.remove("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 = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
|
||||
if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
// eslint-disable-next-line no-undef
|
||||
email,
|
||||
// eslint-disable-next-line no-undef
|
||||
username,
|
||||
oldPassword: "",
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
// eslint-disable-next-line no-undef
|
||||
mastodon: mastodon || {
|
||||
publish: false,
|
||||
url: "",
|
||||
token: "",
|
||||
message:
|
||||
"Je viens d'ajouter {artist} - {album} à ma collection !",
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async testMastodon() {
|
||||
const { url, token } = this.formData.mastodon;
|
||||
|
||||
if (!url) {
|
||||
this.errors.push("emptyUrl");
|
||||
}
|
||||
if (!token) {
|
||||
this.errors.push("emptyToken");
|
||||
}
|
||||
|
||||
if (this.errors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(`/api/v1/mastodon`, { url, token });
|
||||
|
||||
showToastr("Configuration valide !", true);
|
||||
} catch (err) {
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible de tester cette configuration",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async updateProfil() {
|
||||
this.errors = [];
|
||||
const { oldPassword, password, passwordConfirm, mastodon } =
|
||||
this.formData;
|
||||
|
||||
if (password && !oldPassword) {
|
||||
this.errors.push("emptyPassword");
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
this.errors.push("passwordsDiffer");
|
||||
}
|
||||
|
||||
if (this.errors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const data = {
|
||||
mastodon,
|
||||
};
|
||||
|
||||
if (password) {
|
||||
data.password = password;
|
||||
data.oldPassword = oldPassword;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.patch(`/api/v1/me`, data);
|
||||
|
||||
showToastr("Profil mis à jour", true);
|
||||
} catch (err) {
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible de mettre à jour votre profil"
|
||||
);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}).mount("#mon-compte");
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
if (typeof item !== "undefined") {
|
||||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
// eslint-disable-next-line no-undef
|
||||
item,
|
||||
// eslint-disable-next-line no-undef
|
||||
canShareItem,
|
||||
tracklist: [],
|
||||
identifiers: [],
|
||||
modalIsVisible: false,
|
||||
identifiersMode: "preview",
|
||||
identifiersPreviewLength: 16,
|
||||
preview: null,
|
||||
index: null,
|
||||
showModalDelete: false,
|
||||
showModalShare: false,
|
||||
shareMessage: "",
|
||||
shareMessageTransformed: "",
|
||||
shareMessageLength: 0,
|
||||
shareSubmiting: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.setTrackList();
|
||||
this.setIdentifiers();
|
||||
|
||||
window.addEventListener("keydown", this.changeImage);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener("keydown", this.changeImage);
|
||||
},
|
||||
watch: {
|
||||
shareMessage(message) {
|
||||
const video =
|
||||
this.item.videos && this.item.videos.length > 0
|
||||
? this.item.videos[0].uri
|
||||
: "";
|
||||
|
||||
this.shareMessageTransformed = message
|
||||
.replaceAll("{artist}", this.item.artists[0].name)
|
||||
.replaceAll("{format}", this.item.formats[0].name)
|
||||
.replaceAll("{year}", this.item.year)
|
||||
.replaceAll("{video}", video)
|
||||
.replaceAll("{album}", this.item.title);
|
||||
|
||||
this.shareMessageLength = this.shareMessageTransformed.replace(
|
||||
video,
|
||||
new Array(36).join("#")
|
||||
).length;
|
||||
},
|
||||
},
|
||||
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() {
|
||||
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,
|
||||
artists,
|
||||
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,
|
||||
artists,
|
||||
});
|
||||
}
|
||||
}
|
||||
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 "";
|
||||
},
|
||||
shareAlbum() {
|
||||
if (this.shareSubmiting) {
|
||||
return false;
|
||||
}
|
||||
this.shareSubmiting = true;
|
||||
axios
|
||||
.post(`/api/v1/albums/${this.item._id}/share`, {
|
||||
message: this.shareMessageTransformed,
|
||||
})
|
||||
.then(() => {
|
||||
showToastr("Album partagé", true);
|
||||
this.shareMessage = "";
|
||||
this.showModalShare = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible de partager cet album",
|
||||
false
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.shareSubmiting = false;
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}).mount("#ma-collection-details");
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
format: "xml",
|
||||
};
|
||||
},
|
||||
created() {},
|
||||