nodecdtheque => MyMusicLibrary & new UI for adding album
This commit is contained in:
parent
a35c8899ac
commit
a502fe9088
14 changed files with 231 additions and 37 deletions
|
@ -1,6 +1,6 @@
|
|||
# nodecdtheque
|
||||
# My Music Library
|
||||
|
||||
NodeCDThèque (non temporaire faute de mieux haha) est une application Web permettant de lister votre collection de CD ou vinyles.
|
||||
My Music Library est une application Web permettant de lister votre collection de CD ou vinyles.
|
||||
|
||||
## Prérequis
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
version: "2.4"
|
||||
|
||||
services:
|
||||
nodecdtheque-www:
|
||||
container_name: nodecdtheque-www
|
||||
mymusiclibrary-www:
|
||||
container_name: mymusiclibrary-www
|
||||
image: "node:16"
|
||||
restart: always
|
||||
user: "node"
|
||||
|
@ -17,21 +17,21 @@ services:
|
|||
ports:
|
||||
- 3001:3001
|
||||
depends_on:
|
||||
- nodecdtheque-db
|
||||
- mymusiclibrary-db
|
||||
environment:
|
||||
NODE_ENV: ${NODE_ENV}
|
||||
DISCOGS_TOKEN: ${DISCOGS_TOKEN}
|
||||
networks:
|
||||
- nodecdtheque
|
||||
nodecdtheque-db:
|
||||
container_name: nodecdtheque-db
|
||||
- mymusiclibrary
|
||||
mymusiclibrary-db:
|
||||
container_name: mymusiclibrary-db
|
||||
image: mongo:latest
|
||||
restart: always
|
||||
ports:
|
||||
- 27617:27017
|
||||
networks:
|
||||
- nodecdtheque
|
||||
- mymusiclibrary
|
||||
|
||||
networks:
|
||||
nodecdtheque:
|
||||
mymusiclibrary:
|
||||
driver: bridge
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "nodecdtheque",
|
||||
"name": "mymusiclibrary",
|
||||
"version": "1.0.0",
|
||||
"description": "Simple application to manage your CD/Vinyl collection",
|
||||
"scripts": {
|
||||
|
@ -18,7 +18,7 @@
|
|||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@git.darkou.fr:dbroqua/nodecdtheque.git"
|
||||
"url": "git@git.darkou.fr:dbroqua/MyMusicLibrary.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Damien Broqua",
|
||||
|
|
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.3 KiB |
6
sass/bulma.scss
vendored
6
sass/bulma.scss
vendored
|
@ -1 +1,5 @@
|
|||
@use '../node_modules/bulma/bulma.sass';
|
||||
@use '../node_modules/bulma/bulma.sass';
|
||||
|
||||
.modal-content {
|
||||
width: 80%;
|
||||
}
|
|
@ -13,8 +13,9 @@ import { isXhr } from "./helpers";
|
|||
|
||||
import indexRouter from "./routes";
|
||||
import addAlbumRouter from "./routes/addAlbum";
|
||||
import importRouterApiV1 from "./routes/api/v1";
|
||||
|
||||
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
||||
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
||||
|
||||
// Mongoose schema init
|
||||
require("./models/users");
|
||||
|
@ -81,8 +82,8 @@ app.use(
|
|||
|
||||
app.use("/", indexRouter);
|
||||
app.use("/ajouter-un-album", addAlbumRouter);
|
||||
app.use("/api/v1", importRouterApiV1);
|
||||
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
||||
app.use("/api/v1/search", importSearchRouterApiV1);
|
||||
|
||||
// Handle 404
|
||||
app.use((req, res) => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import http from "http";
|
|||
import app from "../app";
|
||||
import { port } from "../config";
|
||||
|
||||
const debug = debugLib("nodecdtheque:server");
|
||||
const debug = debugLib("mymusiclibrary:server");
|
||||
const server = http.createServer(app);
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
module.exports = {
|
||||
nodeEnv: process.env.NODE_ENV || "development",
|
||||
port: parseInt(process.env.PORT || "3001", 10),
|
||||
mongoDbUri: process.env.MONGODB_URI || "mongodb://nodecdtheque-db/cdtheque",
|
||||
mongoDbUri:
|
||||
process.env.MONGODB_URI || "mongodb://mymusiclibrary-db/mymusiclibrary",
|
||||
secret: process.env.SECRET || "waemaeMe5ahc6ce1chaeKohKa6Io8Eik",
|
||||
discogsToken: process.env.DISCOGS_TOKEN,
|
||||
};
|
||||
|
|
|
@ -38,6 +38,10 @@ class Pages {
|
|||
this.pageContent.page[field] = value;
|
||||
}
|
||||
|
||||
getPageContent(field) {
|
||||
return this.pageContent.page[field];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendu de la page
|
||||
* @return {Object}
|
||||
|
|
|
@ -2,16 +2,26 @@ import express from "express";
|
|||
import { ensureLoggedIn } from "connect-ensure-login";
|
||||
|
||||
import { sendResponse } from "../../../libs/format";
|
||||
import { searchSong } from "../../../helpers";
|
||||
import { searchSong, getAlbumDetails } from "../../../helpers";
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const router = express.Router();
|
||||
|
||||
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||
try {
|
||||
const data = await searchSong(req.query.q);
|
||||
|
||||
sendResponse(req, res, data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
router
|
||||
.route("/search")
|
||||
.route("/:discogsId")
|
||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||
try {
|
||||
const data = await searchSong(req.query.q);
|
||||
const data = await getAlbumDetails(req.params.discogsId);
|
||||
|
||||
sendResponse(req, res, data);
|
||||
} catch (err) {
|
|
@ -12,15 +12,12 @@ import render from "../libs/format";
|
|||
const router = express.Router();
|
||||
|
||||
router.route("/").get((req, res, next) => {
|
||||
if (req.user) {
|
||||
return res.redirect("/ma-collection");
|
||||
}
|
||||
try {
|
||||
const page = new Pages(req, "home");
|
||||
|
||||
return render(res, page);
|
||||
render(res, page);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -88,7 +85,11 @@ router
|
|||
|
||||
await page.loadMyCollection();
|
||||
|
||||
render(res, page);
|
||||
if (page.getPageContent("artists").length > 0) {
|
||||
render(res, page);
|
||||
} else {
|
||||
res.redirect("/ajouter-un-album");
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><% if (page.title) { %><%= page.title %> <% } else { %> DarKou - Ma CDThèque <% } %></title>
|
||||
<title><% if (page.title) { %><%= page.title %> <% } else { %> DarKou - My Music Library <% } %></title>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
|
@ -55,7 +55,8 @@
|
|||
<nav class="navbar is-fixed-top is-light" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img src="/logo.png" alt="Ma CDThèque">
|
||||
<img src="/logo.png" alt="Logo">
|
||||
<h1 class="title ml-2">My Music Library</h1>
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
||||
|
@ -87,7 +88,12 @@
|
|||
<% if ( user ) { %>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Mon compte
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-user"></i>
|
||||
</span>
|
||||
<span>
|
||||
<%= user.username %>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="navbar-dropdown">
|
||||
|
@ -165,7 +171,7 @@
|
|||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
<strong>Ma CDThèque</strong> par <a href="https://www.darkou.fr">Damien Broqua</a>.
|
||||
<strong title="Merci Brunus ! 😜">My Music Library</strong> par <a href="https://www.darkou.fr">Damien Broqua</a>.
|
||||
Fait avec ❤ à Bordeaux.
|
||||
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU GPL-3.0-or-later</a>.
|
||||
</p>
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
<img :src="item.thumb" :alt="item.title" style="max-width: 120px;"/>
|
||||
</td>
|
||||
<td>
|
||||
<a :href="'/ajouter-un-album/' + item.id">{{ item.title }}</a>
|
||||
<a @click="loadDetails(item.id)">{{ item.title }}</a>
|
||||
<!-- <a :href="'/ajouter-un-album/' + item.id">{{ item.title }}</a> -->
|
||||
</td>
|
||||
<td>{{ item.year }}</td>
|
||||
<td>{{ item.country }}</td>
|
||||
|
@ -58,6 +59,120 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="modal" :class="{'is-active': modalIsVisible}" id="addAlbum">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">{{details.artists_sort}} - {{details.title}}</p>
|
||||
<button class="delete" aria-label="close" @click="toggleModal"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-12-mobile is-4-desktop">
|
||||
<div class="has-text-centered">
|
||||
<img :src="details.thumb %>" alt="Miniature" />
|
||||
<hr />
|
||||
<img v-for="image in details.images" :src="image.uri150" alt="Miniature" style="max-width: 60px;" />
|
||||
<hr />
|
||||
</div>
|
||||
<ol class="ml-4">
|
||||
<li v-for="track in details.tracklist">{{ track.title }} ({{track.duration}})</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-8-desktop">
|
||||
<div class="column is-12">
|
||||
<div class="columns is-mobile is-multiline">
|
||||
<div class="column is-12-mobile is-6-desktop" v-for="genre in details.genres">
|
||||
<div class="field">
|
||||
<label class="label" for="company">Genre</label>
|
||||
<div class="control">
|
||||
<input type="text" id="genres" name="genres" class="input" v-model="genre" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-desktop" v-for="style in details.styles">
|
||||
<div class="field">
|
||||
<label class="label" for="company">Style</label>
|
||||
<div class="control">
|
||||
<input type="text" id="style" name="style" class="input" v-model="style" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-12-mobile is-6-desktop">
|
||||
<div class="field">
|
||||
<label class="label" for="year">Année</label>
|
||||
<div class="control">
|
||||
<input type="text" id="year" name="year" class="input" v-model="details.year" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-desktop">
|
||||
<div class="field">
|
||||
<label class="label" for="released">Date de sortie</label>
|
||||
<div class="control">
|
||||
<input type="text" id="released" name="released" class="input" v-model="details.released" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-12-mobile is-6-desktop">
|
||||
<div class="field">
|
||||
<label class="label" for="country">Pays</label>
|
||||
<div class="control">
|
||||
<input type="text" id="country" name="country" class="input" v-model="details.country" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-desktop" v-for="format in details.formats">
|
||||
<div class="field">
|
||||
<label class="label" for="format">Format</label>
|
||||
<div class="control">
|
||||
<input type="text" id="format" name="format" class="input" v-model="format.name" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-12-mobile is-6-desktop">
|
||||
<span class="label">Codes barres</span>
|
||||
<ol>
|
||||
<li v-for="identifier in details.identifiers">
|
||||
{{identifier.value}} ({{identifier.type}})
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-desktop">
|
||||
<span class="label">Label</span>
|
||||
<div class="field" v-for="label in details.labels">
|
||||
<div class="control">
|
||||
<input type="text" name="label" class="input" v-model="label.name" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<span class="label">Société</span>
|
||||
<div class="field" v-for="company in details.companies">
|
||||
<div class="control">
|
||||
<input type="text" name="company" class="input" v-model="company.name" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success" @click="add">Ajouter</button>
|
||||
<button class="button" @click="toggleModal">Annuler</button>
|
||||
</footer>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" @click="toggleModal"></button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
Vue.createApp({
|
||||
|
@ -66,6 +181,8 @@
|
|||
q: '',
|
||||
loading: false,
|
||||
items: [],
|
||||
details: {},
|
||||
modalIsVisible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -116,7 +233,38 @@
|
|||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleModal() {
|
||||
this.modalIsVisible = !this.modalIsVisible;
|
||||
},
|
||||
loadDetails(discogsId) {
|
||||
console.log('discogsId:', discogsId);
|
||||
|
||||
axios.get(`/api/v1/search/${discogsId}`)
|
||||
.then( response => {
|
||||
const {
|
||||
data,
|
||||
} = response;
|
||||
|
||||
this.details = data;
|
||||
this.toggleModal();
|
||||
})
|
||||
.catch((err) => {
|
||||
showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album");
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
add() {
|
||||
axios.post('/api/v1/albums', this.details)
|
||||
.then(() => {
|
||||
window.location.href = '/ma-collection';
|
||||
})
|
||||
.catch((err) => {
|
||||
showToastr(err.response?.data?.message || "Impossible d'ajouter ce album pour le moment…");
|
||||
});
|
||||
},
|
||||
}
|
||||
}).mount('#app')
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,35 @@
|
|||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="header"></div>
|
||||
<h1 class="title">
|
||||
Ma CDThèque
|
||||
<h1 class="title is-1">
|
||||
My Music Library
|
||||
</h1>
|
||||
<p class="subtitle">
|
||||
Retrouvez votre CDThèque partout depuis votre PC ou votre smartphone.
|
||||
Retrouvez votre cdthèque partout depuis votre PC ou votre smartphone.
|
||||
</p>
|
||||
<p>
|
||||
Ma CDThèque est un site web permettant de sauvegarder votre liste des CDs ou Vinyles et de la retrouver facilement et n'importe ou !
|
||||
<strong>My Music Library</strong> est un site web permettant de sauvegarder votre liste des CDs ou Vinyles et de la retrouver facilement et n'importe ou !
|
||||
<br />
|
||||
Le code source est publiée sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU GPL-3.0-or-later</a>. Le code source est librement accessible sur <a href="https://git.darkou.fr/dbroqua/nodecdtheque">git.darkou.fr</a>.
|
||||
Le code source est publiée 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</a>. Le code source est librement accessible sur <a href="https://git.darkou.fr/dbroqua/MyMusicLibrary" target="_blank">git.darkou.fr</a>.
|
||||
</p>
|
||||
<h2 class="title is-2">
|
||||
Pourquoi ?
|
||||
</h2>
|
||||
<p>
|
||||
<strong>My Music Library</strong> est né d'un besoin personnel lorsque ma collecion de CD est devenu un peu trop grosse pour m'en souvenir et après avoir acheté par accident plusieurs fois le même CD…
|
||||
<br />
|
||||
Il était donc temps de trouver une solution ! Il existe plusieurs logiciel de gestion de librairies musicales mais, à ma connaissance, aucun facilement accessible via internet (genre calé sous la couette ou dans un magasin de musique 😜).
|
||||
</p>
|
||||
<h2 class="title is-2">
|
||||
Comment ?
|
||||
</h2>
|
||||
<p>
|
||||
Alors là clairement j'ai pas trop réfléchi… <a href="https://nodejs.org/" target="_blank" rel="noopener noreferrer">NodeJS</a> forcément, déformation pro 😅.
|
||||
<br />
|
||||
Pour la base de données je suis parti sur <a href="https://www.mongodb.com/" target="_blank" rel="noopener noreferrer">MongoDB</a> qui me semblait être le plus logique pour stocker une telle collection sans avoir plein de relations dans tous les sens (artiste, pays, genre, style etc…).
|
||||
<br />
|
||||
Pour le front j'ai voulu innover en n'utilisant pas <a href="https://jquery.com/" target="_blank" rel="noopener noreferrer">jQuery</a> et <a href="https://getbootstrap.com/" target="_blank" rel="noopener noreferrer">Boostrap</a> mais <a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer">VueJS</a> et <a href="https://bulma.io/" target="_blank" rel="noopener noreferrer">Bulma</a> !
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</section>
|
Loading…
Reference in a new issue