feat(api,web): auth et first-run — bootstrap admin, login, session, gardes

- api: auth controller/service, config env
- web: App/LoginPage/ProfilePage, garde d'auth, client API, styles
- shared: types/contrats auth
- compose: variables d'environnement first-run

Refs: #14
This commit is contained in:
Git Agent
2026-08-23 11:01:00 +02:00
parent e94524f119
commit 8f7555cfe4
12 changed files with 326 additions and 16 deletions

View File

@ -1,12 +1,14 @@
import type {
BookDto,
BookQueryDto,
AuthStatusDto,
BootstrapAdminDto,
CreateLibraryDto,
JobDto,
LibraryDto,
LoginDto,
ProgressDto,
UpdateAccountDto,
UpdateProgressDto,
UserDto
} from "@readabook/shared";
@ -44,6 +46,9 @@ async function request<T>(path: string, options: RequestOptions = {}): Promise<T
});
if (!response.ok) {
if (response.status === 401 && typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("readabook:session-expired"));
}
const detail = await response.text();
throw new Error(detail || `${response.status} ${response.statusText}`);
}
@ -75,6 +80,9 @@ export const api = {
return { user: null, degraded: false };
}
},
async authStatus(): Promise<AuthStatusDto> {
return request<AuthStatusDto>("/auth/status");
},
async bootstrap(input: BootstrapAdminDto): Promise<UserDto> {
const result = await request<UserDto>("/auth/bootstrap", { method: "POST", body: JSON.stringify(input) });
return result;
@ -86,6 +94,9 @@ export const api = {
async logout(): Promise<void> {
await request<{ ok: true }>("/auth/logout", { method: "POST" });
},
async updateMe(input: UpdateAccountDto): Promise<UserDto> {
return request<UserDto>("/auth/me", { method: "PATCH", body: JSON.stringify(input) });
},
async books(query: Partial<BookQueryDto> = {}): Promise<BookDto[]> {
return request<BookDto[]>(`/books${queryString({ limit: 50, offset: 0, ...query })}`, { fallback: mockBooks });
},