/**
* @fileoverview Project repository implementation
*
* Concrete implementation of IProjectRepository using DeepSource API.
*/
import { IProjectRepository } from '../../domain/aggregates/project/project.repository.js';
import { Project } from '../../domain/aggregates/project/project.aggregate.js';
import { ProjectKey } from '../../types/branded.js';
import { ProjectsClient } from '../../client/projects-client.js';
import { ProjectMapper } from '../mappers/project.mapper.js';
import { createLogger } from '../../utils/logging/logger.js';
const logger = createLogger('ProjectRepository');
/**
* Concrete implementation of IProjectRepository using DeepSource API
*
* This repository provides access to Project aggregates by fetching data
* from the DeepSource API and mapping it to domain models.
*
* Note: Since the DeepSource API doesn't support individual project queries
* or persistence operations, some methods fetch all projects and filter locally.
* This ensures fresh data retrieval on every request as per requirements.
*/
export class ProjectRepository implements IProjectRepository {
constructor(private readonly projectsClient: ProjectsClient) {
// projectsClient is stored for use in methods
}
/**
* Finds a project by its unique identifier
*
* @param id - The project key
* @returns The project if found, null otherwise
*/
async findById(id: ProjectKey): Promise<Project | null> {
return this.findByKey(id);
}
/**
* Finds a project by its unique key
*
* @param key - The project key
* @returns The project if found, null otherwise
*/
async findByKey(key: ProjectKey): Promise<Project | null> {
try {
logger.debug('Finding project by key', { key });
// Fetch all projects and find the one with matching key
const apiProjects = await this.projectsClient.listProjects();
const matchingProject = apiProjects.find((p) => p.key === key);
if (!matchingProject) {
logger.debug('Project not found', { key });
return null;
}
const project = ProjectMapper.toDomain(matchingProject);
logger.debug('Project found and mapped', { key, name: project.name });
return project;
} catch (error) {
logger.error('Error finding project by key', { key, error });
throw error;
}
}
/**
* Finds all projects
*
* @returns All projects in the repository
*/
async findAll(): Promise<Project[]> {
try {
logger.debug('Finding all projects');
const apiProjects = await this.projectsClient.listProjects();
const projects = ProjectMapper.toDomainList(apiProjects);
logger.debug('Projects found', { count: projects.length });
return projects;
} catch (error) {
logger.error('Error finding all projects', { error });
throw error;
}
}
/**
* Finds all active projects
*
* @returns All projects with ACTIVE status
*/
async findActive(): Promise<Project[]> {
try {
logger.debug('Finding active projects');
const allProjects = await this.findAll();
const activeProjects = allProjects.filter((p) => p.status === 'ACTIVE');
logger.debug('Active projects found', { count: activeProjects.length });
return activeProjects;
} catch (error) {
logger.error('Error finding active projects', { error });
throw error;
}
}
/**
* Finds projects by repository provider
*
* @param provider - The VCS provider (e.g., 'GITHUB', 'GITLAB')
* @returns Projects using the specified provider
*/
async findByProvider(provider: string): Promise<Project[]> {
try {
logger.debug('Finding projects by provider', { provider });
const allProjects = await this.findAll();
const providerProjects = allProjects.filter((p) => p.repository.provider === provider);
logger.debug('Projects found for provider', {
provider,
count: providerProjects.length,
});
return providerProjects;
} catch (error) {
logger.error('Error finding projects by provider', { provider, error });
throw error;
}
}
/**
* Checks if a project exists with the given key
*
* @param key - The project key to check
* @returns True if the project exists, false otherwise
*/
async exists(key: ProjectKey): Promise<boolean> {
try {
logger.debug('Checking if project exists', { key });
const project = await this.findByKey(key);
const exists = project !== null;
logger.debug('Project existence check', { key, exists });
return exists;
} catch (error) {
logger.error('Error checking project existence', { key, error });
throw error;
}
}
/**
* Counts the total number of projects
*
* @returns The total project count
*/
async count(): Promise<number> {
try {
logger.debug('Counting all projects');
const projects = await this.findAll();
const count = projects.length;
logger.debug('Total project count', { count });
return count;
} catch (error) {
logger.error('Error counting projects', { error });
throw error;
}
}
/**
* Counts the number of active projects
*
* @returns The active project count
*/
async countActive(): Promise<number> {
try {
logger.debug('Counting active projects');
const activeProjects = await this.findActive();
const count = activeProjects.length;
logger.debug('Active project count', { count });
return count;
} catch (error) {
logger.error('Error counting active projects', { error });
throw error;
}
}
/**
* Saves a project
*
* Note: The DeepSource API doesn't support creating/updating projects
* through the GraphQL API. This method is implemented to satisfy the
* interface but will throw an error indicating the operation is not supported.
*
* @param project - The project to save
* @throws Error indicating the operation is not supported
*/
// skipcq: JS-0105 - Repository method required by interface contract
async save(project: Project): Promise<void> {
logger.warn('Attempted to save project', {
key: project.key,
name: project.name,
});
throw new Error(
'Save operation is not supported by DeepSource API. ' +
'Projects must be created and managed through the DeepSource UI.'
);
}
/**
* Deletes a project
*
* Note: The DeepSource API doesn't support deleting projects
* through the GraphQL API. This method is implemented to satisfy the
* interface but will throw an error indicating the operation is not supported.
*
* @param key - The project key to delete
* @throws Error indicating the operation is not supported
*/
// skipcq: JS-0105 - Repository method required by interface contract
async delete(key: ProjectKey): Promise<void> {
logger.warn('Attempted to delete project', { key });
throw new Error(
'Delete operation is not supported by DeepSource API. ' +
'Projects must be deleted through the DeepSource UI.'
);
}
}