deepsource-mcp-server
Version:
Model Context Protocol server for DeepSource
348 lines • 13.6 kB
JavaScript
/**
* @fileoverview ComplianceReport repository implementation
*
* Concrete implementation of IComplianceReportRepository using DeepSource API.
*/
import { ReportType } from '../../types/report-types.js';
import { ComplianceReportMapper } from '../mappers/compliance-report.mapper.js';
import { createLogger } from '../../utils/logging/logger.js';
const logger = createLogger('ComplianceReportRepository');
/**
* Concrete implementation of IComplianceReportRepository using DeepSource API
*
* This repository provides access to ComplianceReport aggregates by fetching data
* from the DeepSource API and mapping it to domain models.
*
* Note: The DeepSource API provides compliance reports for specific types
* (OWASP_TOP_10, SANS_TOP_25, MISRA_C). This repository ensures fresh data
* retrieval on every request as per requirements.
*/
export class ComplianceReportRepository {
client;
constructor(client) {
this.client = client;
// client is stored for use in methods
}
/**
* Helper method to get repository ID for a project
*
* @param projectKey - The project key
* @returns The repository GraphQL ID
*/
async getRepositoryId(projectKey) {
const projects = await this.client.listProjects();
const project = projects.find((p) => p.key === projectKey);
if (!project) {
throw new Error(`Project not found: ${projectKey}`);
}
// Get repository ID from runs query (similar to QualityMetricsRepository)
try {
const runs = await this.client.listRuns(projectKey, { first: 1 });
if (runs.items.length > 0 && runs.items[0]?.repository?.id) {
return runs.items[0].repository.id;
}
// Fallback: Direct repository query
const repoQuery = `
query($login: String!, $name: String!, $provider: VCSProvider!) {
repository(login: $login, name: $name, vcsProvider: $provider) {
id
}
}
`;
// Access protected client member via type assertion
const clientWithAxios = this.client;
const response = await clientWithAxios.client.post('', {
query: repoQuery.trim(),
variables: {
login: project.repository.login,
name: project.name,
provider: project.repository.provider,
},
});
if (response.data.errors) {
throw new Error(`GraphQL Errors: ${response.data.errors.map((e) => e.message).join(', ')}`);
}
const repositoryId = response.data.data?.repository?.id;
if (!repositoryId) {
throw new Error(`Repository ID not found for project: ${projectKey}`);
}
return repositoryId;
}
catch (error) {
logger.error('Error fetching repository ID', { projectKey, error });
throw error;
}
}
/**
* Finds a report by composite ID
*
* @param id - The composite ID (projectKey:reportType)
* @returns The report if found, null otherwise
*/
async findById(id) {
try {
logger.debug('Finding compliance report by composite ID', { id });
// Parse composite ID
const parts = id.split(':');
if (parts.length !== 2) {
logger.error('Invalid composite ID format', { id });
return null;
}
const [projectKey, reportType] = parts;
return this.findByProjectAndType(projectKey, reportType);
}
catch (error) {
logger.error('Error finding compliance report by ID', { id, error });
throw error;
}
}
/**
* Finds a report by project and type
*
* @param projectKey - The project key
* @param reportType - The report type
* @returns The report if found, null otherwise
*/
async findByProjectAndType(projectKey, reportType) {
try {
logger.debug('Finding compliance report by project and type', {
projectKey,
reportType,
});
const repositoryId = await this.getRepositoryId(projectKey);
const apiReport = await this.client.getComplianceReport(projectKey, reportType);
if (!apiReport) {
logger.debug('Compliance report not found', { projectKey, reportType });
return null;
}
const domainReport = ComplianceReportMapper.toDomain(apiReport, projectKey, repositoryId);
logger.debug('Compliance report found and mapped', {
projectKey,
reportType,
id: domainReport.id,
});
return domainReport;
}
catch (error) {
logger.error('Error finding compliance report by project and type', {
projectKey,
reportType,
error,
});
throw error;
}
}
/**
* Finds the latest reports for a project
*
* @param projectKey - The project key
* @returns All reports for the project, sorted by generation date (newest first)
*/
async findLatest(projectKey) {
try {
logger.debug('Finding latest compliance reports for project', { projectKey });
const repositoryId = await this.getRepositoryId(projectKey);
const reportTypes = [ReportType.OWASP_TOP_10, ReportType.SANS_TOP_25, ReportType.MISRA_C];
const reports = [];
// Fetch all available report types
for (const reportType of reportTypes) {
try {
const apiReport = await this.client.getComplianceReport(projectKey, reportType);
if (apiReport) {
const domainReport = ComplianceReportMapper.toDomain(apiReport, projectKey, repositoryId);
reports.push(domainReport);
}
}
catch (error) {
// Log but don't fail - some report types may not be available
logger.debug('Report type not available', { projectKey, reportType, error });
}
}
// Sort by generation date (newest first)
reports.sort((a, b) => b.generatedAt.getTime() - a.generatedAt.getTime());
logger.debug('Latest compliance reports found', {
projectKey,
count: reports.length,
});
return reports;
}
catch (error) {
logger.error('Error finding latest compliance reports', { projectKey, error });
throw error;
}
}
/**
* Finds reports by status
*
* @param projectKey - The project key
* @param status - The report status
* @returns Reports matching the status
*/
async findByStatus(projectKey, status) {
try {
logger.debug('Finding compliance reports by status', { projectKey, status });
const allReports = await this.findLatest(projectKey);
const statusReports = allReports.filter((report) => report.status === status);
logger.debug('Status-filtered compliance reports found', {
projectKey,
status,
count: statusReports.length,
});
return statusReports;
}
catch (error) {
logger.error('Error finding reports by status', { projectKey, status, error });
throw error;
}
}
/**
* Finds all compliant reports for a project
*
* @param projectKey - The project key
* @returns Reports with compliance score >= 85%
*/
async findCompliantReports(projectKey) {
try {
logger.debug('Finding compliant reports for project', { projectKey });
const allReports = await this.findLatest(projectKey);
const compliantReports = allReports.filter((report) => report.isCompliant);
logger.debug('Compliant reports found', {
projectKey,
count: compliantReports.length,
});
return compliantReports;
}
catch (error) {
logger.error('Error finding compliant reports', { projectKey, error });
throw error;
}
}
/**
* Finds reports with critical issues
*
* @param projectKey - The project key
* @returns Reports that have critical severity issues
*/
async findReportsWithCriticalIssues(projectKey) {
try {
logger.debug('Finding reports with critical issues for project', { projectKey });
const allReports = await this.findLatest(projectKey);
const criticalReports = allReports.filter((report) => report.hasCriticalIssues);
logger.debug('Reports with critical issues found', {
projectKey,
count: criticalReports.length,
});
return criticalReports;
}
catch (error) {
logger.error('Error finding reports with critical issues', { projectKey, error });
throw error;
}
}
/**
* Finds a report by composite ID components
*
* @param id - The composite ID components
* @returns The report if found, null otherwise
*/
async findByCompositeId(id) {
try {
logger.debug('Finding compliance report by composite ID components', { id });
return this.findByProjectAndType(id.projectKey, id.reportType);
}
catch (error) {
logger.error('Error finding report by composite ID', { id, error });
throw error;
}
}
/**
* Counts total reports for a project
*
* @param projectKey - The project key
* @returns The total number of reports
*/
async countByProject(projectKey) {
try {
logger.debug('Counting compliance reports for project', { projectKey });
const reports = await this.findLatest(projectKey);
const count = reports.length;
logger.debug('Compliance report count for project', { projectKey, count });
return count;
}
catch (error) {
logger.error('Error counting reports', { projectKey, error });
throw error;
}
}
/**
* Counts reports by type for a project
*
* @param projectKey - The project key
* @param reportType - The report type
* @returns The number of reports of the given type
*/
async countByType(projectKey, reportType) {
try {
logger.debug('Counting compliance reports by type', { projectKey, reportType });
const report = await this.findByProjectAndType(projectKey, reportType);
const count = report ? 1 : 0;
logger.debug('Report count by type', { projectKey, reportType, count });
return count;
}
catch (error) {
logger.error('Error counting reports by type', { projectKey, reportType, error });
throw error;
}
}
/**
* Checks if a report exists for a project and type
*
* @param projectKey - The project key
* @param reportType - The report type
* @returns True if a report exists, false otherwise
*/
async exists(projectKey, reportType) {
try {
logger.debug('Checking if compliance report exists', { projectKey, reportType });
const report = await this.findByProjectAndType(projectKey, reportType);
const exists = report !== null;
logger.debug('Compliance report existence check', { projectKey, reportType, exists });
return exists;
}
catch (error) {
logger.error('Error checking report existence', { projectKey, reportType, error });
throw error;
}
}
/**
* Saves compliance report
*
* Note: The DeepSource API doesn't support creating or updating compliance reports.
* Reports are generated automatically based on repository analysis.
*
* @param report - The report to save
* @throws Error indicating the operation is not supported
*/
// skipcq: JS-0105 - Repository method required by interface contract
async save(report) {
logger.warn('Attempted to save compliance report', { id: report.id });
throw new Error('Save operation is not supported by DeepSource API. ' +
'Compliance reports are generated automatically based on repository analysis.');
}
/**
* Deletes compliance report
*
* Note: The DeepSource API doesn't support deleting compliance reports.
* Reports are managed automatically based on repository configuration.
*
* @param id - The report ID to delete
* @throws Error indicating the operation is not supported
*/
// skipcq: JS-0105 - Repository method required by interface contract
async delete(id) {
logger.warn('Attempted to delete compliance report', { id });
throw new Error('Delete operation is not supported by DeepSource API. ' +
'Compliance reports are managed automatically based on repository configuration.');
}
}
//# sourceMappingURL=compliance-report.repository.js.map