All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [x] Pas de régression - [x] TU/TI/TF rédigée - [x] TU/TI/TF OK - [ ] CHANGELOG modifié Co-authored-by: Matthieu <mtholot19@gmail.com> Reviewed-on: #8 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
/**
|
|
* Composable de gestion du site courant (ticket 3 module Sites).
|
|
*
|
|
* Pattern aligne sur `useSidebar` : state singleton au niveau module,
|
|
* hydrate depuis `useAuthStore().user`, mute de maniere optimistic avec
|
|
* rollback si la requete PATCH `/api/me/current-site` echoue.
|
|
*
|
|
* Garantie d'unicite : le flag `switching` bloque les double-clicks
|
|
* concurrents. Le reset explicite est appele au logout
|
|
* (voir `modules/core/pages/logout.vue`).
|
|
*
|
|
* Auto-select : aucun. Le backend (`UserRbacProcessor::ensureCurrentSiteConsistency`)
|
|
* garantit deja l'invariant "user avec sites non vide => currentSite non null"
|
|
* apres tout PATCH /rbac. Le front consomme l'etat renvoye tel quel.
|
|
*
|
|
* Contrainte d'appel : `useCurrentSite()` doit etre invoque au top du
|
|
* `setup()` d'un composant (ou d'un autre composable appele au setup).
|
|
* Les dependances `useI18n`, `useApi` et `useAuthStore` sont resolues
|
|
* a l'initialisation et reutilisees par `switchSite` — ceci evite le
|
|
* "Must be called at the top of a setup function" qui se produirait
|
|
* si on les appelait paresseusement depuis une fonction async declenchee
|
|
* par un handler d'event (hors contexte setup).
|
|
*/
|
|
import { ref } from 'vue'
|
|
import type { Site } from '~/shared/types/sites'
|
|
import { onAuthSessionCleared } from '~/shared/stores/auth'
|
|
|
|
const currentSite = ref<Site | null>(null)
|
|
const availableSites = ref<Site[]>([])
|
|
const switching = ref(false)
|
|
|
|
// Enregistrement unique au niveau module (singleton) : quand clearSession()
|
|
// est appelee par l'intercepteur 401 de useApi, le state local est purgé
|
|
// de la meme facon qu'au logout explicite (logout.vue).
|
|
onAuthSessionCleared(() => {
|
|
currentSite.value = null
|
|
availableSites.value = []
|
|
switching.value = false
|
|
})
|
|
|
|
export function useCurrentSite() {
|
|
// Resolution au setup : les 3 services doivent etre invoques dans un
|
|
// contexte composant. Leur capture ici permet a switchSite() de
|
|
// s'executer plus tard (handler de click, async) sans crash.
|
|
const auth = useAuthStore()
|
|
const api = useApi()
|
|
const { t } = useI18n()
|
|
const { loadSidebar } = useSidebar()
|
|
|
|
/**
|
|
* Synchronise le state singleton depuis le store auth. A appeler au
|
|
* mount du SiteSelector (ou via un watcher sur `auth.user`).
|
|
*/
|
|
function syncFromAuth(): void {
|
|
availableSites.value = auth.user?.sites ?? []
|
|
currentSite.value = auth.user?.currentSite ?? null
|
|
}
|
|
|
|
/**
|
|
* Bascule le site courant. Optimistic UI : la mutation locale precede
|
|
* la requete HTTP. En cas d'echec (`api.patch` throw), l'etat local est
|
|
* restaure — le store auth n'a PAS ete muté a ce stade (la propagation
|
|
* `auth.setCurrentSite` se fait uniquement apres un succes HTTP), donc
|
|
* aucun rollback cote auth n'est necessaire.
|
|
*
|
|
* Garde anti-double-submit : si un switch est deja en vol, le second
|
|
* appel est un no-op silencieux.
|
|
*/
|
|
async function switchSite(site: Site): Promise<void> {
|
|
if (switching.value) {
|
|
return
|
|
}
|
|
|
|
const previousLocal = currentSite.value
|
|
currentSite.value = site
|
|
switching.value = true
|
|
|
|
try {
|
|
await api.patch(
|
|
'/me/current-site',
|
|
{ site: `/api/sites/${site.id}` },
|
|
{ toastSuccessMessage: t('sites.selector.switchSuccess') },
|
|
)
|
|
// Propage au store auth via l'action dediee — plus tracable que
|
|
// la mutation directe et garantit la notification des watchers.
|
|
// N'est appele qu'apres un succes HTTP donc pas de rollback a
|
|
// prevoir sur cette ligne.
|
|
auth.setCurrentSite(site)
|
|
|
|
// Apres un switch reussi : recharger la sidebar (les filtres de
|
|
// modules peuvent dependre du site courant via SiteScopedQueryExtension)
|
|
// et invalider toutes les donnees de page pour eviter que l'utilisateur
|
|
// voie les donnees de l'ancien site sous un toast "Site change".
|
|
try {
|
|
await loadSidebar()
|
|
} catch {
|
|
// No-op : la sidebar non rafraichie n'est pas bloquante.
|
|
}
|
|
try {
|
|
await refreshNuxtData()
|
|
} catch {
|
|
// No-op : certaines pages n'ont pas de useAsyncData a invalider.
|
|
}
|
|
} catch (error) {
|
|
currentSite.value = previousLocal
|
|
throw error
|
|
} finally {
|
|
switching.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vide l'etat singleton. Appele au logout pour eviter qu'un user
|
|
* suivant (connecte sur le meme onglet) voie les sites de l'ancien.
|
|
*/
|
|
function resetCurrentSite(): void {
|
|
currentSite.value = null
|
|
availableSites.value = []
|
|
switching.value = false
|
|
}
|
|
|
|
return {
|
|
currentSite,
|
|
availableSites,
|
|
switching,
|
|
switchSite,
|
|
syncFromAuth,
|
|
resetCurrentSite,
|
|
}
|
|
}
|