215 lines
9.9 KiB
C#
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')
|
|
);
|
|
""");
|
|
}
|
|
}
|