#35 - Je peux accéder à mon compte #44

Merged
dbroqua merged 1 commit from issue/35 into develop 2022-04-10 15:21:15 +02:00
13 changed files with 284 additions and 57 deletions

0
public/robots.txt Normal file
View file

View file

@ -11,4 +11,12 @@
.header { .header {
font-weight: 800; font-weight: 800;
} }
&.info {
background-color: $warning-color;
}
&.success {
background-color: $success-color;
}
} }

View file

@ -132,6 +132,8 @@
} }
&:hover { &:hover {
background-color: var(--default-color);
.navbar-link { .navbar-link {
background-color: var(--default-hl-color); background-color: var(--default-hl-color);
color: rgba(0,0,0,.7); color: rgba(0,0,0,.7);
@ -252,6 +254,13 @@
padding-bottom: .5rem; padding-bottom: .5rem;
padding-top: .5rem; padding-top: .5rem;
hr {
background-color: var(--font-color);
border: none;
height: 2px;
margin: .5rem 0;
}
.navbar-item { .navbar-item {
cursor: pointer; cursor: pointer;
padding-left: 1.5rem; padding-left: 1.5rem;

View file

@ -15,6 +15,7 @@ import { isXhr } from "./helpers";
import indexRouter from "./routes"; import indexRouter from "./routes";
import maCollectionRouter from "./routes/ma-collection"; import maCollectionRouter from "./routes/ma-collection";
import monCompteRouter from "./routes/mon-compte";
import collectionRouter from "./routes/collection"; import collectionRouter from "./routes/collection";
import importJobsRouter from "./routes/jobs"; import importJobsRouter from "./routes/jobs";
@ -83,6 +84,7 @@ app.use(
); );
app.use("/", indexRouter); app.use("/", indexRouter);
app.use("/mon-compte", monCompteRouter);
app.use("/ma-collection", maCollectionRouter); app.use("/ma-collection", maCollectionRouter);
app.use("/collection", collectionRouter); app.use("/collection", collectionRouter);
app.use("/jobs", importJobsRouter); app.use("/jobs", importJobsRouter);
@ -97,15 +99,22 @@ app.use((req, res) => {
} else { } else {
res.status(404).render("index", { res.status(404).render("index", {
page: { title: `404: Cette page n'existe pas.` }, page: { title: `404: Cette page n'existe pas.` },
errorCode: 404,
viewname: "error", viewname: "error",
user: req.user || null,
config,
session: req.session || null, session: req.session || null,
flashInfo: null, flash: {
query: null, info: req.flash("info"),
params: null, error: [
error: null, ...req.flash("error"),
...(req.session?.flash?.error || []),
],
success: req.flash("success"),
},
query: req.query,
params: req.params,
user: req.user,
config,
getBaseUrl: null,
errorCode: 404,
}); });
} }
}); });
@ -122,15 +131,22 @@ app.use((error, req, res, next) => {
title: error.title || "500: Oups… le serveur a crashé !", title: error.title || "500: Oups… le serveur a crashé !",
error, error,
}, },
errorCode: error.errorCode || 500,
viewname: "error", viewname: "error",
user: req.user || null,
config,
session: req.session || null, session: req.session || null,
flashInfo: null, flash: {
query: null, info: req.flash("info"),
params: null, error: [
error: null, ...req.flash("error"),
...(req.session?.flash?.error || []),
],
success: req.flash("success"),
},
query: req.query,
params: req.params,
user: req.user,
config,
getBaseUrl: null,
errorCode: error.errorCode || 500,
}); });
next(); next();

View file

@ -98,7 +98,7 @@ class Albums extends Pages {
if (!this.req.user && !collectionUserId) { if (!this.req.user && !collectionUserId) {
throw new ErrorEvent( throw new ErrorEvent(
401, 401,
"Cette collection n'est pas publique", "Collection",
"Cette collection n'est pas publique" "Cette collection n'est pas publique"
); );
} }
@ -114,7 +114,7 @@ class Albums extends Pages {
) { ) {
throw new ErrorEvent( throw new ErrorEvent(
401, 401,
"Cette collection n'est pas publique", "Collection",
"Cette collection n'est pas publique" "Cette collection n'est pas publique"
); );
} }
@ -233,6 +233,7 @@ class Albums extends Pages {
if (!user || !user.isPublicCollection) { if (!user || !user.isPublicCollection) {
throw new ErrorEvent( throw new ErrorEvent(
401, 401,
"Collection non partagée",
"Cet utilisateur ne souhaite pas partager sa collection" "Cet utilisateur ne souhaite pas partager sa collection"
); );
} }

View file

@ -1,15 +1,12 @@
import Joi from "joi"; import Joi from "joi";
import UsersModel from "../models/users"; import UsersModel from "../models/users";
import Pages from "./Pages";
/** /**
* Classe permettant la gestion de l'utilisateur connecté * Classe permettant la gestion de l'utilisateur connecté
*/ */
class Me { class Me extends Pages {
constructor(req) {
this.req = req;
}
/** /**
* Méthode permettant de modifier le profil d'un utilisateur * Méthode permettant de modifier le profil d'un utilisateur
* @return {Object} * @return {Object}
@ -40,6 +37,33 @@ class Me {
return update; return update;
} }
/**
* Méthode permettant de modifier le mot de passe d'un utilisateur
*/
async updatePassword() {
const { body } = this.req;
const { _id } = this.req.user;
const schema = Joi.object({
oldPassword: Joi.string().required(),
password: Joi.string().required(),
passwordConfirm: Joi.ref("password"),
});
const value = await schema.validateAsync(body);
const user = await UsersModel.findById(_id);
if (!user.validPassword(value.oldPassword)) {
throw new Error("Votre ancien mot de passe n'est pas valide");
}
user.salt = value.password;
await user.save();
this.req.flash("success", "Profil correctement mis à jour");
}
} }
export default Me; export default Me;

View file

@ -52,21 +52,20 @@ class Pages {
*/ */
render() { render() {
this.pageContent.session = this.req.session; this.pageContent.session = this.req.session;
this.pageContent.flashInfo = this.req.flash("info"); this.pageContent.flash = {
this.pageContent.error = this.req.flash("error") || null; info: this.req.flash("info"),
error: [
...this.req.flash("error"),
...(this.req.session?.flash?.error || []),
],
success: this.req.flash("success"),
};
this.pageContent.query = this.req.query; this.pageContent.query = this.req.query;
this.pageContent.params = this.req.params; this.pageContent.params = this.req.params;
this.pageContent.user = this.user; this.pageContent.user = this.user;
this.pageContent.config = config; this.pageContent.config = config;
this.pageContent.getBaseUrl = getBaseUrl(this.req); this.pageContent.getBaseUrl = getBaseUrl(this.req);
if (this.req.session.flash && this.req.session.flash.error) {
// eslint-disable-next-line prefer-destructuring
this.pageContent.page.failureFlash =
this.req.session.flash.error[0];
this.req.session.flash = null;
}
return this.pageContent; return this.pageContent;
} }
} }

35
src/routes/mon-compte.js Normal file
View file

@ -0,0 +1,35 @@
import express from "express";
import { ensureLoggedIn } from "connect-ensure-login";
import Me from "../middleware/Me";
import render from "../libs/format";
// eslint-disable-next-line new-cap
const router = express.Router();
router
.route("/")
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
try {
const page = new Me(req, "mon-compte/index");
page.setPageTitle("Mon compte");
render(res, page);
} catch (err) {
next(err);
}
})
.post(ensureLoggedIn("/connexion"), async (req, res) => {
try {
const page = new Me(req, "mon-compte/index");
await page.updatePassword();
} catch (err) {
req.flash("error", err.toString());
}
res.redirect("/mon-compte");
});
export default router;

View file

@ -1,10 +1,8 @@
<main class="layout-maxed error"> <main class="layout-maxed error">
<h1><%= page.title %></h1> <h1><%= page.title %></h1>
<% if ( errorCode && errorCode === 404 ) { %>
<p class="text-center"> <p class="text-center">
<img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" /> <img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" />
</p> </p>
<% } %>
<% if ( process.env.NODE_ENV !== 'production' ) { %> <% if ( process.env.NODE_ENV !== 'production' ) { %>
<div> <div>
<pre><%= page.error %></pre> <pre><%= page.error %></pre>

View file

@ -83,6 +83,10 @@
</a> </a>
<div class="navbar-dropdown"> <div class="navbar-dropdown">
<a class="navbar-item" href="/mon-compte">
Mon compte
</a>
<hr />
<a class="navbar-item" href="/ma-collection"> <a class="navbar-item" href="/ma-collection">
Ma collection Ma collection
</a> </a>
@ -125,32 +129,50 @@
<span></span> <span></span>
</div> </div>
<% if ( page.failureFlash || (error && error.length > 0 ) ) {%> <%
if ( flash.error.length > 0 ) {
for ( let i = 0 ; i < flash.error.length ; i += 1 ) {
%>
<div class="flash"> <div class="flash">
<% if ( page.failureFlash ) {%>
<div class="header"> <div class="header">
Erreur Erreur
</div> </div>
<div class="body"> <div class="body">
<%= page.failureFlash %> <%= flash.error[i].replace('Error: ', '') %>
</div>
</div> </div>
<% } %>
<% <%
if (error && error.length > 0) { }
for( let i = 0 ; i < error.length ; i += 1 ) { }
if ( flash.info.length > 0 ) {
for ( let i = 0 ; i < flash.info.length ; i += 1 ) {
%> %>
<div class="flash info">
<div class="header"> <div class="header">
Erreur Information
</div> </div>
<div class="body"> <div class="body">
<%= error %> <%= flash.info[i] %>
</div>
</div>
<%
}
}
if ( flash.success.length > 0 ) {
for ( let i = 0 ; i < flash.success.length ; i += 1 ) {
%>
<div class="flash success">
<div class="header">
Succès
</div>
<div class="body">
<%= flash.success[i] %>
</div>
</div> </div>
<% <%
} }
} }
%> %>
</div>
<% } %>
<%- include(viewname) %> <%- include(viewname) %>

View file

@ -274,6 +274,22 @@
Ceci est une erreur Ceci est une erreur
</div> </div>
</div> </div>
<div class="flash info">
<div class="header">
Information
</div>
<div class="body">
Ceci est une information
</div>
</div>
<div class="flash success">
<div class="header">
Succès
</div>
<div class="body">
Ceci est un succès
</div>
</div>
<pre> <pre>
&lt;div class="flash"&gt; &lt;div class="flash"&gt;
&lt;div class="header"&gt; &lt;div class="header"&gt;

View file

@ -0,0 +1,99 @@
<main class="layout-maxed collection" id="app">
<h1>
Mon compte
</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
<form method="POST" action="/mon-compte" @submit="updateProfil">
<div class="field">
<label for="email">Adresse e-mail</label>
<input
type="email"
readonly
disabled
name="email"
id="email"
v-model="email"
/>
</div>
<div class="field">
<label for="username">Nom d'utilisateur</label>
<input
type="string"
readonly
disabled
name="username"
id="username"
v-model="username"
/>
</div>
<div class="field">
<label for="oldPassword">Mot de passe actuel</label>
<input
type="password"
name="oldPassword"
id="oldPassword"
required
placeholder="Saisisssez votre mot de passe actuel"
v-model="oldPassword"
/>
</div>
<div></div>
<div class="field">
<label for="password">Nouveau mot de passe</label>
<input
type="password"
name="password"
id="password"
required
placeholder="Saisisssez votre nouveau mot de passe"
v-model="password"
/>
</div>
<div class="field">
<label for="passwordConfirm">Nouveau mot de passe (confirmation)</label>
<input
type="password"
name="passwordConfirm"
id="passwordConfirm"
required
placeholder="Confirmez votre nouveau mot de passe"
v-model="passwordConfirm"
/>
</div>
<button type="submit" class="button is-primary mt-10" :disabled="loading">
<span v-if="!loading">Mettre à jour</span>
<i class="icon-spin animate-spin" v-if="loading"></i>
</button>
</form>
</div>
</main>
<script>
Vue.createApp({
data() {
return {
email: '<%= user.email %>',
username: '<%= user.username %>',
oldPassword: '',
password: '',
passwordConfirm: '',
loading: false,
}
},
methods: {
async updateProfil(event) {
// try {
// if ( this.password !== this.passwordConfirm ) {
// throw "La confirnation du mot de passe ne correspond pas";
// }
// } catch(err) {
// event.preventDefault();
// showToastr(err);
// }
},
}
}).mount('#app');
</script>