UNPKG

@vizzly-testing/cli

Version:

Visual review platform for UI developers and designers

136 lines (129 loc) 5.1 kB
/** * Project Service * Wraps project operations for use by the HTTP server * * Provides the interface expected by src/server/routers/projects.js: * - listProjects() - Returns [] if not authenticated * - listMappings() - Returns [] if no mappings * - getMapping(directory) - Returns null if not found * - createMapping(directory, projectData) - Throws on invalid input * - removeMapping(directory) - Throws on invalid directory * - getRecentBuilds(projectSlug, organizationSlug, options) - Returns [] if not authenticated * * Error handling: * - API methods (listProjects, getRecentBuilds) return empty arrays when not authenticated * - Local methods (listMappings, getMapping) never require authentication * - Validation errors (createMapping, removeMapping) throw with descriptive messages */ import { createAuthClient } from '../auth/client.js'; import * as projectOps from '../project/operations.js'; import { getApiUrl } from '../utils/environment-config.js'; import { deleteProjectMapping, getAuthTokens, getProjectMapping, getProjectMappings, saveProjectMapping } from '../utils/global-config.js'; /** * Create a project service instance * @param {Object} [options] * @param {string} [options.apiUrl] - API base URL (defaults to VIZZLY_API_URL or https://app.vizzly.dev) * @param {Object} [options.httpClient] - Injectable HTTP client (for testing) * @param {Object} [options.mappingStore] - Injectable mapping store (for testing) * @param {Function} [options.getAuthTokens] - Injectable token getter (for testing) * @returns {Object} Project service */ export function createProjectService(options = {}) { let apiUrl = options.apiUrl || getApiUrl(); // Create HTTP client once at service creation (not per-request) // Allow injection for testing let httpClient = options.httpClient || createAuthClient({ baseUrl: apiUrl }); // Create mapping store adapter for global config // Allow injection for testing let mappingStore = options.mappingStore || { getMappings: getProjectMappings, getMapping: getProjectMapping, saveMapping: saveProjectMapping, deleteMapping: deleteProjectMapping }; // Allow injection of getAuthTokens for testing let tokenGetter = options.getAuthTokens || getAuthTokens; /** * Create an OAuth client with current access token * @returns {Promise<Object|null>} OAuth client or null if not authenticated */ async function createOAuthClient() { let auth = await tokenGetter(); if (!auth?.accessToken) { return null; } // Wrap authenticatedRequest to auto-inject the access token return { authenticatedRequest: (endpoint, fetchOptions = {}) => httpClient.authenticatedRequest(endpoint, auth.accessToken, fetchOptions) }; } return { /** * List all projects from API * Returns empty array if not authenticated (projectOps handles null oauthClient) * @returns {Promise<Array>} Array of projects, empty if not authenticated */ async listProjects() { let oauthClient = await createOAuthClient(); // projectOps.listProjects handles null oauthClient by returning [] return projectOps.listProjects({ oauthClient, apiClient: null }); }, /** * List all project mappings * @returns {Promise<Array>} Array of project mappings */ async listMappings() { return projectOps.listMappings(mappingStore); }, /** * Get project mapping for a specific directory * @param {string} directory - Directory path * @returns {Promise<Object|null>} Project mapping or null */ async getMapping(directory) { return projectOps.getMapping(mappingStore, directory); }, /** * Create or update project mapping * @param {string} directory - Directory path * @param {Object} projectData - Project data * @returns {Promise<Object>} Created mapping */ async createMapping(directory, projectData) { return projectOps.createMapping(mappingStore, directory, projectData); }, /** * Remove project mapping * @param {string} directory - Directory path * @returns {Promise<void>} */ async removeMapping(directory) { return projectOps.removeMapping(mappingStore, directory); }, /** * Get recent builds for a project * Returns empty array if not authenticated (projectOps handles null oauthClient) * @param {string} projectSlug - Project slug * @param {string} organizationSlug - Organization slug * @param {Object} options - Query options * @returns {Promise<Array>} Array of builds, empty if not authenticated */ async getRecentBuilds(projectSlug, organizationSlug, options = {}) { let oauthClient = await createOAuthClient(); // projectOps.getRecentBuilds handles null oauthClient by returning [] return projectOps.getRecentBuilds({ oauthClient, apiClient: null, projectSlug, organizationSlug, limit: options.limit, branch: options.branch }); } }; }