/**
* Folder Service
* Handles folder-related operations with backend
*/
import { HttpClient } from '../../../shared/http/HttpClient.js';
import { ErrorUtils } from '../../../shared/utils/index.js';
export class FolderService {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.httpClient = new HttpClient({
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
});
}
/**
* List folders for a project
*/
async listFolders(request) {
try {
if (!request.projectId) {
return {
success: false,
error: 'Project ID is required'
};
}
const params = new URLSearchParams();
if (request.parentId) {
params.append('parent_id', request.parentId);
}
if (request.activeOnly !== false) {
params.append('is_active', 'true');
}
const url = `${this.baseUrl}/?act=project_folders&id=${request.projectId}${params.toString() ? '&' + params.toString() : ''}`;
const response = await this.httpClient.get(url);
if (response.success && response.data) {
// Handle nested response structure from backend
const foldersData = response.data.data || response.data;
// Ensure foldersData is an array
if (!Array.isArray(foldersData)) {
return {
success: false,
error: 'Invalid response format: expected array of folders'
};
}
let result = {
success: true,
data: foldersData,
message: `Retrieved ${foldersData.length} folders`
};
// Build tree if requested
if (request.includeTree) {
result.tree = this.buildFolderTree(foldersData);
}
return result;
}
return {
success: false,
error: response.error || 'Failed to retrieve folders'
};
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Get folder details
*/
async getFolderDetails(folderId) {
try {
const url = `${this.baseUrl}/?act=folder&id=${folderId}`;
const response = await this.httpClient.get(url);
if (response.success && response.data) {
// Handle nested response structure from backend
const folderData = response.data.data || response.data;
return {
success: true,
data: folderData
};
}
return {
success: false,
error: response.error || 'Failed to get folder details'
};
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Create a new folder
*/
async createFolder(request) {
try {
// Validate request
const validation = this.validateCreateRequest(request);
if (!validation.valid) {
return {
success: false,
error: 'Validation failed',
details: validation.errors
};
}
const url = `${this.baseUrl}/?act=folder_create&id=${request.projectId}`;
const createData = {
name: request.name.trim(),
description: request.description?.trim(),
parent_id: request.parentId,
headers: {}, // Required by backend (empty object if not provided)
variables: {}, // Required by backend (empty object if not provided)
is_default: 0 // Required by backend (0 = not default)
};
const response = await this.httpClient.post(url, createData);
if (response.success && response.data) {
// Handle nested response structure from backend
const folderData = response.data.data || response.data;
return {
success: true,
data: folderData,
message: 'Folder created successfully'
};
}
return {
success: false,
error: response.error || 'Failed to create folder'
};
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Update folder
*/
async updateFolder(request) {
try {
// Validate request
const validation = this.validateUpdateRequest(request);
if (!validation.valid) {
return {
success: false,
error: 'Validation failed',
details: validation.errors
};
}
const url = `${this.baseUrl}/?act=folder_update&id=${request.folderId}`;
const updateData = {};
if (request.name !== undefined) {
updateData.name = request.name.trim();
}
if (request.description !== undefined) {
updateData.description = request.description?.trim() || null;
}
if (request.parentId !== undefined) {
updateData.parent_id = request.parentId;
}
const response = await this.httpClient.put(url, updateData);
if (response.success && response.data) {
return {
success: true,
data: response.data,
message: 'Folder updated successfully'
};
}
return {
success: false,
error: response.error || 'Failed to update folder'
};
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Move folder to new parent
*/
async moveFolder(request) {
try {
const url = `${this.baseUrl}/?act=folder_update&id=${request.folderId}`;
const moveData = {
parent_id: request.newParentId
};
const response = await this.httpClient.post(url, moveData);
if (response.success) {
return {
success: true,
message: 'Folder moved successfully'
};
}
return {
success: false,
error: response.error || 'Failed to move folder'
};
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Delete folder
*/
async deleteFolder(folderId) {
try {
const url = `${this.baseUrl}/?act=folder_delete&id=${folderId}`;
const response = await this.httpClient.delete(url);
if (response.success) {
return {
success: true,
message: 'Folder deleted successfully'
};
}
return {
success: false,
error: response.error || 'Failed to delete folder'
};
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Build folder tree from flat list
*/
buildFolderTree(folders) {
const folderMap = new Map();
const rootFolders = [];
// Create all nodes first
folders.forEach(folder => {
folderMap.set(folder.id, {
id: folder.id,
name: folder.name,
description: folder.description,
endpoint_count: 0, // Will be updated later
children: [],
level: 0,
parent_id: folder.folder_id,
project_id: folder.project_id,
created_at: folder.created_at,
updated_at: folder.updated_at
});
});
// Build tree structure
folders.forEach(folder => {
const node = folderMap.get(folder.id);
const parentId = folder.folder_id;
if (parentId && folderMap.has(parentId)) {
const parent = folderMap.get(parentId);
parent.children.push(node);
node.level = parent.level + 1;
}
else {
rootFolders.push(node);
}
});
// Sort by name at each level
const sortChildren = (nodes) => {
nodes.sort((a, b) => a.name.localeCompare(b.name));
nodes.forEach(node => sortChildren(node.children));
};
sortChildren(rootFolders);
return rootFolders;
}
/**
* Duplicate folder
*/
async duplicateFolder(folderId, newName, newParentId) {
try {
// Get source folder details
const sourceDetails = await this.getFolderDetails(folderId);
if (!sourceDetails.success || !sourceDetails.data) {
return {
success: false,
error: 'Failed to get source folder details'
};
}
// Create duplicate
const createRequest = {
name: newName,
description: sourceDetails.data.description
? `Copy of: ${sourceDetails.data.description}`
: 'Duplicated folder',
parentId: newParentId || sourceDetails.data.folder_id,
projectId: sourceDetails.data.project_id
};
return await this.createFolder(createRequest);
}
catch (error) {
return {
success: false,
error: ErrorUtils.extractMessage(error)
};
}
}
/**
* Get folder statistics
*/
async getFolderStats(projectId) {
try {
const foldersResponse = await this.listFolders({
projectId,
includeTree: true
});
if (!foldersResponse.success || !foldersResponse.data) {
throw new Error('Failed to retrieve folders');
}
const folders = foldersResponse.data;
const tree = foldersResponse.tree || [];
let totalEndpoints = 0;
let maxDepth = 0;
let emptyFolders = 0;
const calculateDepth = (nodes, currentDepth = 0) => {
maxDepth = Math.max(maxDepth, currentDepth);
nodes.forEach(node => {
if (node.endpoint_count !== undefined) {
totalEndpoints += node.endpoint_count;
if (node.endpoint_count === 0) {
emptyFolders++;
}
}
if (node.children.length > 0) {
calculateDepth(node.children, currentDepth + 1);
}
});
};
calculateDepth(tree);
return {
totalFolders: folders.length,
totalEndpoints,
maxDepth: maxDepth + 1,
averageEndpointsPerFolder: folders.length > 0 ? totalEndpoints / folders.length : 0,
emptyFolders
};
}
catch (error) {
throw new Error(`Failed to calculate folder stats: ${ErrorUtils.extractMessage(error)}`);
}
}
/**
* Export folder structure
*/
async exportFolderStructure(projectId) {
try {
const foldersResponse = await this.listFolders({
projectId,
includeTree: true
});
if (!foldersResponse.success || !foldersResponse.tree) {
throw new Error('Failed to retrieve folder structure');
}
const exportData = {
version: '1.0',
exportedAt: new Date().toISOString(),
projectId,
tree: foldersResponse.tree
};
return JSON.stringify(exportData, null, 2);
}
catch (error) {
throw new Error(`Export failed: ${ErrorUtils.extractMessage(error)}`);
}
}
/**
* Validate create folder request
*/
validateCreateRequest(request) {
const errors = [];
const warnings = [];
if (!request.name || request.name.trim().length === 0) {
errors.push('Folder name is required');
}
if (request.name && request.name.length > 100) {
errors.push('Folder name must be 100 characters or less');
}
if (request.description && request.description.length > 500) {
warnings.push('Description is very long (>500 characters)');
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
/**
* Validate update folder request
*/
validateUpdateRequest(request) {
const errors = [];
const warnings = [];
if (request.name !== undefined && request.name.length > 0) {
if (request.name.trim().length === 0) {
errors.push('Folder name cannot be empty');
}
if (request.name.length > 100) {
errors.push('Folder name must be 100 characters or less');
}
}
if (request.description !== undefined && request.description.length > 500) {
warnings.push('Description is very long (>500 characters)');
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
}
//# sourceMappingURL=FolderService.js.map