Init
This commit is contained in:
+13
-8
@@ -1,13 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/favicon.ico" rel="icon">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Welcome to Vuetify 4</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/src/main.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -18,9 +18,10 @@ export function useApi() {
|
||||
// headers.set('Authorization', `Bearer ${authStore.token}`)
|
||||
// }
|
||||
|
||||
const config = {
|
||||
const config: RequestInit = {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
body: options.body && typeof options.body === 'object'
|
||||
? JSON.stringify(options.body)
|
||||
: options.body
|
||||
|
||||
@@ -15,13 +15,13 @@ import App from './App.vue'
|
||||
|
||||
// Styles
|
||||
import 'unfonts.css'
|
||||
import {useAppStore} from "@/stores/app.ts";
|
||||
import {useSessionStore} from "@/stores/session.ts";
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
registerPlugins(app)
|
||||
|
||||
const appStore = useAppStore()
|
||||
appStore.initialize()
|
||||
const sessionStore = useSessionStore()
|
||||
await sessionStore.bootstrap()
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useApi} from "@/composables/useApi.ts";
|
||||
import {useSessionStore} from "@/stores/session.ts";
|
||||
|
||||
|
||||
const api = useApi()
|
||||
const authStore = useAuthStore()
|
||||
const sessionStore = useSessionStore()
|
||||
const router = useRouter()
|
||||
|
||||
const username = ref('')
|
||||
@@ -23,26 +21,10 @@ async function handleLogin() {
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const response = await api.post('/auth/login', {
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
|
||||
if (response.status === 401) {
|
||||
error.value = "Identifiants incorrects"
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
error.value = "Une erreur est survenue lors de la connexion"
|
||||
return
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
await authStore.setToken(data.token)
|
||||
await sessionStore.login(username.value, password.value)
|
||||
await router.push('/')
|
||||
} catch (err) {
|
||||
error.value = 'Impossible de contacter le serveur'
|
||||
error.value = err instanceof Error ? err.message : 'Impossible de contacter le serveur'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useSessionStore} from '@/stores/session'
|
||||
import AuthLayout from "@/layouts/AuthLayout.vue";
|
||||
import AppLayout from "@/layouts/AppLayout.vue";
|
||||
import AdminLayout from "@/layouts/AdminLayout.vue";
|
||||
@@ -80,9 +81,10 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const sessionStore = useSessionStore()
|
||||
const authStore = useAuthStore()
|
||||
if (!authStore.isInitialized) {
|
||||
await authStore.initialize()
|
||||
if (!sessionStore.isReady) {
|
||||
await sessionStore.bootstrap()
|
||||
}
|
||||
|
||||
const publicPages = ['login', 'join']
|
||||
|
||||
@@ -1,49 +1,7 @@
|
||||
// Utilities
|
||||
import {defineStore} from 'pinia'
|
||||
import {useAuthStore} from "@/stores/auth.ts";
|
||||
import {useGatewayStore} from "@/stores/gateway.ts";
|
||||
import {useServerStore} from "@/stores/server.ts";
|
||||
import {useCategoryStore} from "@/stores/category.ts";
|
||||
import {useChannelStore} from "@/stores/channel.ts";
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
baseurl: 'http://localhost:8080',
|
||||
}),
|
||||
actions: {
|
||||
async initialize() {
|
||||
const authStore = useAuthStore()
|
||||
const gatewayStore = useGatewayStore()
|
||||
const serverStore = useServerStore()
|
||||
const categoryStore = useCategoryStore()
|
||||
const channelStore = useChannelStore()
|
||||
|
||||
// Définition du trigger de chargement des données de l'application
|
||||
const loadAppData = async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
serverStore.fetchServers(),
|
||||
categoryStore.fetchCategories(),
|
||||
channelStore.fetchChannels()
|
||||
])
|
||||
} catch (e) {
|
||||
console.error("Erreur lors du chargement des données initiales :", e)
|
||||
}
|
||||
}
|
||||
|
||||
// On s'abonne à l'événement de connexion de la gateway
|
||||
window.addEventListener('gateway:connected', loadAppData)
|
||||
|
||||
// On connecte la gateway (qui déclenchera 'gateway:connected')
|
||||
await gatewayStore.connect()
|
||||
|
||||
// Initialisation de l'authentification (qui lancera la connexion à la gateway)
|
||||
// await authStore.initialize()
|
||||
|
||||
// Sécurité : si la gateway s'est déjà connectée entre-temps
|
||||
if (gatewayStore.status === 'connected') {
|
||||
await loadAppData()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+28
-73
@@ -12,11 +12,7 @@ export interface User {
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
// Plus de token stocké dans le localStorage !
|
||||
user: null as User | null,
|
||||
isInitialized: false,
|
||||
// Nous ne gardons ce token en mémoire que pour la connexion au WebSocket
|
||||
// gatewayToken: null as string | null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
@@ -26,86 +22,45 @@ export const useAuthStore = defineStore('auth', {
|
||||
},
|
||||
|
||||
actions: {
|
||||
async initialize() {
|
||||
async fetchCurrentUser() {
|
||||
const api = useApi()
|
||||
try {
|
||||
const response = await api.get('/auth/me')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
this.user = data.user
|
||||
} else {
|
||||
this.user = null
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Auth initialization failed", e)
|
||||
// this.logout()
|
||||
} finally {
|
||||
this.isInitialized = true
|
||||
this.user = null
|
||||
}
|
||||
},
|
||||
|
||||
logout() {
|
||||
async login(username: string, password: string) {
|
||||
const api = useApi()
|
||||
this.user = null
|
||||
api.post('/auth/logout')
|
||||
// Note : si vous implémentez une route /auth/logout côté Axum,
|
||||
// elle devra nettoyer le cookie en renvoyant un Set-Cookie expiré.
|
||||
}
|
||||
}
|
||||
})
|
||||
const response = await api.post('/auth/login', {username, password})
|
||||
|
||||
////// Version with token + local storage
|
||||
// export const useAuthStore = defineStore('auth', {
|
||||
// state: () => ({
|
||||
// token: localStorage.getItem('token') || null as string | null,
|
||||
// user: null as User | null,
|
||||
// isInitialized: false,
|
||||
// }),
|
||||
//
|
||||
// getters: {
|
||||
// isAuthenticated: (state) => !!state.token && !!state.user,
|
||||
// isAdmin: (state) => state.user?.is_superuser || false,
|
||||
// currentUser: (state) => state.user,
|
||||
// },
|
||||
//
|
||||
// actions: {
|
||||
// async initialize() {
|
||||
// if (!this.token) {
|
||||
// this.isInitialized = true
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// const api = useApi()
|
||||
// try {
|
||||
// const response = await api.get('/auth/me')
|
||||
// if (response.ok) {
|
||||
// const data = await response.json()
|
||||
// this.user = data.user
|
||||
//
|
||||
// // Initialisation du gateway
|
||||
// const gatewayStore = useGatewayStore()
|
||||
// await gatewayStore.connect()
|
||||
// } else {
|
||||
// this.logout()
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error("Auth initialization failed", e)
|
||||
// this.logout()
|
||||
// } finally {
|
||||
// this.isInitialized = true
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// setToken(token: string) {
|
||||
// this.token = token
|
||||
// localStorage.setItem('token', token)
|
||||
// // On déclenche la récupération des infos utilisateur immédiatement
|
||||
// return this.initialize()
|
||||
// },
|
||||
//
|
||||
// logout() {
|
||||
// this.token = null
|
||||
// this.user = null
|
||||
// localStorage.removeItem('token')
|
||||
// // On ne redirige pas ici pour laisser le router ou le composant décider
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new Error('Identifiants invalides')
|
||||
}
|
||||
|
||||
const error = await response.json().catch(() => null)
|
||||
throw new Error(error?.message || 'Erreur de connexion')
|
||||
}
|
||||
|
||||
await this.fetchCurrentUser()
|
||||
},
|
||||
|
||||
async logout() {
|
||||
const api = useApi()
|
||||
try {
|
||||
await api.post('/auth/logout')
|
||||
} finally {
|
||||
this.user = null
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -10,6 +10,9 @@ export const useCategoryStore = defineStore("category", {
|
||||
let api = useApi();
|
||||
let response = await api.get("/categories");
|
||||
this.categories = await response.json();
|
||||
},
|
||||
reset() {
|
||||
this.categories = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -55,6 +55,11 @@ export const useChannelStore = defineStore('channel', {
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.channels = [];
|
||||
this.loading = false;
|
||||
this.error = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -8,29 +8,27 @@ export const useGatewayStore = defineStore('gateway', {
|
||||
socket: null as WebSocket | null,
|
||||
status: 'disconnected' as GatewayStatus,
|
||||
reconnectAttempts: 0,
|
||||
shouldReconnect: false,
|
||||
reconnectTimer: null as number | null,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async connect() {
|
||||
if (this.socket && this.status === 'connected') {
|
||||
if (this.status === 'connecting' || this.status === 'connected') {
|
||||
return
|
||||
}
|
||||
|
||||
const appStore = useAppStore()
|
||||
// const authStore = useAuthStore()
|
||||
|
||||
this.status = 'connecting'
|
||||
// version token
|
||||
// const token = authStore.token
|
||||
// if (!token) {
|
||||
// this.status = 'error'
|
||||
// return
|
||||
// }
|
||||
this.shouldReconnect = true
|
||||
if (this.reconnectTimer) {
|
||||
window.clearTimeout(this.reconnectTimer)
|
||||
this.reconnectTimer = null
|
||||
}
|
||||
|
||||
const apiUri = appStore.baseurl ? new URL(appStore.baseurl) : new URL(window.location.href)
|
||||
const wsProtocol = apiUri.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
// version token
|
||||
// const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway?token=${encodeURIComponent(token)}`
|
||||
const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway`
|
||||
const socket = new WebSocket(wsUrl)
|
||||
|
||||
@@ -38,14 +36,16 @@ export const useGatewayStore = defineStore('gateway', {
|
||||
this.status = 'connected'
|
||||
this.reconnectAttempts = 0
|
||||
|
||||
// Émission d'un événement global indiquant que la connexion est établie et prête
|
||||
window.dispatchEvent(new CustomEvent('gateway:connected'))
|
||||
}
|
||||
|
||||
socket.onclose = () => {
|
||||
this.status = 'disconnected'
|
||||
this.socket = null
|
||||
this.scheduleReconnect()
|
||||
if (this.socket === socket) {
|
||||
this.socket = null
|
||||
}
|
||||
if (this.shouldReconnect) {
|
||||
this.scheduleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
socket.onerror = () => {
|
||||
@@ -60,9 +60,15 @@ export const useGatewayStore = defineStore('gateway', {
|
||||
},
|
||||
|
||||
async disconnect() {
|
||||
this.shouldReconnect = false
|
||||
if (this.reconnectTimer) {
|
||||
window.clearTimeout(this.reconnectTimer)
|
||||
this.reconnectTimer = null
|
||||
}
|
||||
this.socket?.close()
|
||||
this.socket = null
|
||||
this.status = 'disconnected'
|
||||
this.reconnectAttempts = 0
|
||||
},
|
||||
|
||||
async send(payload: object) {
|
||||
@@ -81,7 +87,8 @@ export const useGatewayStore = defineStore('gateway', {
|
||||
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000)
|
||||
this.reconnectAttempts += 1
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.reconnectTimer = window.setTimeout(() => {
|
||||
this.reconnectTimer = null
|
||||
this.connect()
|
||||
}, delay)
|
||||
},
|
||||
|
||||
@@ -12,6 +12,9 @@ export const useMessageStore = defineStore("message", {
|
||||
let api = useApi();
|
||||
let response = await api.get("/api/messages");
|
||||
this.messages = await response.json();
|
||||
},
|
||||
reset() {
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {useApi} from "@/composables/useApi.ts";
|
||||
|
||||
interface Server {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const useServerStore = defineStore("server", {
|
||||
state: () => ({
|
||||
servers: []
|
||||
servers: [] as Server[]
|
||||
}),
|
||||
actions: {
|
||||
async fetchServers() {
|
||||
let api = useApi();
|
||||
const response = await api.get("/servers");
|
||||
this.servers = await response.json();
|
||||
},
|
||||
reset() {
|
||||
this.servers = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useAuthStore} from '@/stores/auth.ts'
|
||||
import {useGatewayStore} from '@/stores/gateway.ts'
|
||||
import {useServerStore} from '@/stores/server.ts'
|
||||
import {useCategoryStore} from '@/stores/category.ts'
|
||||
import {useChannelStore} from '@/stores/channel.ts'
|
||||
import {useMessageStore} from '@/stores/message.ts'
|
||||
|
||||
let bootstrapPromise: Promise<void> | null = null
|
||||
|
||||
export const useSessionStore = defineStore('session', {
|
||||
state: () => ({
|
||||
isBootstrapping: false,
|
||||
isReady: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async bootstrap() {
|
||||
if (this.isReady) {
|
||||
return
|
||||
}
|
||||
|
||||
if (bootstrapPromise) {
|
||||
return bootstrapPromise
|
||||
}
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
bootstrapPromise = (async () => {
|
||||
this.isBootstrapping = true
|
||||
try {
|
||||
await authStore.fetchCurrentUser()
|
||||
|
||||
if (authStore.isAuthenticated) {
|
||||
await this.startAuthenticatedSession()
|
||||
}
|
||||
} finally {
|
||||
this.isReady = true
|
||||
this.isBootstrapping = false
|
||||
bootstrapPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return bootstrapPromise
|
||||
},
|
||||
|
||||
async startAuthenticatedSession() {
|
||||
const gatewayStore = useGatewayStore()
|
||||
|
||||
await gatewayStore.connect()
|
||||
await this.loadInitialData()
|
||||
},
|
||||
|
||||
async stopAuthenticatedSession() {
|
||||
const gatewayStore = useGatewayStore()
|
||||
const serverStore = useServerStore()
|
||||
const categoryStore = useCategoryStore()
|
||||
const channelStore = useChannelStore()
|
||||
const messageStore = useMessageStore()
|
||||
|
||||
await gatewayStore.disconnect()
|
||||
serverStore.reset()
|
||||
categoryStore.reset()
|
||||
channelStore.reset()
|
||||
messageStore.reset()
|
||||
},
|
||||
|
||||
async loadInitialData() {
|
||||
const serverStore = useServerStore()
|
||||
const categoryStore = useCategoryStore()
|
||||
const channelStore = useChannelStore()
|
||||
|
||||
await Promise.all([
|
||||
serverStore.fetchServers(),
|
||||
categoryStore.fetchCategories(),
|
||||
channelStore.fetchChannels(),
|
||||
])
|
||||
},
|
||||
|
||||
async login(username: string, password: string) {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
await authStore.login(username, password)
|
||||
await this.startAuthenticatedSession()
|
||||
this.isReady = true
|
||||
},
|
||||
|
||||
async logout() {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
await this.stopAuthenticatedSession()
|
||||
await authStore.logout()
|
||||
this.isReady = true
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user