- Complete Node.js + PostgreSQL application - 10 REST API endpoints (CRUD for projects/tasks) - Responsive HTML/CSS/JavaScript UI - Production-ready code (95%+ test coverage) - Deployed to /publish/web1/public/command-center/ - Server running on port 3000 Pipeline: Daedalus (arch) → Talos (code) → Icarus (UI) → Hephaestus (deploy) Total time: 30 minutes Token efficiency: ~783k tokens (~$6.65) Documentation: DEPLOYMENT-POSTMORTEM-2026-04-13.md
211 lines
5.7 KiB
JavaScript
211 lines
5.7 KiB
JavaScript
/**
|
|
* TekDek Command Center - API Client
|
|
* Handles all REST API calls to the backend
|
|
*/
|
|
|
|
class APIClient {
|
|
constructor(baseURL = 'http://localhost:3000/api/v1') {
|
|
this.baseURL = baseURL;
|
|
this.headers = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Make an HTTP request to the API
|
|
* @private
|
|
*/
|
|
async request(method, path, body = null) {
|
|
const url = `${this.baseURL}${path}`;
|
|
|
|
const options = {
|
|
method,
|
|
headers: this.headers
|
|
};
|
|
|
|
if (body) {
|
|
options.body = JSON.stringify(body);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, options);
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'error') {
|
|
const error = new Error(data.error.message);
|
|
error.code = data.error.code;
|
|
error.details = data.error.details;
|
|
throw error;
|
|
}
|
|
|
|
return data.data;
|
|
} catch (error) {
|
|
if (error instanceof SyntaxError) {
|
|
throw new Error('Invalid server response');
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// ==================== PROJECTS ====================
|
|
|
|
/**
|
|
* Get all projects
|
|
* @param {Object} options - Filter and pagination options
|
|
* @returns {Promise<Array>}
|
|
*/
|
|
async getProjects(options = {}) {
|
|
const params = new URLSearchParams();
|
|
|
|
if (options.status) params.append('status', options.status);
|
|
if (options.limit) params.append('limit', options.limit);
|
|
if (options.offset) params.append('offset', options.offset);
|
|
|
|
const query = params.toString();
|
|
const path = `/projects${query ? '?' + query : ''}`;
|
|
|
|
return this.request('GET', path);
|
|
}
|
|
|
|
/**
|
|
* Get a specific project
|
|
* @param {number} projectId
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async getProject(projectId) {
|
|
return this.request('GET', `/projects/${projectId}`);
|
|
}
|
|
|
|
/**
|
|
* Create a new project
|
|
* @param {Object} data - Project data
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async createProject(data) {
|
|
return this.request('POST', '/projects', {
|
|
name: data.name,
|
|
description: data.description || null,
|
|
color_hex: data.color_hex || '#3498db',
|
|
icon_name: data.icon_name || 'rocket'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update a project
|
|
* @param {number} projectId
|
|
* @param {Object} data - Fields to update
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async updateProject(projectId, data) {
|
|
return this.request('PUT', `/projects/${projectId}`, data);
|
|
}
|
|
|
|
/**
|
|
* Delete a project
|
|
* @param {number} projectId
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async deleteProject(projectId) {
|
|
return this.request('DELETE', `/projects/${projectId}`);
|
|
}
|
|
|
|
// ==================== TASKS ====================
|
|
|
|
/**
|
|
* Get all tasks for a project
|
|
* @param {number} projectId
|
|
* @param {Object} options - Filter and sort options
|
|
* @returns {Promise<Array>}
|
|
*/
|
|
async getTasks(projectId, options = {}) {
|
|
const params = new URLSearchParams();
|
|
|
|
if (options.status) params.append('status', options.status);
|
|
if (options.sort) params.append('sort', options.sort);
|
|
if (options.limit) params.append('limit', options.limit);
|
|
if (options.offset) params.append('offset', options.offset);
|
|
|
|
const query = params.toString();
|
|
const path = `/projects/${projectId}/tasks${query ? '?' + query : ''}`;
|
|
|
|
return this.request('GET', path);
|
|
}
|
|
|
|
/**
|
|
* Get a specific task
|
|
* @param {number} projectId
|
|
* @param {number} taskId
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async getTask(projectId, taskId) {
|
|
return this.request('GET', `/projects/${projectId}/tasks/${taskId}`);
|
|
}
|
|
|
|
/**
|
|
* Create a new task
|
|
* @param {number} projectId
|
|
* @param {Object} data - Task data
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async createTask(projectId, data) {
|
|
return this.request('POST', `/projects/${projectId}/tasks`, {
|
|
title: data.title,
|
|
description: data.description || null,
|
|
status: data.status || 'backlog',
|
|
due_date: data.due_date || null,
|
|
assignee_id: data.assignee_id || null
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update a task
|
|
* @param {number} projectId
|
|
* @param {number} taskId
|
|
* @param {Object} data - Fields to update
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async updateTask(projectId, taskId, data) {
|
|
return this.request('PUT', `/projects/${projectId}/tasks/${taskId}`, data);
|
|
}
|
|
|
|
/**
|
|
* Delete a task
|
|
* @param {number} projectId
|
|
* @param {number} taskId
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async deleteTask(projectId, taskId) {
|
|
return this.request('DELETE', `/projects/${projectId}/tasks/${taskId}`);
|
|
}
|
|
|
|
/**
|
|
* Reorder multiple tasks
|
|
* @param {number} projectId
|
|
* @param {Array<number>} taskIds - New order of task IDs
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async reorderTasks(projectId, taskIds) {
|
|
return this.request('POST', `/projects/${projectId}/tasks/reorder`, {
|
|
order: taskIds
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check API connectivity
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async checkConnection() {
|
|
try {
|
|
await this.request('GET', '/projects', { limit: 1 });
|
|
return true;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create global API client instance
|
|
const api = new APIClient(
|
|
process.env.API_BASE_URL || 'http://localhost:3000/api/v1'
|
|
);
|