Initial commit - Projet Managerr
This commit is contained in:
commit
848a79a04e
36 changed files with 3850 additions and 0 deletions
100
backend/controllers/authController.js
Normal file
100
backend/controllers/authController.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
|
||||
// Inscription d'un nouvel utilisateur
|
||||
exports.register = async (req, res) => {
|
||||
try {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
// Vérifier si l'utilisateur existe déjà
|
||||
let user = await User.findOne({ email });
|
||||
if (user) {
|
||||
return res.status(400).json({ message: 'Cet utilisateur existe déjà' });
|
||||
}
|
||||
|
||||
// Créer un nouvel utilisateur
|
||||
user = new User({
|
||||
username,
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
// Hashage du mot de passe
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password = await bcrypt.hash(password, salt);
|
||||
|
||||
// Sauvegarder l'utilisateur dans la base de données
|
||||
await user.save();
|
||||
|
||||
// Générer un token JWT
|
||||
const payload = {
|
||||
user: {
|
||||
id: user.id
|
||||
}
|
||||
};
|
||||
|
||||
jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET || 'secret',
|
||||
{ expiresIn: '24h' },
|
||||
(err, token) => {
|
||||
if (err) throw err;
|
||||
res.json({ token });
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send('Erreur serveur');
|
||||
}
|
||||
};
|
||||
|
||||
// Connexion d'un utilisateur
|
||||
exports.login = async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Vérifier si l'utilisateur existe
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: 'Identifiants invalides' });
|
||||
}
|
||||
|
||||
// Vérifier le mot de passe
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ message: 'Identifiants invalides' });
|
||||
}
|
||||
|
||||
// Générer un token JWT
|
||||
const payload = {
|
||||
user: {
|
||||
id: user.id
|
||||
}
|
||||
};
|
||||
|
||||
jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET || 'secret',
|
||||
{ expiresIn: '24h' },
|
||||
(err, token) => {
|
||||
if (err) throw err;
|
||||
res.json({ token });
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send('Erreur serveur');
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer les informations de l'utilisateur connecté
|
||||
exports.getMe = async (req, res) => {
|
||||
try {
|
||||
const user = await User.findById(req.user.id).select('-password');
|
||||
res.json(user);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send('Erreur serveur');
|
||||
}
|
||||
};
|
||||
83
backend/controllers/radarrController.js
Normal file
83
backend/controllers/radarrController.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
const axios = require('axios');
|
||||
const config = require('../../config/radarr');
|
||||
|
||||
// Client API pour Radarr
|
||||
const radarrClient = axios.create({
|
||||
baseURL: config.baseURL,
|
||||
headers: {
|
||||
'X-Api-Key': config.apiKey
|
||||
}
|
||||
});
|
||||
|
||||
// Récupérer tous les films
|
||||
exports.getAllMovies = async (req, res) => {
|
||||
try {
|
||||
const response = await radarrClient.get('/movie');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des films:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération des films' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer les films manquants
|
||||
exports.getMissingMovies = async (req, res) => {
|
||||
try {
|
||||
const response = await radarrClient.get('/movie');
|
||||
const missingMovies = response.data.filter(movie => !movie.hasFile);
|
||||
res.json(missingMovies);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des films manquants:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération des films manquants' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer les films à venir
|
||||
exports.getUpcomingMovies = async (req, res) => {
|
||||
try {
|
||||
const today = new Date();
|
||||
const response = await radarrClient.get('/movie');
|
||||
|
||||
const upcomingMovies = response.data.filter(movie => {
|
||||
const releaseDate = new Date(movie.digitalRelease || movie.physicalRelease || movie.inCinemas);
|
||||
return releaseDate > today;
|
||||
});
|
||||
|
||||
res.json(upcomingMovies);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des films à venir:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération des films à venir' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer l'historique des téléchargements
|
||||
exports.getHistory = async (req, res) => {
|
||||
try {
|
||||
const response = await radarrClient.get('/history');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération de l\'historique:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération de l\'historique' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer le calendrier
|
||||
exports.getCalendar = async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
const startDate = start || new Date().toISOString();
|
||||
const endDate = end || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
const response = await radarrClient.get('/calendar', {
|
||||
params: {
|
||||
start: startDate,
|
||||
end: endDate
|
||||
}
|
||||
});
|
||||
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération du calendrier:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération du calendrier' });
|
||||
}
|
||||
};
|
||||
112
backend/controllers/settingsController.js
Normal file
112
backend/controllers/settingsController.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
const Settings = require('../models/Settings');
|
||||
const axios = require('axios');
|
||||
|
||||
// Récupérer les paramètres de l'utilisateur connecté
|
||||
exports.getSettings = async (req, res) => {
|
||||
try {
|
||||
// Rechercher les paramètres pour l'utilisateur connecté
|
||||
const settings = await Settings.findOne({ user: req.user.id });
|
||||
|
||||
if (!settings) {
|
||||
return res.status(404).json({ message: "Aucun paramètre trouvé pour cet utilisateur" });
|
||||
}
|
||||
|
||||
// Ne pas envoyer l'ID de l'utilisateur ni l'ID du document dans la réponse
|
||||
const { sonarr, radarr } = settings;
|
||||
|
||||
res.json({ sonarr, radarr });
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des paramètres:', error);
|
||||
res.status(500).json({ message: 'Erreur serveur' });
|
||||
}
|
||||
};
|
||||
|
||||
// Sauvegarder ou mettre à jour les paramètres
|
||||
exports.saveSettings = async (req, res) => {
|
||||
try {
|
||||
const { sonarr, radarr } = req.body;
|
||||
|
||||
// Validation des entrées
|
||||
if (!sonarr || !radarr) {
|
||||
return res.status(400).json({ message: 'Les configurations Sonarr et Radarr sont requises' });
|
||||
}
|
||||
|
||||
if (!sonarr.url || !sonarr.apiKey || !radarr.url || !radarr.apiKey) {
|
||||
return res.status(400).json({ message: 'Tous les champs sont requis' });
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur a déjà des paramètres
|
||||
let settings = await Settings.findOne({ user: req.user.id });
|
||||
|
||||
if (settings) {
|
||||
// Mettre à jour les paramètres existants
|
||||
settings.sonarr = sonarr;
|
||||
settings.radarr = radarr;
|
||||
settings.updatedAt = Date.now();
|
||||
} else {
|
||||
// Créer de nouveaux paramètres
|
||||
settings = new Settings({
|
||||
user: req.user.id,
|
||||
sonarr,
|
||||
radarr
|
||||
});
|
||||
}
|
||||
|
||||
await settings.save();
|
||||
|
||||
res.json({ message: 'Paramètres enregistrés avec succès' });
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de l\'enregistrement des paramètres:', error);
|
||||
res.status(500).json({ message: 'Erreur serveur' });
|
||||
}
|
||||
};
|
||||
|
||||
// Tester la connexion à une API (Sonarr ou Radarr)
|
||||
exports.testConnection = async (req, res) => {
|
||||
try {
|
||||
const { type, url, apiKey } = req.body;
|
||||
|
||||
// Validation des entrées
|
||||
if (!type || !url || !apiKey) {
|
||||
return res.status(400).json({ message: 'Type, URL et clé API sont requis' });
|
||||
}
|
||||
|
||||
if (type !== 'sonarr' && type !== 'radarr') {
|
||||
return res.status(400).json({ message: 'Type invalide. Doit être "sonarr" ou "radarr"' });
|
||||
}
|
||||
|
||||
// Tentative de connexion à l'API
|
||||
try {
|
||||
// Utiliser le endpoint /system/status qui est disponible à la fois sur Sonarr et Radarr
|
||||
const response = await axios.get(`${url}/system/status`, {
|
||||
headers: {
|
||||
'X-Api-Key': apiKey
|
||||
},
|
||||
timeout: 5000 // Timeout de 5 secondes
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Connexion établie avec succès',
|
||||
details: {
|
||||
version: response.data.version,
|
||||
appName: response.data.appName
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({ success: false, message: 'Échec de la connexion' });
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error(`Erreur lors du test de l'API ${type}:`, apiError);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Échec de la connexion',
|
||||
error: apiError.message
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du test de connexion:', error);
|
||||
res.status(500).json({ message: 'Erreur serveur' });
|
||||
}
|
||||
};
|
||||
76
backend/controllers/sonarrController.js
Normal file
76
backend/controllers/sonarrController.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
const axios = require('axios');
|
||||
const config = require('../../config/sonarr');
|
||||
|
||||
// Client API pour Sonarr
|
||||
const sonarrClient = axios.create({
|
||||
baseURL: config.baseURL,
|
||||
headers: {
|
||||
'X-Api-Key': config.apiKey
|
||||
}
|
||||
});
|
||||
|
||||
// Récupérer toutes les séries
|
||||
exports.getAllSeries = async (req, res) => {
|
||||
try {
|
||||
const response = await sonarrClient.get('/series');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des séries:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération des séries' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer les séries manquantes
|
||||
exports.getMissingSeries = async (req, res) => {
|
||||
try {
|
||||
const response = await sonarrClient.get('/wanted/missing');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des séries manquantes:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération des séries manquantes' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer les séries à venir
|
||||
exports.getUpcomingSeries = async (req, res) => {
|
||||
try {
|
||||
const response = await sonarrClient.get('/calendar', {
|
||||
params: {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() // 30 jours
|
||||
}
|
||||
});
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des séries à venir:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération des séries à venir' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer l'historique des téléchargements
|
||||
exports.getHistory = async (req, res) => {
|
||||
try {
|
||||
const response = await sonarrClient.get('/history');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération de l\'historique:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération de l\'historique' });
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer le calendrier
|
||||
exports.getCalendar = async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
const response = await sonarrClient.get('/calendar', {
|
||||
params: {
|
||||
start: start || new Date().toISOString(),
|
||||
end: end || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
});
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération du calendrier:', error);
|
||||
res.status(500).json({ message: 'Erreur lors de la récupération du calendrier' });
|
||||
}
|
||||
};
|
||||
20
backend/middleware/auth.js
Normal file
20
backend/middleware/auth.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const jwt = require('jsonwebtoken');
|
||||
|
||||
module.exports = function (req, res, next) {
|
||||
// Récupérer le token du header
|
||||
const token = req.header('x-auth-token');
|
||||
|
||||
// Vérifier si le token existe
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'Pas de token, autorisation refusée' });
|
||||
}
|
||||
|
||||
// Vérifier le token
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
|
||||
req.user = decoded.user;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: 'Token invalide' });
|
||||
}
|
||||
};
|
||||
39
backend/models/Settings.js
Normal file
39
backend/models/Settings.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const SettingsSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
sonarr: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
apiKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
}
|
||||
},
|
||||
radarr: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
apiKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
}
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Settings', SettingsSchema);
|
||||
23
backend/models/User.js
Normal file
23
backend/models/User.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const UserSchema = new mongoose.Schema({
|
||||
username: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('User', UserSchema);
|
||||
25
backend/package.json
Normal file
25
backend/package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "managerr-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend pour l'application Managerr - Gestion de films et séries via Sonarr et Radarr",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mongoose": "^7.5.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.1",
|
||||
"axios": "^1.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.6.4",
|
||||
"nodemon": "^3.0.1",
|
||||
"supertest": "^6.3.3"
|
||||
}
|
||||
}
|
||||
15
backend/routes/auth.js
Normal file
15
backend/routes/auth.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const authController = require('../controllers/authController');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
// Inscription d'un nouvel utilisateur
|
||||
router.post('/register', authController.register);
|
||||
|
||||
// Connexion d'un utilisateur
|
||||
router.post('/login', authController.login);
|
||||
|
||||
// Récupération des informations de l'utilisateur connecté
|
||||
router.get('/me', auth, authController.getMe);
|
||||
|
||||
module.exports = router;
|
||||
24
backend/routes/radarr.js
Normal file
24
backend/routes/radarr.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const radarrController = require('../controllers/radarrController');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
// Appliquer le middleware d'authentification à toutes les routes
|
||||
router.use(auth);
|
||||
|
||||
// Récupérer tous les films
|
||||
router.get('/movies', radarrController.getAllMovies);
|
||||
|
||||
// Récupérer les films manquants
|
||||
router.get('/movies/missing', radarrController.getMissingMovies);
|
||||
|
||||
// Récupérer les films à venir
|
||||
router.get('/movies/upcoming', radarrController.getUpcomingMovies);
|
||||
|
||||
// Récupérer l'historique des téléchargements
|
||||
router.get('/history', radarrController.getHistory);
|
||||
|
||||
// Récupérer le calendrier
|
||||
router.get('/calendar', radarrController.getCalendar);
|
||||
|
||||
module.exports = router;
|
||||
18
backend/routes/settings.js
Normal file
18
backend/routes/settings.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const settingsController = require('../controllers/settingsController');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
// Appliquer le middleware d'authentification à toutes les routes
|
||||
router.use(auth);
|
||||
|
||||
// Récupérer les paramètres de l'utilisateur
|
||||
router.get('/', settingsController.getSettings);
|
||||
|
||||
// Enregistrer ou mettre à jour les paramètres
|
||||
router.post('/', settingsController.saveSettings);
|
||||
|
||||
// Tester la connexion à une API
|
||||
router.post('/test-connection', settingsController.testConnection);
|
||||
|
||||
module.exports = router;
|
||||
24
backend/routes/sonarr.js
Normal file
24
backend/routes/sonarr.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const sonarrController = require('../controllers/sonarrController');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
// Appliquer le middleware d'authentification à toutes les routes
|
||||
router.use(auth);
|
||||
|
||||
// Récupérer toutes les séries
|
||||
router.get('/series', sonarrController.getAllSeries);
|
||||
|
||||
// Récupérer les séries manquantes
|
||||
router.get('/series/missing', sonarrController.getMissingSeries);
|
||||
|
||||
// Récupérer les séries à venir
|
||||
router.get('/series/upcoming', sonarrController.getUpcomingSeries);
|
||||
|
||||
// Récupérer l'historique des téléchargements
|
||||
router.get('/history', sonarrController.getHistory);
|
||||
|
||||
// Récupérer le calendrier
|
||||
router.get('/calendar', sonarrController.getCalendar);
|
||||
|
||||
module.exports = router;
|
||||
53
backend/server.js
Normal file
53
backend/server.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const mongoose = require('mongoose');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
// Chargement des variables d'environnement
|
||||
dotenv.config({ path: '../config/.env' });
|
||||
|
||||
// Initialisation de l'application Express
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Routes
|
||||
const authRoutes = require('./routes/auth');
|
||||
const sonarrRoutes = require('./routes/sonarr');
|
||||
const radarrRoutes = require('./routes/radarr');
|
||||
const settingsRoutes = require('./routes/settings');
|
||||
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/sonarr', sonarrRoutes);
|
||||
app.use('/api/radarr', radarrRoutes);
|
||||
app.use('/api/settings', settingsRoutes);
|
||||
|
||||
// Route de base pour vérifier que l'API fonctionne
|
||||
app.get('/', (req, res) => {
|
||||
res.json({ message: 'API Managerr fonctionne correctement' });
|
||||
});
|
||||
|
||||
// Gestion des erreurs
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({ message: 'Une erreur est survenue', error: err.message });
|
||||
});
|
||||
|
||||
// Connexion à la base de données MongoDB
|
||||
const PORT = process.env.PORT || 5000;
|
||||
const DB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/managerr';
|
||||
|
||||
mongoose
|
||||
.connect(DB_URI)
|
||||
.then(() => {
|
||||
console.log('Connecté à la base de données MongoDB');
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Serveur démarré sur le port ${PORT}`);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Erreur de connexion à MongoDB:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue