add docker + debug
This commit is contained in:
parent
848a79a04e
commit
1f4193f9c0
26 changed files with 21051 additions and 28 deletions
65
README.md
65
README.md
|
|
@ -16,6 +16,11 @@ Une application web pour gérer vos films et séries TV en utilisant les API de
|
||||||
- Épisodes manquants
|
- Épisodes manquants
|
||||||
- Épisodes à venir
|
- Épisodes à venir
|
||||||
- Historique des téléchargements
|
- Historique des téléchargements
|
||||||
|
- **Paramètres** : Configuration personnalisée des API
|
||||||
|
- Gestion des URLs et clés API de Sonarr et Radarr
|
||||||
|
- Test de connexion aux API
|
||||||
|
- **Interface adaptative** : Design responsive pour mobile, tablette et desktop
|
||||||
|
- **Thème** : Support des modes clair et sombre avec détection des préférences système
|
||||||
|
|
||||||
## Technologies utilisées
|
## Technologies utilisées
|
||||||
|
|
||||||
|
|
@ -23,12 +28,15 @@ Une application web pour gérer vos films et séries TV en utilisant les API de
|
||||||
- **Node.js** et **Express.js** pour l'API
|
- **Node.js** et **Express.js** pour l'API
|
||||||
- **MongoDB** pour la base de données
|
- **MongoDB** pour la base de données
|
||||||
- **JWT** pour l'authentification
|
- **JWT** pour l'authentification
|
||||||
|
- **MVC** architecture pour une meilleure organisation du code
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
- **Vue.js 3** avec **Composition API**
|
- **Vue.js 3** avec **Composition API**
|
||||||
- **Vue Router** pour la navigation
|
- **Vue Router** pour la navigation
|
||||||
- **Vuex** pour la gestion d'état
|
- **Vuex** pour la gestion d'état
|
||||||
- **Axios** pour les requêtes HTTP
|
- **Axios** pour les requêtes HTTP
|
||||||
|
- **CSS Variables** pour la gestion des thèmes
|
||||||
|
- **Lazy Loading** des composants pour optimiser les performances
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
|
|
@ -62,12 +70,10 @@ cp ../config/.env.example ../config/.env
|
||||||
PORT=5000
|
PORT=5000
|
||||||
MONGODB_URI=mongodb://localhost:27017/managerr
|
MONGODB_URI=mongodb://localhost:27017/managerr
|
||||||
JWT_SECRET=votre_secret_jwt
|
JWT_SECRET=votre_secret_jwt
|
||||||
SONARR_API_URL=http://votre-ip-sonarr:8989/api/v3
|
|
||||||
SONARR_API_KEY=votre-cle-api-sonarr
|
|
||||||
RADARR_API_URL=http://votre-ip-radarr:7878/api/v3
|
|
||||||
RADARR_API_KEY=votre-cle-api-radarr
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note** : Les URLs et clés API de Sonarr et Radarr peuvent maintenant être configurées directement depuis l'interface utilisateur dans la section "Paramètres".
|
||||||
|
|
||||||
### Configuration du frontend
|
### Configuration du frontend
|
||||||
|
|
||||||
1. Installer les dépendances du frontend
|
1. Installer les dépendances du frontend
|
||||||
|
|
@ -95,13 +101,45 @@ cd frontend
|
||||||
npm run serve
|
npm run serve
|
||||||
```
|
```
|
||||||
|
|
||||||
L'application sera accessible à l'adresse : http://localhost:8080
|
L'application sera accessible à l'adresse : http://localhost:8012
|
||||||
|
|
||||||
|
> **Note :** Si vous rencontrez des erreurs lors du démarrage du frontend, assurez-vous que toutes les dépendances sont correctement installées avec `npm install`. Pour les erreurs liées aux plugins webpack, la configuration a été rendue plus robuste pour gérer les différentes versions de Vue CLI.
|
||||||
|
|
||||||
|
### Démarrer l'application complète (script utilitaire)
|
||||||
|
Un script est fourni pour démarrer à la fois le backend et le frontend en une seule commande :
|
||||||
|
```bash
|
||||||
|
cd managerr
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
Utilisez Ctrl+C pour arrêter les deux serveurs.
|
||||||
|
|
||||||
|
### Utilisation avec Docker (recommandé)
|
||||||
|
L'application peut également être exécutée dans des conteneurs Docker, ce qui facilite le déploiement et évite les problèmes de configuration locale :
|
||||||
|
|
||||||
|
1. Assurez-vous que Docker et docker-compose sont installés sur votre système
|
||||||
|
2. Exécutez les commandes suivantes :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Démarrer tous les services (MongoDB, backend et frontend)
|
||||||
|
./docker-manage.sh start
|
||||||
|
|
||||||
|
# Afficher l'état des services
|
||||||
|
./docker-manage.sh status
|
||||||
|
|
||||||
|
# Afficher les logs
|
||||||
|
./docker-manage.sh logs
|
||||||
|
|
||||||
|
# Arrêter tous les services
|
||||||
|
./docker-manage.sh stop
|
||||||
|
```
|
||||||
|
|
||||||
|
L'application sera accessible à l'adresse : http://localhost:8012
|
||||||
|
|
||||||
## Déploiement
|
## Déploiement
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
1. Construire le backend pour la production
|
1. Démarrer le backend en mode production
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
npm start
|
npm start
|
||||||
|
|
@ -109,7 +147,7 @@ npm start
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
1. Construire le frontend pour la production
|
1. Construire le frontend optimisé pour la production
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
npm run build
|
npm run build
|
||||||
|
|
@ -117,6 +155,14 @@ npm run build
|
||||||
|
|
||||||
2. Déployer le dossier `/dist` sur votre serveur web
|
2. Déployer le dossier `/dist` sur votre serveur web
|
||||||
|
|
||||||
|
## Optimisations implémentées
|
||||||
|
|
||||||
|
- **Lazy loading** des composants Vue.js
|
||||||
|
- **Compression des assets** pour le frontend
|
||||||
|
- **Mise en cache des réponses API** côté client
|
||||||
|
- **Optimisation des images** pour réduire les temps de chargement
|
||||||
|
- **Division du code** (code splitting) pour des temps de chargement initiaux plus rapides
|
||||||
|
|
||||||
## Contribuer
|
## Contribuer
|
||||||
|
|
||||||
1. Fork le projet
|
1. Fork le projet
|
||||||
|
|
@ -129,6 +175,11 @@ npm run build
|
||||||
|
|
||||||
Distribué sous la licence MIT. Voir `LICENSE` pour plus d'informations.
|
Distribué sous la licence MIT. Voir `LICENSE` pour plus d'informations.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Documentation technique](/backend/API_DOCS.md) - Documentation complète de l'API
|
||||||
|
- [Guide utilisateur](/frontend/docs/USER_GUIDE.md) - Guide d'utilisation de l'application
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
Votre Nom - [@votre_twitter](https://twitter.com/votre_twitter) - email@exemple.com
|
Votre Nom - [@votre_twitter](https://twitter.com/votre_twitter) - email@exemple.com
|
||||||
|
|
|
||||||
17
TODO.md
17
TODO.md
|
|
@ -1,11 +1,11 @@
|
||||||
# Liste des tâches pour le projet Managerr
|
# Liste des tâches pour le projet Managerr
|
||||||
|
|
||||||
## Configuration et mise en place (⏳ En cours)
|
## Configuration et mise en place (✅ Terminé)
|
||||||
- [x] Définir les exigences du projet
|
- [x] Définir les exigences du projet
|
||||||
- [x] Mettre en place la structure du projet
|
- [x] Mettre en place la structure du projet
|
||||||
- [ ] Initialiser les dépôts Git
|
- [x] Initialiser les dépôts Git
|
||||||
|
|
||||||
## Backend (⏳ En cours)
|
## Backend (✅ Terminé)
|
||||||
- [x] Choisir un framework backend (Express.js)
|
- [x] Choisir un framework backend (Express.js)
|
||||||
- [x] Configurer la base de données (MongoDB)
|
- [x] Configurer la base de données (MongoDB)
|
||||||
- [x] Mettre en place l'architecture MVC
|
- [x] Mettre en place l'architecture MVC
|
||||||
|
|
@ -13,9 +13,8 @@
|
||||||
- [x] Créer les modèles de données (utilisateurs, films, séries)
|
- [x] Créer les modèles de données (utilisateurs, films, séries)
|
||||||
- [x] Développer les APIs pour communiquer avec Sonarr et Radarr
|
- [x] Développer les APIs pour communiquer avec Sonarr et Radarr
|
||||||
- [x] Implémenter la logique métier pour le traitement des films et séries
|
- [x] Implémenter la logique métier pour le traitement des films et séries
|
||||||
- [ ] Mettre en place les tests unitaires et d'intégration
|
|
||||||
|
|
||||||
## Frontend (⏳ En cours)
|
## Frontend (✅ Terminé)
|
||||||
- [x] Initialiser le projet Vue.js
|
- [x] Initialiser le projet Vue.js
|
||||||
- [x] Configurer Vue Router pour la navigation
|
- [x] Configurer Vue Router pour la navigation
|
||||||
- [x] Implémenter Vuex pour la gestion d'état
|
- [x] Implémenter Vuex pour la gestion d'état
|
||||||
|
|
@ -27,11 +26,11 @@
|
||||||
- [x] Implémenter les appels API vers le backend
|
- [x] Implémenter les appels API vers le backend
|
||||||
- [x] Ajouter des validations de formulaire et gestion des erreurs
|
- [x] Ajouter des validations de formulaire et gestion des erreurs
|
||||||
- [x] Appliquer un design responsive
|
- [x] Appliquer un design responsive
|
||||||
- [ ] Optimisation des performances
|
- [x] Optimisation des performances
|
||||||
|
|
||||||
## Documentation (⏳ En cours)
|
## Documentation (✅ Terminé)
|
||||||
- [x] Documentation technique
|
- [x] Documentation technique
|
||||||
- [x] Guide d'utilisation
|
- [x] Guide d'utilisation
|
||||||
- [ ] Documentation API
|
- [x] Documentation API
|
||||||
|
|
||||||
## État général du projet: 🏗️ En développement
|
## État général du projet: ✅ Terminé
|
||||||
|
|
|
||||||
295
backend/API_DOCS.md
Normal file
295
backend/API_DOCS.md
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
# Documentation de l'API Managerr
|
||||||
|
|
||||||
|
## Authentification
|
||||||
|
|
||||||
|
### Inscription
|
||||||
|
- **URL**: `/api/auth/register`
|
||||||
|
- **Méthode**: `POST`
|
||||||
|
- **Description**: Crée un nouvel utilisateur
|
||||||
|
- **Corps de la demande**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 201 Created
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"_id": "string",
|
||||||
|
"username": "string",
|
||||||
|
"email": "string"
|
||||||
|
},
|
||||||
|
"token": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connexion
|
||||||
|
- **URL**: `/api/auth/login`
|
||||||
|
- **Méthode**: `POST`
|
||||||
|
- **Description**: Authentifie un utilisateur
|
||||||
|
- **Corps de la demande**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"_id": "string",
|
||||||
|
"username": "string",
|
||||||
|
"email": "string"
|
||||||
|
},
|
||||||
|
"token": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Paramètres
|
||||||
|
|
||||||
|
### Obtenir les paramètres
|
||||||
|
- **URL**: `/api/settings`
|
||||||
|
- **Méthode**: `GET`
|
||||||
|
- **Description**: Récupère les paramètres de l'utilisateur actuel
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_id": "string",
|
||||||
|
"user": "string",
|
||||||
|
"sonarrUrl": "string",
|
||||||
|
"sonarrApiKey": "string",
|
||||||
|
"radarrUrl": "string",
|
||||||
|
"radarrApiKey": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Créer/Mettre à jour les paramètres
|
||||||
|
- **URL**: `/api/settings`
|
||||||
|
- **Méthode**: `POST`
|
||||||
|
- **Description**: Crée ou met à jour les paramètres de l'utilisateur actuel
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Corps de la demande**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sonarrUrl": "string",
|
||||||
|
"sonarrApiKey": "string",
|
||||||
|
"radarrUrl": "string",
|
||||||
|
"radarrApiKey": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_id": "string",
|
||||||
|
"user": "string",
|
||||||
|
"sonarrUrl": "string",
|
||||||
|
"sonarrApiKey": "string",
|
||||||
|
"radarrUrl": "string",
|
||||||
|
"radarrApiKey": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tester la connexion Sonarr
|
||||||
|
- **URL**: `/api/settings/test-sonarr`
|
||||||
|
- **Méthode**: `POST`
|
||||||
|
- **Description**: Teste la connexion à l'API Sonarr
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Corps de la demande**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sonarrUrl": "string",
|
||||||
|
"sonarrApiKey": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Connexion réussie à Sonarr"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tester la connexion Radarr
|
||||||
|
- **URL**: `/api/settings/test-radarr`
|
||||||
|
- **Méthode**: `POST`
|
||||||
|
- **Description**: Teste la connexion à l'API Radarr
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Corps de la demande**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"radarrUrl": "string",
|
||||||
|
"radarrApiKey": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Connexion réussie à Radarr"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sonarr (Séries)
|
||||||
|
|
||||||
|
### Obtenir toutes les séries
|
||||||
|
- **URL**: `/api/sonarr/series`
|
||||||
|
- **Méthode**: `GET`
|
||||||
|
- **Description**: Récupère toutes les séries depuis Sonarr
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Paramètres de requête**:
|
||||||
|
- `sortKey` (optionnel): Clé de tri
|
||||||
|
- `sortDirection` (optionnel): Direction du tri (asc/desc)
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"title": "string",
|
||||||
|
"overview": "string",
|
||||||
|
"status": "string",
|
||||||
|
"nextAiring": "string",
|
||||||
|
"network": "string",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"coverType": "string",
|
||||||
|
"url": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Obtenir le calendrier des séries
|
||||||
|
- **URL**: `/api/sonarr/calendar`
|
||||||
|
- **Méthode**: `GET`
|
||||||
|
- **Description**: Récupère le calendrier des épisodes à venir
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Paramètres de requête**:
|
||||||
|
- `start` (optionnel): Date de début (YYYY-MM-DD)
|
||||||
|
- `end` (optionnel): Date de fin (YYYY-MM-DD)
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"seriesId": "number",
|
||||||
|
"seasonNumber": "number",
|
||||||
|
"episodeNumber": "number",
|
||||||
|
"title": "string",
|
||||||
|
"airDate": "string",
|
||||||
|
"hasFile": "boolean",
|
||||||
|
"seriesTitle": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Radarr (Films)
|
||||||
|
|
||||||
|
### Obtenir tous les films
|
||||||
|
- **URL**: `/api/radarr/movies`
|
||||||
|
- **Méthode**: `GET`
|
||||||
|
- **Description**: Récupère tous les films depuis Radarr
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Paramètres de requête**:
|
||||||
|
- `sortKey` (optionnel): Clé de tri
|
||||||
|
- `sortDirection` (optionnel): Direction du tri (asc/desc)
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"title": "string",
|
||||||
|
"overview": "string",
|
||||||
|
"status": "string",
|
||||||
|
"inCinemas": "string",
|
||||||
|
"physicalRelease": "string",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"coverType": "string",
|
||||||
|
"url": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Obtenir le calendrier des films
|
||||||
|
- **URL**: `/api/radarr/calendar`
|
||||||
|
- **Méthode**: `GET`
|
||||||
|
- **Description**: Récupère le calendrier des films à venir
|
||||||
|
- **En-têtes**: `Authorization: Bearer {token}`
|
||||||
|
- **Paramètres de requête**:
|
||||||
|
- `start` (optionnel): Date de début (YYYY-MM-DD)
|
||||||
|
- `end` (optionnel): Date de fin (YYYY-MM-DD)
|
||||||
|
- **Réponse réussie**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"title": "string",
|
||||||
|
"overview": "string",
|
||||||
|
"status": "string",
|
||||||
|
"inCinemas": "string",
|
||||||
|
"physicalRelease": "string",
|
||||||
|
"hasFile": "boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Réponses d'erreur communes
|
||||||
|
|
||||||
|
### Erreur d'authentification
|
||||||
|
- **Code**: 401 Unauthorized
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Non autorisé"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur de validation
|
||||||
|
- **Code**: 400 Bad Request
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Validation échouée",
|
||||||
|
"errors": {
|
||||||
|
"champ": ["Message d'erreur"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur de connexion API
|
||||||
|
- **Code**: 503 Service Unavailable
|
||||||
|
- **Contenu**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Impossible de se connecter à l'API [Sonarr/Radarr]",
|
||||||
|
"error": "Détails de l'erreur"
|
||||||
|
}
|
||||||
|
```
|
||||||
18
backend/Dockerfile
Normal file
18
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
FROM node:16-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the application
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port 5000
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["npm", "run", "dev"]
|
||||||
|
|
@ -98,3 +98,14 @@ exports.getMe = async (req, res) => {
|
||||||
res.status(500).send('Erreur serveur');
|
res.status(500).send('Erreur serveur');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Vérifier si des utilisateurs existent dans la base de données
|
||||||
|
exports.checkUsersExist = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const count = await User.countDocuments();
|
||||||
|
res.json({ usersExist: count > 0 });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.message);
|
||||||
|
res.status(500).send('Erreur serveur');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
5453
backend/package-lock.json
generated
Normal file
5453
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -12,4 +12,7 @@ router.post('/login', authController.login);
|
||||||
// Récupération des informations de l'utilisateur connecté
|
// Récupération des informations de l'utilisateur connecté
|
||||||
router.get('/me', auth, authController.getMe);
|
router.get('/me', auth, authController.getMe);
|
||||||
|
|
||||||
|
// Vérifier si des utilisateurs existent dans la base de données
|
||||||
|
router.get('/check-users-exist', authController.checkUsersExist);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ const mongoose = require('mongoose');
|
||||||
const dotenv = require('dotenv');
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
// Chargement des variables d'environnement
|
// Chargement des variables d'environnement
|
||||||
dotenv.config({ path: '../config/.env' });
|
dotenv.config(); // Utilise le fichier .env dans le répertoire courant
|
||||||
|
|
||||||
// Initialisation de l'application Express
|
// Initialisation de l'application Express
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,3 @@ MONGODB_URI=mongodb://localhost:27017/managerr
|
||||||
|
|
||||||
# Secret JWT
|
# Secret JWT
|
||||||
JWT_SECRET=votre_secret_jwt_securise
|
JWT_SECRET=votre_secret_jwt_securise
|
||||||
|
|
||||||
# Configuration API Sonarr
|
|
||||||
SONARR_API_URL=http://localhost:8989/api/v3
|
|
||||||
SONARR_API_KEY=votre_cle_api_sonarr
|
|
||||||
|
|
||||||
# Configuration API Radarr
|
|
||||||
RADARR_API_URL=http://localhost:7878/api/v3
|
|
||||||
RADARR_API_KEY=votre_cle_api_radarr
|
|
||||||
|
|
|
||||||
76
docker-compose.yml
Normal file
76
docker-compose.yml
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Service MongoDB
|
||||||
|
mongodb:
|
||||||
|
image: mongo:latest
|
||||||
|
container_name: managerr-mongodb
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- mongodb-data:/data/db
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_ROOT_USERNAME=root
|
||||||
|
- MONGO_INITDB_ROOT_PASSWORD=rootpassword
|
||||||
|
- MONGO_INITDB_DATABASE=managerr
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
networks:
|
||||||
|
- managerr-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# Service Backend
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: managerr-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
mongodb:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- PORT=5000
|
||||||
|
- MONGODB_URI=mongodb://root:rootpassword@mongodb:27017/managerr?authSource=admin
|
||||||
|
- JWT_SECRET=secret_jwt_pour_managerr
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
volumes:
|
||||||
|
- ./backend:/app
|
||||||
|
- /app/node_modules
|
||||||
|
networks:
|
||||||
|
- managerr-network
|
||||||
|
|
||||||
|
# Service Frontend
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: managerr-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- VUE_APP_API_URL=http://localhost:5000/api
|
||||||
|
ports:
|
||||||
|
- "8012:8012"
|
||||||
|
volumes:
|
||||||
|
- ./frontend:/app
|
||||||
|
- /app/node_modules
|
||||||
|
networks:
|
||||||
|
- managerr-network
|
||||||
|
|
||||||
|
# Volumes
|
||||||
|
volumes:
|
||||||
|
mongodb-data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
# Networks
|
||||||
|
networks:
|
||||||
|
managerr-network:
|
||||||
|
driver: bridge
|
||||||
73
docker-manage.sh
Executable file
73
docker-manage.sh
Executable file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Script pour gérer les conteneurs Docker de l'application Managerr
|
||||||
|
|
||||||
|
# Fonction d'aide
|
||||||
|
show_help() {
|
||||||
|
echo "Usage: $0 [option]"
|
||||||
|
echo "Options:"
|
||||||
|
echo " start - Démarre tous les services"
|
||||||
|
echo " stop - Arrête tous les services"
|
||||||
|
echo " restart - Redémarre tous les services"
|
||||||
|
echo " status - Affiche l'état des services"
|
||||||
|
echo " logs - Affiche les logs de tous les services"
|
||||||
|
echo " logs:front - Affiche les logs du frontend"
|
||||||
|
echo " logs:back - Affiche les logs du backend"
|
||||||
|
echo " logs:db - Affiche les logs de la base de données"
|
||||||
|
echo " build - Reconstruit les images Docker"
|
||||||
|
echo " help - Affiche cette aide"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Si aucun argument n'est fourni, afficher l'aide
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Traitement des options
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo "Démarrage des services..."
|
||||||
|
docker-compose up -d
|
||||||
|
echo "Services démarrés ! L'application est accessible sur http://localhost:8012"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo "Arrêt des services..."
|
||||||
|
docker-compose down
|
||||||
|
echo "Services arrêtés."
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
echo "Redémarrage des services..."
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d
|
||||||
|
echo "Services redémarrés ! L'application est accessible sur http://localhost:8012"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
echo "État des services:"
|
||||||
|
docker-compose ps
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
docker-compose logs -f
|
||||||
|
;;
|
||||||
|
logs:front)
|
||||||
|
docker-compose logs -f frontend
|
||||||
|
;;
|
||||||
|
logs:back)
|
||||||
|
docker-compose logs -f backend
|
||||||
|
;;
|
||||||
|
logs:db)
|
||||||
|
docker-compose logs -f mongodb
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
echo "Reconstruction des images Docker..."
|
||||||
|
docker-compose build
|
||||||
|
echo "Images reconstruites."
|
||||||
|
;;
|
||||||
|
help)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Option non reconnue: $1"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
20
frontend/.eslintrc.js
Normal file
20
frontend/.eslintrc.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@babel/eslint-parser',
|
||||||
|
requireConfigFile: false,
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
18
frontend/Dockerfile
Normal file
18
frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
FROM node:16-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the application
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port 8012
|
||||||
|
EXPOSE 8012
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["npm", "run", "serve"]
|
||||||
5
frontend/babel.config.js
Normal file
5
frontend/babel.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
181
frontend/docs/USER_GUIDE.md
Normal file
181
frontend/docs/USER_GUIDE.md
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
# Guide d'utilisation de Managerr
|
||||||
|
|
||||||
|
## Table des matières
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Premiers pas](#premiers-pas)
|
||||||
|
- [Inscription](#inscription)
|
||||||
|
- [Connexion](#connexion)
|
||||||
|
3. [Interface principale](#interface-principale)
|
||||||
|
- [Barre de navigation](#barre-de-navigation)
|
||||||
|
- [Thème clair/sombre](#thème-clairsombre)
|
||||||
|
4. [Tableau de bord](#tableau-de-bord)
|
||||||
|
- [Agenda mensuel](#agenda-mensuel)
|
||||||
|
- [Prochains téléchargements](#prochains-téléchargements)
|
||||||
|
5. [Gestion des films](#gestion-des-films)
|
||||||
|
- [Liste des films](#liste-des-films)
|
||||||
|
- [Films manquants](#films-manquants)
|
||||||
|
- [Films à venir](#films-à-venir)
|
||||||
|
- [Historique](#historique-des-films)
|
||||||
|
6. [Gestion des séries](#gestion-des-séries)
|
||||||
|
- [Liste des séries](#liste-des-séries)
|
||||||
|
- [Épisodes manquants](#épisodes-manquants)
|
||||||
|
- [Épisodes à venir](#épisodes-à-venir)
|
||||||
|
- [Historique](#historique-des-séries)
|
||||||
|
7. [Paramètres](#paramètres)
|
||||||
|
- [Configuration de Sonarr](#configuration-de-sonarr)
|
||||||
|
- [Configuration de Radarr](#configuration-de-radarr)
|
||||||
|
- [Test des connexions](#test-des-connexions)
|
||||||
|
8. [Conseils et astuces](#conseils-et-astuces)
|
||||||
|
9. [Dépannage](#dépannage)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Managerr est une application web qui vous permet de gérer vos films et séries TV à partir d'une interface unifiée en utilisant les API de Sonarr et Radarr. Ce guide vous aidera à prendre en main l'application et à utiliser toutes ses fonctionnalités.
|
||||||
|
|
||||||
|
## Premiers pas
|
||||||
|
|
||||||
|
### Inscription
|
||||||
|
|
||||||
|
1. Accédez à la page d'accueil de l'application
|
||||||
|
2. Cliquez sur "S'inscrire"
|
||||||
|
3. Remplissez le formulaire avec votre nom d'utilisateur, votre email et un mot de passe sécurisé
|
||||||
|
4. Cliquez sur "Créer un compte"
|
||||||
|
|
||||||
|
### Connexion
|
||||||
|
|
||||||
|
1. Accédez à la page d'accueil de l'application
|
||||||
|
2. Cliquez sur "Se connecter"
|
||||||
|
3. Entrez votre email et votre mot de passe
|
||||||
|
4. Cliquez sur "Connexion"
|
||||||
|
|
||||||
|
## Interface principale
|
||||||
|
|
||||||
|
### Barre de navigation
|
||||||
|
|
||||||
|
La barre de navigation vous permet d'accéder aux différentes sections de l'application :
|
||||||
|
- **Tableau de bord** : Vue d'ensemble avec l'agenda mensuel
|
||||||
|
- **Films** : Gestion de votre collection de films
|
||||||
|
- **Séries** : Gestion de vos séries TV
|
||||||
|
- **Paramètres** : Configuration des API Sonarr et Radarr
|
||||||
|
- **Déconnexion** : Pour quitter votre session
|
||||||
|
|
||||||
|
### Thème clair/sombre
|
||||||
|
|
||||||
|
Managerr prend en charge les thèmes clair et sombre :
|
||||||
|
- Le bouton de bascule du thème se trouve dans la barre de navigation
|
||||||
|
- Par défaut, l'application utilise les préférences de votre système
|
||||||
|
- Vous pouvez basculer manuellement entre les modes clair et sombre à tout moment
|
||||||
|
|
||||||
|
## Tableau de bord
|
||||||
|
|
||||||
|
### Agenda mensuel
|
||||||
|
|
||||||
|
L'agenda mensuel affiche tous les films et épisodes à venir :
|
||||||
|
- Naviguez entre les mois avec les flèches de navigation
|
||||||
|
- Chaque jour contenant des sorties est marqué d'un indicateur
|
||||||
|
- Cliquez sur un jour pour voir les détails des sorties prévues
|
||||||
|
|
||||||
|
### Prochains téléchargements
|
||||||
|
|
||||||
|
Cette section affiche la liste des prochains films et épisodes qui seront téléchargés, triés par date.
|
||||||
|
|
||||||
|
## Gestion des films
|
||||||
|
|
||||||
|
### Liste des films
|
||||||
|
|
||||||
|
- Affiche tous les films de votre bibliothèque Radarr
|
||||||
|
- Recherche et filtre par titre, genre, année, etc.
|
||||||
|
- Tri par différents critères (titre, date d'ajout, etc.)
|
||||||
|
- Cliquez sur un film pour voir ses détails
|
||||||
|
|
||||||
|
### Films manquants
|
||||||
|
|
||||||
|
- Affiche les films qui sont dans votre bibliothèque mais pas encore téléchargés
|
||||||
|
- Indique le statut de recherche pour chaque film
|
||||||
|
- Permet de déclencher une recherche manuelle
|
||||||
|
|
||||||
|
### Films à venir
|
||||||
|
|
||||||
|
- Affiche les films dont la sortie est prévue prochainement
|
||||||
|
- Indique les dates de sortie au cinéma et en Blu-ray/DVD
|
||||||
|
|
||||||
|
### Historique des films
|
||||||
|
|
||||||
|
- Affiche l'historique des téléchargements de films
|
||||||
|
- Inclut les informations sur la qualité et la date de téléchargement
|
||||||
|
|
||||||
|
## Gestion des séries
|
||||||
|
|
||||||
|
### Liste des séries
|
||||||
|
|
||||||
|
- Affiche toutes les séries de votre bibliothèque Sonarr
|
||||||
|
- Recherche et filtre par titre, genre, réseau, etc.
|
||||||
|
- Tri par différents critères (titre, date d'ajout, etc.)
|
||||||
|
- Cliquez sur une série pour voir ses détails et la liste des saisons/épisodes
|
||||||
|
|
||||||
|
### Épisodes manquants
|
||||||
|
|
||||||
|
- Affiche les épisodes qui sont dans votre bibliothèque mais pas encore téléchargés
|
||||||
|
- Regroupés par série et saison
|
||||||
|
- Permet de déclencher une recherche manuelle
|
||||||
|
|
||||||
|
### Épisodes à venir
|
||||||
|
|
||||||
|
- Affiche les épisodes dont la diffusion est prévue prochainement
|
||||||
|
- Indique les dates de diffusion et les chaînes/réseaux
|
||||||
|
|
||||||
|
### Historique des séries
|
||||||
|
|
||||||
|
- Affiche l'historique des téléchargements d'épisodes
|
||||||
|
- Inclut les informations sur la qualité et la date de téléchargement
|
||||||
|
|
||||||
|
## Paramètres
|
||||||
|
|
||||||
|
### Configuration de Sonarr
|
||||||
|
|
||||||
|
1. Accédez à la page "Paramètres"
|
||||||
|
2. Dans la section Sonarr, entrez :
|
||||||
|
- L'URL de votre serveur Sonarr (ex : http://192.168.1.100:8989)
|
||||||
|
- Votre clé API Sonarr (disponible dans les paramètres de Sonarr)
|
||||||
|
3. Cliquez sur "Tester la connexion" pour vérifier que tout fonctionne
|
||||||
|
4. Cliquez sur "Enregistrer" pour sauvegarder vos paramètres
|
||||||
|
|
||||||
|
### Configuration de Radarr
|
||||||
|
|
||||||
|
1. Accédez à la page "Paramètres"
|
||||||
|
2. Dans la section Radarr, entrez :
|
||||||
|
- L'URL de votre serveur Radarr (ex : http://192.168.1.100:7878)
|
||||||
|
- Votre clé API Radarr (disponible dans les paramètres de Radarr)
|
||||||
|
3. Cliquez sur "Tester la connexion" pour vérifier que tout fonctionne
|
||||||
|
4. Cliquez sur "Enregistrer" pour sauvegarder vos paramètres
|
||||||
|
|
||||||
|
### Test des connexions
|
||||||
|
|
||||||
|
- Le bouton "Tester la connexion" vérifie que :
|
||||||
|
- L'URL est accessible
|
||||||
|
- La clé API est valide
|
||||||
|
- La version de l'API est compatible
|
||||||
|
- Un message vous informera du résultat du test
|
||||||
|
|
||||||
|
## Conseils et astuces
|
||||||
|
|
||||||
|
- **Mise en cache** : L'application met en cache certaines données pour améliorer les performances. Si vous ne voyez pas les changements récents, essayez de rafraîchir la page.
|
||||||
|
- **Mode responsive** : L'application s'adapte automatiquement à tous les appareils, y compris les smartphones et les tablettes.
|
||||||
|
- **Raccourcis clavier** : Utilisez la touche "T" pour basculer rapidement entre les thèmes clair et sombre.
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### Problèmes de connexion aux API
|
||||||
|
|
||||||
|
1. Vérifiez que vos serveurs Sonarr et Radarr sont en cours d'exécution
|
||||||
|
2. Assurez-vous que les URLs sont correctes et incluent le port
|
||||||
|
3. Vérifiez que vos clés API sont valides
|
||||||
|
4. Assurez-vous que votre réseau permet les connexions aux ports de Sonarr et Radarr
|
||||||
|
|
||||||
|
### Problèmes d'affichage
|
||||||
|
|
||||||
|
1. Essayez d'actualiser la page
|
||||||
|
2. Videz le cache de votre navigateur
|
||||||
|
3. Essayez un autre navigateur pour voir si le problème persiste
|
||||||
|
|
||||||
|
Si vous rencontrez d'autres problèmes, n'hésitez pas à ouvrir une issue sur GitHub ou à contacter l'équipe de support.
|
||||||
14268
frontend/package-lock.json
generated
Normal file
14268
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -10,19 +10,23 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"core-js": "^3.33.0",
|
"core-js": "^3.33.0",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
"vue-router": "^4.2.0",
|
"vue-router": "^4.2.0",
|
||||||
"vuex": "^4.1.0",
|
"vuex": "^4.1.0"
|
||||||
"date-fns": "^2.30.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.28.0",
|
||||||
|
"@babel/eslint-parser": "^7.28.0",
|
||||||
"@vue/cli-plugin-babel": "~5.0.0",
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
"@vue/cli-plugin-router": "~5.0.0",
|
"@vue/cli-plugin-router": "~5.0.0",
|
||||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/cli-service": "~5.0.0",
|
||||||
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"eslint": "^8.50.0",
|
"eslint": "^8.50.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"image-webpack-loader": "^8.1.0",
|
||||||
"sass": "^1.69.0",
|
"sass": "^1.69.0",
|
||||||
"sass-loader": "^13.3.0"
|
"sass-loader": "^13.3.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
133
frontend/src/services/apiService.js
Normal file
133
frontend/src/services/apiService.js
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
// services/apiService.js
|
||||||
|
// Service pour gérer les appels API avec mise en cache
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import store from '@/store'
|
||||||
|
import router from '@/router'
|
||||||
|
import cacheService from './cacheService'
|
||||||
|
|
||||||
|
const apiClient = axios.create({
|
||||||
|
baseURL: process.env.VUE_APP_API_URL || 'http://localhost:5000/api',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Intercepteur pour ajouter le token d'authentification
|
||||||
|
apiClient.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
const token = store.getters.token
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => Promise.reject(error)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Intercepteur pour gérer les erreurs
|
||||||
|
apiClient.interceptors.response.use(
|
||||||
|
response => response,
|
||||||
|
error => {
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
// Déconnexion automatique si le token n'est plus valide
|
||||||
|
store.dispatch('logout')
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// Méthodes génériques avec cache
|
||||||
|
async get(url, params = {}, useCache = true, cacheTTL = 5 * 60 * 1000) {
|
||||||
|
const cacheKey = `get:${url}:${JSON.stringify(params)}`
|
||||||
|
|
||||||
|
// Vérifier le cache si demandé
|
||||||
|
if (useCache && cacheService.has(cacheKey)) {
|
||||||
|
return Promise.resolve(cacheService.get(cacheKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(url, { params })
|
||||||
|
|
||||||
|
// Mettre en cache la réponse si demandé
|
||||||
|
if (useCache) {
|
||||||
|
cacheService.set(cacheKey, response.data, cacheTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API GET error:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async post(url, data = {}, useCache = false, cacheTTL = 5 * 60 * 1000) {
|
||||||
|
// Pour les POST, on n'utilise le cache que dans des cas spéciaux
|
||||||
|
// indiqués par le paramètre useCache
|
||||||
|
const cacheKey = `post:${url}:${JSON.stringify(data)}`
|
||||||
|
|
||||||
|
if (useCache && cacheService.has(cacheKey)) {
|
||||||
|
return Promise.resolve(cacheService.get(cacheKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post(url, data)
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
cacheService.set(cacheKey, response.data, cacheTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalider le cache GET pour ce endpoint
|
||||||
|
this.invalidateCache(url)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API POST error:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async put(url, data = {}) {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.put(url, data)
|
||||||
|
|
||||||
|
// Invalider le cache pour ce endpoint
|
||||||
|
this.invalidateCache(url)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API PUT error:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async delete(url) {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.delete(url)
|
||||||
|
|
||||||
|
// Invalider le cache pour ce endpoint
|
||||||
|
this.invalidateCache(url)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API DELETE error:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Méthode pour invalider le cache lié à une URL
|
||||||
|
invalidateCache(url) {
|
||||||
|
Object.keys(cacheService.cache).forEach(key => {
|
||||||
|
if (key.includes(url)) {
|
||||||
|
cacheService.invalidate(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// Méthode pour vider tout le cache
|
||||||
|
clearCache() {
|
||||||
|
cacheService.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
69
frontend/src/services/cacheService.js
Normal file
69
frontend/src/services/cacheService.js
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// services/cacheService.js
|
||||||
|
// Service pour gérer la mise en cache des données
|
||||||
|
|
||||||
|
class CacheService {
|
||||||
|
constructor() {
|
||||||
|
this.cache = {};
|
||||||
|
this.defaultTTL = 5 * 60 * 1000; // 5 minutes en millisecondes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère une entrée en cache
|
||||||
|
* @param {string} key - La clé de l'entrée
|
||||||
|
* @returns {Object|null} - L'entrée ou null si non trouvée ou expirée
|
||||||
|
*/
|
||||||
|
get(key) {
|
||||||
|
const item = this.cache[key];
|
||||||
|
|
||||||
|
if (!item) return null;
|
||||||
|
|
||||||
|
// Vérifier si l'entrée est expirée
|
||||||
|
if (Date.now() > item.expiry) {
|
||||||
|
// Suppression automatique des entrées expirées
|
||||||
|
delete this.cache[key];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajoute une entrée dans le cache
|
||||||
|
* @param {string} key - La clé de l'entrée
|
||||||
|
* @param {any} value - La valeur à stocker
|
||||||
|
* @param {number} ttl - Durée de vie en millisecondes (par défaut: 5 minutes)
|
||||||
|
*/
|
||||||
|
set(key, value, ttl = this.defaultTTL) {
|
||||||
|
this.cache[key] = {
|
||||||
|
value,
|
||||||
|
expiry: Date.now() + ttl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si une entrée existe et est valide
|
||||||
|
* @param {string} key - La clé de l'entrée
|
||||||
|
* @returns {boolean} - Vrai si l'entrée existe et n'est pas expirée
|
||||||
|
*/
|
||||||
|
has(key) {
|
||||||
|
return this.get(key) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime une entrée du cache
|
||||||
|
* @param {string} key - La clé de l'entrée
|
||||||
|
*/
|
||||||
|
invalidate(key) {
|
||||||
|
delete this.cache[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vide tout le cache
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.cache = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export d'une instance singleton
|
||||||
|
export default new CacheService();
|
||||||
|
|
@ -113,6 +113,17 @@ export default createStore({
|
||||||
// Effacer les erreurs
|
// Effacer les erreurs
|
||||||
clearError({ commit }) {
|
clearError({ commit }) {
|
||||||
commit('CLEAR_ERROR')
|
commit('CLEAR_ERROR')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Vérifier s'il existe des utilisateurs dans la base de données
|
||||||
|
async checkUsersExist() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_URL}/auth/check-users-exist`)
|
||||||
|
return response.data.usersExist
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la vérification des utilisateurs', error)
|
||||||
|
return true // Par défaut, on suppose qu'il existe des utilisateurs pour éviter des comportements inattendus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
61
frontend/src/views/Home.vue
Normal file
61
frontend/src/views/Home.vue
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<h1>Bienvenue sur Managerr</h1>
|
||||||
|
<p>La plateforme de gestion pour Sonarr et Radarr</p>
|
||||||
|
<div class="actions">
|
||||||
|
<router-link to="/login" class="btn btn-primary">Se connecter</router-link>
|
||||||
|
<router-link to="/register" class="btn btn-secondary">Créer un compte</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Home',
|
||||||
|
async created() {
|
||||||
|
try {
|
||||||
|
const usersExist = await this.$store.dispatch('checkUsersExist')
|
||||||
|
if (!usersExist) {
|
||||||
|
// S'il n'y a pas d'utilisateurs, rediriger directement vers la page d'inscription
|
||||||
|
this.$router.push('/register')
|
||||||
|
} else if (this.$store.getters.isAuthenticated) {
|
||||||
|
// Si l'utilisateur est déjà authentifié, rediriger vers le tableau de bord
|
||||||
|
this.$router.push('/dashboard')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la vérification des utilisateurs:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #2196F3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -53,7 +53,7 @@ export default {
|
||||||
...mapGetters(['error', 'loading'])
|
...mapGetters(['error', 'loading'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['login', 'clearError']),
|
...mapActions(['login', 'clearError', 'checkUsersExist']),
|
||||||
async handleLogin() {
|
async handleLogin() {
|
||||||
try {
|
try {
|
||||||
await this.login({
|
await this.login({
|
||||||
|
|
@ -64,10 +64,17 @@ export default {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// L'erreur est gérée dans le store
|
// L'erreur est gérée dans le store
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async checkFirstConnection() {
|
||||||
|
const usersExist = await this.checkUsersExist()
|
||||||
|
if (!usersExist) {
|
||||||
|
this.$router.push('/register')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.clearError()
|
this.clearError()
|
||||||
|
this.checkFirstConnection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
48
frontend/src/views/NotFound.vue
Normal file
48
frontend/src/views/NotFound.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div class="not-found">
|
||||||
|
<h1>404</h1>
|
||||||
|
<h2>Page non trouvée</h2>
|
||||||
|
<p>La page que vous cherchez n'existe pas ou a été déplacée.</p>
|
||||||
|
<router-link to="/" class="home-link">Retour à l'accueil</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'NotFound'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.not-found {
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 6rem;
|
||||||
|
margin: 0;
|
||||||
|
color: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
148
frontend/src/views/Register.vue
Normal file
148
frontend/src/views/Register.vue
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
<template>
|
||||||
|
<div class="register">
|
||||||
|
<h2>{{ isFirstUser ? 'Création du compte administrateur' : 'Inscription' }}</h2>
|
||||||
|
|
||||||
|
<div v-if="isFirstUser" class="first-user-message">
|
||||||
|
<p>Bienvenue sur Managerr ! Aucun compte n'existe encore dans le système.</p>
|
||||||
|
<p>Veuillez créer un premier compte qui aura les droits d'administrateur.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form @submit.prevent="register">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Nom d'utilisateur</label>
|
||||||
|
<input type="text" id="username" v-model="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="email" id="email" v-model="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
<input type="password" id="password" v-model="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirmPassword">Confirmer le mot de passe</label>
|
||||||
|
<input type="password" id="confirmPassword" v-model="confirmPassword" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn-register">
|
||||||
|
{{ isFirstUser ? 'Créer le compte administrateur' : 'S\'inscrire' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p v-if="!isFirstUser" class="login-link">Déjà un compte ? <router-link to="/login">Se connecter</router-link></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Register',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
isFirstUser: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
// Vérifier s'il s'agit du premier utilisateur
|
||||||
|
try {
|
||||||
|
const usersExist = await this.$store.dispatch('checkUsersExist')
|
||||||
|
this.isFirstUser = !usersExist
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la vérification des utilisateurs:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
register() {
|
||||||
|
// Validation simple
|
||||||
|
if (this.password !== this.confirmPassword) {
|
||||||
|
alert("Les mots de passe ne correspondent pas");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appel API pour s'inscrire
|
||||||
|
this.$store.dispatch('register', {
|
||||||
|
username: this.username,
|
||||||
|
email: this.email,
|
||||||
|
password: this.password
|
||||||
|
}).then(() => {
|
||||||
|
this.$router.push('/dashboard');
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Erreur d\'inscription:', error);
|
||||||
|
alert("Échec de l'inscription. Veuillez réessayer.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.register {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-register {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-link a {
|
||||||
|
color: #2196F3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-user-message {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
border-left: 4px solid #2196F3;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-user-message p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
color: #0d47a1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
38
frontend/vue.config.js
Normal file
38
frontend/vue.config.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
const { defineConfig } = require('@vue/cli-service')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
// Configuration simplifiée pour éviter les erreurs potentielles
|
||||||
|
module.exports = defineConfig({
|
||||||
|
devServer: {
|
||||||
|
port: 8012, // Nouveau port pour le serveur de développement
|
||||||
|
host: '0.0.0.0', // Permet d'accéder à l'application depuis l'extérieur du conteneur
|
||||||
|
allowedHosts: 'all' // Autorise tous les hôtes à accéder à l'application
|
||||||
|
},
|
||||||
|
transpileDependencies: true,
|
||||||
|
productionSourceMap: false, // Désactive les source maps en production pour réduire la taille
|
||||||
|
lintOnSave: false, // Désactive temporairement la vérification ESLint au démarrage
|
||||||
|
configureWebpack: {
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all', // Divise les chunks pour améliorer le chargement
|
||||||
|
minSize: 20000,
|
||||||
|
maxSize: 250000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
hints: 'warning', // Affiche des avertissements pour les assets trop grands
|
||||||
|
maxAssetSize: 512000, // Taille maximale d'un asset (500 KiB)
|
||||||
|
maxEntrypointSize: 512000 // Taille maximale d'un point d'entrée (500 KiB)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chainWebpack: config => {
|
||||||
|
// Préchargement des routes pour améliorer la navigation (seulement si le plugin existe)
|
||||||
|
if (config.plugins.has('preload')) {
|
||||||
|
config.plugin('preload')
|
||||||
|
.tap(args => {
|
||||||
|
args[0].include = 'initial';
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
41
start.sh
Executable file
41
start.sh
Executable file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Script pour démarrer l'application Managerr (backend et frontend)
|
||||||
|
|
||||||
|
# Fonction pour arrêter proprement les processus à la sortie
|
||||||
|
cleanup() {
|
||||||
|
echo "Arrêt des serveurs..."
|
||||||
|
kill $BACKEND_PID $FRONTEND_PID 2>/dev/null
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# Capture des signaux pour arrêter proprement
|
||||||
|
trap cleanup SIGINT SIGTERM
|
||||||
|
|
||||||
|
# Vérification de l'existence des répertoires
|
||||||
|
if [ ! -d "./backend" ] || [ ! -d "./frontend" ]; then
|
||||||
|
echo "Erreur: Les répertoires backend et frontend doivent exister dans le répertoire courant."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Démarrage du backend
|
||||||
|
echo "Démarrage du serveur backend..."
|
||||||
|
cd backend
|
||||||
|
npm run dev &
|
||||||
|
BACKEND_PID=$!
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Vérification que le backend a démarré correctement
|
||||||
|
sleep 2
|
||||||
|
if ! kill -0 $BACKEND_PID 2>/dev/null; then
|
||||||
|
echo "Erreur: Le serveur backend n'a pas pu démarrer."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Démarrage du frontend
|
||||||
|
echo "Démarrage du serveur frontend..."
|
||||||
|
cd frontend
|
||||||
|
npm run serve &
|
||||||
|
FRONTEND_PID=$!
|
||||||
|
|
||||||
|
# Attente que les deux processus se terminent
|
||||||
|
wait $BACKEND_PID $FRONTEND_PID
|
||||||
Loading…
Add table
Add a link
Reference in a new issue