Init
This commit is contained in:
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-main>
|
||||
<router-view />
|
||||
</v-main>
|
||||
</v-app>
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,8 @@ import {useAuthStore} from "@/stores/auth";
|
||||
export function useApi() {
|
||||
const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
const baseUrl = `${appStore.baseurl}/api`
|
||||
// const baseUrl = `${appStore.baseurl}/api`
|
||||
const baseUrl = '/api'
|
||||
|
||||
const request = async (endpoint: string, options: RequestInit = {}) => {
|
||||
const headers = new Headers(options.headers)
|
||||
@@ -13,9 +14,9 @@ export function useApi() {
|
||||
headers.set('Content-Type', 'application/json')
|
||||
}
|
||||
|
||||
if (authStore.token) {
|
||||
headers.set('Authorization', `Bearer ${authStore.token}`)
|
||||
}
|
||||
// if (authStore.token) {
|
||||
// headers.set('Authorization', `Bearer ${authStore.token}`)
|
||||
// }
|
||||
|
||||
const config = {
|
||||
...options,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app class="admin-layout">
|
||||
<v-app-bar color="error" dark>
|
||||
<v-app-bar-title>Console d'Administration</v-app-bar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn prepend-icon="mdi-arrow-left" to="/">Retour à l'App</v-btn>
|
||||
</v-app-bar>
|
||||
|
||||
<v-navigation-drawer permanent>
|
||||
<v-list>
|
||||
<v-list-item subtitle="Contrôle" title="Admin Panel"></v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item prepend-icon="mdi-view-dashboard" title="Dashboard" to="/admin"></v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<!-- Les pages d'administration s'injecteront ici -->
|
||||
<router-view/>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<!--Top bar-->
|
||||
<v-system-bar>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-icon>mdi-square</v-icon>
|
||||
|
||||
<v-icon>mdi-circle</v-icon>
|
||||
|
||||
<v-icon>mdi-triangle</v-icon>
|
||||
</v-system-bar>
|
||||
|
||||
<v-navigation-drawer
|
||||
color="grey-lighten-3"
|
||||
rail
|
||||
>
|
||||
<v-avatar
|
||||
class="d-block text-center mx-auto mt-4"
|
||||
color="grey-darken-1"
|
||||
size="36"
|
||||
></v-avatar>
|
||||
|
||||
<v-divider class="mx-3 my-5"></v-divider>
|
||||
|
||||
<v-avatar
|
||||
v-for="n in 6"
|
||||
:key="n"
|
||||
class="d-block text-center mx-auto mb-9"
|
||||
color="grey-lighten-1"
|
||||
size="28"
|
||||
></v-avatar>
|
||||
</v-navigation-drawer>
|
||||
<v-navigation-drawer width="244">
|
||||
<v-sheet
|
||||
color="grey-lighten-5"
|
||||
height="128"
|
||||
width="100%"
|
||||
></v-sheet>
|
||||
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="n in 5"
|
||||
:key="n"
|
||||
:title="`Item ${ n }`"
|
||||
link
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<!-- Les pages de l'application s'injecteront ici -->
|
||||
<router-view/>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app class="auth-layout">
|
||||
<v-main>
|
||||
<!-- On injecte directement les pages qui gèrent déjà leur propre centrage et v-card -->
|
||||
<router-view/>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -137,7 +137,7 @@ async function handleRegister() {
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<div class="text-center mt-2">
|
||||
<router-link to="/login">Déjà un compte ? Connectez-vous</router-link>
|
||||
<router-link to="/auth/login">Déjà un compte ? Connectez-vous</router-link>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
@@ -93,7 +93,7 @@ async function handleLogin() {
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<div class="text-center mt-4">
|
||||
<router-link to="/join">Pas encore de compte ? Rejoindre</router-link>
|
||||
<router-link to="/auth/join">Pas encore de compte ? Rejoindre</router-link>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
@@ -6,48 +6,75 @@
|
||||
|
||||
// Composables
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import Index from '@/pages/index.vue'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import AuthLayout from "@/layouts/AuthLayout.vue";
|
||||
import AppLayout from "@/layouts/AppLayout.vue";
|
||||
import AdminLayout from "@/layouts/AdminLayout.vue";
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
// ----------------- GROUPE AUTHENTIFICATION -----------------
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/pages/login.vue'),
|
||||
},
|
||||
{
|
||||
path: '/join',
|
||||
name: 'join',
|
||||
component: () => import('@/pages/join.vue'),
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: Index,
|
||||
},
|
||||
{
|
||||
// Cette regex capture soit un UUID, soit le mot "default"
|
||||
path: '/server/:id(default|[0-9a-fA-F-]{36})',
|
||||
name: 'server-dashboard',
|
||||
component: () => import('@/pages/server/index.vue'),
|
||||
props: true,
|
||||
path: '/auth',
|
||||
component: AuthLayout,
|
||||
children: [
|
||||
{
|
||||
path: 'channel/:channelId',
|
||||
name: 'server-channel',
|
||||
component: () => import('@/pages/server/channel/index.vue'),
|
||||
path: 'login',
|
||||
name: 'login',
|
||||
component: () => import('@/pages/auth/login.vue'),
|
||||
},
|
||||
{
|
||||
path: 'join',
|
||||
name: 'join',
|
||||
component: () => import('@/pages/auth/join.vue'),
|
||||
},
|
||||
// Vous pouvez ajouter ici 'reset-password', etc.
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------- GROUPE APPLICATION GÉNÉRALE -----------------
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
component: () => import('@/pages/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'server/:id(default|[0-9a-fA-F-]{36})',
|
||||
name: 'server-dashboard',
|
||||
component: () => import('@/pages/server/index.vue'),
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: 'channel/:channelId',
|
||||
name: 'server-channel',
|
||||
component: () => import('@/pages/server/channel/index.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------- GROUPE ADMINISTRATION -----------------
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin-dashboard',
|
||||
component: () => import('@/pages/admin/dashboard.vue'),
|
||||
meta: {requiresAdmin: true}
|
||||
component: AdminLayout,
|
||||
meta: {requiresAdmin: true},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'admin-dashboard',
|
||||
component: () => import('@/pages/admin/dashboard.vue'),
|
||||
},
|
||||
// Ajoutez d'autres pages admin ici
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -64,7 +91,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
if (authRequired && !authStore.isAuthenticated) {
|
||||
// Non connecté -> Login
|
||||
next('/login')
|
||||
next('/auth/login')
|
||||
} else if (to.name === 'login' && authStore.isAuthenticated) {
|
||||
// Déjà connecté -> Accueil
|
||||
next('/')
|
||||
|
||||
@@ -34,8 +34,11 @@ export const useAppStore = defineStore('app', {
|
||||
// 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()
|
||||
// await authStore.initialize()
|
||||
|
||||
// Sécurité : si la gateway s'est déjà connectée entre-temps
|
||||
if (gatewayStore.status === 'connected') {
|
||||
|
||||
+68
-26
@@ -1,6 +1,5 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useApi} from "@/composables/useApi";
|
||||
import {useGatewayStore} from "@/stores/gateway.ts";
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
@@ -13,57 +12,100 @@ export interface User {
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
token: localStorage.getItem('token') || null as string | null,
|
||||
// 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: {
|
||||
isAuthenticated: (state) => !!state.token && !!state.user,
|
||||
isAuthenticated: (state) => !!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()
|
||||
// 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
|
||||
const api = useApi()
|
||||
this.user = null
|
||||
localStorage.removeItem('token')
|
||||
// On ne redirige pas ici pour laisser le router ou le composant décider
|
||||
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é.
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
////// 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
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
@@ -8,7 +8,7 @@ export const useCategoryStore = defineStore("category", {
|
||||
actions: {
|
||||
async fetchCategories() {
|
||||
let api = useApi();
|
||||
let response = await api.get("/api/categories");
|
||||
let response = await api.get("/categories");
|
||||
this.categories = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const useChannelStore = defineStore('channel', {
|
||||
actions: {
|
||||
async fetchChannels() {
|
||||
let api = useApi();
|
||||
let response = await api.get("/api/channels");
|
||||
let response = await api.get("/channels");
|
||||
this.channels = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {useAppStore} from "@/stores/app.ts";
|
||||
import {useAuthStore} from "@/stores/auth.ts";
|
||||
|
||||
type GatewayStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
||||
|
||||
@@ -18,20 +17,21 @@ export const useGatewayStore = defineStore('gateway', {
|
||||
}
|
||||
|
||||
const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// const authStore = useAuthStore()
|
||||
|
||||
this.status = 'connecting'
|
||||
const token = authStore.token
|
||||
if (!token) {
|
||||
this.status = 'error'
|
||||
return
|
||||
}
|
||||
// version token
|
||||
// const token = authStore.token
|
||||
// if (!token) {
|
||||
// this.status = 'error'
|
||||
// return
|
||||
// }
|
||||
|
||||
const apiUri = appStore.baseurl ? new URL(appStore.baseurl) : new URL(window.location.href)
|
||||
const wsProtocol = apiUri.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
|
||||
const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway?token=${encodeURIComponent(token)}`
|
||||
// version token
|
||||
// const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway?token=${encodeURIComponent(token)}`
|
||||
const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway`
|
||||
const socket = new WebSocket(wsUrl)
|
||||
|
||||
socket.onopen = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const useServerStore = defineStore("server", {
|
||||
actions: {
|
||||
async fetchServers() {
|
||||
let api = useApi();
|
||||
const response = await api.get("/api/servers");
|
||||
const response = await api.get("/servers");
|
||||
this.servers = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import {fileURLToPath, URL} from 'node:url'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import Fonts from 'unplugin-fonts/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||
import {defineConfig} from 'vite'
|
||||
import Vuetify, {transformAssetUrls} from 'vite-plugin-vuetify'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Vue({
|
||||
template: { transformAssetUrls },
|
||||
template: {transformAssetUrls},
|
||||
}),
|
||||
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||
Vuetify({
|
||||
@@ -29,7 +29,7 @@ export default defineConfig({
|
||||
},
|
||||
}),
|
||||
],
|
||||
define: { 'process.env': {} },
|
||||
define: {'process.env': {}},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('src', import.meta.url)),
|
||||
@@ -46,5 +46,11 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user