add docker + debug

This commit is contained in:
mahek 2025-07-21 16:38:07 +02:00
parent 848a79a04e
commit 1f4193f9c0
26 changed files with 21051 additions and 28 deletions

View 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()
}
}

View 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();

View file

@ -113,6 +113,17 @@ export default createStore({
// Effacer les erreurs
clearError({ commit }) {
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
}
}
},

View 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>

View file

@ -53,7 +53,7 @@ export default {
...mapGetters(['error', 'loading'])
},
methods: {
...mapActions(['login', 'clearError']),
...mapActions(['login', 'clearError', 'checkUsersExist']),
async handleLogin() {
try {
await this.login({
@ -64,10 +64,17 @@ export default {
} catch (error) {
// L'erreur est gérée dans le store
}
},
async checkFirstConnection() {
const usersExist = await this.checkUsersExist()
if (!usersExist) {
this.$router.push('/register')
}
}
},
created() {
this.clearError()
this.checkFirstConnection()
}
}
</script>

View 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>

View 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>