feat: consolidate frontend API layer, add query-client, and harden backend Fase 1

Frontend:
- Consolidate duplicate API layers into single src/lib/axios.ts per app
- Remove src/lib/api-client.ts and src/utils/api.ts (admin)
- Add src/lib/query-client.ts with TanStack Query config per app
- Update all imports and auto-import config

Backend:
- Fix organisations.billing_status default to 'trial'
- Fix user_invitations.invited_by_user_id to nullOnDelete
- Add MeResource with separated app_roles and pivot-based org roles
- Add cross-org check to EventPolicy view() and update()
- Restrict EventPolicy create/update to org_admin/event_manager (not org_member)
- Attach creator as org_admin on organisation store
- Add query scopes to Event and UserInvitation models
- Improve factories with Dutch test data
- Expand test suite from 29 to 41 tests (90 assertions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 17:35:34 +02:00
parent 611c311854
commit 0d24506c89
36 changed files with 454 additions and 118 deletions

57
apps/app/src/lib/axios.ts Normal file
View File

@@ -0,0 +1,57 @@
import axios from 'axios'
import { parse } from 'cookie-es'
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
const apiClient: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
timeout: 30000,
})
function getAccessToken(): string | null {
if (typeof document === 'undefined') return null
const cookies = parse(document.cookie)
return cookies.accessToken ?? null
}
apiClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = getAccessToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
if (import.meta.env.DEV) {
console.log(`🚀 ${config.method?.toUpperCase()} ${config.url}`, config.data)
}
return config
},
error => Promise.reject(error),
)
apiClient.interceptors.response.use(
response => {
if (import.meta.env.DEV) {
console.log(`${response.status} ${response.config.url}`, response.data)
}
return response
},
error => {
if (import.meta.env.DEV) {
console.error(`${error.response?.status} ${error.config?.url}`, error.response?.data)
}
if (error.response?.status === 401) {
document.cookie = 'accessToken=; path=/; max-age=0'
document.cookie = 'userData=; path=/; max-age=0'
document.cookie = 'userAbilityRules=; path=/; max-age=0'
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
window.location.href = '/login'
}
}
return Promise.reject(error)
},
)
export { apiClient }