- .NET 8 LTS solution with 7 projects (domain/application/infrastructure/api/shared/pos.core/pos[WPF]) - Central package management (Directory.Packages.props), .editorconfig, global.json pin to 8.0.417 - PostgreSQL 14 dev DB via existing brew service; food_market database created - ASP.NET Identity + OpenIddict 5 (password + refresh token flows) with ephemeral dev keys - EF Core 8 + Npgsql; multi-tenant query filter via reflection over ITenantEntity - Initial migration: 13 tables (Identity + OpenIddict + organizations) - AuthorizationController implements /connect/token; seeders create demo org + admin - Protected /api/me endpoint returns current user + org claims - React 19 + Vite 8 + Tailwind v4 SPA with TanStack Query, React Router 7 - Login flow with dev-admin placeholder, bearer interceptor + refresh token fallback - docs/architecture.md, CLAUDE.md, README.md Verified end-to-end: health check, password grant issues JWT with org_id, web app builds successfully (310 kB gzipped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36 lines
1.1 KiB
TypeScript
36 lines
1.1 KiB
TypeScript
import axios, { AxiosError, type InternalAxiosRequestConfig } from 'axios'
|
|
import { getAccessToken, refreshTokens, clearTokens } from './auth'
|
|
|
|
export const api = axios.create({
|
|
baseURL: '',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
|
|
api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
|
const token = getAccessToken()
|
|
if (token) {
|
|
config.headers.set('Authorization', `Bearer ${token}`)
|
|
}
|
|
return config
|
|
})
|
|
|
|
let refreshing: Promise<string | null> | null = null
|
|
|
|
api.interceptors.response.use(
|
|
(res) => res,
|
|
async (error: AxiosError) => {
|
|
const original = error.config as InternalAxiosRequestConfig & { __retried?: boolean }
|
|
if (error.response?.status === 401 && !original.__retried) {
|
|
original.__retried = true
|
|
refreshing ??= refreshTokens().finally(() => { refreshing = null })
|
|
const newToken = await refreshing
|
|
if (newToken) {
|
|
original.headers.set('Authorization', `Bearer ${newToken}`)
|
|
return api(original)
|
|
}
|
|
clearTokens()
|
|
}
|
|
return Promise.reject(error)
|
|
},
|
|
)
|