Datenbank bereinigt / Gitea-Integration gefixt
Dieser Commit ist enthalten in:
committet von
Server Deploy
Ursprung
395598c2b0
Commit
c21be47428
@ -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
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren