198 lines
7.8 KiB
C#
198 lines
7.8 KiB
C#
using System.Data;
|
|
using System.Text;
|
|
using Dapper;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.Data;
|
|
|
|
public interface IImportRepository
|
|
{
|
|
Task<bool> ProjectTitleExistsAsync(string projectTitle);
|
|
Task<ImportResult> ImportAsync(PlotLineImportPackage package);
|
|
}
|
|
|
|
public sealed class ImportRepository(ISqlConnectionFactory connectionFactory) : IImportRepository
|
|
{
|
|
private const string ImportMarker = "Imported via JSON Import Phase 1A";
|
|
|
|
public async Task<bool> ProjectTitleExistsAsync(string projectTitle)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var count = await connection.ExecuteScalarAsync<int>(
|
|
"""
|
|
SELECT COUNT(1)
|
|
FROM dbo.Projects
|
|
WHERE IsArchived = 0 AND ProjectName = @ProjectName;
|
|
""",
|
|
new { ProjectName = projectTitle });
|
|
|
|
return count > 0;
|
|
}
|
|
|
|
public async Task<ImportResult> ImportAsync(PlotLineImportPackage package)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
connection.Open();
|
|
using var transaction = connection.BeginTransaction();
|
|
|
|
try
|
|
{
|
|
var lookupIds = await GetLookupIdsAsync(connection, transaction);
|
|
var projectId = await connection.QuerySingleAsync<int>(
|
|
"dbo.Project_Save",
|
|
new
|
|
{
|
|
ProjectID = 0,
|
|
ProjectName = Clean(package.Project.Title),
|
|
Description = CombineText(package.Project.Description, package.Project.Notes, ImportMarker)
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
foreach (var book in package.Books.OrderBy(x => x.SeriesOrder))
|
|
{
|
|
var bookId = await connection.QuerySingleAsync<int>(
|
|
"dbo.Book_Save",
|
|
new
|
|
{
|
|
BookID = 0,
|
|
ProjectID = projectId,
|
|
BookTitle = Clean(FormatBookTitle(book)),
|
|
BookNumber = book.SeriesOrder,
|
|
Description = CombineText(
|
|
book.Description,
|
|
book.DependsOnPreviousBook ? "Depends on previous book." : null,
|
|
book.Notes,
|
|
ImportMarker)
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
foreach (var chapter in book.Chapters.OrderBy(x => x.Order))
|
|
{
|
|
var chapterId = await connection.QuerySingleAsync<int>(
|
|
"dbo.Chapter_Save",
|
|
new
|
|
{
|
|
ChapterID = 0,
|
|
BookID = bookId,
|
|
chapter.ChapterNumber,
|
|
ChapterTitle = Clean(chapter.Title),
|
|
Summary = CombineText(chapter.Summary, chapter.Notes, ImportMarker),
|
|
RevisionStatusID = lookupIds.RevisionStatusID
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
foreach (var scene in chapter.Scenes.OrderBy(x => x.Order))
|
|
{
|
|
await connection.QuerySingleAsync<int>(
|
|
"dbo.Scene_Save",
|
|
new
|
|
{
|
|
SceneID = 0,
|
|
ChapterID = chapterId,
|
|
scene.SceneNumber,
|
|
SceneTitle = Clean(scene.Title),
|
|
Summary = CombineText(
|
|
scene.Summary,
|
|
MetadataLine("POV", scene.PovCharacterName),
|
|
MetadataLine("Location", scene.LocationName),
|
|
scene.Notes,
|
|
ImportMarker),
|
|
TimeModeID = lookupIds.TimeModeID,
|
|
StartDateTime = (DateTime?)null,
|
|
EndDateTime = (DateTime?)null,
|
|
DurationAmount = (decimal?)null,
|
|
DurationUnitID = (int?)null,
|
|
RelativeTimeText = Truncate(scene.DateTimeText, 200),
|
|
TimeConfidenceID = lookupIds.TimeConfidenceID,
|
|
ScenePurposeNotes = CombineText(scene.PurposeText),
|
|
SceneOutcomeNotes = CombineText(scene.OutcomeText),
|
|
RevisionStatusID = lookupIds.RevisionStatusID
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
}
|
|
}
|
|
|
|
transaction.Commit();
|
|
return new ImportResult
|
|
{
|
|
Succeeded = true,
|
|
ProjectID = projectId,
|
|
Message = "Import complete."
|
|
};
|
|
}
|
|
catch
|
|
{
|
|
transaction.Rollback();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static async Task<ImportLookupIds> GetLookupIdsAsync(IDbConnection connection, IDbTransaction transaction)
|
|
{
|
|
var revisionStatusId = await connection.QuerySingleOrDefaultAsync<int?>(
|
|
"SELECT TOP (1) RevisionStatusID FROM dbo.RevisionStatuses WHERE StatusName = N'Planned' ORDER BY SortOrder;",
|
|
transaction: transaction);
|
|
var timeModeId = await connection.QuerySingleOrDefaultAsync<int?>(
|
|
"SELECT TOP (1) TimeModeID FROM dbo.TimeModes WHERE TimeModeName = N'Relative' ORDER BY SortOrder;",
|
|
transaction: transaction);
|
|
var timeConfidenceId = await connection.QuerySingleOrDefaultAsync<int?>(
|
|
"SELECT TOP (1) TimeConfidenceID FROM dbo.TimeConfidences WHERE TimeConfidenceName = N'Unknown' ORDER BY SortOrder;",
|
|
transaction: transaction);
|
|
|
|
if (!revisionStatusId.HasValue || !timeModeId.HasValue || !timeConfidenceId.HasValue)
|
|
{
|
|
throw new InvalidOperationException("Import lookup values are missing. Run the core database script before importing.");
|
|
}
|
|
|
|
return new ImportLookupIds(revisionStatusId.Value, timeModeId.Value, timeConfidenceId.Value);
|
|
}
|
|
|
|
private static string FormatBookTitle(ImportBookDto book)
|
|
=> string.IsNullOrWhiteSpace(book.Subtitle) ? book.Title : $"{book.Title}: {book.Subtitle}";
|
|
|
|
private static string Clean(string? value) => (value ?? string.Empty).Trim();
|
|
|
|
private static string? MetadataLine(string label, string? value)
|
|
=> string.IsNullOrWhiteSpace(value) ? null : $"{label}: {value.Trim()}";
|
|
|
|
private static string? Truncate(string? value, int maxLength)
|
|
{
|
|
var clean = Clean(value);
|
|
return clean.Length == 0 ? null : clean[..Math.Min(clean.Length, maxLength)];
|
|
}
|
|
|
|
private static string? CombineText(params string?[] values)
|
|
{
|
|
var parts = values
|
|
.Select(Clean)
|
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
if (parts.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var builder = new StringBuilder();
|
|
foreach (var part in parts)
|
|
{
|
|
if (builder.Length > 0)
|
|
{
|
|
builder.AppendLine().AppendLine();
|
|
}
|
|
|
|
builder.Append(part);
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
private sealed record ImportLookupIds(int RevisionStatusID, int TimeModeID, int TimeConfidenceID);
|
|
}
|