Init
This commit is contained in:
@@ -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'}),
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
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>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Utilities
|
||||
import { defineStore } from 'pinia'
|
||||
import {defineStore} from 'pinia'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
//
|
||||
baseurl: 'http://localhost:8080',
|
||||
}),
|
||||
})
|
||||
actions: {}
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import {defineStore} from 'pinia'
|
||||
|
||||
export const useCategoryStore = defineStore("category", {
|
||||
state: () => ({}),
|
||||
actions: {}
|
||||
});
|
||||
@@ -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: {}
|
||||
})
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import {defineStore} from "pinia";
|
||||
|
||||
export const useMessageStore = defineStore("message", {
|
||||
state: () => ({}),
|
||||
actions: {}
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import {defineStore} from "pinia";
|
||||
|
||||
export const useServerStore = defineStore("server", {
|
||||
state: () => ({}),
|
||||
actions: {}
|
||||
});
|
||||
Reference in New Issue
Block a user