Initial commit
Dieser Commit ist enthalten in:
862
frontend/js/api.js
Normale Datei
862
frontend/js/api.js
Normale Datei
@ -0,0 +1,862 @@
|
||||
/**
|
||||
* TASKMATE - API Client
|
||||
* =====================
|
||||
*/
|
||||
|
||||
class ApiClient {
|
||||
constructor() {
|
||||
this.baseUrl = '/api';
|
||||
this.token = null;
|
||||
this.csrfToken = null;
|
||||
this.refreshingToken = false;
|
||||
this.requestQueue = [];
|
||||
}
|
||||
|
||||
// Token Management
|
||||
setToken(token) {
|
||||
this.token = token;
|
||||
if (token) {
|
||||
localStorage.setItem('auth_token', token);
|
||||
} else {
|
||||
localStorage.removeItem('auth_token');
|
||||
}
|
||||
}
|
||||
|
||||
getToken() {
|
||||
if (!this.token) {
|
||||
this.token = localStorage.getItem('auth_token');
|
||||
}
|
||||
return this.token;
|
||||
}
|
||||
|
||||
setCsrfToken(token) {
|
||||
this.csrfToken = token;
|
||||
if (token) {
|
||||
sessionStorage.setItem('csrf_token', token);
|
||||
} else {
|
||||
sessionStorage.removeItem('csrf_token');
|
||||
}
|
||||
}
|
||||
|
||||
getCsrfToken() {
|
||||
if (!this.csrfToken) {
|
||||
this.csrfToken = sessionStorage.getItem('csrf_token');
|
||||
}
|
||||
return this.csrfToken;
|
||||
}
|
||||
|
||||
// Base Request Method
|
||||
async request(endpoint, options = {}) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
};
|
||||
|
||||
// Add auth token
|
||||
const token = this.getToken();
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// Add CSRF token for mutating requests
|
||||
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(options.method)) {
|
||||
const csrfToken = this.getCsrfToken();
|
||||
if (csrfToken) {
|
||||
headers['X-CSRF-Token'] = csrfToken;
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
method: options.method || 'GET',
|
||||
headers,
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
|
||||
if (options.body && config.method !== 'GET') {
|
||||
config.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
|
||||
// Update CSRF token if provided
|
||||
const newCsrfToken = response.headers.get('X-CSRF-Token');
|
||||
if (newCsrfToken) {
|
||||
this.setCsrfToken(newCsrfToken);
|
||||
}
|
||||
|
||||
// Handle 401 Unauthorized
|
||||
if (response.status === 401) {
|
||||
this.setToken(null);
|
||||
window.dispatchEvent(new CustomEvent('auth:logout'));
|
||||
throw new ApiError('Sitzung abgelaufen', 401);
|
||||
}
|
||||
|
||||
// Handle CSRF errors - update token and retry once
|
||||
if (response.status === 403) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
if (errorData.code === 'CSRF_ERROR' && errorData.csrfToken) {
|
||||
// Store the new CSRF token
|
||||
this.setCsrfToken(errorData.csrfToken);
|
||||
|
||||
// Retry the request once with the new token
|
||||
if (!options._csrfRetried) {
|
||||
return this.request(endpoint, { ...options, _csrfRetried: true });
|
||||
}
|
||||
}
|
||||
throw new ApiError(
|
||||
errorData.error || 'Zugriff verweigert',
|
||||
response.status,
|
||||
errorData
|
||||
);
|
||||
}
|
||||
|
||||
// Handle other errors
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new ApiError(
|
||||
errorData.error || `HTTP Error ${response.status}`,
|
||||
response.status,
|
||||
errorData
|
||||
);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Network error
|
||||
if (!navigator.onLine) {
|
||||
throw new ApiError('Keine Internetverbindung', 0, { offline: true });
|
||||
}
|
||||
|
||||
throw new ApiError('Netzwerkfehler: ' + error.message, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience Methods
|
||||
get(endpoint, options = {}) {
|
||||
return this.request(endpoint, { ...options, method: 'GET' });
|
||||
}
|
||||
|
||||
post(endpoint, body, options = {}) {
|
||||
return this.request(endpoint, { ...options, method: 'POST', body });
|
||||
}
|
||||
|
||||
put(endpoint, body, options = {}) {
|
||||
return this.request(endpoint, { ...options, method: 'PUT', body });
|
||||
}
|
||||
|
||||
patch(endpoint, body, options = {}) {
|
||||
return this.request(endpoint, { ...options, method: 'PATCH', body });
|
||||
}
|
||||
|
||||
delete(endpoint, options = {}) {
|
||||
return this.request(endpoint, { ...options, method: 'DELETE' });
|
||||
}
|
||||
|
||||
// File Upload
|
||||
async uploadFile(endpoint, file, onProgress, _retried = false) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const formData = new FormData();
|
||||
formData.append('files', file);
|
||||
|
||||
const token = this.getToken();
|
||||
const csrfToken = this.getCsrfToken();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('POST', url);
|
||||
|
||||
if (token) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
if (csrfToken) {
|
||||
xhr.setRequestHeader('X-CSRF-Token', csrfToken);
|
||||
}
|
||||
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable && onProgress) {
|
||||
const percentage = Math.round((e.loaded / e.total) * 100);
|
||||
onProgress(percentage);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', async () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
resolve(JSON.parse(xhr.responseText));
|
||||
} catch {
|
||||
resolve(xhr.responseText);
|
||||
}
|
||||
} else if (xhr.status === 403 && !_retried) {
|
||||
// Handle CSRF error - get new token and retry
|
||||
try {
|
||||
const errorData = JSON.parse(xhr.responseText);
|
||||
if (errorData.code === 'CSRF_ERROR' && errorData.csrfToken) {
|
||||
this.setCsrfToken(errorData.csrfToken);
|
||||
// Retry with new token
|
||||
try {
|
||||
const result = await this.uploadFile(endpoint, file, onProgress, true);
|
||||
resolve(result);
|
||||
} catch (retryError) {
|
||||
reject(retryError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
reject(new ApiError('Upload fehlgeschlagen', xhr.status));
|
||||
} else {
|
||||
try {
|
||||
const error = JSON.parse(xhr.responseText);
|
||||
reject(new ApiError(error.error || 'Upload fehlgeschlagen', xhr.status));
|
||||
} catch {
|
||||
reject(new ApiError('Upload fehlgeschlagen', xhr.status));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', () => {
|
||||
reject(new ApiError('Netzwerkfehler beim Upload', 0));
|
||||
});
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
// Download File
|
||||
async downloadFile(endpoint, filename) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const token = this.getToken();
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError('Download fehlgeschlagen', response.status);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// AUTH ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async login(username, password) {
|
||||
const response = await this.post('/auth/login', { username, password });
|
||||
this.setToken(response.token);
|
||||
// Store CSRF token from login response
|
||||
if (response.csrfToken) {
|
||||
this.setCsrfToken(response.csrfToken);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await this.post('/auth/logout', {});
|
||||
} finally {
|
||||
this.setToken(null);
|
||||
this.setCsrfToken(null);
|
||||
}
|
||||
}
|
||||
|
||||
async changePassword(currentPassword, newPassword) {
|
||||
return this.post('/auth/password', { currentPassword, newPassword });
|
||||
}
|
||||
|
||||
async updateUserColor(color) {
|
||||
return this.put('/auth/color', { color });
|
||||
}
|
||||
|
||||
async getUsers() {
|
||||
return this.get('/auth/users');
|
||||
}
|
||||
|
||||
// =====================
|
||||
// PROJECT ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getProjects() {
|
||||
return this.get('/projects');
|
||||
}
|
||||
|
||||
async getProject(id) {
|
||||
return this.get(`/projects/${id}`);
|
||||
}
|
||||
|
||||
async createProject(data) {
|
||||
return this.post('/projects', data);
|
||||
}
|
||||
|
||||
async updateProject(id, data) {
|
||||
return this.put(`/projects/${id}`, data);
|
||||
}
|
||||
|
||||
async deleteProject(id, force = true) {
|
||||
return this.delete(`/projects/${id}?force=${force}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// COLUMN ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getColumns(projectId) {
|
||||
return this.get(`/columns/${projectId}`);
|
||||
}
|
||||
|
||||
async createColumn(projectId, data) {
|
||||
return this.post('/columns', { ...data, projectId });
|
||||
}
|
||||
|
||||
async updateColumn(projectId, columnId, data) {
|
||||
return this.put(`/columns/${columnId}`, data);
|
||||
}
|
||||
|
||||
async deleteColumn(projectId, columnId) {
|
||||
return this.delete(`/columns/${columnId}`);
|
||||
}
|
||||
|
||||
async reorderColumns(projectId, columnId, newPosition) {
|
||||
return this.put(`/columns/${columnId}/position`, { newPosition });
|
||||
}
|
||||
|
||||
// =====================
|
||||
// TASK ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getTasks(projectId, params = {}) {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
const endpoint = `/tasks/project/${projectId}${queryString ? '?' + queryString : ''}`;
|
||||
return this.get(endpoint);
|
||||
}
|
||||
|
||||
async getTask(projectId, taskId) {
|
||||
return this.get(`/tasks/${taskId}`);
|
||||
}
|
||||
|
||||
async createTask(projectId, data) {
|
||||
return this.post('/tasks', { ...data, projectId });
|
||||
}
|
||||
|
||||
async updateTask(projectId, taskId, data) {
|
||||
return this.put(`/tasks/${taskId}`, data);
|
||||
}
|
||||
|
||||
async deleteTask(projectId, taskId) {
|
||||
return this.delete(`/tasks/${taskId}`);
|
||||
}
|
||||
|
||||
async moveTask(projectId, taskId, columnId, position) {
|
||||
return this.put(`/tasks/${taskId}/move`, { columnId, position });
|
||||
}
|
||||
|
||||
async duplicateTask(projectId, taskId) {
|
||||
return this.post(`/tasks/${taskId}/duplicate`, {});
|
||||
}
|
||||
|
||||
async archiveTask(projectId, taskId) {
|
||||
return this.put(`/tasks/${taskId}/archive`, { archived: true });
|
||||
}
|
||||
|
||||
async restoreTask(projectId, taskId) {
|
||||
return this.put(`/tasks/${taskId}/archive`, { archived: false });
|
||||
}
|
||||
|
||||
async getTaskHistory(projectId, taskId) {
|
||||
return this.get(`/tasks/${taskId}/history`);
|
||||
}
|
||||
|
||||
async searchTasks(projectId, query) {
|
||||
return this.get(`/tasks/search?projectId=${projectId}&q=${encodeURIComponent(query)}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// SUBTASK ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getSubtasks(projectId, taskId) {
|
||||
return this.get(`/subtasks/${taskId}`);
|
||||
}
|
||||
|
||||
async createSubtask(projectId, taskId, data) {
|
||||
return this.post('/subtasks', { ...data, taskId });
|
||||
}
|
||||
|
||||
async updateSubtask(projectId, taskId, subtaskId, data) {
|
||||
return this.put(`/subtasks/${subtaskId}`, data);
|
||||
}
|
||||
|
||||
async deleteSubtask(projectId, taskId, subtaskId) {
|
||||
return this.delete(`/subtasks/${subtaskId}`);
|
||||
}
|
||||
|
||||
async reorderSubtasks(projectId, taskId, subtaskId, newPosition) {
|
||||
return this.put(`/subtasks/${subtaskId}/position`, { newPosition });
|
||||
}
|
||||
|
||||
// =====================
|
||||
// COMMENT ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getComments(projectId, taskId) {
|
||||
return this.get(`/comments/${taskId}`);
|
||||
}
|
||||
|
||||
async createComment(projectId, taskId, data) {
|
||||
return this.post('/comments', { ...data, taskId });
|
||||
}
|
||||
|
||||
async updateComment(projectId, taskId, commentId, data) {
|
||||
return this.put(`/comments/${commentId}`, data);
|
||||
}
|
||||
|
||||
async deleteComment(projectId, taskId, commentId) {
|
||||
return this.delete(`/comments/${commentId}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// LABEL ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getLabels(projectId) {
|
||||
return this.get(`/labels/${projectId}`);
|
||||
}
|
||||
|
||||
async createLabel(projectId, data) {
|
||||
return this.post('/labels', { ...data, projectId });
|
||||
}
|
||||
|
||||
async updateLabel(projectId, labelId, data) {
|
||||
return this.put(`/labels/${labelId}`, data);
|
||||
}
|
||||
|
||||
async deleteLabel(projectId, labelId) {
|
||||
return this.delete(`/labels/${labelId}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// FILE ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getFiles(projectId, taskId) {
|
||||
return this.get(`/files/${taskId}`);
|
||||
}
|
||||
|
||||
async uploadTaskFile(projectId, taskId, file, onProgress) {
|
||||
return this.uploadFile(`/files/${taskId}`, file, onProgress);
|
||||
}
|
||||
|
||||
async downloadTaskFile(projectId, taskId, fileId, filename) {
|
||||
return this.downloadFile(`/files/download/${fileId}`, filename);
|
||||
}
|
||||
|
||||
async deleteFile(projectId, taskId, fileId) {
|
||||
return this.delete(`/files/${fileId}`);
|
||||
}
|
||||
|
||||
getFilePreviewUrl(projectId, taskId, fileId) {
|
||||
const token = this.getToken();
|
||||
const url = `${this.baseUrl}/files/preview/${fileId}`;
|
||||
// Add token as query parameter for img src authentication
|
||||
return token ? `${url}?token=${encodeURIComponent(token)}` : url;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// LINK ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getLinks(projectId, taskId) {
|
||||
return this.get(`/links/${taskId}`);
|
||||
}
|
||||
|
||||
async createLink(projectId, taskId, data) {
|
||||
return this.post('/links', { ...data, taskId });
|
||||
}
|
||||
|
||||
async updateLink(projectId, taskId, linkId, data) {
|
||||
return this.put(`/links/${linkId}`, data);
|
||||
}
|
||||
|
||||
async deleteLink(projectId, taskId, linkId) {
|
||||
return this.delete(`/links/${linkId}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// TEMPLATE ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getTemplates() {
|
||||
return this.get('/templates');
|
||||
}
|
||||
|
||||
async getTemplate(id) {
|
||||
return this.get(`/templates/${id}`);
|
||||
}
|
||||
|
||||
async createTemplate(data) {
|
||||
return this.post('/templates', data);
|
||||
}
|
||||
|
||||
async updateTemplate(id, data) {
|
||||
return this.put(`/templates/${id}`, data);
|
||||
}
|
||||
|
||||
async deleteTemplate(id) {
|
||||
return this.delete(`/templates/${id}`);
|
||||
}
|
||||
|
||||
async createTaskFromTemplate(projectId, templateId, columnId) {
|
||||
return this.post(`/templates/${templateId}/apply`, { projectId, columnId });
|
||||
}
|
||||
|
||||
// =====================
|
||||
// STATS ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getStats(projectId) {
|
||||
const endpoint = projectId ? `/stats/dashboard?projectId=${projectId}` : '/stats/dashboard';
|
||||
return this.get(endpoint);
|
||||
}
|
||||
|
||||
async getCompletionStats(projectId, weeks = 8) {
|
||||
const endpoint = projectId
|
||||
? `/stats/completed-per-week?projectId=${projectId}&weeks=${weeks}`
|
||||
: `/stats/completed-per-week?weeks=${weeks}`;
|
||||
return this.get(endpoint);
|
||||
}
|
||||
|
||||
async getTimeStats(projectId) {
|
||||
return this.get('/stats/time-per-project');
|
||||
}
|
||||
|
||||
async getOverdueTasks(projectId) {
|
||||
// Placeholder - overdue data comes from dashboard stats
|
||||
return [];
|
||||
}
|
||||
|
||||
async getDueTodayTasks(projectId) {
|
||||
// Placeholder - due today data comes from dashboard stats
|
||||
return [];
|
||||
}
|
||||
|
||||
// =====================
|
||||
// EXPORT/IMPORT ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async exportProject(projectId, format = 'json') {
|
||||
const response = await this.get(`/export/project/${projectId}?format=${format}`);
|
||||
|
||||
if (format === 'csv') {
|
||||
// For CSV, trigger download
|
||||
const blob = new Blob([response], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `project_${projectId}_export.csv`;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async exportAll(format = 'json') {
|
||||
return this.get(`/export/all?format=${format}`);
|
||||
}
|
||||
|
||||
async importProject(data) {
|
||||
return this.post('/import', data);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// HEALTH ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async healthCheck() {
|
||||
return this.get('/health');
|
||||
}
|
||||
|
||||
async getSystemInfo() {
|
||||
return this.get('/health/info');
|
||||
}
|
||||
|
||||
// =====================
|
||||
// ADMIN ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getAdminUsers() {
|
||||
return this.get('/admin/users');
|
||||
}
|
||||
|
||||
async createAdminUser(data) {
|
||||
return this.post('/admin/users', data);
|
||||
}
|
||||
|
||||
async updateAdminUser(userId, data) {
|
||||
return this.put(`/admin/users/${userId}`, data);
|
||||
}
|
||||
|
||||
async deleteAdminUser(userId) {
|
||||
return this.delete(`/admin/users/${userId}`);
|
||||
}
|
||||
|
||||
async getUploadSettings() {
|
||||
return this.get('/admin/upload-settings');
|
||||
}
|
||||
|
||||
async updateUploadSettings(settings) {
|
||||
return this.put('/admin/upload-settings', settings);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// PROPOSALS (GENEHMIGUNGEN) ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getProposals(sort = 'date', archived = false, projectId = null) {
|
||||
let url = `/proposals?sort=${sort}&archived=${archived ? '1' : '0'}`;
|
||||
if (projectId) {
|
||||
url += `&projectId=${projectId}`;
|
||||
}
|
||||
return this.get(url);
|
||||
}
|
||||
|
||||
async createProposal(data) {
|
||||
return this.post('/proposals', data);
|
||||
}
|
||||
|
||||
async approveProposal(proposalId, approved) {
|
||||
return this.put(`/proposals/${proposalId}/approve`, { approved });
|
||||
}
|
||||
|
||||
async archiveProposal(proposalId, archived) {
|
||||
return this.put(`/proposals/${proposalId}/archive`, { archived });
|
||||
}
|
||||
|
||||
async deleteProposal(proposalId) {
|
||||
return this.delete(`/proposals/${proposalId}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// TASKS FOR PROPOSALS
|
||||
// =====================
|
||||
|
||||
async getAllTasks() {
|
||||
return this.get('/tasks/all');
|
||||
}
|
||||
|
||||
// =====================
|
||||
// NOTIFICATION ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getNotifications() {
|
||||
return this.get('/notifications');
|
||||
}
|
||||
|
||||
async getNotificationCount() {
|
||||
return this.get('/notifications/count');
|
||||
}
|
||||
|
||||
async markNotificationRead(id) {
|
||||
return this.put(`/notifications/${id}/read`, {});
|
||||
}
|
||||
|
||||
async markAllNotificationsRead() {
|
||||
return this.put('/notifications/read-all', {});
|
||||
}
|
||||
|
||||
async deleteNotification(id) {
|
||||
return this.delete(`/notifications/${id}`);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// GITEA ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async testGiteaConnection() {
|
||||
return this.get('/gitea/test');
|
||||
}
|
||||
|
||||
async getGiteaRepositories(page = 1, limit = 50) {
|
||||
return this.get(`/gitea/repositories?page=${page}&limit=${limit}`);
|
||||
}
|
||||
|
||||
async createGiteaRepository(data) {
|
||||
return this.post('/gitea/repositories', data);
|
||||
}
|
||||
|
||||
async getGiteaRepository(owner, repo) {
|
||||
return this.get(`/gitea/repositories/${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async deleteGiteaRepository(owner, repo) {
|
||||
return this.delete(`/gitea/repositories/${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async getGiteaBranches(owner, repo) {
|
||||
return this.get(`/gitea/repositories/${owner}/${repo}/branches`);
|
||||
}
|
||||
|
||||
async getGiteaCommits(owner, repo, options = {}) {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page) params.append('page', options.page);
|
||||
if (options.limit) params.append('limit', options.limit);
|
||||
if (options.branch) params.append('branch', options.branch);
|
||||
const queryString = params.toString();
|
||||
return this.get(`/gitea/repositories/${owner}/${repo}/commits${queryString ? '?' + queryString : ''}`);
|
||||
}
|
||||
|
||||
async getGiteaUser() {
|
||||
return this.get('/gitea/user');
|
||||
}
|
||||
|
||||
// =====================
|
||||
// APPLICATIONS (Projekt-Repository-Verknüpfung) ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async getProjectApplication(projectId) {
|
||||
return this.get(`/applications/${projectId}`);
|
||||
}
|
||||
|
||||
async saveProjectApplication(data) {
|
||||
return this.post('/applications', data);
|
||||
}
|
||||
|
||||
async deleteProjectApplication(projectId) {
|
||||
return this.delete(`/applications/${projectId}`);
|
||||
}
|
||||
|
||||
async getUserBasePath() {
|
||||
return this.get('/applications/user/base-path');
|
||||
}
|
||||
|
||||
async setUserBasePath(basePath) {
|
||||
return this.put('/applications/user/base-path', { basePath });
|
||||
}
|
||||
|
||||
async syncProjectApplication(projectId) {
|
||||
return this.post(`/applications/${projectId}/sync`, {});
|
||||
}
|
||||
|
||||
// =====================
|
||||
// GIT OPERATIONS ENDPOINTS
|
||||
// =====================
|
||||
|
||||
async cloneRepository(data) {
|
||||
return this.post('/git/clone', data);
|
||||
}
|
||||
|
||||
async getGitStatus(projectId) {
|
||||
return this.get(`/git/status/${projectId}`);
|
||||
}
|
||||
|
||||
async gitPull(projectId, branch = null) {
|
||||
return this.post(`/git/pull/${projectId}`, { branch });
|
||||
}
|
||||
|
||||
async gitPush(projectId, branch = null) {
|
||||
return this.post(`/git/push/${projectId}`, { branch });
|
||||
}
|
||||
|
||||
async gitCommit(projectId, message, stageAll = true) {
|
||||
return this.post(`/git/commit/${projectId}`, { message, stageAll });
|
||||
}
|
||||
|
||||
async getGitCommits(projectId, limit = 20) {
|
||||
return this.get(`/git/commits/${projectId}?limit=${limit}`);
|
||||
}
|
||||
|
||||
async getGitBranches(projectId) {
|
||||
return this.get(`/git/branches/${projectId}`);
|
||||
}
|
||||
|
||||
async gitCheckout(projectId, branch) {
|
||||
return this.post(`/git/checkout/${projectId}`, { branch });
|
||||
}
|
||||
|
||||
async gitFetch(projectId) {
|
||||
return this.post(`/git/fetch/${projectId}`, {});
|
||||
}
|
||||
|
||||
async gitStage(projectId) {
|
||||
return this.post(`/git/stage/${projectId}`, {});
|
||||
}
|
||||
|
||||
async getGitRemote(projectId) {
|
||||
return this.get(`/git/remote/${projectId}`);
|
||||
}
|
||||
|
||||
async validatePath(path) {
|
||||
return this.post('/git/validate-path', { path });
|
||||
}
|
||||
|
||||
async prepareRepository(projectId, repoUrl, branch = 'main') {
|
||||
return this.post(`/git/prepare/${projectId}`, { repoUrl, branch });
|
||||
}
|
||||
|
||||
async setGitRemote(projectId, repoUrl) {
|
||||
return this.post(`/git/set-remote/${projectId}`, { repoUrl });
|
||||
}
|
||||
|
||||
async gitInitPush(projectId, branch = 'main') {
|
||||
return this.post(`/git/init-push/${projectId}`, { branch });
|
||||
}
|
||||
}
|
||||
|
||||
// Custom API Error Class
|
||||
class ApiError extends Error {
|
||||
constructor(message, status, data = {}) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.status = status;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
get isOffline() {
|
||||
return this.data.offline === true;
|
||||
}
|
||||
|
||||
get isUnauthorized() {
|
||||
return this.status === 401;
|
||||
}
|
||||
|
||||
get isNotFound() {
|
||||
return this.status === 404;
|
||||
}
|
||||
|
||||
get isValidationError() {
|
||||
return this.status === 400;
|
||||
}
|
||||
|
||||
get isServerError() {
|
||||
return this.status >= 500;
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
const api = new ApiClient();
|
||||
|
||||
export { api, ApiError };
|
||||
export default api;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren