Version 1.0 #26
28 changed files with 630 additions and 66 deletions
13
package.json
13
package.json
|
@ -27,9 +27,6 @@
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.17.0",
|
|
||||||
"@babel/core": "^7.17.2",
|
|
||||||
"@babel/preset-env": "^7.16.11",
|
|
||||||
"eslint": "^8.9.0",
|
"eslint": "^8.9.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
@ -38,11 +35,12 @@
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"lint-staged": "^12.3.3",
|
"lint-staged": "^12.3.3",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"npm-run-all": "^4.1.5",
|
"prettier": "^2.5.1"
|
||||||
"prettier": "^2.5.1",
|
|
||||||
"rimraf": "^3.0.2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/cli": "^7.17.0",
|
||||||
|
"@babel/core": "^7.17.2",
|
||||||
|
"@babel/preset-env": "^7.16.11",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
"connect-ensure-login": "^0.1.1",
|
"connect-ensure-login": "^0.1.1",
|
||||||
"connect-flash": "^0.1.1",
|
"connect-flash": "^0.1.1",
|
||||||
|
@ -54,14 +52,17 @@
|
||||||
"excel4node": "^1.7.2",
|
"excel4node": "^1.7.2",
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.2",
|
||||||
"express-session": "^1.17.2",
|
"express-session": "^1.17.2",
|
||||||
|
"joi": "^17.6.0",
|
||||||
"knacss": "^8.0.4",
|
"knacss": "^8.0.4",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"moment-timezone": "^0.5.34",
|
"moment-timezone": "^0.5.34",
|
||||||
"mongoose": "^6.2.1",
|
"mongoose": "^6.2.1",
|
||||||
"mongoose-unique-validator": "^3.0.0",
|
"mongoose-unique-validator": "^3.0.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"passport": "^0.5.2",
|
"passport": "^0.5.2",
|
||||||
"passport-http": "^0.3.0",
|
"passport-http": "^0.3.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.49.7",
|
"sass": "^1.49.7",
|
||||||
"vue": "^3.2.31"
|
"vue": "^3.2.31"
|
||||||
},
|
},
|
||||||
|
|
Binary file not shown.
|
@ -34,6 +34,8 @@
|
||||||
|
|
||||||
<glyph glyph-name="moon" unicode="" d="M704 123q-30-5-61-5-102 0-188 50t-137 137-50 188q0 107 58 199-112-33-183-128t-72-214q0-72 29-139t76-113 114-77 139-28q80 0 152 34t123 96z m114 47q-53-113-159-181t-230-68q-87 0-167 34t-136 92-92 137-34 166q0 85 32 163t87 135 132 92 161 38q25 1 34-22 11-23-8-40-48-43-73-101t-26-122q0-83 41-152t111-111 152-41q66 0 127 29 23 10 40-7 8-8 10-19t-2-22z" horiz-adv-x="857.1" />
|
<glyph glyph-name="moon" unicode="" d="M704 123q-30-5-61-5-102 0-188 50t-137 137-50 188q0 107 58 199-112-33-183-128t-72-214q0-72 29-139t76-113 114-77 139-28q80 0 152 34t123 96z m114 47q-53-113-159-181t-230-68q-87 0-167 34t-136 92-92 137-34 166q0 85 32 163t87 135 132 92 161 38q25 1 34-22 11-23-8-40-48-43-73-101t-26-122q0-83 41-152t111-111 152-41q66 0 127 29 23 10 40-7 8-8 10-19t-2-22z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="share" unicode="" d="M679 279q74 0 126-53t52-126-52-126-126-53-127 53-52 126q0 7 1 19l-201 100q-51-48-121-48-75 0-127 53t-52 126 52 126 127 53q70 0 121-48l201 100q-1 12-1 19 0 74 52 126t127 53 126-53 52-126-52-126-126-53q-71 0-122 48l-201-100q1-12 1-19t-1-19l201-100q51 48 122 48z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
<glyph glyph-name="trash" unicode="" d="M286 82v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m143 0v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m142 0v393q0 8-5 13t-12 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q7 0 12 5t5 13z m-303 554h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
|
<glyph glyph-name="trash" unicode="" d="M286 82v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m143 0v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m142 0v393q0 8-5 13t-12 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q7 0 12 5t5 13z m-303 554h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
<glyph glyph-name="blind" unicode="" d="M204 677q-35 0-61 25t-26 62q0 35 26 61t61 25 61-25 26-61q0-37-26-62t-61-25z m308-359q0-28-17-37t-35-4-27 19l-205 244q-4 7-8 9t-6 1l-1-2q-4-4 2-12l68-77 1-198-90-255q-38-107-52-130-8-15-15-18-28-15-58-1-16 7-23 24t-5 32q1 9 110 345l3 232-48-91 20-124q2-14-1-24t-8-15-10-9-10-4l-4-1q-10-2-19 1t-13 9-8 13-4 10-2 6l-25 167 118 212q12 19 63 19 41 0 59-22l237-291q4-3 8-9l1-2 0-1q4-7 4-16z m-225-83q24-64 49-126t39-94l13-31q21-51 24-69 6-39-20-54-20-13-37-9t-28 12-17 19h0q-4 9-5 14l-69 196z m460-331q17-27 17-32 0-3-2-4-5-2-8 1t-8 14-9 17q-64 96-236 369 1 0 4 1t3 2l2 1q6 5 6 10z" horiz-adv-x="785.7" />
|
<glyph glyph-name="blind" unicode="" d="M204 677q-35 0-61 25t-26 62q0 35 26 61t61 25 61-25 26-61q0-37-26-62t-61-25z m308-359q0-28-17-37t-35-4-27 19l-205 244q-4 7-8 9t-6 1l-1-2q-4-4 2-12l68-77 1-198-90-255q-38-107-52-130-8-15-15-18-28-15-58-1-16 7-23 24t-5 32q1 9 110 345l3 232-48-91 20-124q2-14-1-24t-8-15-10-9-10-4l-4-1q-10-2-19 1t-13 9-8 13-4 10-2 6l-25 167 118 212q12 19 63 19 41 0 59-22l237-291q4-3 8-9l1-2 0-1q4-7 4-16z m-225-83q24-64 49-126t39-94l13-31q21-51 24-69 6-39-20-54-20-13-37-9t-28 12-17 19h0q-4 9-5 14l-69 196z m460-331q17-27 17-32 0-3-2-4-5-2-8 1t-8 14-9 17q-64 96-236 369 1 0 4 1t3 2l2 1q6 5 6 10z" horiz-adv-x="785.7" />
|
||||||
|
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,13 +2,16 @@
|
||||||
* Fonction permettant d'afficher un message dans un toastr
|
* Fonction permettant d'afficher un message dans un toastr
|
||||||
* @param {String} message
|
* @param {String} message
|
||||||
*/
|
*/
|
||||||
function showToastr(message) {
|
function showToastr(message, success = false) {
|
||||||
let x = document.getElementById("toastr");
|
let x = document.getElementById("toastr");
|
||||||
if ( message ) {
|
if ( message ) {
|
||||||
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
x.className = `${x.className} show`;
|
x.className = `${x.className} show`.replace("sucess", "");
|
||||||
|
if ( success ) {
|
||||||
|
x.className = `${x.className} success`;
|
||||||
|
}
|
||||||
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000);
|
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
.ma-collection {
|
.collection {
|
||||||
|
h1 {
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
.filters {
|
.filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: end;
|
justify-content: end;
|
|
@ -52,6 +52,23 @@ $pagination-hover-color: rgb(115, 151, 186);
|
||||||
--box-shadow-color: #{rgba($nord4, 0.35)};
|
--box-shadow-color: #{rgba($nord4, 0.35)};
|
||||||
|
|
||||||
--border-color: #{$nord4};
|
--border-color: #{$nord4};
|
||||||
|
|
||||||
|
--nord0: #{$nord0};
|
||||||
|
--nord1: #{$nord1};
|
||||||
|
--nord2: #{$nord2};
|
||||||
|
--nord3: #{$nord3};
|
||||||
|
--nord4: #{$nord4};
|
||||||
|
--nord5: #{$nord5};
|
||||||
|
--nord6: #{$nord6};
|
||||||
|
--nord7: #{$nord7};
|
||||||
|
--nord8: #{$nord8};
|
||||||
|
--nord9: #{$nord9};
|
||||||
|
--nord10: #{$nord10};
|
||||||
|
--nord11: #{$nord11};
|
||||||
|
--nord12: #{$nord12};
|
||||||
|
--nord13: #{$nord13};
|
||||||
|
--nord14: #{$nord14};
|
||||||
|
--nord15: #{$nord15};
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
|
|
13
sass/composants.scss
Normal file
13
sass/composants.scss
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
.composants {
|
||||||
|
.couleur {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
border: 1px solid var(--input-active-color);
|
||||||
|
box-shadow: var(--box-shadow-color) 0px 3px 6px 0px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,3 +83,7 @@ html {
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-danger {
|
||||||
|
color: $nord12;
|
||||||
|
}
|
|
@ -46,6 +46,7 @@
|
||||||
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
.icon-sun:before { content: '\f185'; } /* '' */
|
.icon-sun:before { content: '\f185'; } /* '' */
|
||||||
.icon-moon:before { content: '\f186'; } /* '' */
|
.icon-moon:before { content: '\f186'; } /* '' */
|
||||||
|
.icon-share:before { content: '\f1e0'; } /* '' */
|
||||||
.icon-trash:before { content: '\f1f8'; } /* '' */
|
.icon-trash:before { content: '\f1f8'; } /* '' */
|
||||||
.icon-blind:before { content: '\f29d'; } /* '' */
|
.icon-blind:before { content: '\f29d'; } /* '' */
|
||||||
|
|
||||||
|
|
|
@ -43,5 +43,6 @@
|
||||||
@import './error';
|
@import './error';
|
||||||
@import './home';
|
@import './home';
|
||||||
@import './ajouter-un-album';
|
@import './ajouter-un-album';
|
||||||
@import './ma-collection';
|
@import './collection';
|
||||||
@import './ma-collection-details';
|
@import './ma-collection-details';
|
||||||
|
@import './composants';
|
|
@ -13,6 +13,11 @@
|
||||||
color: $button-alternate-color;
|
color: $button-alternate-color;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background-color: $success-color;
|
||||||
|
color: $button-font-color;
|
||||||
|
}
|
||||||
|
|
||||||
&.show {
|
&.show {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
animation: toastrFadein 0.5s, toastrFadeout 0.5s 2.5s;
|
animation: toastrFadein 0.5s, toastrFadeout 0.5s 2.5s;
|
||||||
|
|
|
@ -13,9 +13,11 @@ 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 collectionRouter from "./routes/collection";
|
||||||
|
|
||||||
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 importMeRouterApiV1 from "./routes/api/v1/me";
|
||||||
|
|
||||||
// Mongoose schema init
|
// Mongoose schema init
|
||||||
require("./models/users");
|
require("./models/users");
|
||||||
|
@ -82,8 +84,10 @@ app.use(
|
||||||
|
|
||||||
app.use("/", indexRouter);
|
app.use("/", indexRouter);
|
||||||
app.use("/ma-collection", maCollectionRouter);
|
app.use("/ma-collection", maCollectionRouter);
|
||||||
|
app.use("/collection", collectionRouter);
|
||||||
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/me", importMeRouterApiV1);
|
||||||
|
|
||||||
// Handle 404
|
// Handle 404
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
|
@ -113,7 +117,10 @@ app.use((error, req, res, next) => {
|
||||||
} else {
|
} else {
|
||||||
res.status(error.errorCode || 500);
|
res.status(error.errorCode || 500);
|
||||||
res.render("index", {
|
res.render("index", {
|
||||||
page: { title: "500: Oups… le serveur a crashé !", error },
|
page: {
|
||||||
|
title: error.title || "500: Oups… le serveur a crashé !",
|
||||||
|
error,
|
||||||
|
},
|
||||||
errorCode: error.errorCode || 500,
|
errorCode: error.errorCode || 500,
|
||||||
viewname: "error",
|
viewname: "error",
|
||||||
user: req.user || null,
|
user: req.user || null,
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
class ErrorEvent extends Error {
|
class ErrorEvent extends Error {
|
||||||
/**
|
/**
|
||||||
* @param {Number} errorCode
|
* @param {Number} errorCode
|
||||||
|
* @param {String} title
|
||||||
* @param {Mixed} ...params
|
* @param {Mixed} ...params
|
||||||
*/
|
*/
|
||||||
constructor(errorCode, ...params) {
|
constructor(errorCode, title, ...params) {
|
||||||
super(...params);
|
super(...params);
|
||||||
|
|
||||||
if (Error.captureStackTrace) {
|
if (Error.captureStackTrace) {
|
||||||
|
@ -14,6 +15,7 @@ class ErrorEvent extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errorCode = parseInt(errorCode, 10);
|
this.errorCode = parseInt(errorCode, 10);
|
||||||
|
this.title = title;
|
||||||
this.date = new Date();
|
this.date = new Date();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,19 @@ import xl from "excel4node";
|
||||||
import Pages from "./Pages";
|
import Pages from "./Pages";
|
||||||
|
|
||||||
import AlbumsModel from "../models/albums";
|
import AlbumsModel from "../models/albums";
|
||||||
|
import UsersModel from "../models/users";
|
||||||
import ErrorEvent from "../libs/error";
|
import ErrorEvent from "../libs/error";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classe permettant la gestion des albums d'un utilisateur
|
* Classe permettant la gestion des albums d'un utilisateur
|
||||||
*/
|
*/
|
||||||
class Albums extends Pages {
|
class Albums extends Pages {
|
||||||
|
/**
|
||||||
|
* Méthode permettant de remplacer certains cartactères par leur équivalents html
|
||||||
|
* @param {String} str
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
static replaceSpecialChars(str) {
|
static replaceSpecialChars(str) {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -487,7 +494,7 @@ class Albums extends Pages {
|
||||||
static async getAllDistincts(field, user) {
|
static async getAllDistincts(field, user) {
|
||||||
const distincts = await AlbumsModel.find(
|
const distincts = await AlbumsModel.find(
|
||||||
{
|
{
|
||||||
user,
|
User: user,
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
|
@ -513,8 +520,11 @@ class Albums extends Pages {
|
||||||
order = "asc",
|
order = "asc",
|
||||||
artists_sort,
|
artists_sort,
|
||||||
format,
|
format,
|
||||||
|
userId: collectionUserId,
|
||||||
} = this.req.query;
|
} = this.req.query;
|
||||||
|
|
||||||
|
let userId = this.req.user?._id;
|
||||||
|
|
||||||
const where = {};
|
const where = {};
|
||||||
|
|
||||||
if (artists_sort) {
|
if (artists_sort) {
|
||||||
|
@ -524,8 +534,35 @@ class Albums extends Pages {
|
||||||
where["formats.name"] = format;
|
where["formats.name"] = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.req.user && !collectionUserId) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
401,
|
||||||
|
"Cette collection n'est pas publique",
|
||||||
|
"Cette collection n'est pas publique"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionUserId) {
|
||||||
|
const userIsSharingCollection = await UsersModel.findById(
|
||||||
|
collectionUserId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!userIsSharingCollection ||
|
||||||
|
!userIsSharingCollection.isPublicCollection
|
||||||
|
) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
401,
|
||||||
|
"Cette collection n'est pas publique",
|
||||||
|
"Cette collection n'est pas publique"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
userId = userIsSharingCollection._id;
|
||||||
|
}
|
||||||
|
|
||||||
const count = await AlbumsModel.count({
|
const count = await AlbumsModel.count({
|
||||||
user: this.req.user._id,
|
User: userId,
|
||||||
...where,
|
...where,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -547,7 +584,7 @@ class Albums extends Pages {
|
||||||
|
|
||||||
const rows = await AlbumsModel.find(
|
const rows = await AlbumsModel.find(
|
||||||
{
|
{
|
||||||
user: this.req.user._id,
|
User: userId,
|
||||||
...where,
|
...where,
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
@ -619,6 +656,29 @@ class Albums extends Pages {
|
||||||
|
|
||||||
this.setPageContent("item", item);
|
this.setPageContent("item", item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de créer la page "collection/:userId"
|
||||||
|
*/
|
||||||
|
async loadPublicCollection() {
|
||||||
|
const { userId } = this.req.params;
|
||||||
|
|
||||||
|
const user = await UsersModel.findById(userId);
|
||||||
|
|
||||||
|
if (!user || !user.isPublicCollection) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
401,
|
||||||
|
"Cet utilisateur ne souhaite pas partager sa collection"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const artists = await Albums.getAllDistincts("artists_sort", userId);
|
||||||
|
const formats = await Albums.getAllDistincts("formats.name", userId);
|
||||||
|
|
||||||
|
this.setPageContent("username", user.username);
|
||||||
|
this.setPageContent("artists", artists);
|
||||||
|
this.setPageContent("formats", formats);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Albums;
|
export default Albums;
|
||||||
|
|
45
src/middleware/Me.js
Normal file
45
src/middleware/Me.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
import UsersModel from "../models/users";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe permettant la gestion de l'utilisateur connecté
|
||||||
|
*/
|
||||||
|
class Me {
|
||||||
|
constructor(req) {
|
||||||
|
this.req = req;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de modifier le profil d'un utilisateur
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
async patchMe() {
|
||||||
|
const { body, user } = this.req;
|
||||||
|
|
||||||
|
const schema = Joi.object({
|
||||||
|
isPublicCollection: Joi.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = await schema.validateAsync(body);
|
||||||
|
const update = await UsersModel.findByIdAndUpdate(
|
||||||
|
user._id,
|
||||||
|
{ $set: value },
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.req.login(update, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Me;
|
|
@ -1,5 +1,7 @@
|
||||||
/* eslint-disable func-names */
|
/* eslint-disable func-names */
|
||||||
/* eslint-disable no-invalid-this */
|
/* eslint-disable no-invalid-this */
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import uniqueValidator from "mongoose-unique-validator";
|
import uniqueValidator from "mongoose-unique-validator";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
@ -23,8 +25,20 @@ const UserSchema = new mongoose.Schema(
|
||||||
},
|
},
|
||||||
hash: String,
|
hash: String,
|
||||||
salt: String,
|
salt: String,
|
||||||
|
isPublicCollection: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{
|
||||||
|
timestamps: true,
|
||||||
|
toJSON: {
|
||||||
|
transform(doc, ret) {
|
||||||
|
delete ret.hash;
|
||||||
|
delete ret.salt;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
UserSchema.plugin(uniqueValidator, { message: "est déjà utilisé" });
|
UserSchema.plugin(uniqueValidator, { message: "est déjà utilisé" });
|
||||||
|
|
|
@ -9,7 +9,7 @@ const router = express.Router();
|
||||||
|
|
||||||
router
|
router
|
||||||
.route("/")
|
.route("/")
|
||||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
.get(async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const albums = new Albums(req);
|
const albums = new Albums(req);
|
||||||
const data = await albums.getAll();
|
const data = await albums.getAll();
|
||||||
|
|
24
src/routes/api/v1/me.js
Normal file
24
src/routes/api/v1/me.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import express from "express";
|
||||||
|
import { ensureLoggedIn } from "connect-ensure-login";
|
||||||
|
|
||||||
|
import { sendResponse } from "../../../libs/format";
|
||||||
|
|
||||||
|
import Me from "../../../middleware/Me";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/")
|
||||||
|
.patch(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const me = new Me(req);
|
||||||
|
const data = await me.patchMe();
|
||||||
|
|
||||||
|
return sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
22
src/routes/collection.js
Normal file
22
src/routes/collection.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import express from "express";
|
||||||
|
|
||||||
|
import Albums from "../middleware/Albums";
|
||||||
|
|
||||||
|
import render from "../libs/format";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.route("/:userId").get(async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(req, "collection");
|
||||||
|
|
||||||
|
await page.loadPublicCollection();
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -10,7 +10,7 @@ const router = express.Router();
|
||||||
|
|
||||||
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const page = new Albums(req, "mon-compte/ma-collection");
|
const page = new Albums(req, "mon-compte/ma-collection/index");
|
||||||
|
|
||||||
await page.loadMyCollection();
|
await page.loadMyCollection();
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
<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' ) { %>
|
||||||
<div>
|
<div>
|
||||||
<pre><%= page.error %></pre>
|
<pre><%= page.error %></pre>
|
||||||
</div>
|
</div>
|
||||||
|
<% } %>
|
||||||
</main>
|
</main>
|
|
@ -1,5 +1,8 @@
|
||||||
<main class="layout-maxed ma-collection" id="app">
|
<main class="layout-maxed collection" id="app">
|
||||||
<h1>Ma collection</h1>
|
<h1>
|
||||||
|
Collection de <%= page.username %>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="artist">Artiste</label>
|
<label for="artist">Artiste</label>
|
||||||
|
@ -40,12 +43,11 @@
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
||||||
<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">
|
||||||
<a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a>
|
{{ item.artists_sort}} - {{ item.title }}
|
||||||
<i class="icon-trash" @click="showConfirmDelete(item._id)"></i>
|
|
||||||
</span>
|
</span>
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4">
|
<div class="grid grid-cols-2 md:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a>
|
<img :src="item.thumb" :alt="item.title" />
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-3">
|
<div class="md:col-span-3">
|
||||||
<span><strong>Année :</strong> {{ item.year }}</span>
|
<span><strong>Année :</strong> {{ item.year }}</span>
|
||||||
|
@ -91,23 +93,14 @@
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="modal" :class="{'is-visible': showModalDelete}">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header></header>
|
|
||||||
<section>
|
|
||||||
Êtes-vous sûr de vouloir supprimer cet album ?
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<button class="button is-primary" @click="deleteItem">Supprimer</button>
|
|
||||||
<button class="button" @click="toggleModal">Annuler</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const {
|
||||||
|
protocol,
|
||||||
|
host
|
||||||
|
} = window.location;
|
||||||
|
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -122,8 +115,7 @@
|
||||||
sortOrder: 'artists_sort-asc',
|
sortOrder: 'artists_sort-asc',
|
||||||
sort: 'artists_sort',
|
sort: 'artists_sort',
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
itemId: null,
|
userId: "<%= params.userId %>",
|
||||||
showModalDelete: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -133,7 +125,7 @@
|
||||||
fetch() {
|
fetch() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
if ( this.artist ) {
|
if ( this.artist ) {
|
||||||
url += `&artists_sort=${this.artist}`;
|
url += `&artists_sort=${this.artist}`;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +141,7 @@
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToastr(err.response?.data?.message || "Impossible de charger votre collection");
|
showToastr(err.response?.data?.message || "Impossible de charger cette collection");
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -187,25 +179,6 @@
|
||||||
|
|
||||||
this.fetch();
|
this.fetch();
|
||||||
},
|
},
|
||||||
toggleModal() {
|
|
||||||
this.showModalDelete = !this.showModalDelete;
|
|
||||||
},
|
|
||||||
showConfirmDelete(itemId) {
|
|
||||||
this.itemId = itemId;
|
|
||||||
this.toggleModal();
|
|
||||||
},
|
|
||||||
deleteItem() {
|
|
||||||
axios.delete(`/api/v1/albums/${this.itemId}`)
|
|
||||||
.then( () => {
|
|
||||||
this.fetch();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.toggleModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
</script>
|
</script>
|
|
@ -1,8 +1,9 @@
|
||||||
<main class="layout-maxed" id="app">
|
<main class="layout-maxed composants" id="app">
|
||||||
<h1>Les composants</h1>
|
<h1>Les composants</h1>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#titres">Les titres</a></li>
|
<li><a href="#titres">Les titres</a></li>
|
||||||
|
<li><a href="#couleurs">Les couleurs</a></li>
|
||||||
<li><a href="#grilles">Les grilles</a></li>
|
<li><a href="#grilles">Les grilles</a></li>
|
||||||
<li><a href="#boutons">Les boutons</a></li>
|
<li><a href="#boutons">Les boutons</a></li>
|
||||||
<li><a href="#formulaires">Les formulaires</a></li>
|
<li><a href="#formulaires">Les formulaires</a></li>
|
||||||
|
@ -24,6 +25,87 @@
|
||||||
<h5>Titre de niveau 5</h5>
|
<h5>Titre de niveau 5</h5>
|
||||||
<h6>Titre de niveau 6</h6>
|
<h6>Titre de niveau 6</h6>
|
||||||
|
|
||||||
|
<h2 id="couleurs">Les couleurs</h2>
|
||||||
|
<h3>Polar Night</h3>
|
||||||
|
<div class="grid grid-cols-5 gap-5">
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord0);"> </div>
|
||||||
|
nord0
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord1);"> </div>
|
||||||
|
nord1
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord2);"> </div>
|
||||||
|
nord2
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord3);"> </div>
|
||||||
|
nord3
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Snow Storm</h3>
|
||||||
|
<div class="grid grid-cols-5 gap-5">
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord4);"> </div>
|
||||||
|
nord4
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord5);"> </div>
|
||||||
|
nord5
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord6);"> </div>
|
||||||
|
nord6
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Frost</h3>
|
||||||
|
<div class="grid grid-cols-5 gap-5">
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord7);"> </div>
|
||||||
|
nord7
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord8);"> </div>
|
||||||
|
nord8
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord9);"> </div>
|
||||||
|
nord9
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord10);"> </div>
|
||||||
|
nord10
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Aurora</h3>
|
||||||
|
<div class="grid grid-cols-5 gap-5">
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord11);"> </div>
|
||||||
|
nord11
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord12);"> </div>
|
||||||
|
nord12
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord13);"> </div>
|
||||||
|
nord13
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord14);"> </div>
|
||||||
|
nord14
|
||||||
|
</div>
|
||||||
|
<div class="couleur">
|
||||||
|
<div style="background-color: var(--nord15);"> </div>
|
||||||
|
nord15
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Vous pourrez trouver plus d'informations sur le <a href="https://www.nordtheme.com/" target="_blank" rel="noopener noreferrer">site offciel</a> du projet nord.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 id="grilles">Les grilles</h2>
|
<h2 id="grilles">Les grilles</h2>
|
||||||
<p>
|
<p>
|
||||||
Se référer à la documentation de <a href="https://www.knacss.com/doc.html#grid" target="_blank" rel="noopener noreferrer">Knacss</a>.
|
Se référer à la documentation de <a href="https://www.knacss.com/doc.html#grid" target="_blank" rel="noopener noreferrer">Knacss</a>.
|
||||||
|
@ -225,13 +307,15 @@
|
||||||
<i class="icon-link-ext">.icon-link-ext</i>
|
<i class="icon-link-ext">.icon-link-ext</i>
|
||||||
<i class="icon-heart">.icon-heart</i>
|
<i class="icon-heart">.icon-heart</i>
|
||||||
<i class="icon-eye">.icon-eye</i>
|
<i class="icon-eye">.icon-eye</i>
|
||||||
|
<i class="icon-left-open">.icon-left-open</i>
|
||||||
|
<i class="icon-right-open">.icon-right-open</i>
|
||||||
|
<i class="icon-export">.icon-export</i>
|
||||||
|
<i class="icon-share">.icon-share</i>
|
||||||
<i class="icon-spin">.icon-spin</i>
|
<i class="icon-spin">.icon-spin</i>
|
||||||
<i class="icon-sun">.icon-sun</i>
|
<i class="icon-sun">.icon-sun</i>
|
||||||
<i class="icon-moon">.icon-moon</i>
|
<i class="icon-moon">.icon-moon</i>
|
||||||
<i class="icon-trash">.icon-trash</i>
|
<i class="icon-trash">.icon-trash</i>
|
||||||
<i class="icon-blind">.icon-blind</i>
|
<i class="icon-blind">.icon-blind</i>
|
||||||
<i class="icon-left-open">.icon-left-open</i>
|
|
||||||
<i class="icon-right-open">.icon-right-open</i>
|
|
||||||
|
|
||||||
<h2 id="listes">Les listes</h2>
|
<h2 id="listes">Les listes</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<main class="layout-maxed ma-collection-exporter" id="app">
|
<main class="layout-maxed" id="app">
|
||||||
<h1>Exporter ma collection</h1>
|
<h1>Exporter ma collection</h1>
|
||||||
<p>
|
<p>
|
||||||
Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir :
|
Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir :
|
||||||
|
|
279
views/pages/mon-compte/ma-collection/index.ejs
Normal file
279
views/pages/mon-compte/ma-collection/index.ejs
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
<main class="layout-maxed collection" id="app">
|
||||||
|
<h1>
|
||||||
|
Ma collection
|
||||||
|
<i class="icon-share" @click="toggleModalShare" aria-label="Partager ma collection" title="Votre collection sera visible en lecture aux personnes ayant le lien de partage"></i>
|
||||||
|
</h1>
|
||||||
|
<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>
|
||||||
|
<select id="artist" v-model="artist" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.artists.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.artists[i]}">${page.artists[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="format">Format</label>
|
||||||
|
<select id="format" v-model="format" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.formats.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.formats[i]}">${page.formats[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="sortOrder">Trier par</label>
|
||||||
|
<select id="sortOrder" v-model="sortOrder" @change="changeSort">
|
||||||
|
<option value="artists_sort-asc">Artiste (A-Z)</option>
|
||||||
|
<option value="artists_sort-desc">Artiste (Z-A)</option>
|
||||||
|
<option value="year-asc">Année (A-Z)</option>
|
||||||
|
<option value="year-desc">Année (Z-A)</option>
|
||||||
|
<option value="country-asc">Pays (A-Z)</option>
|
||||||
|
<option value="country-desc">Pays (Z-A)</option>
|
||||||
|
<option value="formats.name-asc">Format (A-Z)</option>
|
||||||
|
<option value="formats.name-desc">Format (Z-A)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a>
|
||||||
|
<i class="icon-trash" @click="showConfirmDelete(item._id)"></i>
|
||||||
|
</span>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4">
|
||||||
|
<div>
|
||||||
|
<a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-3">
|
||||||
|
<span><strong>Année :</strong> {{ item.year }}</span>
|
||||||
|
<br />
|
||||||
|
<span><strong>Pays :</strong> {{ item.country }}</span>
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
<strong>Format : </strong>
|
||||||
|
<span v-for="(format, index) in item.formats">
|
||||||
|
{{ format.name }}
|
||||||
|
<template v-if="format.descriptions">
|
||||||
|
(<template v-for="(description, j) in format.descriptions">
|
||||||
|
{{description}}<template v-if="j < format.descriptions.length - 1">, </template>
|
||||||
|
</template>)
|
||||||
|
</template>
|
||||||
|
<template v-if="index < item.formats.length - 1">, </template>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span><strong>Genre :</strong> <template v-for="(genre, index) in item.genres">{{ genre }}<template v-if="index < item.genres.length - 1">, </template></template></span>
|
||||||
|
<br />
|
||||||
|
<span><strong>Style :</strong> <template v-for="(style, index) in item.styles">{{ style }}<template v-if="index < item.styles.length - 1">, </template></template></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="total">
|
||||||
|
<strong>Nombre total d'éléments : </strong>{{total}}
|
||||||
|
</div>
|
||||||
|
<nav class="pagination" role="navigation" aria-label="Pagination">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
<template v-for="p in Array.from({length: totalPages}, (v, i) => (i+1))">
|
||||||
|
<template v-if="p < 2 || p > (totalPages - 1) || (page - 1) <= p && page + 1 >= p">
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link" :class="{'is-current': p === page}" @click="goTo(p)" aria-label="Aller à la page {{p}}">{{ p }}</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<template v-if="(page - 3 === p && page - 2 > 1) || (page + 2 === p && page + 2 < totalPages - 1)">
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link is-disabled">…</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="modal" :class="{'is-visible': showModalDelete}">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header></header>
|
||||||
|
<section>
|
||||||
|
Êtes-vous sûr de vouloir supprimer cet album ?
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<button class="button is-primary" @click="deleteItem">Supprimer</button>
|
||||||
|
<button class="button" @click="toggleModal">Annuler</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal" :class="{'is-visible': showModalShare}">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header>
|
||||||
|
Partager ma collection
|
||||||
|
</header>
|
||||||
|
<section>
|
||||||
|
<template v-if="!isPublicCollection">
|
||||||
|
Votre collection sera visible de toute personne disposant du lien suivant :
|
||||||
|
<br />
|
||||||
|
<a :href="shareLink" target="_blank">{{shareLink}}</a>
|
||||||
|
<br />
|
||||||
|
Ce lien permet uniquement de visualiser l'ensemble de votre collection mais ne perment <strong class="is-danger">en aucun cas</strong> de la modifier.
|
||||||
|
<br />
|
||||||
|
Vous pourrez à tout moment supprimer le lien de partage en cliquant à nouveau sur l'icône <i class="icon-share"></i> sur votre collection.
|
||||||
|
</template>
|
||||||
|
<template v-if="isPublicCollection">
|
||||||
|
Vous êtes sur le point de rendre votre collection privée.
|
||||||
|
<br />
|
||||||
|
Toute les personnes ayant le lien partagé ne pourront plus accéder à votre collection.
|
||||||
|
<br />
|
||||||
|
Vous pourrez à tout moment rendre à nouveau votre collection publique en cliquant sur l'icône <i class="icon-share"></i>.
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<button v-if="!isPublicCollection" class="button is-primary" @click="shareCollection">Partager</button>
|
||||||
|
<button v-if="isPublicCollection" class="button is-danger" @click="shareCollection">Supprimer</button>
|
||||||
|
<button class="button" @click="toggleModalShare">Annuler</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const {
|
||||||
|
protocol,
|
||||||
|
host
|
||||||
|
} = window.location;
|
||||||
|
|
||||||
|
Vue.createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
limit: 16,
|
||||||
|
artist: '',
|
||||||
|
format: '',
|
||||||
|
sortOrder: 'artists_sort-asc',
|
||||||
|
sort: 'artists_sort',
|
||||||
|
order: 'asc',
|
||||||
|
itemId: null,
|
||||||
|
showModalDelete: false,
|
||||||
|
showModalShare: false,
|
||||||
|
shareLink: `${protocol}//${host}/collection/<%= user._id %>`,
|
||||||
|
isPublicCollection: <%= user.isPublicCollection ? 'true' : 'false' %>,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
|
if ( this.artist ) {
|
||||||
|
url += `&artists_sort=${this.artist}`;
|
||||||
|
}
|
||||||
|
if ( this.format ) {
|
||||||
|
url += `&format=${this.format}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get(url)
|
||||||
|
.then( response => {
|
||||||
|
this.items = response.data.rows;
|
||||||
|
this.total = response.data.count;
|
||||||
|
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de charger votre collection");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
next(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.page += 1;
|
||||||
|
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
previous(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.page -= 1;
|
||||||
|
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
goTo(page) {
|
||||||
|
this.page = page;
|
||||||
|
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
changeSort() {
|
||||||
|
const [sort,order] = this.sortOrder.split('-');
|
||||||
|
this.sort = sort;
|
||||||
|
this.order = order;
|
||||||
|
this.page = 1;
|
||||||
|
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
changeFilter() {
|
||||||
|
this.page = 1;
|
||||||
|
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
toggleModal() {
|
||||||
|
this.showModalDelete = !this.showModalDelete;
|
||||||
|
},
|
||||||
|
toggleModalShare() {
|
||||||
|
this.showModalShare = !this.showModalShare;
|
||||||
|
},
|
||||||
|
showConfirmDelete(itemId) {
|
||||||
|
this.itemId = itemId;
|
||||||
|
this.toggleModal();
|
||||||
|
},
|
||||||
|
deleteItem() {
|
||||||
|
axios.delete(`/api/v1/albums/${this.itemId}`)
|
||||||
|
.then( () => {
|
||||||
|
this.fetch();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.toggleModal();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
shareCollection() {
|
||||||
|
axios.patch(`/api/v1/me`, {
|
||||||
|
isPublicCollection: !this.isPublicCollection,
|
||||||
|
})
|
||||||
|
.then( (res) => {
|
||||||
|
this.isPublicCollection = res.data.isPublicCollection;
|
||||||
|
|
||||||
|
if ( this.isPublicCollection ) {
|
||||||
|
showToastr("Votre collection est désormais publique", true);
|
||||||
|
} else {
|
||||||
|
showToastr("Votre collection n'est plus partagée", true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.toggleModalShare();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).mount('#app');
|
||||||
|
</script>
|
Loading…
Reference in a new issue