1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 21:19:58 +01:00

feat(frontend): TypeScript-Monorepo mit Terminal-UI für Effigenix ERP

Monorepo-Setup (pnpm workspaces) mit vier shared Packages und einer TUI-App:

Shared Packages:
- @effigenix/types: TypeScript-DTOs (UserDTO, RoleDTO, AuthDTO, Enums)
- @effigenix/config: API-Konfiguration und Shared Constants
- @effigenix/validation: Zod-Schemas für Username, E-Mail und Passwort
- @effigenix/api-client: axios-Client mit JWT-Handling (proaktiver + reaktiver
  Token-Refresh), AuthInterceptor, ErrorInterceptor, Resources für auth/users/roles

TUI (apps/cli, Ink 5 / React):
- Authentication: Login/Logout, Session-Restore beim Start, JWT-Refresh
- User Management: Liste, Anlage (Zod-Inline-Validation), Detailansicht,
  Passwort ändern, Sperren/Entsperren mit ConfirmDialog
- Role Management: Liste, Detailansicht, Zuweisen/Entfernen per RoleSelectList (↑↓)
- UX: SuccessDisplay (Auto-Dismiss 3 s), ConfirmDialog (J/N),
  FormInput mit Inline-Fehlern, StatusBar mit API-URL
- Layout: Fullscreen-Modus (alternate screen buffer), Header mit eingeloggtem User
- Tests: vitest + ink-testing-library (15 Tests)
This commit is contained in:
Sebastian Frick 2026-02-18 12:23:11 +01:00
parent 87123df2e4
commit bbe9e87c33
65 changed files with 6955 additions and 1 deletions

View file

@ -0,0 +1,5 @@
import { createEffigenixClient } from '@effigenix/api-client';
import type { EffigenixClient } from '@effigenix/api-client';
import { tokenStorage } from './token-storage.js';
export const client: EffigenixClient = createEffigenixClient(tokenStorage);

View file

@ -0,0 +1,82 @@
/**
* Filesystem-based TokenProvider.
* Stores tokens in ~/.effigenix/config.json with file permissions 600.
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';
import type { TokenProvider } from '@effigenix/api-client';
interface StoredAuth {
accessToken: string;
refreshToken: string;
expiresAt: string;
}
interface StoredConfig {
apiBaseUrl?: string;
auth?: StoredAuth;
}
const CONFIG_DIR = path.join(os.homedir(), '.effigenix');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
async function loadConfig(): Promise<StoredConfig> {
try {
const raw = await fs.readFile(CONFIG_FILE, 'utf-8');
return JSON.parse(raw) as StoredConfig;
} catch {
return {};
}
}
async function saveConfig(config: StoredConfig): Promise<void> {
await fs.mkdir(CONFIG_DIR, { recursive: true });
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
}
export const tokenStorage: TokenProvider = {
async getAccessToken(): Promise<string | null> {
const config = await loadConfig();
if (!config.auth) return null;
// Proaktiver Refresh: wenn weniger als 5 Minuten bis Ablauf
const expiresAt = new Date(config.auth.expiresAt).getTime();
const timeUntilExpiry = expiresAt - Date.now();
if (timeUntilExpiry < 5 * 60 * 1000) {
// Token bald abgelaufen Aufrufer (RefreshInterceptor) übernimmt Refresh
return null;
}
return config.auth.accessToken;
},
async getRefreshToken(): Promise<string | null> {
const config = await loadConfig();
return config.auth?.refreshToken ?? null;
},
async saveTokens(accessToken: string, refreshToken: string, expiresAt: string): Promise<void> {
const config = await loadConfig();
config.auth = { accessToken, refreshToken, expiresAt };
await saveConfig(config);
},
async clearTokens(): Promise<void> {
const config = await loadConfig();
delete config.auth;
await saveConfig(config);
},
};
export async function getStoredApiBaseUrl(): Promise<string | undefined> {
const config = await loadConfig();
return config.apiBaseUrl;
}
export async function saveApiBaseUrl(url: string): Promise<void> {
const config = await loadConfig();
config.apiBaseUrl = url;
await saveConfig(config);
}