Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ public static class CacheKeys
/// </summary>
public const string EventsPattern = "thrive:events:*";

// ============================================
// Transcript Cache Keys
// ============================================

/// <summary>
/// Sermon transcript blob. Format: thrive:transcripts:blob:{messageId}
/// </summary>
public const string TranscriptBlob = "thrive:transcripts:blob:{0}";

/// <summary>
/// Pattern for invalidating all transcript caches
/// </summary>
public const string TranscriptsPattern = "thrive:transcripts:*";

// ============================================
// Bible Passage Cache Keys
// ============================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@ namespace ThriveChurchOfficialAPI.Services
public class TranscriptService : ITranscriptService
{
private readonly BlobContainerClient _containerClient;
private readonly ICacheService _cache;

/// <summary>
/// Cache expiration for transcript data (365 days - transcripts are immutable once created)
/// </summary>
private static readonly TimeSpan CacheExpiration = TimeSpan.FromDays(365);

/// <summary>
/// Constructor for TranscriptService
/// </summary>
/// <param name="connectionString">Azure Storage connection string</param>
/// <param name="containerName">Name of the blob container storing transcripts</param>
public TranscriptService(string connectionString, string containerName)
/// <param name="cache">Cache service instance</param>
public TranscriptService(string connectionString, string containerName, ICacheService cache)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));

if (string.IsNullOrEmpty(connectionString))
{
Log.Warning("TranscriptService initialized with empty connection string");
Expand All @@ -39,9 +48,11 @@ public TranscriptService(string connectionString, string containerName)
/// Constructor for testing - allows injecting a mock container client
/// </summary>
/// <param name="containerClient">Mock blob container client for testing</param>
public TranscriptService(BlobContainerClient containerClient)
/// <param name="cache">Cache service instance</param>
public TranscriptService(BlobContainerClient containerClient, ICacheService cache)
{
_containerClient = containerClient;
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
Log.Information("TranscriptService initialized with injected container client");
}

Expand Down Expand Up @@ -212,10 +223,20 @@ public async Task<SystemResponse<StudyGuideResponse>> GetStudyGuideAsync(string
#region Private Helper Methods

/// <summary>
/// Downloads and parses the transcript blob
/// Downloads and parses the transcript blob with caching
/// </summary>
private async Task<TranscriptBlob> DownloadBlobAsync(string messageId)
{
// Check cache first
var cacheKey = string.Format(CacheKeys.TranscriptBlob, messageId.ToLowerInvariant());
var cachedBlob = _cache.ReadFromCache<TranscriptBlob>(cacheKey);
if (cachedBlob != null)
{
Log.Debug("Cache hit for transcript blob: {MessageId}", messageId);
return cachedBlob;
}

// Cache miss - download from Azure Blob Storage
var blobName = $"{messageId}.json";
var blobClient = _containerClient.GetBlobClient(blobName);

Expand All @@ -227,7 +248,16 @@ private async Task<TranscriptBlob> DownloadBlobAsync(string messageId)

var downloadResponse = await blobClient.DownloadContentAsync();
var content = downloadResponse.Value.Content.ToString();
return JsonConvert.DeserializeObject<TranscriptBlob>(content);
var blob = JsonConvert.DeserializeObject<TranscriptBlob>(content);

// Cache the result
if (blob != null)
{
_cache.InsertIntoCache(cacheKey, blob, CacheExpiration);
Log.Debug("Cached transcript blob for message: {MessageId}", messageId);
}

return blob;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Threading.Tasks;
using ThriveChurchOfficialAPI.Core;
using ThriveChurchOfficialAPI.Services;

namespace ThriveChurchOfficialAPI.Tests.Services
Expand All @@ -12,13 +14,21 @@ namespace ThriveChurchOfficialAPI.Tests.Services
[TestClass]
public class TranscriptServiceTests
{
private Mock<ICacheService> _mockCache;

[TestInitialize]
public void Setup()
{
_mockCache = new Mock<ICacheService>();
}

#region Constructor Tests

[TestMethod]
public void Constructor_WithNullConnectionString_CreatesServiceWithWarning()
{
// Act - Using connection string constructor with null
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Assert - Service should be created but will return errors when used
Assert.IsNotNull(service);
Expand All @@ -28,7 +38,7 @@ public void Constructor_WithNullConnectionString_CreatesServiceWithWarning()
public void Constructor_WithEmptyConnectionString_CreatesServiceWithWarning()
{
// Act
var service = new TranscriptService(string.Empty, "transcripts");
var service = new TranscriptService(string.Empty, "transcripts", _mockCache.Object);

// Assert
Assert.IsNotNull(service);
Expand All @@ -42,7 +52,7 @@ public void Constructor_WithEmptyConnectionString_CreatesServiceWithWarning()
public async Task GetTranscriptAsync_NullMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetTranscriptAsync(null);
Expand All @@ -56,7 +66,7 @@ public async Task GetTranscriptAsync_NullMessageId_ReturnsError()
public async Task GetTranscriptAsync_EmptyMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetTranscriptAsync(string.Empty);
Expand All @@ -70,7 +80,7 @@ public async Task GetTranscriptAsync_EmptyMessageId_ReturnsError()
public async Task GetTranscriptAsync_WhitespaceMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetTranscriptAsync(" ");
Expand All @@ -87,7 +97,7 @@ public async Task GetTranscriptAsync_WhitespaceMessageId_ReturnsError()
public async Task GetTranscriptAsync_ServiceNotConfigured_ReturnsConfigurationError()
{
// Arrange - Create service with null connection string
var unconfiguredService = new TranscriptService(null, "transcripts");
var unconfiguredService = new TranscriptService(null, "transcripts", _mockCache.Object);
var messageId = "507f1f77bcf86cd799439011";

// Act
Expand All @@ -102,7 +112,7 @@ public async Task GetTranscriptAsync_ServiceNotConfigured_ReturnsConfigurationEr
public async Task GetTranscriptAsync_EmptyConnectionString_ReturnsConfigurationError()
{
// Arrange
var service = new TranscriptService(string.Empty, "transcripts");
var service = new TranscriptService(string.Empty, "transcripts", _mockCache.Object);
var messageId = "507f1f77bcf86cd799439011";

// Act
Expand All @@ -121,7 +131,7 @@ public async Task GetTranscriptAsync_EmptyConnectionString_ReturnsConfigurationE
public async Task GetSermonNotesAsync_NullMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetSermonNotesAsync(null);
Expand All @@ -135,7 +145,7 @@ public async Task GetSermonNotesAsync_NullMessageId_ReturnsError()
public async Task GetSermonNotesAsync_EmptyMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetSermonNotesAsync(string.Empty);
Expand All @@ -149,7 +159,7 @@ public async Task GetSermonNotesAsync_EmptyMessageId_ReturnsError()
public async Task GetSermonNotesAsync_WhitespaceMessageId_ReturnsConfigurationError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetSermonNotesAsync(" ");
Expand All @@ -167,7 +177,7 @@ public async Task GetSermonNotesAsync_WhitespaceMessageId_ReturnsConfigurationEr
public async Task GetSermonNotesAsync_ServiceNotConfigured_ReturnsConfigurationError()
{
// Arrange - Create service with null connection string
var unconfiguredService = new TranscriptService(null, "transcripts");
var unconfiguredService = new TranscriptService(null, "transcripts", _mockCache.Object);
var messageId = "507f1f77bcf86cd799439011";

// Act
Expand All @@ -182,7 +192,7 @@ public async Task GetSermonNotesAsync_ServiceNotConfigured_ReturnsConfigurationE
public async Task GetSermonNotesAsync_EmptyConnectionString_ReturnsConfigurationError()
{
// Arrange
var service = new TranscriptService(string.Empty, "transcripts");
var service = new TranscriptService(string.Empty, "transcripts", _mockCache.Object);
var messageId = "507f1f77bcf86cd799439011";

// Act
Expand All @@ -201,7 +211,7 @@ public async Task GetSermonNotesAsync_EmptyConnectionString_ReturnsConfiguration
public async Task GetStudyGuideAsync_NullMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetStudyGuideAsync(null);
Expand All @@ -215,7 +225,7 @@ public async Task GetStudyGuideAsync_NullMessageId_ReturnsError()
public async Task GetStudyGuideAsync_EmptyMessageId_ReturnsError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetStudyGuideAsync(string.Empty);
Expand All @@ -229,7 +239,7 @@ public async Task GetStudyGuideAsync_EmptyMessageId_ReturnsError()
public async Task GetStudyGuideAsync_WhitespaceMessageId_ReturnsConfigurationError()
{
// Arrange
var service = new TranscriptService(null, "transcripts");
var service = new TranscriptService(null, "transcripts", _mockCache.Object);

// Act
var result = await service.GetStudyGuideAsync(" ");
Expand All @@ -247,7 +257,7 @@ public async Task GetStudyGuideAsync_WhitespaceMessageId_ReturnsConfigurationErr
public async Task GetStudyGuideAsync_ServiceNotConfigured_ReturnsConfigurationError()
{
// Arrange - Create service with null connection string
var unconfiguredService = new TranscriptService(null, "transcripts");
var unconfiguredService = new TranscriptService(null, "transcripts", _mockCache.Object);
var messageId = "507f1f77bcf86cd799439011";

// Act
Expand All @@ -262,7 +272,7 @@ public async Task GetStudyGuideAsync_ServiceNotConfigured_ReturnsConfigurationEr
public async Task GetStudyGuideAsync_EmptyConnectionString_ReturnsConfigurationError()
{
// Arrange
var service = new TranscriptService(string.Empty, "transcripts");
var service = new TranscriptService(string.Empty, "transcripts", _mockCache.Object);
var messageId = "507f1f77bcf86cd799439011";

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,10 @@ public void ConfigureServices(IServiceCollection services)
// Azure Blob Storage services (for transcripts)
var azureStorageConnectionString = Configuration["AzureStorageConnectionString"];
services.AddSingleton<ITranscriptService>(sp =>
new TranscriptService(azureStorageConnectionString, "transcripts"));
new TranscriptService(
azureStorageConnectionString,
"transcripts",
sp.GetRequiredService<ICacheService>()));

Log.Information("Services configured.");
}
Expand Down
Loading