develop #91

Merged
dbroqua merged 25 commits from develop into master 2023-08-02 16:11:57 +02:00
32 changed files with 530 additions and 337 deletions

View file

@ -22,7 +22,7 @@ module.exports = {
camelcase: [ camelcase: [
"error", "error",
{ {
allow: ["artists_sort"], allow: ["artists_sort", "access_token", "api_url", "media_ids"],
}, },
], ],
}, },

View file

@ -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

View file

@ -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

View file

@ -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…"

View file

@ -21,7 +21,7 @@ Vue.createApp({
showModalDelete: false, showModalDelete: false,
showModalShare: false, showModalShare: false,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
shareLink: `${protocol}//${host}/collection/${userId}`, shareLink: `/collection/${userId}`,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
isPublicCollection, isPublicCollection,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -67,7 +67,7 @@ Vue.createApp({
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if (this.artist) { if (this.artist) {
url += `&artists_sort=${this.formatParams(this.artist)}`; url += `&artist=${this.formatParams(this.artist)}`;
} }
if (this.format) { if (this.format) {
url += `&format=${this.formatParams(this.format)}`; url += `&format=${this.formatParams(this.format)}`;
@ -218,5 +218,20 @@ Vue.createApp({
this.toggleModalShare(); 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"); }).mount("#collection");

View file

@ -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 ()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 ()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";
}
}); });

View file

@ -2,6 +2,7 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
Vue.createApp({ Vue.createApp({
data() { data() {
return { return {
formData: {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
email, email,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -9,20 +10,92 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
oldPassword: "", oldPassword: "",
password: "", password: "",
passwordConfirm: "", 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
View 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
);

View file

@ -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",

View file

@ -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);

View file

@ -1,4 +1,6 @@
.error { main {
&.error {
min-height: calc(100vh - 3.25rem - 100px); min-height: calc(100vh - 3.25rem - 100px);
padding-top: 4rem; padding-top: 4rem;
}
} }

View file

@ -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

View file

@ -24,9 +24,6 @@
} }
} }
label {
font-weight: 800;
}
input, input,
textarea, textarea,
select { select {

View file

@ -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;

View file

@ -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';

View file

@ -42,7 +42,6 @@
} }
.title { .title {
font-weight: 800;
font-size: 1.4rem; font-size: 1.4rem;
} }

9
sass/messages.scss Normal file
View file

@ -0,0 +1,9 @@
.message {
margin: 8px 0;
padding: 0;
font-size: 0.8rem;
&.error {
color: $error-color-hl;
}
}

View file

@ -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() {}

View file

@ -3,15 +3,18 @@
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;
border-radius: 6px;
&.error {
background-color: $danger-color; background-color: $danger-color;
color: $button-alternate-color; color: $button-alternate-color;
border-radius: 6px; }
&.success { &.success {
background-color: $success-color; background-color: $success-color;

View file

@ -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);

View file

@ -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,
}; };
try {
const User = await UsersModel.findOne({ _id: user._id });
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); const job = new JobsModel(jobData);
job.save(); job.save();
} catch (err) {
throw new ErrorEvent(
500,
"Mastodon",
"Album ajouté à votre collection mais impossible de publier sur Mastodon"
);
}
return album; return album;
} }
@ -80,7 +158,7 @@ class Albums extends Pages {
exportFormat = "json", exportFormat = "json",
sort = "artists_sort", sort = "artists_sort",
order = "asc", order = "asc",
artists_sort, artist,
format, format,
year, year,
genre, genre,
@ -92,8 +170,8 @@ class Albums extends Pages {
const where = {}; const where = {};
if (artists_sort) { if (artist) {
where.artists_sort = artists_sort; where["artists.name"] = artist;
} }
if (format) { if (format) {
where["formats.name"] = format; where["formats.name"] = format;
@ -236,7 +314,7 @@ class Albums extends Pages {
*/ */
async loadMyCollection() { async loadMyCollection() {
const artists = await Albums.getAllDistincts( const artists = await Albums.getAllDistincts(
"artists_sort", "artists.name",
this.req.user._id this.req.user._id
); );
const formats = await Albums.getAllDistincts( const formats = await Albums.getAllDistincts(
@ -301,7 +379,7 @@ class Albums extends Pages {
); );
} }
const artists = await Albums.getAllDistincts("artists_sort", userId); const artists = await Albums.getAllDistincts("artists.name", userId);
const formats = await Albums.getAllDistincts("formats.name", userId); const formats = await Albums.getAllDistincts("formats.name", userId);
const years = await Albums.getAllDistincts("year", userId); const years = await Albums.getAllDistincts("year", userId);
const genres = await Albums.getAllDistincts("genres", userId); const genres = await Albums.getAllDistincts("genres", userId);

View file

@ -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");
} }
} }

View file

@ -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,

View 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;

View file

@ -8,9 +8,7 @@ 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("/")
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
try { try {
const page = new Me(req, "mon-compte/index"); const page = new Me(req, "mon-compte/index");
@ -20,16 +18,6 @@ router
} 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;

View file

@ -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>

View file

@ -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>

View file

@ -11,9 +11,11 @@
} %> } %>
</h1> </h1>
<% if ( pageType === 'private' ) { %> <% if ( pageType === 'private' ) { %>
<div>
<a :href="shareLink" v-if="isPublicCollection" target="_blank"> <a :href="shareLink" v-if="isPublicCollection" target="_blank">
<i class="icon-share"></i> Voir ma collection partagée <i class="icon-share"></i> Voir ma collection partagée
</a> </a>
</div>
<% } %> <% } %>
<%- include('../components/filters/index') %> <%- include('../components/filters/index') %>
@ -28,7 +30,7 @@
<div class="item" v-if="!loading" v-for="item in items"> <div class="item" v-if="!loading" v-for="item in items">
<span class="title"> <span class="title">
<% if ( pageType === 'private' ) { %> <% if ( pageType === 'private' ) { %>
<a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a> <a :href="'/ma-collection/' + item._id">{{ renderAlbumTitle(item) }}</a>
<i class="icon-trash" @click="showConfirmDelete(item._id)"></i> <i class="icon-trash" @click="showConfirmDelete(item._id)"></i>
<% } else { %> <% } else { %>
{{ item.artists_sort}} - {{ item.title }} {{ item.artists_sort}} - {{ item.title }}

View file

@ -497,9 +497,6 @@
&lt;/div&gt; &lt;/div&gt;
&lt;div class="navbar-item"&gt; &lt;div class="navbar-item"&gt;
&lt;div class="buttons"&gt; &lt;div class="buttons"&gt;
&lt;button type="button" class="button is-primary" id="switchAriaTheme" aria-label="Renforcer la visibilité de ce site" title="Renforcer la visibilité de ce site"&gt;
&lt;i class="icon-eye"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;a class="button is-danger" href="/se-deconnecter"&gt; &lt;a class="button is-danger" href="/se-deconnecter"&gt;
Déconnexion Déconnexion
&lt;/a&gt; &lt;/a&gt;

View file

@ -3,9 +3,11 @@
Mon compte Mon compte
</h1> </h1>
<form method="POST" @submit.prevent="updateProfil">
<div class="grid grid-cols-1 md:grid-cols-2 gap-10"> <div class="grid grid-cols-1 md:grid-cols-2 gap-10">
<form method="POST" action="/mon-compte" @submit="updateProfil"> <div>
<h2>Mes données personnelles</h2>
<div>
<div class="field"> <div class="field">
<label for="email">Adresse e-mail</label> <label for="email">Adresse e-mail</label>
<input <input
@ -14,7 +16,7 @@
disabled disabled
name="email" name="email"
id="email" id="email"
v-model="email" v-model="formData.email"
/> />
</div> </div>
<div class="field"> <div class="field">
@ -25,7 +27,7 @@
disabled disabled
name="username" name="username"
id="username" id="username"
v-model="username" v-model="formData.username"
/> />
</div> </div>
<div class="field"> <div class="field">
@ -34,21 +36,21 @@
type="password" type="password"
name="oldPassword" name="oldPassword"
id="oldPassword" id="oldPassword"
required
placeholder="Saisisssez votre mot de passe actuel" placeholder="Saisisssez votre mot de passe actuel"
v-model="oldPassword" 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>
<div></div>
<div class="field"> <div class="field">
<label for="password">Nouveau mot de passe</label> <label for="password">Nouveau mot de passe</label>
<input <input
type="password" type="password"
name="password" name="password"
id="password" id="password"
required
placeholder="Saisisssez votre nouveau mot de passe" placeholder="Saisisssez votre nouveau mot de passe"
v-model="password" v-model="formData.password"
/> />
</div> </div>
<div class="field"> <div class="field">
@ -57,21 +59,86 @@
type="password" type="password"
name="passwordConfirm" name="passwordConfirm"
id="passwordConfirm" id="passwordConfirm"
required
placeholder="Confirmez votre nouveau mot de passe" placeholder="Confirmez votre nouveau mot de passe"
v-model="passwordConfirm" 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>
<h2>Mon activité</h2>
<div>
<div class="field">
<label for="mastodon.publish">Publier sur le fédiverse lorsque j'ajoute un album</label>
<select id="format" v-model="formData.mastodon.publish">
<option value="true">Oui</option>
<option value="false">Non</option>
</select>
<!-- <input
type="checkbox"
name="mastodon.publish"
id="mastodon.publish"
v-model="mastodon.publish"
/> -->
</div>
<div class="field">
<label for="mastodon.url">Url de l'API de votre instance</label>
<input
type="text"
name="mastodon.url"
id="mastodon.url"
v-model="formData.mastodon.url"
placeholder="https://mastodon.social/api/v1/"
/>
</div>
<div class="field">
<label for="mastodon.token">Jeton d'accès (droits nécessaires : write:media et write:statuses)</label>
<input
type="text"
name="mastodon.token"
id="mastodon.token"
v-model="formData.mastodon.token"
/>
</div>
<div class="field">
<label for="mastodon.message">Message</label>
<input
type="text"
name="mastodon.message"
id="mastodon.message"
v-model="formData.mastodon.message"
/>
<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>

View file

@ -1,7 +1,11 @@
<main class="layout-maxed ma-collection-details" id="ma-collection-details" v-cloak @keyup="changeImage"> <main class="layout-maxed ma-collection-details" id="ma-collection-details" v-cloak @keyup="changeImage">
<h1> <h1>
<a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artists_sort=${item.artists_sort}`">{{item.artists_sort}}</a> - {{item.title}} <template v-for="artist in item.artists">
<a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artist=${artist.name}`">{{artist.name}}</a>
<template v-if="artist.join">&nbsp;{{artist.join}}&nbsp;</template>
</template>
- {{item.title}}
<i class="icon-trash" title="Supprimer cette fiche" @click="showConfirmDelete()"></i> <i class="icon-trash" title="Supprimer cette fiche" @click="showConfirmDelete()"></i>
<i class="icon-refresh" title="Mettre à jour les données de cette fiche" @click="updateItem()"></i> <i class="icon-refresh" title="Mettre à jour les données de cette fiche" @click="updateItem()"></i>
</h1> </h1>