This commit is contained in:
2026-06-15 00:07:19 +02:00
parent eb2652f7e9
commit 4466b6c1ca
14 changed files with 213 additions and 56 deletions
+47
View File
@@ -0,0 +1,47 @@
import {useAppStore} from "@/stores/app";
import {useAuthStore} from "@/stores/auth";
export function useApi() {
const appStore = useAppStore()
const authStore = useAuthStore()
const baseUrl = `${appStore.baseurl}/api`
const request = async (endpoint: string, options: RequestInit = {}) => {
const headers = new Headers(options.headers)
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
if (authStore.token) {
headers.set('Authorization', `Bearer ${authStore.token}`)
}
const config = {
...options,
headers,
body: options.body && typeof options.body === 'object'
? JSON.stringify(options.body)
: options.body
}
// Ici on ne fait QUE la requête.
// On laisse l'appelant décider de ce qu'il fait du status (401, 404, etc.)
return fetch(`${baseUrl}${endpoint}`, config)
}
return {
request,
get: (url: string, options?: RequestInit) =>
request(url, {...options, method: 'GET'}),
post: (url: string, data?: any, options?: RequestInit) =>
request(url, {...options, method: 'POST', body: data}),
put: (url: string, data?: any, options?: RequestInit) =>
request(url, {...options, method: 'PUT', body: data}),
delete: (url: string, options?: RequestInit) =>
request(url, {...options, method: 'DELETE'}),
}
}
+6 -6
View File
@@ -1,7 +1,10 @@
<script lang="ts" setup>
import {ref, watch} from 'vue'
import {useRouter} from 'vue-router'
import {apiFetch} from '@/plugins/api'
import {useApi} from "@/composables/useApi.ts";
const api = useApi()
const router = useRouter()
@@ -47,17 +50,14 @@ async function handleRegister() {
}
try {
const response = await apiFetch('/join', {
method: 'POST',
body
})
const response = await api.post('/join', body)
if (!response.ok) {
const errData = await response.json().catch(() => ({message: 'Erreur lors de l\'inscription'}))
throw new Error(errData.message || 'Échec de l\'inscription')
}
router.push('/login')
await router.push('/login')
} catch (err) {
error.value = err instanceof Error ? err.message : 'Une erreur est survenue'
} finally {
+14 -10
View File
@@ -2,8 +2,10 @@
import {ref} from 'vue'
import {useAuthStore} from '@/stores/auth'
import {useRouter} from 'vue-router'
import {apiFetch} from "@/plugins/api.ts";
import {useApi} from "@/composables/useApi.ts";
const api = useApi()
const authStore = useAuthStore()
const router = useRouter()
@@ -21,24 +23,26 @@ async function handleLogin() {
error.value = ''
try {
// Appel direct via votre helper
const response = await apiFetch('/auth/login', {
method: 'POST',
body: {
username: username.value,
password: password.value
}
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) {
throw new Error('Identifiants invalides')
error.value = "Une erreur est survenue lors de la connexion"
return
}
const data = await response.json()
await authStore.setToken(data.token)
await router.push('/')
} catch (err) {
error.value = 'Erreur de connexion'
error.value = 'Impossible de contacter le serveur'
} finally {
loading.value = false
}
@@ -0,0 +1,11 @@
<script lang="ts" setup>
</script>
<template>
</template>
<style scoped>
</style>
+11
View File
@@ -0,0 +1,11 @@
<script lang="ts" setup>
</script>
<template>
</template>
<style scoped>
</style>
-34
View File
@@ -1,34 +0,0 @@
// frontend/src/utils/api.ts
import {useAuthStore} from '@/stores/auth'
export async function apiFetch(endpoint: string, options: RequestInit = {}) {
const authStore = useAuthStore()
// Remplacer par l'URL réelle de votre backend Rust
const baseUrl = 'http://localhost:8080/api'
const headers = new Headers(options.headers)
headers.set('Content-Type', 'application/json')
if (authStore.token) {
headers.set('Authorization', `Bearer ${authStore.token}`)
}
// Si un body est fourni et est un objet, on le stringify automatiquement
const config = {
...options,
headers,
body: options.body && typeof options.body === 'object'
? JSON.stringify(options.body)
: options.body
}
const response = await fetch(`${baseUrl}${endpoint}`, config)
if (response.status === 401) {
authStore.logout()
window.location.href = '/login'
}
return response
}
+16
View File
@@ -10,6 +10,7 @@ import Index from '@/pages/index.vue'
import {useAuthStore} from '@/stores/auth'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
@@ -27,6 +28,21 @@ const router = createRouter({
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,
children: [
{
path: 'channel/:channelId',
name: 'server-channel',
component: () => import('@/pages/server/channel/index.vue'),
props: true,
},
],
},
{
path: '/admin',
name: 'admin-dashboard',
+4 -3
View File
@@ -1,8 +1,9 @@
// Utilities
import { defineStore } from 'pinia'
import {defineStore} from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({
//
baseurl: 'http://localhost:8080',
}),
})
actions: {}
});
+3 -3
View File
@@ -1,5 +1,5 @@
import {defineStore} from 'pinia'
import {apiFetch} from "@/plugins/api.ts";
import {useApi} from "@/composables/useApi";
export interface User {
id: string
@@ -30,11 +30,11 @@ export const useAuthStore = defineStore('auth', {
return
}
const api = useApi()
try {
const response = await apiFetch('/auth/me', {method: 'GET'})
const response = await api.get('/auth/me')
if (response.ok) {
const data = await response.json()
// On s'attend à ce que /auth/check renvoie l'objet user complet
this.user = data.user
} else {
this.logout()
+6
View File
@@ -0,0 +1,6 @@
import {defineStore} from 'pinia'
export const useCategoryStore = defineStore("category", {
state: () => ({}),
actions: {}
});
+11
View File
@@ -0,0 +1,11 @@
import {defineStore} from 'pinia'
/*
* This file contains the store for channels.
* Centralisation des channels de l'ensemble des serveurs pour faciliter l'intéraction depuis les différents éléments du site
*/
export const useChannelStore = defineStore('channel', {
state: () => ({
channels: []
}),
actions: {}
})
+72
View File
@@ -0,0 +1,72 @@
import {defineStore} from 'pinia';
type GatewayStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
export const useGatewayStore = defineStore('gateway', {
state: () => ({
socket: null as WebSocket | null,
status: 'disconnected' as GatewayStatus,
reconnectAttempts: 0,
}),
actions: {
async connect() {
if (this.socket && this.status === 'connected') {
return
}
this.status = 'connecting'
const wsUrl = `ws://localhost:3000/ws`
const socket = new WebSocket(wsUrl)
socket.onopen = () => {
this.status = 'connected'
this.reconnectAttempts = 0
}
socket.onclose = () => {
this.status = 'disconnected'
this.socket = null
this.scheduleReconnect()
}
socket.onerror = () => {
this.status = 'error'
}
socket.onmessage = event => {
this.handleMessage(event.data)
}
this.socket = socket
},
async disconnect() {
this.socket?.close()
this.socket = null
this.status = 'disconnected'
},
async send(payload: object) {
if (!this.socket || this.status !== 'connected') {
console.warn('WebSocket is not connected')
return
}
this.socket.send(JSON.stringify(payload))
},
async handleMessage(rawData: string) {
},
async scheduleReconnect() {
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000)
this.reconnectAttempts += 1
window.setTimeout(() => {
this.connect()
}, delay)
},
}
});
+6
View File
@@ -0,0 +1,6 @@
import {defineStore} from "pinia";
export const useMessageStore = defineStore("message", {
state: () => ({}),
actions: {}
});
+6
View File
@@ -0,0 +1,6 @@
import {defineStore} from "pinia";
export const useServerStore = defineStore("server", {
state: () => ({}),
actions: {}
});