Datenbank bereinigt / Gitea-Integration gefixt

Dieser Commit ist enthalten in:
hendrik_gebhardt@gmx.de
2026-01-04 00:24:11 +00:00
committet von Server Deploy
Ursprung 395598c2b0
Commit c21be47428
37 geänderte Dateien mit 30993 neuen und 809 gelöschten Zeilen

Datei anzeigen

@ -7,9 +7,25 @@ class ApiClient {
constructor() {
this.baseUrl = '/api';
this.token = null;
this.refreshToken = null;
this.csrfToken = null;
this.refreshingToken = false;
this.requestQueue = [];
this.refreshTimer = null;
this.init();
}
init() {
// Token aus Storage laden
this.token = localStorage.getItem('auth_token');
this.refreshToken = localStorage.getItem('refresh_token');
this.csrfToken = sessionStorage.getItem('csrf_token');
console.log('[API] init() - Token loaded:', this.token ? this.token.substring(0, 20) + '...' : 'NULL');
// Starte Timer wenn Token und Refresh-Token vorhanden sind
if (this.token && this.refreshToken) {
this.startTokenRefreshTimer();
}
}
// Token Management
@ -18,10 +34,22 @@ class ApiClient {
this.token = token;
if (token) {
localStorage.setItem('auth_token', token);
// Starte proaktiven Token-Refresh Timer (nach 10 Minuten)
this.startTokenRefreshTimer();
} else {
this.token = null;
localStorage.removeItem('auth_token');
localStorage.removeItem('current_user');
this.clearTokenRefreshTimer();
}
}
setRefreshToken(token) {
this.refreshToken = token;
if (token) {
localStorage.setItem('refresh_token', token);
} else {
localStorage.removeItem('refresh_token');
}
}
@ -49,6 +77,94 @@ class ApiClient {
return token;
}
// Refresh Access Token using Refresh Token
async refreshAccessToken() {
if (this.refreshingToken) {
// Warte auf laufenden Refresh
return new Promise((resolve) => {
const checkRefresh = () => {
if (!this.refreshingToken) {
resolve();
} else {
setTimeout(checkRefresh, 100);
}
};
checkRefresh();
});
}
this.refreshingToken = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('Kein Refresh-Token vorhanden');
}
console.log('[API] Refreshing access token...');
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken })
});
if (!response.ok) {
throw new Error(`Refresh failed: ${response.status}`);
}
const data = await response.json();
this.setToken(data.token);
if (data.csrfToken) {
this.setCsrfToken(data.csrfToken);
}
console.log('[API] Token refresh successful');
window.dispatchEvent(new CustomEvent('auth:token-refreshed', {
detail: { token: data.token }
}));
} catch (error) {
console.log('[API] Token refresh error:', error.message);
throw error;
} finally {
this.refreshingToken = false;
}
}
// Handle authentication failure
handleAuthFailure() {
console.log('[API] Authentication failed - clearing tokens');
this.setToken(null);
this.setRefreshToken(null);
this.setCsrfToken(null);
window.dispatchEvent(new CustomEvent('auth:logout'));
}
// Proaktiver Token-Refresh Timer
startTokenRefreshTimer() {
this.clearTokenRefreshTimer();
// Refresh nach 10 Minuten (Token läuft nach 15 Minuten ab)
this.refreshTimer = setTimeout(async () => {
if (this.refreshToken && !this.refreshingToken) {
try {
console.log('[API] Proactive token refresh...');
await this.refreshAccessToken();
} catch (error) {
console.log('[API] Proactive refresh failed:', error.message);
// Bei Fehler nicht automatisch ausloggen, warten bis Token wirklich abläuft
}
}
}, 10 * 60 * 1000); // 10 Minuten
}
clearTokenRefreshTimer() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
}
// Base Request Method
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
@ -58,6 +174,11 @@ class ApiClient {
...options.headers
};
// Sicherstellen, dass Token aktuell ist
if (!this.token && localStorage.getItem('auth_token')) {
this.init();
}
// Add auth token
const token = this.getToken();
console.log('[API] Request:', endpoint, 'Token:', token ? token.substring(0, 20) + '...' : 'NULL');
@ -103,22 +224,25 @@ class ApiClient {
// Handle 401 Unauthorized
if (response.status === 401) {
// Token der für diesen Request verwendet wurde
const requestToken = token;
const currentToken = localStorage.getItem('auth_token');
console.log('[API] 401 received for:', endpoint);
console.log('[API] Request token:', requestToken ? requestToken.substring(0, 20) + '...' : 'NULL');
console.log('[API] Current token:', currentToken ? currentToken.substring(0, 20) + '...' : 'NULL');
// Nur ausloggen wenn der Token der gleiche ist (kein neuer Login in der Zwischenzeit)
if (!currentToken || currentToken === requestToken) {
console.log('[API] Token invalid, triggering logout');
this.setToken(null);
window.dispatchEvent(new CustomEvent('auth:logout'));
} else {
console.log('[API] 401 ignored - new login occurred while request was in flight');
// Versuche Token mit Refresh-Token zu erneuern
if (this.refreshToken && !this.refreshingToken && !options._tokenRefreshAttempted) {
console.log('[API] Attempting token refresh...');
try {
await this.refreshAccessToken();
// Wiederhole original Request mit neuem Token
return this.request(endpoint, { ...options, _tokenRefreshAttempted: true });
} catch (refreshError) {
console.log('[API] Token refresh failed:', refreshError.message);
// Fallback zum Logout
this.handleAuthFailure();
throw new ApiError('Sitzung abgelaufen', 401);
}
}
// Kein Refresh-Token oder Refresh bereits versucht
this.handleAuthFailure();
throw new ApiError('Sitzung abgelaufen', 401);
}
@ -297,6 +421,12 @@ class ApiClient {
const response = await this.post('/auth/login', { username, password });
console.log('[API] login() response:', response ? 'OK' : 'NULL', 'token:', response?.token ? 'EXISTS' : 'MISSING');
this.setToken(response.token);
// Store refresh token if provided (new auth system)
if (response.refreshToken) {
this.setRefreshToken(response.refreshToken);
}
// Store CSRF token from login response
if (response.csrfToken) {
this.setCsrfToken(response.csrfToken);
@ -309,6 +439,7 @@ class ApiClient {
await this.post('/auth/logout', {});
} finally {
this.setToken(null);
this.setRefreshToken(null);
this.setCsrfToken(null);
}
}
@ -1071,6 +1202,62 @@ class ApiClient {
async searchKnowledge(query) {
return this.get(`/knowledge/search?q=${encodeURIComponent(query)}`);
}
// =====================
// CODING
// =====================
async getCodingDirectories() {
return this.get('/coding/directories');
}
async createCodingDirectory(data) {
return this.post('/coding/directories', data);
}
async updateCodingDirectory(id, data) {
return this.put(`/coding/directories/${id}`, data);
}
async deleteCodingDirectory(id) {
return this.delete(`/coding/directories/${id}`);
}
async getCodingDirectoryStatus(id) {
return this.get(`/coding/directories/${id}/status`);
}
async codingGitFetch(id) {
return this.post(`/coding/directories/${id}/fetch`);
}
async codingGitPull(id) {
return this.post(`/coding/directories/${id}/pull`);
}
async codingGitPush(id, force = false) {
return this.post(`/coding/directories/${id}/push`, { force });
}
async codingGitCommit(id, message) {
return this.post(`/coding/directories/${id}/commit`, { message });
}
async getCodingDirectoryBranches(id) {
return this.get(`/coding/directories/${id}/branches`);
}
async codingGitCheckout(id, branch) {
return this.post(`/coding/directories/${id}/checkout`, { branch });
}
async getCodingDirectoryCommits(id, limit = 20) {
return this.get(`/coding/directories/${id}/commits?limit=${limit}`);
}
async validateCodingPath(path) {
return this.post('/coding/validate-path', { path });
}
}
// Custom API Error Class