diff --git a/frontend/src/composables/useApi.ts b/frontend/src/composables/useApi.ts new file mode 100644 index 0000000..3645976 --- /dev/null +++ b/frontend/src/composables/useApi.ts @@ -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'}), + } +} \ No newline at end of file diff --git a/frontend/src/pages/join.vue b/frontend/src/pages/join.vue index 4bd1242..7b3cea7 100644 --- a/frontend/src/pages/join.vue +++ b/frontend/src/pages/join.vue @@ -1,7 +1,10 @@ + + + + \ No newline at end of file diff --git a/frontend/src/pages/server/index.vue b/frontend/src/pages/server/index.vue new file mode 100644 index 0000000..27324a8 --- /dev/null +++ b/frontend/src/pages/server/index.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/plugins/api.ts b/frontend/src/plugins/api.ts deleted file mode 100644 index b43e486..0000000 --- a/frontend/src/plugins/api.ts +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 870afa2..e5ece13 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -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', diff --git a/frontend/src/stores/app.ts b/frontend/src/stores/app.ts index 7429543..e1f11cb 100644 --- a/frontend/src/stores/app.ts +++ b/frontend/src/stores/app.ts @@ -1,8 +1,9 @@ // Utilities -import { defineStore } from 'pinia' +import {defineStore} from 'pinia' export const useAppStore = defineStore('app', { state: () => ({ - // + baseurl: 'http://localhost:8080', }), -}) + actions: {} +}); diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index b9b98b0..444c835 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -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() diff --git a/frontend/src/stores/category.ts b/frontend/src/stores/category.ts new file mode 100644 index 0000000..5a9c760 --- /dev/null +++ b/frontend/src/stores/category.ts @@ -0,0 +1,6 @@ +import {defineStore} from 'pinia' + +export const useCategoryStore = defineStore("category", { + state: () => ({}), + actions: {} +}); \ No newline at end of file diff --git a/frontend/src/stores/channel.ts b/frontend/src/stores/channel.ts new file mode 100644 index 0000000..bfa569e --- /dev/null +++ b/frontend/src/stores/channel.ts @@ -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: {} +}) \ No newline at end of file diff --git a/frontend/src/stores/gateway.ts b/frontend/src/stores/gateway.ts new file mode 100644 index 0000000..7f210b6 --- /dev/null +++ b/frontend/src/stores/gateway.ts @@ -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) + }, + } +}); \ No newline at end of file diff --git a/frontend/src/stores/message.ts b/frontend/src/stores/message.ts new file mode 100644 index 0000000..eb043c4 --- /dev/null +++ b/frontend/src/stores/message.ts @@ -0,0 +1,6 @@ +import {defineStore} from "pinia"; + +export const useMessageStore = defineStore("message", { + state: () => ({}), + actions: {} +}); diff --git a/frontend/src/stores/server.ts b/frontend/src/stores/server.ts new file mode 100644 index 0000000..b53262a --- /dev/null +++ b/frontend/src/stores/server.ts @@ -0,0 +1,6 @@ +import {defineStore} from "pinia"; + +export const useServerStore = defineStore("server", { + state: () => ({}), + actions: {} +}); \ No newline at end of file