refactor: align codebase with EventCrew domain and trim legacy band stack

- Update API: events, users, policies, routes, resources, migrations
- Remove deprecated models/resources (customers, setlists, invitations, etc.)
- Refresh admin app and docs; remove apps/band

Made-with: Cursor
This commit is contained in:
2026-03-29 23:19:06 +02:00
parent 34e12e00b3
commit 1cb7674d52
1034 changed files with 7453 additions and 8743 deletions

View File

@@ -1,49 +1,40 @@
import { ofetch } from 'ofetch'
import type { AxiosRequestConfig } from 'axios'
import { apiClient } from '@/lib/api-client'
export const $api = ofetch.create({
baseURL: import.meta.env.VITE_API_URL || '/api',
async onRequest({ options }) {
options.headers = options.headers || new Headers()
type ApiOptions = {
method?: string
body?: unknown
query?: Record<string, string | number | boolean | undefined>
onResponseError?: (ctx: { response: { status: number; _data?: { errors?: Record<string, string[]>; message?: string } } }) => void
}
const accessToken = useCookie('accessToken').value
if (accessToken) {
if (options.headers instanceof Headers) {
options.headers.set('Authorization', `Bearer ${accessToken}`)
}
/**
* Thin ofetch-style wrapper around the single axios client (lib/axios).
* Use apiClient from @/lib/axios directly in new code; $api remains for Vuexy template compatibility.
*/
export async function $api<T = unknown>(url: string, options: ApiOptions = {}): Promise<T> {
const { method = 'GET', body, query, onResponseError } = options
const config: AxiosRequestConfig = {
method: method.toLowerCase() as AxiosRequestConfig['method'],
url,
params: query,
data: body,
}
try {
const response = await apiClient.request<T>(config)
return response.data
}
catch (error: any) {
if (onResponseError && error.response) {
onResponseError({
response: {
status: error.response.status,
_data: error.response.data,
},
})
}
// Set default headers
if (options.headers instanceof Headers) {
options.headers.set('Accept', 'application/json')
options.headers.set('Content-Type', 'application/json')
}
},
async onResponseError({ response }) {
// Handle 401 by redirecting to login
if (response.status === 401) {
if (import.meta.env.DEV) {
console.error('❌ API 401 Error:', {
url: response.url,
pathname: window.location.pathname,
willRedirect: window.location.pathname !== '/login',
})
}
// Clear auth data
useCookie('accessToken').value = null
useCookie('userData').value = null
useCookie('userAbilityRules').value = null
// Only redirect if not already on login page
// Add a small delay to prevent redirect loops
if (window.location.pathname !== '/login') {
// Use setTimeout to break any potential redirect loops
setTimeout(() => {
if (window.location.pathname !== '/login') {
window.location.href = '/login'
}
}, 100)
}
}
},
})
throw error
}
}

View File

@@ -0,0 +1,23 @@
import type { Rule } from '@/plugins/casl/ability'
/**
* CASL rules from Spatie role names returned by the API (`/auth/login`, etc.).
*/
export function getUserAbilityRules(roles: string[]): Rule[] {
if (roles.includes('super_admin')) {
return [{ action: 'manage', subject: 'all' }]
}
if (roles.includes('org_admin')) {
return [
{ action: 'read', subject: 'all' },
{ action: 'manage', subject: 'Event' },
{ action: 'manage', subject: 'Organisation' },
]
}
return [
{ action: 'read', subject: 'Event' },
{ action: 'read', subject: 'Organisation' },
]
}