687 lines
20 KiB
JavaScript
687 lines
20 KiB
JavaScript
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 Export from "./Export";
|
|
|
|
import WantListModel from "../models/wantlist";
|
|
import JobsModel from "../models/jobs";
|
|
import UsersModel from "../models/users";
|
|
import ErrorEvent from "../libs/error";
|
|
|
|
import { getAlbumDetails, getAllDistincts } from "../helpers";
|
|
|
|
/**
|
|
* Classe permettant la gestion da la liste de souhaits d'un utilisateur
|
|
*/
|
|
class Wantlist extends Pages {
|
|
/**
|
|
* Méthode permettant d'ajouter un album dans une collection
|
|
* @param {Object} req
|
|
* @return {Object}
|
|
*/
|
|
static async postAddOne(req) {
|
|
const { body, user } = req;
|
|
const { share, discogsId } = body;
|
|
|
|
let albumDetails = body.album;
|
|
if (discogsId) {
|
|
albumDetails = await getAlbumDetails(discogsId);
|
|
body.id = discogsId;
|
|
}
|
|
|
|
if (!albumDetails) {
|
|
throw new ErrorEvent(406, "Aucun album à ajouter");
|
|
}
|
|
|
|
const data = {
|
|
...albumDetails,
|
|
discogsId: albumDetails.id,
|
|
User: user._id,
|
|
};
|
|
// eslint-disable-next-line no-nested-ternary
|
|
data.released = data.released
|
|
? typeof data.released === "string"
|
|
? new Date(data.released.replace("-00", "-01"))
|
|
: data.released
|
|
: null;
|
|
delete data.id;
|
|
|
|
const album = new WantListModel(data);
|
|
|
|
await album.save();
|
|
|
|
const jobData = {
|
|
model: "WantList",
|
|
id: album._id,
|
|
};
|
|
const job = new JobsModel(jobData);
|
|
|
|
job.save();
|
|
|
|
try {
|
|
const User = await UsersModel.findOne({ _id: user._id });
|
|
|
|
const { mastodon: mastodonConfig } = User;
|
|
|
|
const { publish, token, url, wantlist } = mastodonConfig;
|
|
|
|
if (share && publish && url && token) {
|
|
const M = new Mastodon({
|
|
access_token: token,
|
|
api_url: url,
|
|
});
|
|
|
|
const video =
|
|
data.videos && data.videos.length > 0
|
|
? data.videos[0].uri
|
|
: "";
|
|
|
|
const status = `${(
|
|
wantlist ||
|
|
"Je viens d'ajouter {artist} - {album} à ma liste de souhaits !"
|
|
)
|
|
.replaceAll("{artist}", data.artists[0].name)
|
|
.replaceAll("{format}", data.formats[0].name)
|
|
.replaceAll("{genres}", data.genres.join())
|
|
.replaceAll("{styles}", data.styles.join())
|
|
.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,
|
|
{
|
|
headers: {
|
|
"User-Agent":
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
|
},
|
|
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 });
|
|
}
|
|
} catch (err) {
|
|
throw new ErrorEvent(
|
|
500,
|
|
"Mastodon",
|
|
"Album ajouté à votre collection mais impossible de publier sur Mastodon"
|
|
);
|
|
}
|
|
|
|
return album;
|
|
}
|
|
|
|
constructor(req, viewname) {
|
|
super(req, viewname);
|
|
|
|
this.colors = [
|
|
"#2e3440",
|
|
"#d8dee9",
|
|
"#8fbcbb",
|
|
"#5e81ac",
|
|
"#d08770",
|
|
"#bf616a",
|
|
"#ebcb8b",
|
|
"#a3be8c",
|
|
"#b48ead",
|
|
];
|
|
|
|
this.setPageContent("action", "wantlist");
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant de récupérer la liste des albums d'une collection
|
|
* @return {Object}
|
|
*/
|
|
async getAll() {
|
|
const {
|
|
page,
|
|
exportFormat = "json",
|
|
sort = "artists_sort",
|
|
order = "asc",
|
|
artist,
|
|
format,
|
|
year,
|
|
genre,
|
|
style,
|
|
userId: collectionUserId,
|
|
discogsIds,
|
|
discogsId,
|
|
} = this.req.query;
|
|
|
|
const limit = this.req.user?.pagination || 16;
|
|
|
|
let userId = this.req.user?._id;
|
|
|
|
const where = {};
|
|
|
|
if (artist) {
|
|
where["artists.name"] = artist;
|
|
}
|
|
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,
|
|
"Collection",
|
|
"Cette collection n'est pas publique"
|
|
);
|
|
}
|
|
|
|
if (collectionUserId) {
|
|
const userIsSharingCollection = await UsersModel.findById(
|
|
collectionUserId
|
|
);
|
|
|
|
if (
|
|
!userIsSharingCollection ||
|
|
!userIsSharingCollection.isPublicCollection
|
|
) {
|
|
throw new ErrorEvent(
|
|
401,
|
|
"Collection",
|
|
"Cette collection n'est pas publique"
|
|
);
|
|
}
|
|
|
|
userId = userIsSharingCollection._id;
|
|
}
|
|
|
|
if (discogsIds) {
|
|
where.discogsId = { $in: discogsIds };
|
|
}
|
|
if (discogsId) {
|
|
where.discogsId = Number(discogsId);
|
|
}
|
|
|
|
const count = await WantListModel.count({
|
|
User: userId,
|
|
...where,
|
|
});
|
|
|
|
let params = {
|
|
sort: {
|
|
[sort]: order.toLowerCase() === "asc" ? 1 : -1,
|
|
},
|
|
};
|
|
|
|
if (exportFormat === "json" && page && limit) {
|
|
const skip = (page - 1) * limit;
|
|
|
|
params = {
|
|
...params,
|
|
skip,
|
|
limit,
|
|
};
|
|
}
|
|
|
|
const rows = await WantListModel.find(
|
|
{
|
|
User: userId,
|
|
...where,
|
|
},
|
|
[],
|
|
params
|
|
);
|
|
|
|
switch (exportFormat) {
|
|
case "csv":
|
|
return Export.convertToCsv(rows);
|
|
case "xls":
|
|
return Export.convertToXls(rows);
|
|
case "xml":
|
|
return Export.convertToXml(rows);
|
|
case "musictopus":
|
|
return Export.convertToMusicTopus(rows);
|
|
case "json":
|
|
default:
|
|
return {
|
|
rows,
|
|
limit,
|
|
count,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant de récupérer le détails d'un album
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
async getOne() {
|
|
const { itemId: _id } = this.req.params;
|
|
const { _id: User } = this.req.user;
|
|
const album = await WantListModel.findOne({
|
|
_id,
|
|
User,
|
|
});
|
|
|
|
return {
|
|
...album.toJSON(),
|
|
released: album.released
|
|
? formatDate(album.released, "MM/dd/yyyy")
|
|
: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant de mettre à jour un album
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
async patchOne() {
|
|
const { itemId: _id } = this.req.params;
|
|
const { _id: User } = this.req.user;
|
|
const query = {
|
|
_id,
|
|
User,
|
|
};
|
|
const album = await WantListModel.findOne(query);
|
|
|
|
if (!album) {
|
|
throw new ErrorEvent(
|
|
404,
|
|
"Mise à jour",
|
|
"Impossible de trouver cet album"
|
|
);
|
|
}
|
|
|
|
const values = await getAlbumDetails(album.discogsId);
|
|
|
|
await WantListModel.findOneAndUpdate(query, values, { new: true });
|
|
|
|
return this.getOne();
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant de supprimer un élément d'une collection
|
|
* @return {Boolean}
|
|
*/
|
|
async deleteOne() {
|
|
const res = await WantListModel.findOneAndDelete({
|
|
User: this.req.user._id,
|
|
_id: this.req.params.itemId,
|
|
});
|
|
|
|
if (res) {
|
|
return true;
|
|
}
|
|
|
|
throw new ErrorEvent(
|
|
404,
|
|
"Suppression",
|
|
"Impossible de trouver cet album"
|
|
);
|
|
}
|
|
|
|
async shareOne() {
|
|
const { message: status } = this.req.body;
|
|
const { itemId: _id } = this.req.params;
|
|
const { _id: User } = this.req.user;
|
|
const query = {
|
|
_id,
|
|
User,
|
|
};
|
|
|
|
const album = await WantListModel.findOne(query);
|
|
|
|
if (!album) {
|
|
throw new ErrorEvent(
|
|
404,
|
|
"Mise à jour",
|
|
"Impossible de trouver cet album"
|
|
);
|
|
}
|
|
|
|
const { mastodon: mastodonConfig } = this.req.user;
|
|
const { publish, token, url } = mastodonConfig;
|
|
|
|
if (publish && url && token) {
|
|
const M = new Mastodon({
|
|
access_token: token,
|
|
api_url: url,
|
|
});
|
|
|
|
const media_ids = [];
|
|
|
|
if (album.images.length > 0) {
|
|
for (let i = 0; i < album.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(
|
|
album.images[i].uri,
|
|
{
|
|
headers: {
|
|
"User-Agent":
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
|
},
|
|
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 });
|
|
} else {
|
|
throw new ErrorEvent(
|
|
406,
|
|
`Vous n'avez pas configuré vos options de partage sur votre compte`
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant de créer la page "ma-collection"
|
|
*/
|
|
async loadMyCollection() {
|
|
const artists = await getAllDistincts(
|
|
WantListModel,
|
|
"artists.name",
|
|
this.req.user._id
|
|
);
|
|
const formats = await getAllDistincts(
|
|
WantListModel,
|
|
"formats.name",
|
|
this.req.user._id
|
|
);
|
|
const years = await getAllDistincts(
|
|
WantListModel,
|
|
"year",
|
|
this.req.user._id
|
|
);
|
|
const genres = await getAllDistincts(
|
|
WantListModel,
|
|
"genres",
|
|
this.req.user._id
|
|
);
|
|
const styles = await getAllDistincts(
|
|
WantListModel,
|
|
"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");
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant d'afficher le détails d'un album
|
|
*/
|
|
async loadItem() {
|
|
const item = await this.getOne();
|
|
|
|
this.setPageContent("item", item);
|
|
this.setPageTitle(
|
|
`Détails de l'album ${item.title} de ${item.artists_sort}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant de choisir un album de manière aléatoire dans la collection d'un utilisateur
|
|
*/
|
|
async onAir() {
|
|
const { _id: User } = this.req.user;
|
|
const count = await WantListModel.count({
|
|
User,
|
|
});
|
|
|
|
const items = await WantListModel.find(
|
|
{
|
|
User,
|
|
},
|
|
[],
|
|
{
|
|
skip: Math.floor(Math.random() * (count + 1)),
|
|
limit: 1,
|
|
}
|
|
);
|
|
|
|
this.req.params.itemId = items[0]._id;
|
|
|
|
await this.loadItem();
|
|
}
|
|
|
|
/**
|
|
* Méthode permettant d'afficher des statistiques au sujet de ma collection
|
|
*/
|
|
async statistics() {
|
|
const { _id: User } = this.req.user;
|
|
const top = {};
|
|
const byGenres = {};
|
|
const byStyles = {};
|
|
const byFormats = {};
|
|
const top10 = [];
|
|
let byStyles10 = [];
|
|
const max = this.colors.length - 1;
|
|
|
|
const colorsCount = this.colors.length;
|
|
|
|
const albums = await WantListModel.find({
|
|
User,
|
|
artists: { $exists: true, $not: { $size: 0 } },
|
|
});
|
|
|
|
for (let i = 0; i < albums.length; i += 1) {
|
|
const currentFormats = [];
|
|
const { artists, genres, styles, formats } = albums[i];
|
|
|
|
// INFO: On regroupe les artistes par nom pour en faire le top10
|
|
for (let j = 0; j < artists.length; j += 1) {
|
|
const { name } = artists[j];
|
|
if (!top[name]) {
|
|
top[name] = {
|
|
name,
|
|
count: 0,
|
|
};
|
|
}
|
|
top[name].count += 1;
|
|
}
|
|
|
|
// INFO: On regroupe les genres
|
|
for (let j = 0; j < genres.length; j += 1) {
|
|
const name = genres[j];
|
|
if (!byGenres[name]) {
|
|
byGenres[name] = {
|
|
name,
|
|
count: 0,
|
|
color: this.colors[
|
|
Object.keys(byGenres).length % colorsCount
|
|
],
|
|
};
|
|
}
|
|
|
|
byGenres[name].count += 1;
|
|
}
|
|
|
|
// INFO: On regroupe les styles
|
|
for (let j = 0; j < styles.length; j += 1) {
|
|
const name = styles[j];
|
|
if (!byStyles[name]) {
|
|
byStyles[name] = {
|
|
name,
|
|
count: 0,
|
|
color: this.colors[
|
|
Object.keys(byStyles).length % colorsCount
|
|
],
|
|
};
|
|
}
|
|
|
|
byStyles[name].count += 1;
|
|
}
|
|
|
|
// INFO: On regroupe les formats
|
|
for (let j = 0; j < formats.length; j += 1) {
|
|
const { name } = formats[j];
|
|
// INFO: On évite qu'un album avec 2 vinyles soit compté 2x
|
|
if (!currentFormats.includes(name)) {
|
|
if (!byFormats[name]) {
|
|
byFormats[name] = {
|
|
name,
|
|
count: 0,
|
|
color: this.colors[
|
|
Object.keys(byFormats).length % colorsCount
|
|
],
|
|
};
|
|
}
|
|
|
|
byFormats[name].count += 1;
|
|
currentFormats.push(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// INFO: On convertit le top en tableau
|
|
Object.keys(top).forEach((index) => {
|
|
top10.push(top[index]);
|
|
});
|
|
|
|
// INFO: On convertit les styles en tableau
|
|
Object.keys(byStyles).forEach((index) => {
|
|
byStyles10.push(byStyles[index]);
|
|
});
|
|
|
|
// INFO: On ordonne les artistes par quantité d'albums
|
|
top10.sort((a, b) => (a.count > b.count ? -1 : 1));
|
|
|
|
// INFO: On ordonne les styles par quantité
|
|
byStyles10.sort((a, b) => (a.count > b.count ? -1 : 1));
|
|
const tmp = [];
|
|
|
|
// INFO: On recupère le top N des styles et on mets le reste dans le label "autre"
|
|
for (let i = 0; i < byStyles10.length; i += 1) {
|
|
if (i < max) {
|
|
tmp.push({
|
|
...byStyles10[i],
|
|
color: this.colors[max - i],
|
|
});
|
|
} else if (i === max) {
|
|
tmp.push({
|
|
name: "Autre",
|
|
count: 0,
|
|
color: this.colors[0],
|
|
});
|
|
tmp[max].count += byStyles10[i].count;
|
|
} else {
|
|
tmp[max].count += byStyles10[i].count;
|
|
}
|
|
}
|
|
byStyles10 = tmp;
|
|
|
|
this.setPageTitle("Mes statistiques");
|
|
this.setPageContent("top10", top10.splice(0, 10));
|
|
this.setPageContent("byGenres", byGenres);
|
|
this.setPageContent("byStyles", byStyles10);
|
|
this.setPageContent("byFormats", byFormats);
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
"Collection non partagée",
|
|
"Cet utilisateur ne souhaite pas partager sa collection"
|
|
);
|
|
}
|
|
|
|
const artists = await getAllDistincts(
|
|
WantListModel,
|
|
"artists.name",
|
|
userId
|
|
);
|
|
const formats = await getAllDistincts(
|
|
WantListModel,
|
|
"formats.name",
|
|
userId
|
|
);
|
|
const years = await getAllDistincts(WantListModel, "year", userId);
|
|
const genres = await getAllDistincts(WantListModel, "genres", userId);
|
|
const styles = await getAllDistincts(WantListModel, "styles", userId);
|
|
|
|
this.setPageTitle(`Collection publique de ${user.username}`);
|
|
this.setPageContent("username", user.username);
|
|
this.setPageContent("artists", artists);
|
|
this.setPageContent("formats", formats);
|
|
this.setPageContent("years", years);
|
|
this.setPageContent("genres", genres);
|
|
this.setPageContent("styles", styles);
|
|
}
|
|
}
|
|
|
|
export default Wantlist;
|