Merge branch 'develop'
This commit is contained in:
commit
06752ebcec
26 changed files with 533 additions and 90 deletions
21
README.md
21
README.md
|
@ -210,22 +210,23 @@ 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 pour scaleway france par exemple)
|
||||
S3_SIGNATURE # Version de la signature AWS (s3v4 pour scaleway par exemple)
|
||||
S3_BASEFOLDER # Nom du sous dossier dans lequel seront mis les pochettes des albums
|
||||
S3_BUCKET # Nom du bucket
|
||||
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (par exemple musictopus)
|
||||
JOBS_HEADER_VALUE # Valeur de la clé
|
||||
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)
|
||||
```
|
||||
|
||||
## Contributeurs
|
||||
|
|
|
@ -36,6 +36,7 @@ services:
|
|||
S3_SIGNATURE: ${S3_SIGNATURE}
|
||||
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
|
||||
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
|
||||
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
|
||||
networks:
|
||||
- musictopus
|
||||
musictopus-db:
|
||||
|
|
|
@ -36,6 +36,7 @@ services:
|
|||
S3_SIGNATURE: ${S3_SIGNATURE}
|
||||
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
|
||||
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
|
||||
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
|
||||
networks:
|
||||
- musictopus
|
||||
musictopus-db:
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
"connect-flash": "^0.1.1",
|
||||
"connect-mongo": "^4.6.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"date-fns": "^2.28.0",
|
||||
"date-fns-tz": "^1.3.3",
|
||||
"debug": "^4.3.3",
|
||||
"disconnect": "^1.2.2",
|
||||
"ejs": "^3.1.6",
|
||||
|
@ -55,8 +57,6 @@
|
|||
"express-session": "^1.17.2",
|
||||
"joi": "^17.6.0",
|
||||
"knacss": "^8.0.4",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"mongoose": "^6.2.1",
|
||||
"mongoose-unique-validator": "^3.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
|
0
public/robots.txt
Normal file
0
public/robots.txt
Normal file
|
@ -17,6 +17,7 @@
|
|||
}
|
||||
|
||||
@include respond-to("small-up") {
|
||||
width: 33%;
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
@ -28,6 +29,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.showMoreFilters {
|
||||
cursor: pointer;
|
||||
|
||||
.up::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.down::before {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
|
||||
.list{
|
||||
.title {
|
||||
.icon-trash {
|
||||
|
|
|
@ -11,4 +11,12 @@
|
|||
.header {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $success-color;
|
||||
}
|
||||
}
|
|
@ -132,6 +132,8 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--default-color);
|
||||
|
||||
.navbar-link {
|
||||
background-color: var(--default-hl-color);
|
||||
color: rgba(0,0,0,.7);
|
||||
|
@ -252,6 +254,13 @@
|
|||
padding-bottom: .5rem;
|
||||
padding-top: .5rem;
|
||||
|
||||
hr {
|
||||
background-color: var(--font-color);
|
||||
border: none;
|
||||
height: 2px;
|
||||
margin: .5rem 0;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
cursor: pointer;
|
||||
padding-left: 1.5rem;
|
||||
|
|
44
src/app.js
44
src/app.js
|
@ -15,6 +15,7 @@ import { isXhr } from "./helpers";
|
|||
|
||||
import indexRouter from "./routes";
|
||||
import maCollectionRouter from "./routes/ma-collection";
|
||||
import monCompteRouter from "./routes/mon-compte";
|
||||
import collectionRouter from "./routes/collection";
|
||||
|
||||
import importJobsRouter from "./routes/jobs";
|
||||
|
@ -83,6 +84,7 @@ app.use(
|
|||
);
|
||||
|
||||
app.use("/", indexRouter);
|
||||
app.use("/mon-compte", monCompteRouter);
|
||||
app.use("/ma-collection", maCollectionRouter);
|
||||
app.use("/collection", collectionRouter);
|
||||
app.use("/jobs", importJobsRouter);
|
||||
|
@ -97,15 +99,22 @@ app.use((req, res) => {
|
|||
} else {
|
||||
res.status(404).render("index", {
|
||||
page: { title: `404: Cette page n'existe pas.` },
|
||||
errorCode: 404,
|
||||
viewname: "error",
|
||||
user: req.user || null,
|
||||
config,
|
||||
session: req.session || null,
|
||||
flashInfo: null,
|
||||
query: null,
|
||||
params: null,
|
||||
error: null,
|
||||
flash: {
|
||||
info: req.flash("info"),
|
||||
error: [
|
||||
...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é !",
|
||||
error,
|
||||
},
|
||||
errorCode: error.errorCode || 500,
|
||||
viewname: "error",
|
||||
user: req.user || null,
|
||||
config,
|
||||
session: req.session || null,
|
||||
flashInfo: null,
|
||||
query: null,
|
||||
params: null,
|
||||
error: null,
|
||||
flash: {
|
||||
info: req.flash("info"),
|
||||
error: [
|
||||
...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();
|
||||
|
|
|
@ -17,4 +17,6 @@ module.exports = {
|
|||
jobsHeaderKey: process.env.JOBS_HEADER_KEY || "musictopus",
|
||||
jobsHeaderValue:
|
||||
process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew",
|
||||
registrationOpen:
|
||||
(process.env.REGISTRATION_OPEN || "true").toLowerCase() === "true",
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import moment from "moment";
|
||||
import { format as formatDate } from "date-fns";
|
||||
|
||||
import Pages from "./Pages";
|
||||
import Export from "./Export";
|
||||
|
@ -26,7 +26,7 @@ class Albums extends Pages {
|
|||
User: user._id,
|
||||
};
|
||||
data.released = data.released
|
||||
? moment(data.released.replace("-00", "-01"))
|
||||
? new Date(data.released.replace("-00", "-01"))
|
||||
: null;
|
||||
delete data.id;
|
||||
|
||||
|
@ -81,6 +81,9 @@ class Albums extends Pages {
|
|||
order = "asc",
|
||||
artists_sort,
|
||||
format,
|
||||
year,
|
||||
genre,
|
||||
style,
|
||||
userId: collectionUserId,
|
||||
} = this.req.query;
|
||||
|
||||
|
@ -94,11 +97,20 @@ class Albums extends Pages {
|
|||
if (format) {
|
||||
where["formats.name"] = format;
|
||||
}
|
||||
if (year) {
|
||||
where.year = year;
|
||||
}
|
||||
if (genre) {
|
||||
where.genres = genre;
|
||||
}
|
||||
if (style) {
|
||||
where.styles = style;
|
||||
}
|
||||
|
||||
if (!this.req.user && !collectionUserId) {
|
||||
throw new ErrorEvent(
|
||||
401,
|
||||
"Cette collection n'est pas publique",
|
||||
"Collection",
|
||||
"Cette collection n'est pas publique"
|
||||
);
|
||||
}
|
||||
|
@ -114,7 +126,7 @@ class Albums extends Pages {
|
|||
) {
|
||||
throw new ErrorEvent(
|
||||
401,
|
||||
"Cette collection n'est pas publique",
|
||||
"Collection",
|
||||
"Cette collection n'est pas publique"
|
||||
);
|
||||
}
|
||||
|
@ -199,9 +211,21 @@ class Albums extends Pages {
|
|||
"formats.name",
|
||||
this.req.user._id
|
||||
);
|
||||
const years = await Albums.getAllDistincts("year", this.req.user._id);
|
||||
const genres = await Albums.getAllDistincts(
|
||||
"genres",
|
||||
this.req.user._id
|
||||
);
|
||||
const styles = await Albums.getAllDistincts(
|
||||
"styles",
|
||||
this.req.user._id
|
||||
);
|
||||
|
||||
this.setPageContent("artists", artists);
|
||||
this.setPageContent("formats", formats);
|
||||
this.setPageContent("years", years);
|
||||
this.setPageContent("genres", genres);
|
||||
this.setPageContent("styles", styles);
|
||||
this.setPageTitle("Ma collection");
|
||||
}
|
||||
|
||||
|
@ -211,11 +235,16 @@ class Albums extends Pages {
|
|||
async loadItem() {
|
||||
const { itemId: _id } = this.req.params;
|
||||
const { _id: User } = this.req.user;
|
||||
const item = await AlbumsModel.findOne({
|
||||
const album = await AlbumsModel.findOne({
|
||||
_id,
|
||||
User,
|
||||
});
|
||||
|
||||
const item = {
|
||||
...album.toJSON(),
|
||||
released: formatDate(album.released, "MM/dd/yyyy"),
|
||||
};
|
||||
|
||||
this.setPageContent("item", item);
|
||||
this.setPageTitle(
|
||||
`Détails de l'album ${item.title} de ${item.artists_sort}`
|
||||
|
@ -233,17 +262,24 @@ class Albums extends Pages {
|
|||
if (!user || !user.isPublicCollection) {
|
||||
throw new ErrorEvent(
|
||||
401,
|
||||
"Collection non partagée",
|
||||
"Cet utilisateur ne souhaite pas partager sa collection"
|
||||
);
|
||||
}
|
||||
|
||||
const artists = await Albums.getAllDistincts("artists_sort", userId);
|
||||
const formats = await Albums.getAllDistincts("formats.name", userId);
|
||||
const years = await Albums.getAllDistincts("year", userId);
|
||||
const genres = await Albums.getAllDistincts("genres", userId);
|
||||
const styles = await Albums.getAllDistincts("styles", userId);
|
||||
|
||||
this.setPageContent("username", user.username);
|
||||
this.setPageTitle(`Collection publique de ${user.username}`);
|
||||
this.setPageContent("artists", artists);
|
||||
this.setPageContent("formats", formats);
|
||||
this.setPageContent("years", years);
|
||||
this.setPageContent("genres", genres);
|
||||
this.setPageContent("styles", styles);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import momenttz from "moment-timezone";
|
||||
import { utcToZonedTime } from "date-fns-tz";
|
||||
import setHours from "date-fns/setHours";
|
||||
import xl from "excel4node";
|
||||
|
||||
class Export {
|
||||
|
@ -132,7 +133,9 @@ class Export {
|
|||
}
|
||||
if (released) {
|
||||
ws.cell(currentRow, 7)
|
||||
.date(momenttz.tz(released, "Europe/Paris").hour(12))
|
||||
.date(
|
||||
setHours(utcToZonedTime(released, "Europe/Paris"), 12)
|
||||
)
|
||||
.style({ numberFormat: "dd/mm/yyyy" });
|
||||
}
|
||||
ws.cell(currentRow, 8).string(format).style(style);
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import Joi from "joi";
|
||||
|
||||
import UsersModel from "../models/users";
|
||||
import Pages from "./Pages";
|
||||
|
||||
/**
|
||||
* Classe permettant la gestion de l'utilisateur connecté
|
||||
*/
|
||||
class Me {
|
||||
constructor(req) {
|
||||
this.req = req;
|
||||
}
|
||||
|
||||
class Me extends Pages {
|
||||
/**
|
||||
* Méthode permettant de modifier le profil d'un utilisateur
|
||||
* @return {Object}
|
||||
|
@ -40,6 +37,33 @@ class Me {
|
|||
|
||||
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;
|
||||
|
|
|
@ -52,21 +52,20 @@ class Pages {
|
|||
*/
|
||||
render() {
|
||||
this.pageContent.session = this.req.session;
|
||||
this.pageContent.flashInfo = this.req.flash("info");
|
||||
this.pageContent.error = this.req.flash("error") || null;
|
||||
this.pageContent.flash = {
|
||||
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.params = this.req.params;
|
||||
this.pageContent.user = this.user;
|
||||
this.pageContent.config = config;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import Auth from "../middleware/Auth";
|
|||
|
||||
import render from "../libs/format";
|
||||
|
||||
import { registrationOpen } from "../config";
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const router = express.Router();
|
||||
|
||||
|
@ -59,11 +61,33 @@ router
|
|||
}
|
||||
);
|
||||
|
||||
router
|
||||
.route("/inscription")
|
||||
.get((req, res, next) => {
|
||||
if (registrationOpen) {
|
||||
router
|
||||
.route("/inscription")
|
||||
.get((req, res, next) => {
|
||||
try {
|
||||
const page = new Pages(req, "inscription/index");
|
||||
|
||||
page.setPageTitle("Inscription");
|
||||
|
||||
render(res, page);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
})
|
||||
.post(async (req, res) => {
|
||||
try {
|
||||
await Auth.register(req);
|
||||
|
||||
res.redirect("/");
|
||||
} catch (err) {
|
||||
res.redirect("/inscription");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
router.route("/inscription").get((req, res, next) => {
|
||||
try {
|
||||
const page = new Pages(req, "inscription");
|
||||
const page = new Pages(req, "inscription/desactivee");
|
||||
|
||||
page.setPageTitle("Inscription");
|
||||
|
||||
|
@ -71,16 +95,8 @@ router
|
|||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
})
|
||||
.post(async (req, res) => {
|
||||
try {
|
||||
await Auth.register(req);
|
||||
|
||||
res.redirect("/");
|
||||
} catch (err) {
|
||||
res.redirect("/inscription");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
router
|
||||
.route("/ajouter-un-album")
|
||||
|
|
35
src/routes/mon-compte.js
Normal file
35
src/routes/mon-compte.js
Normal 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;
|
|
@ -1,10 +1,8 @@
|
|||
<main class="layout-maxed error">
|
||||
<h1><%= page.title %></h1>
|
||||
<% if ( errorCode && errorCode === 404 ) { %>
|
||||
<p class="text-center">
|
||||
<img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" />
|
||||
</p>
|
||||
<% } %>
|
||||
<% if ( process.env.NODE_ENV !== 'production' ) { %>
|
||||
<div>
|
||||
<pre><%= page.error %></pre>
|
||||
|
|
|
@ -83,6 +83,10 @@
|
|||
</a>
|
||||
|
||||
<div class="navbar-dropdown">
|
||||
<a class="navbar-item" href="/mon-compte">
|
||||
Mon compte
|
||||
</a>
|
||||
<hr />
|
||||
<a class="navbar-item" href="/ma-collection">
|
||||
Ma collection
|
||||
</a>
|
||||
|
@ -125,40 +129,61 @@
|
|||
<span></span>
|
||||
</div>
|
||||
|
||||
<% if ( page.failureFlash || (error && error.length > 0 ) ) {%>
|
||||
<div class="flash">
|
||||
<% if ( page.failureFlash ) {%>
|
||||
<div class="header">
|
||||
Erreur
|
||||
<%
|
||||
if ( flash.error.length > 0 ) {
|
||||
for ( let i = 0 ; i < flash.error.length ; i += 1 ) {
|
||||
%>
|
||||
<div class="flash">
|
||||
<div class="header">
|
||||
Erreur
|
||||
</div>
|
||||
<div class="body">
|
||||
<%= flash.error[i].replace('Error: ', '') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<%= page.failureFlash %>
|
||||
<%
|
||||
}
|
||||
}
|
||||
if ( flash.info.length > 0 ) {
|
||||
for ( let i = 0 ; i < flash.info.length ; i += 1 ) {
|
||||
%>
|
||||
<div class="flash info">
|
||||
<div class="header">
|
||||
Information
|
||||
</div>
|
||||
<div class="body">
|
||||
<%= flash.info[i] %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<%
|
||||
if (error && error.length > 0) {
|
||||
for( let i = 0 ; i < error.length ; i += 1 ) {
|
||||
%>
|
||||
<div class="header">
|
||||
Erreur
|
||||
</div>
|
||||
<div class="body">
|
||||
<%= error %>
|
||||
</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>
|
||||
<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
|
||||
<%- include(viewname) %>
|
||||
|
||||
<footer class="footer layout-hero">
|
||||
<p>
|
||||
<strong title="Merci Brunus ! 😜">MusicTopus</strong> par <a href="https://www.darkou.fr" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>.
|
||||
Logo réalisé par Brunus avec <a href="https://inkscape.org/fr/" target="_blank" rel="noopener noreferrer">Inkscape <i class="icon-link"></i></a>.
|
||||
<br />
|
||||
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a> et disponible sur <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>.
|
||||
<br />
|
||||
Fait avec ❤️ à Bordeaux.
|
||||
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
|
|
@ -239,9 +239,9 @@
|
|||
window.location.href = '/ma-collection';
|
||||
})
|
||||
.catch((err) => {
|
||||
showToastr(err.response?.data?.message || "Impossible d'ajouter ce album pour le moment…");
|
||||
showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…");
|
||||
});
|
||||
},
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -40,6 +40,50 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filters" v-if="moreFilters">
|
||||
<div class="field">
|
||||
<label for="format">Année</label>
|
||||
<select id="format" v-model="year" @change="changeFilter">
|
||||
<option value="">Toutes</option>
|
||||
<%
|
||||
for (let i = 0; i < page.years.length; i += 1 ) {
|
||||
__append(`<option value="${page.years[i]}">${page.years[i]}</option>`);
|
||||
}
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="genre">Genre</label>
|
||||
<select id="genre" v-model="genre" @change="changeFilter">
|
||||
<option value="">Tous</option>
|
||||
<%
|
||||
for (let i = 0; i < page.genres.length; i += 1 ) {
|
||||
__append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`);
|
||||
}
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="style">Style</label>
|
||||
<select id="style" v-model="style" @change="changeFilter">
|
||||
<option value="">Tous</option>
|
||||
<%
|
||||
for (let i = 0; i < page.styles.length; i += 1 ) {
|
||||
__append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`);
|
||||
}
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span @click="showMoreFilters" class="showMoreFilters">
|
||||
<template v-if="!moreFilters">Voir plus de filtres</template>
|
||||
<template v-if="moreFilters">Voir moins de filtres</template>
|
||||
<i class="icon-left-open down" v-if="!moreFilters"></i>
|
||||
<i class="icon-left-open up" v-if="moreFilters"></i>
|
||||
</span>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
||||
<div class="item" v-if="!loading" v-for="item in items">
|
||||
<span class="title">
|
||||
|
@ -105,6 +149,7 @@
|
|||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
moreFilters: false,
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
|
@ -112,6 +157,9 @@
|
|||
limit: 16,
|
||||
artist: '',
|
||||
format: '',
|
||||
year: '',
|
||||
genre: '',
|
||||
style: '',
|
||||
sortOrder: 'artists_sort-asc',
|
||||
sort: 'artists_sort',
|
||||
order: 'asc',
|
||||
|
@ -132,6 +180,15 @@
|
|||
if ( this.format ) {
|
||||
url += `&format=${this.format}`;
|
||||
}
|
||||
if ( this.year ) {
|
||||
url += `&year=${this.year}`;
|
||||
}
|
||||
if ( this.genre ) {
|
||||
url += `&genre=${this.genre.replace('&', '%26')}`;
|
||||
}
|
||||
if ( this.style ) {
|
||||
url += `&style=${this.style.replace('&', '%26')}`;
|
||||
}
|
||||
|
||||
axios.get(url)
|
||||
.then( response => {
|
||||
|
@ -179,6 +236,9 @@
|
|||
|
||||
this.fetch();
|
||||
},
|
||||
showMoreFilters() {
|
||||
this.moreFilters = !this.moreFilters;
|
||||
}
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
|
|
|
@ -274,6 +274,22 @@
|
|||
Ceci est une erreur
|
||||
</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>
|
||||
<div class="flash">
|
||||
<div class="header">
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
<input type="password" name="password" id="password" placeholder="********">
|
||||
</div>
|
||||
|
||||
<% if ( config.registrationOpen === true ) { %>
|
||||
<div class="text-right mt-10">
|
||||
<p>Pas encore inscrit ? <a href="/inscription">Inscrivez-vous</a></p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<button type="submit" class="button is-primary">Connexion</button>
|
||||
</form>
|
||||
|
|
19
views/pages/inscription/desactivee.ejs
Normal file
19
views/pages/inscription/desactivee.ejs
Normal file
|
@ -0,0 +1,19 @@
|
|||
<main class="layout-maxed">
|
||||
<div class="header layout-hero"></div>
|
||||
<h1>
|
||||
Inscription
|
||||
</h1>
|
||||
<div class="container">
|
||||
<div class="text">
|
||||
<p class="text-justify">
|
||||
Les inscriptions sur ce site sont fermées.
|
||||
</p>
|
||||
<p class="text-justify">
|
||||
Vous avez cependant la possibilité d'héberger vous même une version de <a href="https://www.musictopus.fr" target="_blank">MusicTopus</a> en vous rendant directement sur le <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">dépot du projet</a>.
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" />
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
99
views/pages/mon-compte/index.ejs
Normal file
99
views/pages/mon-compte/index.ejs
Normal 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>
|
|
@ -6,6 +6,7 @@
|
|||
<a :href="shareLink" v-if="isPublicCollection" target="_blank">
|
||||
<i class="icon-share"></i> Voir ma collection partagée
|
||||
</a>
|
||||
|
||||
<div class="filters">
|
||||
<div class="field">
|
||||
<label for="artist">Artiste</label>
|
||||
|
@ -43,6 +44,50 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filters" v-if="moreFilters">
|
||||
<div class="field">
|
||||
<label for="format">Année</label>
|
||||
<select id="format" v-model="year" @change="changeFilter">
|
||||
<option value="">Toutes</option>
|
||||
<%
|
||||
for (let i = 0; i < page.years.length; i += 1 ) {
|
||||
__append(`<option value="${page.years[i]}">${page.years[i]}</option>`);
|
||||
}
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="genre">Genre</label>
|
||||
<select id="genre" v-model="genre" @change="changeFilter">
|
||||
<option value="">Tous</option>
|
||||
<%
|
||||
for (let i = 0; i < page.genres.length; i += 1 ) {
|
||||
__append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`);
|
||||
}
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="style">Style</label>
|
||||
<select id="style" v-model="style" @change="changeFilter">
|
||||
<option value="">Tous</option>
|
||||
<%
|
||||
for (let i = 0; i < page.styles.length; i += 1 ) {
|
||||
__append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`);
|
||||
}
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span @click="showMoreFilters" class="showMoreFilters">
|
||||
<template v-if="!moreFilters">Voir plus de filtres</template>
|
||||
<template v-if="moreFilters">Voir moins de filtres</template>
|
||||
<i class="icon-left-open down" v-if="!moreFilters"></i>
|
||||
<i class="icon-left-open up" v-if="moreFilters"></i>
|
||||
</span>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
||||
<div class="item" v-if="!loading" v-for="item in items">
|
||||
<span class="title">
|
||||
|
@ -154,6 +199,7 @@
|
|||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
moreFilters: false,
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
|
@ -161,6 +207,9 @@
|
|||
limit: 16,
|
||||
artist: '',
|
||||
format: '',
|
||||
year: '',
|
||||
genre: '',
|
||||
style: '',
|
||||
sortOrder: 'artists_sort-asc',
|
||||
sort: 'artists_sort',
|
||||
order: 'asc',
|
||||
|
@ -185,6 +234,15 @@
|
|||
if ( this.format ) {
|
||||
url += `&format=${this.format}`;
|
||||
}
|
||||
if ( this.year ) {
|
||||
url += `&year=${this.year}`;
|
||||
}
|
||||
if ( this.genre ) {
|
||||
url += `&genre=${this.genre.replace('&', '%26')}`;
|
||||
}
|
||||
if ( this.style ) {
|
||||
url += `&style=${this.style.replace('&', '%26')}`;
|
||||
}
|
||||
|
||||
axios.get(url)
|
||||
.then( response => {
|
||||
|
@ -232,6 +290,9 @@
|
|||
|
||||
this.fetch();
|
||||
},
|
||||
showMoreFilters() {
|
||||
this.moreFilters = !this.moreFilters;
|
||||
},
|
||||
toggleModal() {
|
||||
this.showModalDelete = !this.showModalDelete;
|
||||
},
|
||||
|
@ -273,7 +334,7 @@
|
|||
.finally(() => {
|
||||
this.toggleModalShare();
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue