develop (#91)
Reviewed-on: #91 Co-authored-by: dbroqua <contact@darkou.fr> Co-committed-by: dbroqua <contact@darkou.fr>
This commit is contained in:
parent
e0f227af08
commit
4109186a47
29 changed files with 497 additions and 325 deletions
|
@ -22,7 +22,7 @@ module.exports = {
|
||||||
camelcase: [
|
camelcase: [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
allow: ["artists_sort"],
|
allow: ["artists_sort", "access_token", "api_url", "media_ids"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
||||||
services:
|
services:
|
||||||
musictopus-www:
|
musictopus-www:
|
||||||
container_name: musictopus-www
|
container_name: musictopus-www
|
||||||
image: "node:16"
|
image: "node:18"
|
||||||
restart: always
|
restart: always
|
||||||
user: "node"
|
user: "node"
|
||||||
working_dir: /home/node/app
|
working_dir: /home/node/app
|
||||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
||||||
services:
|
services:
|
||||||
musictopus-www:
|
musictopus-www:
|
||||||
container_name: musictopus-www
|
container_name: musictopus-www
|
||||||
image: "node:16"
|
image: "node:18"
|
||||||
restart: always
|
restart: always
|
||||||
user: "node"
|
user: "node"
|
||||||
working_dir: /home/node/app
|
working_dir: /home/node/app
|
||||||
|
|
|
@ -9,6 +9,7 @@ Vue.createApp({
|
||||||
items: [],
|
items: [],
|
||||||
details: {},
|
details: {},
|
||||||
modalIsVisible: false,
|
modalIsVisible: false,
|
||||||
|
submitting: false,
|
||||||
formats: [
|
formats: [
|
||||||
"Vinyl",
|
"Vinyl",
|
||||||
"Acetate",
|
"Acetate",
|
||||||
|
@ -160,12 +161,18 @@ Vue.createApp({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
add() {
|
add() {
|
||||||
axios
|
if (this.submitting) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.submitting = true;
|
||||||
|
|
||||||
|
return axios
|
||||||
.post("/api/v1/albums", this.details)
|
.post("/api/v1/albums", this.details)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.href = "/ma-collection";
|
window.location.href = "/ma-collection";
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
this.submitting = false;
|
||||||
showToastr(
|
showToastr(
|
||||||
err.response?.data?.message ||
|
err.response?.data?.message ||
|
||||||
"Impossible d'ajouter cet album pour le moment…"
|
"Impossible d'ajouter cet album pour le moment…"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
const { protocol, host } = window.location;
|
const { protocol, host } = window.location;
|
||||||
|
|
||||||
|
let timeout = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction permettant d'afficher un message dans un toastr
|
* Fonction permettant d'afficher un message dans un toastr
|
||||||
* @param {String} message
|
* @param {String} message
|
||||||
|
@ -11,12 +13,23 @@ function showToastr(message, success = false) {
|
||||||
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
x.className = `${x.className} show`.replace("sucess", "");
|
if (timeout) {
|
||||||
if (success) {
|
clearTimeout(timeout);
|
||||||
x.className = `${x.className} success`;
|
x.classList.remove("show");
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
|
||||||
x.className = x.className.replace("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);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,80 +43,6 @@ function hideToastr() {
|
||||||
x.getElementsByTagName("SPAN")[0].innerHTML = "";
|
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
|
* Ensemble d'actions effectuées au chargement de la page
|
||||||
*/
|
*/
|
||||||
|
@ -123,29 +62,4 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,27 +2,100 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// eslint-disable-next-line no-undef
|
formData: {
|
||||||
email,
|
// eslint-disable-next-line no-undef
|
||||||
// eslint-disable-next-line no-undef
|
email,
|
||||||
username,
|
// eslint-disable-next-line no-undef
|
||||||
oldPassword: "",
|
username,
|
||||||
password: "",
|
oldPassword: "",
|
||||||
passwordConfirm: "",
|
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,
|
loading: false,
|
||||||
|
errors: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
async updateProfil(event) {
|
async testMastodon() {
|
||||||
// try {
|
const { url, token } = this.formData.mastodon;
|
||||||
// if ( this.password !== this.passwordConfirm ) {
|
|
||||||
// throw "La confirnation du mot de passe ne correspond pas";
|
if (!url) {
|
||||||
// }
|
this.errors.push("emptyUrl");
|
||||||
// } catch(err) {
|
}
|
||||||
// event.preventDefault();
|
if (!token) {
|
||||||
// showToastr(err);
|
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");
|
}).mount("#mon-compte");
|
||||||
|
|
71
javascripts/theme.js
Normal file
71
javascripts/theme.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Fonction permettant de sauvegarder dans le stockage local le choix du thème
|
||||||
|
* @param {String} scheme
|
||||||
|
*/
|
||||||
|
function saveColorScheme(scheme) {
|
||||||
|
localStorage.setItem("theme", scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de changer le thème du site
|
||||||
|
* @param {String} scheme
|
||||||
|
*/
|
||||||
|
function setColorScheme(scheme) {
|
||||||
|
document.documentElement.setAttribute("data-theme", scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de récupérer le thème du système
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
function getPreferredColorScheme() {
|
||||||
|
if (window.matchMedia) {
|
||||||
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
|
return "dark";
|
||||||
|
}
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On place un event sur le bouton
|
||||||
|
const toggleSwitch = document.querySelector(
|
||||||
|
'.theme-switch input[type="checkbox"]'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event permettant de détecter les changements de thème du système
|
||||||
|
*/
|
||||||
|
if (window.matchMedia) {
|
||||||
|
const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
colorSchemeQuery.addEventListener("change", () => {
|
||||||
|
const selectedColorScheme = localStorage.getItem("theme") || "system";
|
||||||
|
|
||||||
|
if (selectedColorScheme === "system") {
|
||||||
|
const preferedColorScheme = getPreferredColorScheme();
|
||||||
|
setColorScheme(preferedColorScheme);
|
||||||
|
|
||||||
|
toggleSwitch.checked = preferedColorScheme === "dark";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTheme = localStorage.getItem("theme") || getPreferredColorScheme();
|
||||||
|
|
||||||
|
// INFO: Au chargement de la page on détecte le thème à charger
|
||||||
|
setColorScheme(currentTheme);
|
||||||
|
|
||||||
|
toggleSwitch.checked = currentTheme === "dark";
|
||||||
|
|
||||||
|
toggleSwitch.addEventListener(
|
||||||
|
"change",
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const scheme = e.target.checked ? "dark" : "light";
|
||||||
|
|
||||||
|
saveColorScheme(scheme);
|
||||||
|
setColorScheme(scheme);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
|
@ -14,7 +14,7 @@
|
||||||
"prepare": "npx husky install"
|
"prepare": "npx husky install"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.x",
|
"node": "16.x || 18.x",
|
||||||
"yarn": "1.x"
|
"yarn": "1.x"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -63,6 +63,7 @@
|
||||||
"gulp-uglify": "^3.0.2",
|
"gulp-uglify": "^3.0.2",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.6.0",
|
||||||
"knacss": "^8.0.4",
|
"knacss": "^8.0.4",
|
||||||
|
"mastodon": "^1.2.2",
|
||||||
"mongoose": "^6.2.1",
|
"mongoose": "^6.2.1",
|
||||||
"mongoose-unique-validator": "^3.0.0",
|
"mongoose-unique-validator": "^3.0.0",
|
||||||
"nodemailer": "^6.7.8",
|
"nodemailer": "^6.7.8",
|
||||||
|
|
|
@ -22,10 +22,12 @@ $nord15: #b48ead;
|
||||||
|
|
||||||
$primary-color: $nord8;
|
$primary-color: $nord8;
|
||||||
$danger-color: $nord11;
|
$danger-color: $nord11;
|
||||||
|
$error-color: $nord12;
|
||||||
$warning-color: $nord13;
|
$warning-color: $nord13;
|
||||||
$success-color: $nord14;
|
$success-color: $nord14;
|
||||||
$primary-color-hl: darken($primary-color, $hoverAmount);
|
$primary-color-hl: darken($primary-color, $hoverAmount);
|
||||||
$danger-color-hl: darken($danger-color, $hoverAmount);
|
$danger-color-hl: darken($danger-color, $hoverAmount);
|
||||||
|
$error-color-hl: darken($error-color, $hoverAmount);
|
||||||
$warning-color-hl: darken($warning-color, $hoverAmount);
|
$warning-color-hl: darken($warning-color, $hoverAmount);
|
||||||
$success-color-hl: darken($success-color, $hoverAmount);
|
$success-color-hl: darken($success-color, $hoverAmount);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
.error {
|
main {
|
||||||
min-height: calc(100vh - 3.25rem - 100px);
|
&.error {
|
||||||
padding-top: 4rem;
|
min-height: calc(100vh - 3.25rem - 100px);
|
||||||
|
padding-top: 4rem;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,10 +8,6 @@
|
||||||
width: calc(100% - 6rem);
|
width: calc(100% - 6rem);
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
|
|
||||||
.header {
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.info {
|
&.info {
|
||||||
background-color: $warning-color;
|
background-color: $warning-color;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -24,9 +24,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -7,19 +7,10 @@ html {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 3.5rem;
|
padding-top: 3.5rem;
|
||||||
font-family: 'open_sansregular';
|
font-family: 'lucioleregular';
|
||||||
font-weight: 400;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
@include transition() {}
|
@include transition();
|
||||||
|
|
||||||
&.is-accessible {
|
|
||||||
font-family: 'lucioleregular';
|
|
||||||
|
|
||||||
.text-justify {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer.footer {
|
footer.footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
@import './loader';
|
@import './loader';
|
||||||
|
|
||||||
@import './error';
|
@import './error';
|
||||||
|
@import './messages.scss';
|
||||||
@import './500';
|
@import './500';
|
||||||
@import './home';
|
@import './home';
|
||||||
@import './ajouter-un-album';
|
@import './ajouter-un-album';
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: 800;
|
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
sass/messages.scss
Normal file
9
sass/messages.scss
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.message {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: $error-color-hl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,6 @@
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.125;
|
line-height: 1.125;
|
||||||
margin-left: .5rem !important;
|
margin-left: .5rem !important;
|
||||||
@include transition() {}
|
@include transition() {}
|
||||||
|
|
|
@ -3,16 +3,19 @@
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 31;
|
z-index: 66;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
|
||||||
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
|
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
|
||||||
background-color: $danger-color;
|
|
||||||
color: $button-alternate-color;
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
background-color: $danger-color;
|
||||||
|
color: $button-alternate-color;
|
||||||
|
}
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
background-color: $success-color;
|
background-color: $success-color;
|
||||||
color: $button-font-color;
|
color: $button-font-color;
|
||||||
|
|
|
@ -22,11 +22,13 @@ import importJobsRouter from "./routes/jobs";
|
||||||
|
|
||||||
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
||||||
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
||||||
|
import importMastodonRouterApiV1 from "./routes/api/v1/mastodon";
|
||||||
import importMeRouterApiV1 from "./routes/api/v1/me";
|
import importMeRouterApiV1 from "./routes/api/v1/me";
|
||||||
import importContactRouterApiV1 from "./routes/api/v1/contact";
|
import importContactRouterApiV1 from "./routes/api/v1/contact";
|
||||||
|
|
||||||
passportConfig(passport);
|
passportConfig(passport);
|
||||||
|
|
||||||
|
mongoose.set("strictQuery", false);
|
||||||
mongoose
|
mongoose
|
||||||
.connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
.connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@ -83,6 +85,7 @@ app.use("/collection", collectionRouter);
|
||||||
app.use("/jobs", importJobsRouter);
|
app.use("/jobs", importJobsRouter);
|
||||||
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
||||||
app.use("/api/v1/search", importSearchRouterApiV1);
|
app.use("/api/v1/search", importSearchRouterApiV1);
|
||||||
|
app.use("/api/v1/mastodon", importMastodonRouterApiV1);
|
||||||
app.use("/api/v1/me", importMeRouterApiV1);
|
app.use("/api/v1/me", importMeRouterApiV1);
|
||||||
app.use("/api/v1/contact", importContactRouterApiV1);
|
app.use("/api/v1/contact", importContactRouterApiV1);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { format as formatDate } from "date-fns";
|
import { format as formatDate } from "date-fns";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import Mastodon from "mastodon";
|
||||||
|
import { v4 } from "uuid";
|
||||||
|
import axios from "axios";
|
||||||
import Pages from "./Pages";
|
import Pages from "./Pages";
|
||||||
import Export from "./Export";
|
import Export from "./Export";
|
||||||
|
|
||||||
|
@ -40,9 +44,83 @@ class Albums extends Pages {
|
||||||
id: album._id,
|
id: album._id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const job = new JobsModel(jobData);
|
try {
|
||||||
|
const User = await UsersModel.findOne({ _id: user._id });
|
||||||
|
|
||||||
job.save();
|
const { mastodon: mastodonConfig } = User;
|
||||||
|
|
||||||
|
const { publish, token, url, message } = mastodonConfig;
|
||||||
|
|
||||||
|
if (publish && url && token) {
|
||||||
|
const M = new Mastodon({
|
||||||
|
access_token: token,
|
||||||
|
api_url: url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const video =
|
||||||
|
data.videos && data.videos.length > 0
|
||||||
|
? data.videso[0].uri
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const status = `${(
|
||||||
|
message ||
|
||||||
|
"Je viens d'ajouter {artist} - {album} à ma collection !"
|
||||||
|
)
|
||||||
|
.replaceAll("{artist}", data.artists[0].name)
|
||||||
|
.replaceAll("{format}", data.formats[0].name)
|
||||||
|
.replaceAll("{year}", data.year)
|
||||||
|
.replaceAll("{video}", video)
|
||||||
|
.replaceAll("{album}", data.title)}
|
||||||
|
|
||||||
|
Publié automatiquement via #musictopus`;
|
||||||
|
|
||||||
|
const media_ids = [];
|
||||||
|
|
||||||
|
if (data.images.length > 0) {
|
||||||
|
for (let i = 0; i < data.images.length; i += 1) {
|
||||||
|
if (media_ids.length === 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = `${v4()}.jpg`;
|
||||||
|
const file = `/tmp/${filename}`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: buff } = await axios.get(
|
||||||
|
data.images[i].uri,
|
||||||
|
{
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(file, buff);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: media } = await M.post("media", {
|
||||||
|
file: fs.createReadStream(file),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { id } = media;
|
||||||
|
|
||||||
|
media_ids.push(id);
|
||||||
|
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await M.post("statuses", { status, media_ids });
|
||||||
|
}
|
||||||
|
|
||||||
|
const job = new JobsModel(jobData);
|
||||||
|
|
||||||
|
job.save();
|
||||||
|
} catch (err) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
500,
|
||||||
|
"Mastodon",
|
||||||
|
"Album ajouté à votre collection mais impossible de publier sur Mastodon"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return album;
|
return album;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,21 +12,41 @@ class Me extends Pages {
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
async patchMe() {
|
async patchMe() {
|
||||||
const { body, user } = this.req;
|
const { body } = this.req;
|
||||||
|
const { _id } = this.req.user;
|
||||||
|
|
||||||
const schema = Joi.object({
|
const schema = Joi.object({
|
||||||
isPublicCollection: Joi.boolean(),
|
isPublicCollection: Joi.boolean(),
|
||||||
|
oldPassword: Joi.string(),
|
||||||
|
password: Joi.string(),
|
||||||
|
passwordConfirm: Joi.ref("password"),
|
||||||
|
mastodon: {
|
||||||
|
publish: Joi.boolean(),
|
||||||
|
url: Joi.string().uri().allow(null, ""),
|
||||||
|
token: Joi.string().allow(null, ""),
|
||||||
|
message: Joi.string().allow(null, ""),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const value = await schema.validateAsync(body);
|
const value = await schema.validateAsync(body);
|
||||||
const update = await UsersModel.findByIdAndUpdate(
|
const user = await UsersModel.findById(_id);
|
||||||
user._id,
|
|
||||||
{ $set: value },
|
if (value.oldPassword) {
|
||||||
{ new: true }
|
if (!user.validPassword(value.oldPassword)) {
|
||||||
);
|
throw new Error("Votre ancien mot de passe n'est pas valide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.mastodon = value.mastodon;
|
||||||
|
|
||||||
|
if (value.password) {
|
||||||
|
user.salt = value.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.save();
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
this.req.login(update, (err) => {
|
this.req.login(user, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
@ -35,34 +55,7 @@ class Me extends Pages {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return update;
|
return user;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Méthode permettant de modifier le mot de passe d'un utilisateur
|
|
||||||
*/
|
|
||||||
async updatePassword() {
|
|
||||||
const { body } = this.req;
|
|
||||||
const { _id } = this.req.user;
|
|
||||||
|
|
||||||
const schema = Joi.object({
|
|
||||||
oldPassword: Joi.string().required(),
|
|
||||||
password: Joi.string().required(),
|
|
||||||
passwordConfirm: Joi.ref("password"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const value = await schema.validateAsync(body);
|
|
||||||
const user = await UsersModel.findById(_id);
|
|
||||||
|
|
||||||
if (!user.validPassword(value.oldPassword)) {
|
|
||||||
throw new Error("Votre ancien mot de passe n'est pas valide");
|
|
||||||
}
|
|
||||||
|
|
||||||
user.salt = value.password;
|
|
||||||
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
this.req.flash("success", "Profil correctement mis à jour");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,12 @@ const UserSchema = new mongoose.Schema(
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
mastodon: {
|
||||||
|
publish: Boolean,
|
||||||
|
token: String,
|
||||||
|
url: String,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|
28
src/routes/api/v1/mastodon.js
Normal file
28
src/routes/api/v1/mastodon.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import express from "express";
|
||||||
|
import { ensureLoggedIn } from "connect-ensure-login";
|
||||||
|
|
||||||
|
import Mastodon from "mastodon";
|
||||||
|
import { sendResponse } from "../../../libs/format";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.route("/").post(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { url, token } = req.body;
|
||||||
|
|
||||||
|
const M = new Mastodon({
|
||||||
|
access_token: token,
|
||||||
|
api_url: url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await M.post("statuses", {
|
||||||
|
status: "Test d'intégration de Mastodon sur mon compte #musictopus 👌",
|
||||||
|
});
|
||||||
|
return sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -8,28 +8,16 @@ import render from "../libs/format";
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router
|
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
.route("/")
|
try {
|
||||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
const page = new Me(req, "mon-compte/index");
|
||||||
try {
|
|
||||||
const page = new Me(req, "mon-compte/index");
|
|
||||||
|
|
||||||
page.setPageTitle("Mon compte");
|
page.setPageTitle("Mon compte");
|
||||||
|
|
||||||
render(res, page);
|
render(res, page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.post(ensureLoggedIn("/connexion"), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const page = new Me(req, "mon-compte/index");
|
|
||||||
|
|
||||||
await page.updatePassword();
|
|
||||||
} catch (err) {
|
|
||||||
req.flash("error", err.toString());
|
|
||||||
}
|
|
||||||
res.redirect("/mon-compte");
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -105,9 +105,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="button" class="button is-primary" id="switchAriaTheme" aria-label="Renforcer la visibilité de ce site" title="Renforcer la visibilité de ce site">
|
|
||||||
<i class="icon-eye"></i>
|
|
||||||
</button>
|
|
||||||
<% if ( !user ) { %>
|
<% if ( !user ) { %>
|
||||||
<a class="button is-primary" href="/connexion">
|
<a class="button is-primary" href="/connexion">
|
||||||
<strong>Connexion</strong>
|
<strong>Connexion</strong>
|
||||||
|
|
|
@ -180,7 +180,7 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<button class="button is-primary" @click="add">Ajouter</button>
|
<button :class="['button is-primary', submitting ? 'is-disabled' : '']" @click="add">Ajouter</button>
|
||||||
<button class="button" @click="toggleModal">Annuler</button>
|
<button class="button" @click="toggleModal">Annuler</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -497,9 +497,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="button" class="button is-primary" id="switchAriaTheme" aria-label="Renforcer la visibilité de ce site" title="Renforcer la visibilité de ce site">
|
|
||||||
<i class="icon-eye"></i>
|
|
||||||
</button>
|
|
||||||
<a class="button is-danger" href="/se-deconnecter">
|
<a class="button is-danger" href="/se-deconnecter">
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -3,75 +3,142 @@
|
||||||
Mon compte
|
Mon compte
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
<form method="POST" @submit.prevent="updateProfil">
|
||||||
<form method="POST" action="/mon-compte" @submit="updateProfil">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
|
<div>
|
||||||
<div class="field">
|
<h2>Mes données personnelles</h2>
|
||||||
<label for="email">Adresse e-mail</label>
|
<div>
|
||||||
<input
|
<div class="field">
|
||||||
type="email"
|
<label for="email">Adresse e-mail</label>
|
||||||
readonly
|
<input
|
||||||
disabled
|
type="email"
|
||||||
name="email"
|
readonly
|
||||||
id="email"
|
disabled
|
||||||
v-model="email"
|
name="email"
|
||||||
/>
|
id="email"
|
||||||
|
v-model="formData.email"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="username">Nom d'utilisateur</label>
|
||||||
|
<input
|
||||||
|
type="string"
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="username"
|
||||||
|
id="username"
|
||||||
|
v-model="formData.username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="oldPassword">Mot de passe actuel</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="oldPassword"
|
||||||
|
id="oldPassword"
|
||||||
|
placeholder="Saisisssez votre mot de passe actuel"
|
||||||
|
v-model="formData.oldPassword"
|
||||||
|
/>
|
||||||
|
<div class="message error" v-if="errors.includes('emptyPassword')">
|
||||||
|
Pour changer votre mot de passe vous devez saisir votre mot de passe actuel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="password">Nouveau mot de passe</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="Saisisssez votre nouveau mot de passe"
|
||||||
|
v-model="formData.password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="passwordConfirm">Nouveau mot de passe (confirmation)</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="passwordConfirm"
|
||||||
|
id="passwordConfirm"
|
||||||
|
placeholder="Confirmez votre nouveau mot de passe"
|
||||||
|
v-model="formData.passwordConfirm"
|
||||||
|
/>
|
||||||
|
<div class="message error" v-if="errors.includes('passwordsDiffer')">
|
||||||
|
La confirmation ne correspond pas avec votre nouveau mot de passe
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div>
|
||||||
<label for="username">Nom d'utilisateur</label>
|
<h2>Mon activité</h2>
|
||||||
<input
|
<div>
|
||||||
type="string"
|
<div class="field">
|
||||||
readonly
|
<label for="mastodon.publish">Publier sur le fédiverse lorsque j'ajoute un album</label>
|
||||||
disabled
|
<select id="format" v-model="formData.mastodon.publish">
|
||||||
name="username"
|
<option value="true">Oui</option>
|
||||||
id="username"
|
<option value="false">Non</option>
|
||||||
v-model="username"
|
</select>
|
||||||
/>
|
<!-- <input
|
||||||
</div>
|
type="checkbox"
|
||||||
<div class="field">
|
name="mastodon.publish"
|
||||||
<label for="oldPassword">Mot de passe actuel</label>
|
id="mastodon.publish"
|
||||||
<input
|
v-model="mastodon.publish"
|
||||||
type="password"
|
/> -->
|
||||||
name="oldPassword"
|
</div>
|
||||||
id="oldPassword"
|
<div class="field">
|
||||||
required
|
<label for="mastodon.url">Url de l'API de votre instance</label>
|
||||||
placeholder="Saisisssez votre mot de passe actuel"
|
<input
|
||||||
v-model="oldPassword"
|
type="text"
|
||||||
/>
|
name="mastodon.url"
|
||||||
</div>
|
id="mastodon.url"
|
||||||
<div></div>
|
v-model="formData.mastodon.url"
|
||||||
<div class="field">
|
placeholder="https://mastodon.social/api/v1/"
|
||||||
<label for="password">Nouveau mot de passe</label>
|
/>
|
||||||
<input
|
</div>
|
||||||
type="password"
|
<div class="field">
|
||||||
name="password"
|
<label for="mastodon.token">Jeton d'accès (droits nécessaires : write:media et write:statuses)</label>
|
||||||
id="password"
|
<input
|
||||||
required
|
type="text"
|
||||||
placeholder="Saisisssez votre nouveau mot de passe"
|
name="mastodon.token"
|
||||||
v-model="password"
|
id="mastodon.token"
|
||||||
/>
|
v-model="formData.mastodon.token"
|
||||||
</div>
|
/>
|
||||||
<div class="field">
|
</div>
|
||||||
<label for="passwordConfirm">Nouveau mot de passe (confirmation)</label>
|
<div class="field">
|
||||||
<input
|
<label for="mastodon.message">Message</label>
|
||||||
type="password"
|
<input
|
||||||
name="passwordConfirm"
|
type="text"
|
||||||
id="passwordConfirm"
|
name="mastodon.message"
|
||||||
required
|
id="mastodon.message"
|
||||||
placeholder="Confirmez votre nouveau mot de passe"
|
v-model="formData.mastodon.message"
|
||||||
v-model="passwordConfirm"
|
/>
|
||||||
/>
|
<small>
|
||||||
|
Variables possibles :
|
||||||
|
<ul>
|
||||||
|
<li>{artist}, exemple : Iron Maiden</li>
|
||||||
|
<li>{album}, exemple : Powerslave</li>
|
||||||
|
<li>{format}, exemple : Cassette</li>
|
||||||
|
<li>{year}, exemple: 1984</li>
|
||||||
|
<li>{video}, exemple : https://www.youtube.com/watch?v=Qx0s8OqgBIw</li>
|
||||||
|
</ul>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="button is-secondary mt-10" :disabled="loading" @click="testMastodon">
|
||||||
|
<span>Tester</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="button is-primary mt-10" :disabled="loading">
|
<button type="submit" class="button is-primary mt-10" :disabled="loading">
|
||||||
<span v-if="!loading">Mettre à jour</span>
|
<span v-if="!loading">Mettre à jour</span>
|
||||||
<i class="icon-spin animate-spin" v-if="loading"></i>
|
<i class="icon-spin animate-spin" v-if="loading"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const email = '<%= user.email %>';
|
const email = '<%= user.email %>';
|
||||||
const username = '<%= user.username %>';
|
const username = '<%= user.username %>';
|
||||||
|
const mastodon = <%- JSON.stringify(user.mastodon) %>;
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue