PlotDirector/PlotLine/Data/IllustrationLibraryRepository.cs

215 lines
9.9 KiB
C#

using System.Data;
using Dapper;
using PlotLine.Models;
namespace PlotLine.Data;
public interface IIllustrationLibraryRepository
{
Task<IReadOnlyList<IllustrationLibraryItem>> ListAsync(IllustrationLibraryFilter filter);
Task<IllustrationLibrarySummary> GetSummaryAsync();
Task<IllustrationLibraryItem?> GetAsync(int illustrationLibraryItemId);
Task<IllustrationLibraryItem> UpsertPlannedAsync(IllustrationGenerationSpecification specification, IllustrationPromptBuildResult prompt);
Task<int> QueueAsync(int? illustrationLibraryItemId, string? category);
Task<IllustrationLibraryItem?> ClaimNextAsync();
Task MarkGeneratedAsync(int illustrationLibraryItemId, IllustrationStoredImage image, IllustrationImageGenerationResult providerResult, string prompt, string templateVersion);
Task MarkFailedAsync(int illustrationLibraryItemId, string errorMessage);
Task ApproveAsync(int illustrationLibraryItemId, int userId);
Task RejectAsync(int illustrationLibraryItemId, int userId, string? reason);
Task<IllustrationLibraryItem?> CreateRegenerationAsync(int illustrationLibraryItemId, string? adminNotes);
Task DisableAsync(int illustrationLibraryItemId);
Task UpdateMetadataAsync(int illustrationLibraryItemId, string displayTitle, string? shortDescription, string? metadataJson, string? adminNotes);
Task<IReadOnlyList<IllustrationLibraryItem>> GetApprovedAsync();
Task<int> SupersedeRecoveredFailuresAsync();
}
public sealed class IllustrationLibraryRepository(ISqlConnectionFactory connectionFactory) : IIllustrationLibraryRepository
{
public async Task<IReadOnlyList<IllustrationLibraryItem>> ListAsync(IllustrationLibraryFilter filter)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<IllustrationLibraryItem>(
"dbo.IllustrationLibraryItem_List",
new
{
Category = string.IsNullOrWhiteSpace(filter.Category) ? null : filter.Category,
Status = string.IsNullOrWhiteSpace(filter.Status) ? null : filter.Status,
Search = string.IsNullOrWhiteSpace(filter.Search) ? null : filter.Search
},
commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IllustrationLibrarySummary> GetSummaryAsync()
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<IllustrationLibrarySummary>(
"dbo.IllustrationLibraryItem_Summary",
commandType: CommandType.StoredProcedure);
}
public async Task<IllustrationLibraryItem?> GetAsync(int illustrationLibraryItemId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<IllustrationLibraryItem>(
"dbo.IllustrationLibraryItem_Get",
new { IllustrationLibraryItemID = illustrationLibraryItemId },
commandType: CommandType.StoredProcedure);
}
public async Task<IllustrationLibraryItem> UpsertPlannedAsync(IllustrationGenerationSpecification specification, IllustrationPromptBuildResult prompt)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<IllustrationLibraryItem>(
"dbo.IllustrationLibraryItem_UpsertPlanned",
new
{
Category = specification.Category,
StableCode = specification.Code,
DisplayTitle = specification.Title,
ShortDescription = specification.Description,
MetadataJson = prompt.MetadataJson,
SpecificationJson = prompt.SpecificationJson
},
commandType: CommandType.StoredProcedure);
}
public async Task<int> QueueAsync(int? illustrationLibraryItemId, string? category)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.IllustrationLibraryItem_Queue",
new { IllustrationLibraryItemID = illustrationLibraryItemId, Category = category },
commandType: CommandType.StoredProcedure);
}
public async Task<IllustrationLibraryItem?> ClaimNextAsync()
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<IllustrationLibraryItem>(
"dbo.IllustrationLibraryItem_ClaimNext",
commandType: CommandType.StoredProcedure);
}
public async Task MarkGeneratedAsync(int illustrationLibraryItemId, IllustrationStoredImage image, IllustrationImageGenerationResult providerResult, string prompt, string templateVersion)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.IllustrationLibraryItem_MarkGenerated",
new
{
IllustrationLibraryItemID = illustrationLibraryItemId,
image.FilePath,
image.ThumbnailPath,
image.Width,
image.Height,
image.FileSizeBytes,
image.MimeType,
Provider = providerResult.Provider,
Model = providerResult.Model,
PromptText = prompt,
PromptTemplateVersion = templateVersion,
ProviderRequestID = providerResult.ProviderRequestID
},
commandType: CommandType.StoredProcedure);
}
public async Task MarkFailedAsync(int illustrationLibraryItemId, string errorMessage)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.IllustrationLibraryItem_MarkFailed",
new { IllustrationLibraryItemID = illustrationLibraryItemId, LastError = errorMessage },
commandType: CommandType.StoredProcedure);
}
public async Task ApproveAsync(int illustrationLibraryItemId, int userId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.IllustrationLibraryItem_Approve",
new { IllustrationLibraryItemID = illustrationLibraryItemId, ApprovedByUserID = userId },
commandType: CommandType.StoredProcedure);
}
public async Task RejectAsync(int illustrationLibraryItemId, int userId, string? reason)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.IllustrationLibraryItem_Reject",
new { IllustrationLibraryItemID = illustrationLibraryItemId, RejectedByUserID = userId, RejectionReason = reason },
commandType: CommandType.StoredProcedure);
}
public async Task<IllustrationLibraryItem?> CreateRegenerationAsync(int illustrationLibraryItemId, string? adminNotes)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<IllustrationLibraryItem>(
"dbo.IllustrationLibraryItem_CreateRegeneration",
new { IllustrationLibraryItemID = illustrationLibraryItemId, AdminNotes = adminNotes },
commandType: CommandType.StoredProcedure);
}
public async Task DisableAsync(int illustrationLibraryItemId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.IllustrationLibraryItem_Disable",
new { IllustrationLibraryItemID = illustrationLibraryItemId },
commandType: CommandType.StoredProcedure);
}
public async Task UpdateMetadataAsync(int illustrationLibraryItemId, string displayTitle, string? shortDescription, string? metadataJson, string? adminNotes)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.IllustrationLibraryItem_UpdateMetadata",
new
{
IllustrationLibraryItemID = illustrationLibraryItemId,
DisplayTitle = displayTitle,
ShortDescription = shortDescription,
MetadataJson = metadataJson,
AdminNotes = adminNotes
},
commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<IllustrationLibraryItem>> GetApprovedAsync()
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<IllustrationLibraryItem>(
"dbo.IllustrationLibraryItem_GetApproved",
commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SupersedeRecoveredFailuresAsync()
{
using var connection = connectionFactory.CreateConnection();
return await connection.ExecuteAsync(
"""
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
UPDATE failed
SET Status = N'Superseded',
IsActive = 0,
UpdatedUtc = SYSUTCDATETIME(),
AdminNotes = CONCAT(ISNULL(NULLIF(failed.AdminNotes, N''), N''), CASE WHEN NULLIF(failed.AdminNotes, N'') IS NULL THEN N'' ELSE N' ' END, N'Superseded after a replacement illustration generated successfully.')
FROM dbo.IllustrationLibraryItems failed
WHERE failed.Status = N'Failed'
AND failed.IsActive = 1
AND EXISTS
(
SELECT 1
FROM dbo.IllustrationLibraryItems replacement
WHERE replacement.IllustrationLibraryItemID <> failed.IllustrationLibraryItemID
AND replacement.StableCode = failed.StableCode
AND replacement.IsActive = 1
AND replacement.Status IN (N'Generated', N'Approved')
);
""");
}
}