< Summary

Information
Class: FirebaseAdapter.FirebaseKpiRepository
Assembly: FirebaseAdapter
File(s): /home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseKpiRepository.cs
Line coverage
82%
Covered lines: 104
Uncovered lines: 22
Coverable lines: 126
Total lines: 288
Line coverage: 82.5%
Branch coverage
100%
Covered branches: 22
Total branches: 22
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SaveKpiDocumentAsync()100%6690.63%
GetKpiDocumentAsync()100%2255.56%
GetKpiDocumentAsync()100%2276.47%
GetKpiDocumentVersionsAsync()100%2282.61%
GetAllKpiDocumentsAsync()100%8886.36%
GetLatestVersionAsync()100%2275%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseKpiRepository.cs

#LineLine coverage
 1using EHonda.KicktippAi.Core;
 2using FirebaseAdapter.Models;
 3using Google.Cloud.Firestore;
 4using Microsoft.Extensions.Logging;
 5
 6namespace FirebaseAdapter;
 7
 8/// <summary>
 9/// Firebase Firestore implementation of the KPI repository.
 10/// </summary>
 11public class FirebaseKpiRepository : IKpiRepository
 12{
 13    private readonly FirestoreDb _firestoreDb;
 14    private readonly ILogger<FirebaseKpiRepository> _logger;
 15    private readonly string _competition;
 16
 17    private const string KpiCollectionName = "kpi-documents";
 18
 119    public FirebaseKpiRepository(FirestoreDb firestoreDb, ILogger<FirebaseKpiRepository> logger, string competition = "b
 20    {
 121        _firestoreDb = firestoreDb;
 122        _logger = logger;
 123        _competition = competition;
 124    }
 25
 26    /// <summary>
 27    /// Saves a KPI document with versioning support.
 28    /// Creates a new version if content differs from the latest version.
 29    /// </summary>
 30    /// <param name="documentName">The document name.</param>
 31    /// <param name="content">The document content.</param>
 32    /// <param name="description">The document description.</param>
 33    /// <param name="communityContext">The community context for filtering.</param>
 34    /// <param name="cancellationToken">Cancellation token.</param>
 35    /// <returns>The version number of the saved document.</returns>
 36    public async Task<int> SaveKpiDocumentAsync(
 37        string documentName,
 38        string content,
 39        string description,
 40        string communityContext,
 41        CancellationToken cancellationToken = default)
 42    {
 43        try
 44        {
 145            var now = Timestamp.GetCurrentTimestamp();
 46
 47            // Get the latest version to check if content has changed
 148            var latestVersion = await GetLatestVersionAsync(documentName, communityContext, cancellationToken);
 149            var version = 0;
 50
 151            if (latestVersion >= 0)
 52            {
 53                // Check if content has changed
 154                var latestDocument = await GetKpiDocumentAsync(documentName, communityContext, latestVersion, cancellati
 155                if (latestDocument != null && latestDocument.Content == content)
 56                {
 157                    _logger.LogInformation("Content unchanged for KPI document: {DocumentName} version {Version}", docum
 158                    return latestVersion; // Return existing version if content is the same
 59                }
 60
 161                version = latestVersion + 1;
 162                _logger.LogDebug("Creating new version {Version} for KPI document: {DocumentName}", version, documentNam
 63            }
 64            else
 65            {
 166                _logger.LogDebug("Creating first version (0) for new KPI document: {DocumentName}", documentName);
 67            }
 68
 69            // Create versioned document ID: "{documentName}_{communityContext}_{version}"
 170            var versionedDocumentId = $"{documentName}_{communityContext}_{version}";
 171            var docRef = _firestoreDb.Collection(KpiCollectionName).Document(versionedDocumentId);
 72
 173            var firestoreKpiDocument = new FirestoreKpiDocument
 174            {
 175                Id = versionedDocumentId,
 176                DocumentName = documentName,
 177                Content = content,
 178                Description = description,
 179                Version = version,
 180                CreatedAt = now,
 181                Competition = _competition,
 182                CommunityContext = communityContext
 183            };
 84
 185            await docRef.SetAsync(firestoreKpiDocument, cancellationToken: cancellationToken);
 86
 187            _logger.LogInformation("Created KPI document: {DocumentName} version {Version} for community: {CommunityCont
 188                documentName, version, communityContext);
 89
 190            return version;
 91        }
 092        catch (Exception ex)
 93        {
 094            _logger.LogError(ex, "Failed to save KPI document: {DocumentName}", documentName);
 095            throw;
 96        }
 197    }
 98
 99    /// <summary>
 100    /// Retrieves the latest version of a KPI document.
 101    /// </summary>
 102    /// <param name="documentName">The document name.</param>
 103    /// <param name="communityContext">The community context to filter by.</param>
 104    /// <param name="cancellationToken">Cancellation token.</param>
 105    /// <returns>The latest KPI document if found, otherwise null.</returns>
 106    public async Task<KpiDocument?> GetKpiDocumentAsync(string documentName, string communityContext, CancellationToken 
 107    {
 108        try
 109        {
 1110            var latestVersion = await GetLatestVersionAsync(documentName, communityContext, cancellationToken);
 1111            if (latestVersion < 0)
 112            {
 1113                return null;
 114            }
 115
 1116            return await GetKpiDocumentAsync(documentName, communityContext, latestVersion, cancellationToken);
 117        }
 0118        catch (Exception ex)
 119        {
 0120            _logger.LogError(ex, "Failed to get latest KPI document: {DocumentName} for community: {CommunityContext}",
 0121                documentName, communityContext);
 0122            throw;
 123        }
 1124    }
 125
 126    /// <summary>
 127    /// Retrieves a specific version of a KPI document.
 128    /// </summary>
 129    /// <param name="documentName">The document name.</param>
 130    /// <param name="communityContext">The community context to filter by.</param>
 131    /// <param name="version">The specific version to retrieve.</param>
 132    /// <param name="cancellationToken">Cancellation token.</param>
 133    /// <returns>The KPI document version if found, otherwise null.</returns>
 134    public async Task<KpiDocument?> GetKpiDocumentAsync(string documentName, string communityContext, int version, Cance
 135    {
 136        try
 137        {
 1138            var versionedDocumentId = $"{documentName}_{communityContext}_{version}";
 1139            var docRef = _firestoreDb.Collection(KpiCollectionName).Document(versionedDocumentId);
 1140            var snapshot = await docRef.GetSnapshotAsync(cancellationToken);
 141
 1142            if (!snapshot.Exists)
 143            {
 1144                return null;
 145            }
 146
 1147            var firestoreDoc = snapshot.ConvertTo<FirestoreKpiDocument>();
 1148            return new KpiDocument(
 1149                firestoreDoc.DocumentName,
 1150                firestoreDoc.Content,
 1151                firestoreDoc.Description,
 1152                firestoreDoc.Version,
 1153                firestoreDoc.CreatedAt.ToDateTimeOffset());
 154        }
 0155        catch (Exception ex)
 156        {
 0157            _logger.LogError(ex, "Failed to get KPI document: {DocumentName} version {Version} for community: {Community
 0158                documentName, version, communityContext);
 0159            throw;
 160        }
 1161    }
 162
 163    /// <summary>
 164    /// Retrieves all versions of a KPI document.
 165    /// </summary>
 166    /// <param name="documentName">The document name.</param>
 167    /// <param name="communityContext">The community context to filter by.</param>
 168    /// <param name="cancellationToken">Cancellation token.</param>
 169    /// <returns>A collection of all versions of the KPI document.</returns>
 170    public async Task<IReadOnlyList<KpiDocument>> GetKpiDocumentVersionsAsync(string documentName, string communityConte
 171    {
 172        try
 173        {
 1174            var query = _firestoreDb.Collection(KpiCollectionName)
 1175                .WhereEqualTo("documentName", documentName)
 1176                .WhereEqualTo("communityContext", communityContext)
 1177                .WhereEqualTo("competition", _competition)
 1178                .OrderBy("version");
 179
 1180            var snapshot = await query.GetSnapshotAsync(cancellationToken);
 181
 1182            var versions = new List<KpiDocument>();
 1183            foreach (var document in snapshot.Documents)
 184            {
 1185                var firestoreDoc = document.ConvertTo<FirestoreKpiDocument>();
 1186                versions.Add(new KpiDocument(
 1187                    firestoreDoc.DocumentName,
 1188                    firestoreDoc.Content,
 1189                    firestoreDoc.Description,
 1190                    firestoreDoc.Version,
 1191                    firestoreDoc.CreatedAt.ToDateTimeOffset()));
 192            }
 193
 1194            _logger.LogInformation("Retrieved {Count} versions for KPI document: {DocumentName} in community: {Community
 1195                versions.Count, documentName, communityContext);
 196
 1197            return versions.AsReadOnly();
 198        }
 0199        catch (Exception ex)
 200        {
 0201            _logger.LogError(ex, "Failed to get versions for KPI document: {DocumentName} in community: {CommunityContex
 0202                documentName, communityContext);
 0203            throw;
 204        }
 1205    }
 206
 207    /// <summary>
 208    /// Retrieves all KPI documents for a specific community context (latest versions only).
 209    /// </summary>
 210    /// <param name="communityContext">The community context to filter by.</param>
 211    /// <param name="cancellationToken">Cancellation token.</param>
 212    /// <returns>A collection of KPI documents for the specified community.</returns>
 213    public async Task<IReadOnlyList<KpiDocument>> GetAllKpiDocumentsAsync(string communityContext, CancellationToken can
 214    {
 215        try
 216        {
 1217            var query = _firestoreDb.Collection(KpiCollectionName)
 1218                .WhereEqualTo("competition", _competition)
 1219                .WhereEqualTo("communityContext", communityContext);
 220
 1221            var snapshot = await query.GetSnapshotAsync(cancellationToken);
 222
 223            // Group by documentName and get the latest version for each
 1224            var documentGroups = snapshot.Documents
 1225                .Select(doc => doc.ConvertTo<FirestoreKpiDocument>())
 1226                .GroupBy(doc => doc.DocumentName)
 1227                .ToList();
 228
 1229            var latestDocuments = new List<KpiDocument>();
 1230            foreach (var group in documentGroups)
 231            {
 1232                var latestDoc = group.OrderByDescending(d => d.Version).First();
 1233                latestDocuments.Add(new KpiDocument(
 1234                    latestDoc.DocumentName,
 1235                    latestDoc.Content,
 1236                    latestDoc.Description,
 1237                    latestDoc.Version,
 1238                    latestDoc.CreatedAt.ToDateTimeOffset()));
 239            }
 240
 1241            _logger.LogInformation("Retrieved {Count} latest KPI documents for community: {CommunityContext}",
 1242                latestDocuments.Count, communityContext);
 243
 1244            return latestDocuments.AsReadOnly();
 245        }
 0246        catch (Exception ex)
 247        {
 0248            _logger.LogError(ex, "Failed to get all KPI documents for community: {CommunityContext}", communityContext);
 0249            throw;
 250        }
 1251    }
 252
 253    /// <summary>
 254    /// Gets the latest version number for a specific KPI document.
 255    /// </summary>
 256    /// <param name="documentName">The document name.</param>
 257    /// <param name="communityContext">The community context to filter by.</param>
 258    /// <param name="cancellationToken">Cancellation token.</param>
 259    /// <returns>The latest version number, or -1 if the document doesn't exist.</returns>
 260    public async Task<int> GetLatestVersionAsync(string documentName, string communityContext, CancellationToken cancell
 261    {
 262        try
 263        {
 1264            var query = _firestoreDb.Collection(KpiCollectionName)
 1265                .WhereEqualTo("documentName", documentName)
 1266                .WhereEqualTo("communityContext", communityContext)
 1267                .WhereEqualTo("competition", _competition)
 1268                .OrderByDescending("version")
 1269                .Limit(1);
 270
 1271            var snapshot = await query.GetSnapshotAsync(cancellationToken);
 272
 1273            if (snapshot.Documents.Count == 0)
 274            {
 1275                return -1; // Document doesn't exist
 276            }
 277
 1278            var latestDoc = snapshot.Documents.First().ConvertTo<FirestoreKpiDocument>();
 1279            return latestDoc.Version;
 280        }
 0281        catch (Exception ex)
 282        {
 0283            _logger.LogError(ex, "Failed to get latest version for KPI document: {DocumentName} in community: {Community
 0284                documentName, communityContext);
 0285            throw;
 286        }
 1287    }
 288}