PlotDirector/PlotLine/Data/Repositories.cs
2026-06-01 09:03:59 +01:00

1373 lines
63 KiB
C#

using System.Data;
using Dapper;
using PlotLine.Models;
namespace PlotLine.Data;
public interface IProjectRepository
{
Task<IReadOnlyList<Project>> ListAsync();
Task<Project?> GetAsync(int projectId);
Task<int> SaveAsync(Project project);
Task ArchiveAsync(int projectId);
}
public interface IBookRepository
{
Task<IReadOnlyList<Book>> ListByProjectAsync(int projectId);
Task<Book?> GetAsync(int bookId);
Task<int> SaveAsync(Book book);
Task ArchiveAsync(int bookId);
}
public interface IChapterRepository
{
Task<IReadOnlyList<Chapter>> ListByBookAsync(int bookId);
Task<Chapter?> GetAsync(int chapterId);
Task<int> SaveAsync(Chapter chapter);
Task ArchiveAsync(int chapterId);
}
public interface ISceneRepository
{
Task<IReadOnlyList<Scene>> ListByChapterAsync(int chapterId);
Task<Scene?> GetAsync(int sceneId);
Task<int> SaveAsync(Scene scene);
Task SavePurposesAsync(int sceneId, IEnumerable<int> purposeIds);
Task SaveMetricValuesAsync(int sceneId, IEnumerable<SceneMetricValue> metricValues);
Task ArchiveAsync(int sceneId);
Task MoveAsync(int sceneId, string direction);
Task<IReadOnlyList<ChapterOption>> ListChapterOptionsAsync(int projectId);
Task<SceneMovePreview?> GetMovePreviewAsync(int sceneId, int targetChapterId, string position);
Task MoveToChapterAsync(int sceneId, int targetChapterId, string position, bool renumber);
Task RenumberChapterAsync(int chapterId);
}
public interface ILookupRepository
{
Task<LookupData> GetAllAsync();
}
public interface ITimelineRepository
{
Task<TimelineData> GetByProjectAsync(int projectId, int? bookId);
}
public interface IPlotRepository
{
Task<PlotLookupData> GetLookupsAsync();
Task<IReadOnlyList<PlotLineItem>> ListPlotLinesAsync(int projectId);
Task<PlotLineItem?> GetPlotLineAsync(int plotLineId);
Task<int> SavePlotLineAsync(PlotLineItem plotLine);
Task ArchivePlotLineAsync(int plotLineId);
Task<IReadOnlyList<PlotThread>> ListPlotThreadsByProjectAsync(int projectId);
Task<IReadOnlyList<PlotThread>> ListPlotThreadsByPlotLineAsync(int plotLineId);
Task<PlotThread?> GetPlotThreadAsync(int plotThreadId);
Task<int> SavePlotThreadAsync(PlotThread plotThread);
Task ArchivePlotThreadAsync(int plotThreadId);
Task<IReadOnlyList<ThreadEvent>> ListThreadEventsBySceneAsync(int sceneId);
Task<IReadOnlyList<ThreadEvent>> ListThreadEventsByPlotThreadAsync(int plotThreadId);
Task<int> SaveThreadEventAsync(ThreadEvent threadEvent);
Task DeleteThreadEventAsync(int threadEventId);
Task<IReadOnlyList<SceneOption>> ListSceneOptionsAsync(int projectId);
}
public interface IAssetRepository
{
Task<AssetLookupData> GetLookupsAsync();
Task<IReadOnlyList<StoryAsset>> ListAssetsAsync(int projectId);
Task<StoryAsset?> GetAssetAsync(int storyAssetId);
Task<int> SaveAssetAsync(StoryAsset asset);
Task ArchiveAssetAsync(int storyAssetId);
Task<IReadOnlyList<AssetEvent>> ListEventsBySceneAsync(int sceneId);
Task<IReadOnlyList<AssetEvent>> ListEventsByAssetAsync(int storyAssetId);
Task<int> SaveEventAsync(AssetEvent assetEvent);
Task DeleteEventAsync(int assetEventId);
Task<IReadOnlyList<AssetDependency>> ListDependenciesByAssetAsync(int storyAssetId);
Task<int> SaveDependencyAsync(AssetDependency dependency);
Task DeleteDependencyAsync(int assetDependencyId);
Task<IReadOnlyList<AssetCustodyEvent>> ListCustodyBySceneAsync(int sceneId);
Task<IReadOnlyList<AssetCustodyEvent>> ListCustodyByAssetAsync(int storyAssetId);
Task<int> SaveCustodyEventAsync(AssetCustodyEvent custodyEvent, string custodianNames, IEnumerable<int> custodianCharacterIds, int custodyRoleId);
Task DeleteCustodyEventAsync(int assetCustodyEventId);
Task<(IReadOnlyList<StoryAsset> Assets, IReadOnlyList<AssetEvent> Events)> GetTimelineAsync(int projectId, int? bookId);
Task<IReadOnlyList<AssetDependencyIssue>> CheckDependencyIssuesAsync(int storyAssetId, int sceneId, int? toStateId);
}
public interface ICharacterRepository
{
Task<CharacterLookupData> GetLookupsAsync();
Task<IReadOnlyList<Character>> ListCharactersAsync(int projectId);
Task<Character?> GetCharacterAsync(int characterId);
Task<int> SaveCharacterAsync(Character character);
Task ArchiveCharacterAsync(int characterId);
Task<IReadOnlyList<SceneCharacter>> ListSceneCharactersAsync(int sceneId);
Task<int> SaveSceneCharacterAsync(SceneCharacter sceneCharacter);
Task DeleteSceneCharacterAsync(int sceneCharacterId);
Task<IReadOnlyList<CharacterAttributeEvent>> ListAttributeEventsByCharacterAsync(int characterId);
Task<int> SaveAttributeEventAsync(CharacterAttributeEvent attributeEvent);
Task<IReadOnlyList<CharacterKnowledgeItem>> ListKnowledgeBySceneAsync(int sceneId);
Task<IReadOnlyList<CharacterKnowledgeItem>> ListKnowledgeByCharacterAsync(int characterId);
Task<int> SaveKnowledgeAsync(CharacterKnowledgeItem knowledge);
Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByProjectAsync(int projectId);
Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByCharacterAsync(int characterId);
Task<int> SaveRelationshipAsync(CharacterRelationship relationship);
Task ArchiveRelationshipAsync(int characterRelationshipId);
Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsBySceneAsync(int sceneId);
Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsByCharacterAsync(int characterId);
Task<int> SaveRelationshipEventAsync(RelationshipEvent relationshipEvent);
Task<(IReadOnlyList<Character> Characters, IReadOnlyList<SceneCharacter> Appearances)> GetTimelineAsync(int projectId, int? bookId);
}
public interface ILocationRepository
{
Task<LocationLookupData> GetLookupsAsync();
Task<IReadOnlyList<LocationItem>> ListByProjectAsync(int projectId);
Task<LocationItem?> GetAsync(int locationId);
Task<int> SaveAsync(LocationItem location);
Task ArchiveAsync(int locationId);
Task<IReadOnlyList<LocationRelationship>> ListRelationshipsByProjectAsync(int projectId);
Task<IReadOnlyList<LocationRelationship>> ListRelationshipsByLocationAsync(int locationId);
Task<int> SaveRelationshipAsync(LocationRelationship relationship);
Task ArchiveRelationshipAsync(int locationRelationshipId);
Task<IReadOnlyList<SceneAssetLocation>> ListSceneAssetLocationsAsync(int sceneId);
Task<int> SaveSceneAssetLocationAsync(SceneAssetLocation sceneAssetLocation, bool updateCurrentLocation);
Task DeleteSceneAssetLocationAsync(int sceneAssetLocationId);
Task<IReadOnlyList<Scene>> ListScenesByLocationAsync(int locationId);
Task<IReadOnlyList<SceneCharacter>> ListCharactersByLocationAsync(int locationId);
Task<IReadOnlyList<SceneAssetLocation>> ListAssetsByLocationAsync(int locationId);
}
public interface IWarningRepository
{
Task<WarningLookupData> GetLookupsAsync();
Task<ContinuityValidationSummary> ValidateProjectAsync(int projectId);
Task<ContinuityValidationSummary> ValidateBookAsync(int bookId);
Task<ContinuityValidationSummary> ValidateSceneAsync(int sceneId);
Task<IReadOnlyList<ContinuityWarning>> ListAsync(int projectId, int? bookId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, bool includeDismissed, bool includeIntentional);
Task<IReadOnlyList<ContinuityWarning>> ListBySceneAsync(int sceneId);
Task<IReadOnlyList<SceneWarningCount>> GetSceneCountsAsync(int projectId, int? bookId);
Task DismissAsync(int continuityWarningId);
Task MarkIntentionalAsync(int continuityWarningId);
Task RestoreAsync(int continuityWarningId);
Task FinaliseSceneDependencyWarningsAsync(int projectId, int? bookId);
}
public interface ISceneDependencyRepository
{
Task<SceneDependencyLookupData> GetLookupsAsync();
Task<IReadOnlyList<SceneDependency>> ListBySceneAsync(int sceneId);
Task<IReadOnlyList<SceneDependency>> ListByProjectAsync(int projectId, int? bookId);
Task<IReadOnlyList<SceneDependencyCount>> GetCountsAsync(int projectId, int? bookId);
Task<int> SaveAsync(SceneDependency dependency);
Task ArchiveAsync(int sceneDependencyId);
}
public interface IAnalyticsRepository
{
Task<AnalyticsData> GetStoryHealthAsync(int projectId, int? bookId, decimal? startChapterNumber, decimal? endChapterNumber, decimal? startSceneNumber, decimal? endSceneNumber);
}
public sealed class TimelineData
{
public Project? Project { get; init; }
public IReadOnlyList<Book> Books { get; init; } = [];
public IReadOnlyList<Chapter> Chapters { get; init; } = [];
public IReadOnlyList<Scene> Scenes { get; init; } = [];
public IReadOnlyList<ScenePurposeLabel> PurposeLabels { get; init; } = [];
public IReadOnlyList<SceneMetricType> MetricTypes { get; init; } = [];
public IReadOnlyList<TimelineMetricPoint> MetricPoints { get; init; } = [];
public IReadOnlyList<PlotLineItem> PlotLines { get; init; } = [];
public IReadOnlyList<ThreadEvent> ThreadEvents { get; init; } = [];
}
public sealed class LocationRepository(ISqlConnectionFactory connectionFactory) : ILocationRepository
{
public async Task<LocationLookupData> GetLookupsAsync()
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.LocationLookup_All", commandType: CommandType.StoredProcedure);
return new LocationLookupData
{
LocationTypes = (await result.ReadAsync<LocationType>()).ToList(),
RelationshipTypes = (await result.ReadAsync<LocationRelationshipType>()).ToList()
};
}
public async Task<IReadOnlyList<LocationItem>> ListByProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<LocationItem>("dbo.Location_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<LocationItem?> GetAsync(int locationId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<LocationItem>("dbo.Location_Get", new { LocationID = locationId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SaveAsync(LocationItem location)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Location_Save",
new
{
location.LocationID,
location.ProjectID,
location.ParentLocationID,
location.LocationName,
location.LocationTypeID,
location.Description
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveAsync(int locationId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Location_Archive", new { LocationID = locationId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<LocationRelationship>> ListRelationshipsByProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<LocationRelationship>("dbo.LocationRelationship_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<LocationRelationship>> ListRelationshipsByLocationAsync(int locationId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<LocationRelationship>("dbo.LocationRelationship_ListByLocation", new { LocationID = locationId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveRelationshipAsync(LocationRelationship relationship)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.LocationRelationship_Save",
new
{
relationship.LocationRelationshipID,
relationship.FromLocationID,
relationship.ToLocationID,
relationship.LocationRelationshipTypeID,
relationship.IsBidirectional,
relationship.Strength,
relationship.CanHearNormalSpeech,
relationship.CanHearLoudNoise,
relationship.CanSeeMovement,
relationship.CanSeeClearly,
relationship.CanTravelDirectly,
relationship.Notes
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveRelationshipAsync(int locationRelationshipId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.LocationRelationship_Archive", new { LocationRelationshipID = locationRelationshipId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<SceneAssetLocation>> ListSceneAssetLocationsAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneAssetLocation>("dbo.SceneAssetLocation_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveSceneAssetLocationAsync(SceneAssetLocation sceneAssetLocation, bool updateCurrentLocation)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.SceneAssetLocation_Save",
new
{
sceneAssetLocation.SceneAssetLocationID,
sceneAssetLocation.SceneID,
sceneAssetLocation.StoryAssetID,
sceneAssetLocation.LocationID,
sceneAssetLocation.Description,
UpdateCurrentLocation = updateCurrentLocation
},
commandType: CommandType.StoredProcedure);
}
public async Task DeleteSceneAssetLocationAsync(int sceneAssetLocationId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.SceneAssetLocation_Delete", new { SceneAssetLocationID = sceneAssetLocationId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<Scene>> ListScenesByLocationAsync(int locationId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Scene>("dbo.LocationScene_ListByLocation", new { LocationID = locationId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<SceneCharacter>> ListCharactersByLocationAsync(int locationId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneCharacter>("dbo.LocationCharacter_ListByLocation", new { LocationID = locationId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<SceneAssetLocation>> ListAssetsByLocationAsync(int locationId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneAssetLocation>("dbo.SceneAssetLocation_ListByLocation", new { LocationID = locationId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
}
public sealed class WarningRepository(ISqlConnectionFactory connectionFactory) : IWarningRepository
{
public async Task<WarningLookupData> GetLookupsAsync()
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.WarningLookup_All", commandType: CommandType.StoredProcedure);
return new WarningLookupData
{
WarningTypes = (await result.ReadAsync<WarningType>()).ToList(),
Severities = (await result.ReadAsync<WarningSeverity>()).ToList()
};
}
public async Task<ContinuityValidationSummary> ValidateProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<ContinuityValidationSummary>(
"dbo.ContinuityValidation_ValidateProject",
new { ProjectID = projectId, TriggeredBy = "User" },
commandType: CommandType.StoredProcedure);
}
public async Task<ContinuityValidationSummary> ValidateBookAsync(int bookId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<ContinuityValidationSummary>(
"dbo.ContinuityValidation_ValidateBook",
new { BookID = bookId, TriggeredBy = "User" },
commandType: CommandType.StoredProcedure);
}
public async Task<ContinuityValidationSummary> ValidateSceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<ContinuityValidationSummary>(
"dbo.ContinuityValidation_ValidateScene",
new { SceneID = sceneId, TriggeredBy = "User" },
commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<ContinuityWarning>> ListAsync(int projectId, int? bookId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, bool includeDismissed, bool includeIntentional)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<ContinuityWarning>(
"dbo.ContinuityWarning_List",
new
{
ProjectID = projectId,
BookID = bookId,
SceneID = sceneId,
WarningTypeID = warningTypeId,
WarningSeverityID = warningSeverityId,
EntityType = string.IsNullOrWhiteSpace(entityType) ? null : entityType,
IncludeDismissed = includeDismissed,
IncludeIntentional = includeIntentional
},
commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<ContinuityWarning>> ListBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<ContinuityWarning>("dbo.ContinuityWarning_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<SceneWarningCount>> GetSceneCountsAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneWarningCount>("dbo.ContinuityWarning_SceneCounts", new { ProjectID = projectId, BookID = bookId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task DismissAsync(int continuityWarningId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.ContinuityWarning_Dismiss", new { ContinuityWarningID = continuityWarningId }, commandType: CommandType.StoredProcedure);
}
public async Task MarkIntentionalAsync(int continuityWarningId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.ContinuityWarning_MarkIntentional", new { ContinuityWarningID = continuityWarningId }, commandType: CommandType.StoredProcedure);
}
public async Task RestoreAsync(int continuityWarningId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.ContinuityWarning_Restore", new { ContinuityWarningID = continuityWarningId }, commandType: CommandType.StoredProcedure);
}
public async Task FinaliseSceneDependencyWarningsAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.SceneDependency_FinaliseWarnings", new { ProjectID = projectId, BookID = bookId }, commandType: CommandType.StoredProcedure);
}
}
public sealed class AnalyticsData
{
public IReadOnlyList<AnalyticsMetricPoint> MetricPoints { get; init; } = [];
public IReadOnlyList<AnalyticsRevisionSummary> RevisionSummary { get; init; } = [];
public IReadOnlyList<AnalyticsRevisionScene> RevisionAttentionScenes { get; init; } = [];
public IReadOnlyList<AnalyticsPurposeSummary> PurposeSummary { get; init; } = [];
public IReadOnlyList<AnalyticsSceneGap> SceneGaps { get; init; } = [];
public IReadOnlyList<AnalyticsThreadHealth> ThreadHealth { get; init; } = [];
public IReadOnlyList<AnalyticsAssetHealth> AssetHealth { get; init; } = [];
public IReadOnlyList<AnalyticsWarningSeverityCount> WarningSeverityCounts { get; init; } = [];
public IReadOnlyList<AnalyticsWarningTypeCount> WarningTypeCounts { get; init; } = [];
public IReadOnlyList<AnalyticsWarningHotspot> WarningHotspots { get; init; } = [];
public IReadOnlyList<AnalyticsPovSummary> PovSummary { get; init; } = [];
public IReadOnlyList<AnalyticsMultiPovChapter> MultiPovChapters { get; init; } = [];
public IReadOnlyList<AnalyticsCharacterAppearance> CharacterAppearances { get; init; } = [];
public IReadOnlyList<AnalyticsBusyScene> BusyScenes { get; init; } = [];
public IReadOnlyList<AnalyticsLocationUsage> LocationUsage { get; init; } = [];
}
public sealed class AnalyticsRepository(ISqlConnectionFactory connectionFactory) : IAnalyticsRepository
{
public async Task<AnalyticsData> GetStoryHealthAsync(int projectId, int? bookId, decimal? startChapterNumber, decimal? endChapterNumber, decimal? startSceneNumber, decimal? endSceneNumber)
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync(
"dbo.Analytics_StoryHealth",
new
{
ProjectID = projectId,
BookID = bookId,
StartChapterNumber = startChapterNumber,
EndChapterNumber = endChapterNumber,
StartSceneNumber = startSceneNumber,
EndSceneNumber = endSceneNumber
},
commandType: CommandType.StoredProcedure);
return new AnalyticsData
{
MetricPoints = (await result.ReadAsync<AnalyticsMetricPoint>()).ToList(),
RevisionSummary = (await result.ReadAsync<AnalyticsRevisionSummary>()).ToList(),
RevisionAttentionScenes = (await result.ReadAsync<AnalyticsRevisionScene>()).ToList(),
PurposeSummary = (await result.ReadAsync<AnalyticsPurposeSummary>()).ToList(),
SceneGaps = (await result.ReadAsync<AnalyticsSceneGap>()).ToList(),
ThreadHealth = (await result.ReadAsync<AnalyticsThreadHealth>()).ToList(),
AssetHealth = (await result.ReadAsync<AnalyticsAssetHealth>()).ToList(),
WarningSeverityCounts = (await result.ReadAsync<AnalyticsWarningSeverityCount>()).ToList(),
WarningTypeCounts = (await result.ReadAsync<AnalyticsWarningTypeCount>()).ToList(),
WarningHotspots = (await result.ReadAsync<AnalyticsWarningHotspot>()).ToList(),
PovSummary = (await result.ReadAsync<AnalyticsPovSummary>()).ToList(),
MultiPovChapters = (await result.ReadAsync<AnalyticsMultiPovChapter>()).ToList(),
CharacterAppearances = (await result.ReadAsync<AnalyticsCharacterAppearance>()).ToList(),
BusyScenes = (await result.ReadAsync<AnalyticsBusyScene>()).ToList(),
LocationUsage = (await result.ReadAsync<AnalyticsLocationUsage>()).ToList()
};
}
}
public sealed class SceneDependencyRepository(ISqlConnectionFactory connectionFactory) : ISceneDependencyRepository
{
public async Task<SceneDependencyLookupData> GetLookupsAsync()
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneDependencyType>("dbo.SceneDependencyLookup_All", commandType: CommandType.StoredProcedure);
return new SceneDependencyLookupData { DependencyTypes = rows.ToList() };
}
public async Task<IReadOnlyList<SceneDependency>> ListBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneDependency>("dbo.SceneDependency_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<SceneDependency>> ListByProjectAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneDependency>("dbo.SceneDependency_ListByProject", new { ProjectID = projectId, BookID = bookId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<SceneDependencyCount>> GetCountsAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneDependencyCount>("dbo.SceneDependency_Counts", new { ProjectID = projectId, BookID = bookId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveAsync(SceneDependency dependency)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.SceneDependency_Save",
new
{
dependency.SceneDependencyID,
dependency.ProjectID,
dependency.SourceSceneID,
dependency.TargetSceneID,
dependency.SceneDependencyTypeID,
dependency.Description,
dependency.IsHardDependency
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveAsync(int sceneDependencyId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.SceneDependency_Archive", new { SceneDependencyID = sceneDependencyId }, commandType: CommandType.StoredProcedure);
}
}
public sealed class ProjectRepository(ISqlConnectionFactory connectionFactory) : IProjectRepository
{
public async Task<IReadOnlyList<Project>> ListAsync()
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Project>("dbo.Project_List", commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<Project?> GetAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Project>("dbo.Project_Get", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SaveAsync(Project project)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Project_Save",
new { project.ProjectID, project.ProjectName, project.Description },
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Project_Archive", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
}
}
public sealed class BookRepository(ISqlConnectionFactory connectionFactory) : IBookRepository
{
public async Task<IReadOnlyList<Book>> ListByProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Book>("dbo.Book_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<Book?> GetAsync(int bookId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Book>("dbo.Book_Get", new { BookID = bookId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SaveAsync(Book book)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Book_Save",
new { book.BookID, book.ProjectID, book.BookTitle, book.BookNumber, book.Description },
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveAsync(int bookId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Book_Archive", new { BookID = bookId }, commandType: CommandType.StoredProcedure);
}
}
public sealed class ChapterRepository(ISqlConnectionFactory connectionFactory) : IChapterRepository
{
public async Task<IReadOnlyList<Chapter>> ListByBookAsync(int bookId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Chapter>("dbo.Chapter_ListByBook", new { BookID = bookId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<Chapter?> GetAsync(int chapterId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Chapter>("dbo.Chapter_Get", new { ChapterID = chapterId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SaveAsync(Chapter chapter)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Chapter_Save",
new { chapter.ChapterID, chapter.BookID, chapter.ChapterNumber, chapter.ChapterTitle, chapter.Summary, chapter.RevisionStatusID },
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveAsync(int chapterId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Chapter_Archive", new { ChapterID = chapterId }, commandType: CommandType.StoredProcedure);
}
}
public sealed class SceneRepository(ISqlConnectionFactory connectionFactory) : ISceneRepository
{
public async Task<IReadOnlyList<Scene>> ListByChapterAsync(int chapterId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Scene>("dbo.Scene_ListByChapter", new { ChapterID = chapterId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<Scene?> GetAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.Scene_Get", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
var scene = await result.ReadSingleOrDefaultAsync<Scene>();
if (scene is null)
{
return null;
}
scene.SelectedPurposeTypeIds = (await result.ReadAsync<int>()).ToList();
scene.Metrics = (await result.ReadAsync<SceneMetricValue>()).ToList();
return scene;
}
public async Task<int> SaveAsync(Scene scene)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Scene_Save",
new
{
scene.SceneID,
scene.ChapterID,
scene.SceneNumber,
scene.SceneTitle,
scene.Summary,
scene.TimeModeID,
scene.StartDateTime,
scene.EndDateTime,
scene.DurationAmount,
scene.DurationUnitID,
scene.RelativeTimeText,
scene.TimeConfidenceID,
scene.ScenePurposeNotes,
scene.SceneOutcomeNotes,
scene.RevisionStatusID,
scene.PrimaryLocationID
},
commandType: CommandType.StoredProcedure);
}
public async Task SavePurposesAsync(int sceneId, IEnumerable<int> purposeIds)
{
using var connection = connectionFactory.CreateConnection();
var cleanPurposeIds = purposeIds
.Where(id => id > 0)
.Distinct()
.ToList();
await connection.ExecuteAsync(
"dbo.Scene_SavePurposes",
new { SceneID = sceneId, PurposeIds = string.Join(',', cleanPurposeIds) },
commandType: CommandType.StoredProcedure);
}
public async Task SaveMetricValuesAsync(int sceneId, IEnumerable<SceneMetricValue> metricValues)
{
using var connection = connectionFactory.CreateConnection();
foreach (var metric in metricValues)
{
await connection.ExecuteAsync(
"dbo.SceneMetric_SaveValue",
new { SceneID = sceneId, metric.MetricTypeID, metric.Value, metric.Notes },
commandType: CommandType.StoredProcedure);
}
}
public async Task ArchiveAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Scene_Archive", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
}
public async Task MoveAsync(int sceneId, string direction)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Scene_Move", new { SceneID = sceneId, Direction = direction }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<ChapterOption>> ListChapterOptionsAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<ChapterOption>("dbo.ChapterOption_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<SceneMovePreview?> GetMovePreviewAsync(int sceneId, int targetChapterId, string position)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<SceneMovePreview>(
"dbo.SceneMovePreview_Get",
new { SceneID = sceneId, TargetChapterID = targetChapterId, Position = position },
commandType: CommandType.StoredProcedure);
}
public async Task MoveToChapterAsync(int sceneId, int targetChapterId, string position, bool renumber)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.Scene_MoveToChapter",
new { SceneID = sceneId, TargetChapterID = targetChapterId, Position = position, Renumber = renumber },
commandType: CommandType.StoredProcedure);
}
public async Task RenumberChapterAsync(int chapterId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Scene_RenumberChapter", new { ChapterID = chapterId }, commandType: CommandType.StoredProcedure);
}
}
public sealed class LookupRepository(ISqlConnectionFactory connectionFactory) : ILookupRepository
{
public async Task<LookupData> GetAllAsync()
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.Lookup_All", commandType: CommandType.StoredProcedure);
return new LookupData
{
RevisionStatuses = (await result.ReadAsync<RevisionStatus>()).ToList(),
TimeModes = (await result.ReadAsync<TimeMode>()).ToList(),
DurationUnits = (await result.ReadAsync<DurationUnit>()).ToList(),
TimeConfidences = (await result.ReadAsync<TimeConfidence>()).ToList(),
ScenePurposeTypes = (await result.ReadAsync<ScenePurposeType>()).ToList()
};
}
}
public sealed class TimelineRepository(ISqlConnectionFactory connectionFactory) : ITimelineRepository
{
public async Task<TimelineData> GetByProjectAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync(
"dbo.Timeline_GetByProject",
new { ProjectID = projectId, BookID = bookId },
commandType: CommandType.StoredProcedure);
var project = await result.ReadSingleOrDefaultAsync<Project>();
var books = (await result.ReadAsync<Book>()).ToList();
var chapters = (await result.ReadAsync<Chapter>()).ToList();
var scenes = (await result.ReadAsync<Scene>()).ToList();
var purposeLabels = (await result.ReadAsync<ScenePurposeLabel>()).ToList();
var metricTypes = (await result.ReadAsync<SceneMetricType>()).ToList();
var metricPoints = (await result.ReadAsync<TimelineMetricPoint>()).ToList();
var plotLines = (await result.ReadAsync<PlotLineItem>()).ToList();
var threadEvents = (await result.ReadAsync<ThreadEvent>()).ToList();
return new TimelineData
{
Project = project,
Books = books,
Chapters = chapters,
Scenes = scenes,
PurposeLabels = purposeLabels,
MetricTypes = metricTypes,
MetricPoints = metricPoints,
PlotLines = plotLines,
ThreadEvents = threadEvents
};
}
}
public sealed class PlotRepository(ISqlConnectionFactory connectionFactory) : IPlotRepository
{
public async Task<PlotLookupData> GetLookupsAsync()
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.PlotLookup_All", commandType: CommandType.StoredProcedure);
return new PlotLookupData
{
PlotLineTypes = (await result.ReadAsync<PlotLineType>()).ToList(),
ThreadTypes = (await result.ReadAsync<ThreadType>()).ToList(),
ThreadStatuses = (await result.ReadAsync<ThreadStatus>()).ToList(),
ThreadEventTypes = (await result.ReadAsync<ThreadEventType>()).ToList()
};
}
public async Task<IReadOnlyList<PlotLineItem>> ListPlotLinesAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<PlotLineItem>("dbo.PlotLine_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<PlotLineItem?> GetPlotLineAsync(int plotLineId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<PlotLineItem>("dbo.PlotLine_Get", new { PlotLineID = plotLineId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SavePlotLineAsync(PlotLineItem plotLine)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.PlotLine_Save",
new
{
plotLine.PlotLineID,
plotLine.ProjectID,
plotLine.BookID,
plotLine.PlotLineName,
plotLine.PlotLineTypeID,
plotLine.Description,
plotLine.ParentPlotLineID,
plotLine.SortOrder,
plotLine.Colour,
plotLine.IsMainPlot,
plotLine.IsVisibleOnTimeline
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchivePlotLineAsync(int plotLineId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.PlotLine_Archive", new { PlotLineID = plotLineId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<PlotThread>> ListPlotThreadsByProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<PlotThread>("dbo.PlotThread_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<PlotThread>> ListPlotThreadsByPlotLineAsync(int plotLineId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<PlotThread>("dbo.PlotThread_ListByPlotLine", new { PlotLineID = plotLineId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<PlotThread?> GetPlotThreadAsync(int plotThreadId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<PlotThread>("dbo.PlotThread_Get", new { PlotThreadID = plotThreadId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SavePlotThreadAsync(PlotThread plotThread)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.PlotThread_Save",
new
{
plotThread.PlotThreadID,
plotThread.PlotLineID,
plotThread.ThreadTitle,
plotThread.ThreadTypeID,
plotThread.ThreadStatusID,
plotThread.Importance,
plotThread.Summary,
plotThread.IntroducedSceneID,
plotThread.PlannedResolutionSceneID,
plotThread.ActualResolutionSceneID
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchivePlotThreadAsync(int plotThreadId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.PlotThread_Archive", new { PlotThreadID = plotThreadId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<ThreadEvent>> ListThreadEventsBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<ThreadEvent>("dbo.ThreadEvent_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<ThreadEvent>> ListThreadEventsByPlotThreadAsync(int plotThreadId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<ThreadEvent>("dbo.ThreadEvent_ListByPlotThread", new { PlotThreadID = plotThreadId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveThreadEventAsync(ThreadEvent threadEvent)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.ThreadEvent_Save",
new
{
threadEvent.ThreadEventID,
threadEvent.PlotThreadID,
threadEvent.SceneID,
threadEvent.EventTypeID,
threadEvent.EventTitle,
threadEvent.EventDescription
},
commandType: CommandType.StoredProcedure);
}
public async Task DeleteThreadEventAsync(int threadEventId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.ThreadEvent_Delete", new { ThreadEventID = threadEventId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<SceneOption>> ListSceneOptionsAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneOption>("dbo.SceneOption_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
}
public sealed class AssetRepository(ISqlConnectionFactory connectionFactory) : IAssetRepository
{
public async Task<AssetLookupData> GetLookupsAsync()
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.AssetLookup_All", commandType: CommandType.StoredProcedure);
return new AssetLookupData
{
AssetKinds = (await result.ReadAsync<AssetKind>()).ToList(),
AssetStates = (await result.ReadAsync<AssetState>()).ToList(),
AssetEventTypes = (await result.ReadAsync<AssetEventType>()).ToList(),
AssetDependencyTypes = (await result.ReadAsync<AssetDependencyType>()).ToList(),
AssetCustodyEventTypes = (await result.ReadAsync<AssetCustodyEventType>()).ToList(),
CustodyRoles = (await result.ReadAsync<CustodyRole>()).ToList()
};
}
public async Task<IReadOnlyList<StoryAsset>> ListAssetsAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<StoryAsset>("dbo.StoryAsset_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<StoryAsset?> GetAssetAsync(int storyAssetId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<StoryAsset>("dbo.StoryAsset_Get", new { StoryAssetID = storyAssetId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SaveAssetAsync(StoryAsset asset)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.StoryAsset_Save",
new
{
asset.StoryAssetID,
asset.ProjectID,
asset.AssetName,
asset.AssetKindID,
asset.Description,
asset.Importance,
asset.CurrentStateID,
asset.CurrentLocationID,
asset.IsResolved
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveAssetAsync(int storyAssetId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.StoryAsset_Archive", new { StoryAssetID = storyAssetId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<AssetEvent>> ListEventsBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<AssetEvent>("dbo.AssetEvent_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<AssetEvent>> ListEventsByAssetAsync(int storyAssetId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<AssetEvent>("dbo.AssetEvent_ListByAsset", new { StoryAssetID = storyAssetId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveEventAsync(AssetEvent assetEvent)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.AssetEvent_Save",
new
{
assetEvent.AssetEventID,
assetEvent.StoryAssetID,
assetEvent.SceneID,
assetEvent.AssetEventTypeID,
assetEvent.FromStateID,
assetEvent.ToStateID,
assetEvent.EventTitle,
assetEvent.EventDescription
},
commandType: CommandType.StoredProcedure);
}
public async Task DeleteEventAsync(int assetEventId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.AssetEvent_Delete", new { AssetEventID = assetEventId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<AssetDependency>> ListDependenciesByAssetAsync(int storyAssetId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<AssetDependency>("dbo.AssetDependency_ListByAsset", new { StoryAssetID = storyAssetId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveDependencyAsync(AssetDependency dependency)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.AssetDependency_Save",
new
{
dependency.AssetDependencyID,
dependency.SourceAssetID,
dependency.TargetAssetID,
dependency.AssetDependencyTypeID,
dependency.SourceRequiredStateID,
dependency.TargetBlockedStateID,
dependency.Description
},
commandType: CommandType.StoredProcedure);
}
public async Task DeleteDependencyAsync(int assetDependencyId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.AssetDependency_Delete", new { AssetDependencyID = assetDependencyId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<AssetCustodyEvent>> ListCustodyBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<AssetCustodyEvent>("dbo.AssetCustodyEvent_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<AssetCustodyEvent>> ListCustodyByAssetAsync(int storyAssetId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<AssetCustodyEvent>("dbo.AssetCustodyEvent_ListByAsset", new { StoryAssetID = storyAssetId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveCustodyEventAsync(AssetCustodyEvent custodyEvent, string custodianNames, IEnumerable<int> custodianCharacterIds, int custodyRoleId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.AssetCustodyEvent_Save",
new
{
custodyEvent.AssetCustodyEventID,
custodyEvent.StoryAssetID,
custodyEvent.SceneID,
custodyEvent.AssetCustodyEventTypeID,
custodyEvent.Description,
CustodianNames = custodianNames,
CustodianCharacterIds = string.Join(',', custodianCharacterIds.Where(id => id > 0).Distinct()),
CustodyRoleID = custodyRoleId
},
commandType: CommandType.StoredProcedure);
}
public async Task DeleteCustodyEventAsync(int assetCustodyEventId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.AssetCustodyEvent_Delete", new { AssetCustodyEventID = assetCustodyEventId }, commandType: CommandType.StoredProcedure);
}
public async Task<(IReadOnlyList<StoryAsset> Assets, IReadOnlyList<AssetEvent> Events)> GetTimelineAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.AssetTimeline_GetByProject", new { ProjectID = projectId, BookID = bookId }, commandType: CommandType.StoredProcedure);
var assets = (await result.ReadAsync<StoryAsset>()).ToList();
var events = (await result.ReadAsync<AssetEvent>()).ToList();
return (assets, events);
}
public async Task<IReadOnlyList<AssetDependencyIssue>> CheckDependencyIssuesAsync(int storyAssetId, int sceneId, int? toStateId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<AssetDependencyIssue>(
"dbo.AssetDependency_CheckForEvent",
new { StoryAssetID = storyAssetId, SceneID = sceneId, ToStateID = toStateId },
commandType: CommandType.StoredProcedure);
return rows.ToList();
}
}
public sealed class CharacterRepository(ISqlConnectionFactory connectionFactory) : ICharacterRepository
{
public async Task<CharacterLookupData> GetLookupsAsync()
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.CharacterLookup_All", commandType: CommandType.StoredProcedure);
return new CharacterLookupData
{
RoleTypes = (await result.ReadAsync<CharacterRoleInSceneType>()).ToList(),
PresenceTypes = (await result.ReadAsync<PresenceType>()).ToList(),
AttributeTypes = (await result.ReadAsync<CharacterAttributeType>()).ToList(),
KnowledgeStates = (await result.ReadAsync<KnowledgeState>()).ToList(),
RelationshipTypes = (await result.ReadAsync<RelationshipType>()).ToList(),
RelationshipStates = (await result.ReadAsync<RelationshipState>()).ToList()
};
}
public async Task<IReadOnlyList<Character>> ListCharactersAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Character>("dbo.Character_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<Character?> GetCharacterAsync(int characterId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Character>("dbo.Character_Get", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
}
public async Task<int> SaveCharacterAsync(Character character)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Character_Save",
new
{
character.CharacterID,
character.ProjectID,
character.CharacterName,
character.ShortName,
character.Sex,
character.BirthDate,
character.AgeAtSeriesStart,
character.AgeReferenceSceneID,
character.Height,
character.EyeColour,
character.DefaultDescription
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveCharacterAsync(int characterId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Character_Archive", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<SceneCharacter>> ListSceneCharactersAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<SceneCharacter>("dbo.SceneCharacter_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveSceneCharacterAsync(SceneCharacter sceneCharacter)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.SceneCharacter_Save",
new
{
sceneCharacter.SceneCharacterID,
sceneCharacter.SceneID,
sceneCharacter.CharacterID,
sceneCharacter.RoleInSceneTypeID,
sceneCharacter.PresenceTypeID,
sceneCharacter.LocationID,
sceneCharacter.EntryLocationID,
sceneCharacter.ExitLocationID,
sceneCharacter.AppearanceNotes,
sceneCharacter.OutfitDescription,
sceneCharacter.PhysicalCondition,
sceneCharacter.EmotionalState,
sceneCharacter.KnowledgeNotes
},
commandType: CommandType.StoredProcedure);
}
public async Task DeleteSceneCharacterAsync(int sceneCharacterId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.SceneCharacter_Delete", new { SceneCharacterID = sceneCharacterId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<CharacterAttributeEvent>> ListAttributeEventsByCharacterAsync(int characterId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<CharacterAttributeEvent>("dbo.CharacterAttributeEvent_ListByCharacter", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveAttributeEventAsync(CharacterAttributeEvent attributeEvent)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.CharacterAttributeEvent_Save",
new
{
attributeEvent.CharacterAttributeEventID,
attributeEvent.CharacterID,
attributeEvent.CharacterAttributeTypeID,
attributeEvent.SceneID,
attributeEvent.AttributeValue,
attributeEvent.Description
},
commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<CharacterKnowledgeItem>> ListKnowledgeBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<CharacterKnowledgeItem>("dbo.CharacterKnowledge_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<CharacterKnowledgeItem>> ListKnowledgeByCharacterAsync(int characterId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<CharacterKnowledgeItem>("dbo.CharacterKnowledge_ListByCharacter", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveKnowledgeAsync(CharacterKnowledgeItem knowledge)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.CharacterKnowledge_Save",
new
{
knowledge.CharacterKnowledgeID,
knowledge.CharacterID,
knowledge.StoryAssetID,
knowledge.PlotThreadID,
knowledge.SceneID,
knowledge.KnowledgeStateID,
knowledge.SourceEventID,
knowledge.Description
},
commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<CharacterRelationship>("dbo.Relationship_ListByProject", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByCharacterAsync(int characterId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<CharacterRelationship>("dbo.Relationship_ListByCharacter", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveRelationshipAsync(CharacterRelationship relationship)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.Relationship_Save",
new
{
relationship.CharacterRelationshipID,
relationship.ProjectID,
relationship.CharacterAID,
relationship.CharacterBID,
relationship.RelationshipTypeID,
relationship.IsPermanent,
relationship.StartSceneID,
relationship.EndSceneID,
relationship.IsKnownToReader,
relationship.Notes
},
commandType: CommandType.StoredProcedure);
}
public async Task ArchiveRelationshipAsync(int characterRelationshipId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.Relationship_Archive", new { CharacterRelationshipID = characterRelationshipId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsBySceneAsync(int sceneId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<RelationshipEvent>("dbo.RelationshipEvent_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsByCharacterAsync(int characterId)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<RelationshipEvent>("dbo.RelationshipEvent_ListByCharacter", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<int> SaveRelationshipEventAsync(RelationshipEvent relationshipEvent)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<int>(
"dbo.RelationshipEvent_Save",
new
{
relationshipEvent.RelationshipEventID,
relationshipEvent.CharacterRelationshipID,
relationshipEvent.SceneID,
relationshipEvent.RelationshipStateID,
relationshipEvent.Intensity,
relationshipEvent.IsKnownToOtherCharacter,
relationshipEvent.Description
},
commandType: CommandType.StoredProcedure);
}
public async Task<(IReadOnlyList<Character> Characters, IReadOnlyList<SceneCharacter> Appearances)> GetTimelineAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();
using var result = await connection.QueryMultipleAsync("dbo.CharacterTimeline_GetByProject", new { ProjectID = projectId, BookID = bookId }, commandType: CommandType.StoredProcedure);
var characters = (await result.ReadAsync<Character>()).ToList();
var appearances = (await result.ReadAsync<SceneCharacter>()).ToList();
return (characters, appearances);
}
}