206 lines
9.0 KiB
C#
206 lines
9.0 KiB
C#
using System.Data;
|
|
using Dapper;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.Data;
|
|
|
|
public interface IWordCompanionRepository
|
|
{
|
|
Task<IReadOnlyList<WordCompanionProjectDto>> ListProjectsAsync(int userId);
|
|
Task<IReadOnlyList<WordCompanionBookDto>> ListBooksAsync(int projectId, int userId);
|
|
Task<WordCompanionBookStructureResponse?> GetBookStructureAsync(int bookId, int userId);
|
|
Task<WordCompanionResolveSceneResponse> ResolveSceneAsync(int bookId, int userId, string chapterTitle, string sceneTitle);
|
|
Task<WordCompanionSceneCompanionResponse?> GetSceneCompanionAsync(int sceneId, int userId);
|
|
Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request);
|
|
Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, int userId, WordCompanionSyncManuscriptRequest request);
|
|
Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, int userId, WordCompanionUpdateSceneLinksRequest request);
|
|
Task<WordCompanionUpdateWordCountResponse?> UpdateWordCountAsync(int sceneId, int userId, int actualWords, DateTime? lastWorkedOn);
|
|
}
|
|
|
|
public sealed class WordCompanionRepository(ISqlConnectionFactory connectionFactory) : IWordCompanionRepository
|
|
{
|
|
public async Task<IReadOnlyList<WordCompanionProjectDto>> ListProjectsAsync(int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var rows = await connection.QueryAsync<WordCompanionProjectDto>(
|
|
"dbo.WordCompanion_Project_List",
|
|
new { UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
return rows.ToList();
|
|
}
|
|
|
|
public async Task<IReadOnlyList<WordCompanionBookDto>> ListBooksAsync(int projectId, int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var rows = await connection.QueryAsync<WordCompanionBookDto>(
|
|
"dbo.WordCompanion_Book_ListByProject",
|
|
new { ProjectID = projectId, UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
return rows.ToList();
|
|
}
|
|
|
|
public async Task<WordCompanionBookStructureResponse?> GetBookStructureAsync(int bookId, int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
using var result = await connection.QueryMultipleAsync(
|
|
"dbo.WordCompanion_Book_Structure",
|
|
new { BookID = bookId, UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
var header = await result.ReadSingleOrDefaultAsync<WordCompanionBookStructureResponse>();
|
|
if (header is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var chapters = (await result.ReadAsync<WordCompanionChapterStructureDto>()).ToList();
|
|
var scenes = (await result.ReadAsync<WordCompanionSceneStructureRow>()).ToList();
|
|
var scenesByChapter = scenes.GroupBy(x => x.ChapterId).ToDictionary(x => x.Key, x => x.Select(scene => new WordCompanionSceneStructureDto
|
|
{
|
|
SceneId = scene.SceneId,
|
|
Title = scene.Title,
|
|
SortOrder = scene.SortOrder,
|
|
RevisionStatus = scene.RevisionStatus,
|
|
EstimatedWords = scene.EstimatedWords,
|
|
ActualWords = scene.ActualWords
|
|
}).ToList());
|
|
|
|
foreach (var chapter in chapters)
|
|
{
|
|
chapter.Scenes = scenesByChapter.TryGetValue(chapter.ChapterId, out var chapterScenes) ? chapterScenes : [];
|
|
}
|
|
|
|
return new WordCompanionBookStructureResponse { BookId = header.BookId, Chapters = chapters };
|
|
}
|
|
|
|
public async Task<WordCompanionResolveSceneResponse> ResolveSceneAsync(int bookId, int userId, string chapterTitle, string sceneTitle)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var response = await connection.QuerySingleAsync<WordCompanionResolveSceneResponse>(
|
|
"dbo.WordCompanion_Scene_Resolve",
|
|
new { BookID = bookId, UserID = userId, ChapterTitle = chapterTitle, SceneTitle = sceneTitle },
|
|
commandType: CommandType.StoredProcedure);
|
|
return response;
|
|
}
|
|
|
|
public async Task<WordCompanionSceneCompanionResponse?> GetSceneCompanionAsync(int sceneId, int userId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
using var result = await connection.QueryMultipleAsync(
|
|
"dbo.WordCompanion_Scene_Companion",
|
|
new { SceneID = sceneId, UserID = userId },
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
var scene = await result.ReadSingleOrDefaultAsync<WordCompanionSceneCompanionResponse>();
|
|
if (scene is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new WordCompanionSceneCompanionResponse
|
|
{
|
|
SceneId = scene.SceneId,
|
|
ChapterId = scene.ChapterId,
|
|
ChapterTitle = scene.ChapterTitle,
|
|
SceneTitle = scene.SceneTitle,
|
|
ActualWords = scene.ActualWords,
|
|
EstimatedWords = scene.EstimatedWords,
|
|
WritingBrief = scene.WritingBrief,
|
|
Blocked = scene.Blocked,
|
|
BlockedReason = scene.BlockedReason,
|
|
PrimaryLocationId = scene.PrimaryLocationId,
|
|
Characters = (await result.ReadAsync<WordCompanionLinkItemDto>()).ToList(),
|
|
Assets = (await result.ReadAsync<WordCompanionLinkItemDto>()).ToList(),
|
|
Locations = (await result.ReadAsync<WordCompanionLinkItemDto>()).ToList()
|
|
};
|
|
}
|
|
|
|
public async Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<WordCompanionSyncSceneResponse>(
|
|
"dbo.WordCompanion_Scene_Sync",
|
|
new
|
|
{
|
|
BookID = bookId,
|
|
UserID = userId,
|
|
request.ChapterTitle,
|
|
request.ChapterSortOrder,
|
|
request.SceneTitle,
|
|
request.SceneSortOrder,
|
|
request.ActualWords,
|
|
request.DocumentId,
|
|
request.ContentHash
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, int userId, WordCompanionSyncManuscriptRequest request)
|
|
{
|
|
var chapters = request.Chapters.Select(chapter => new
|
|
{
|
|
chapter.Title,
|
|
chapter.SortOrder,
|
|
chapter.WordCount,
|
|
Scenes = chapter.Scenes.Select(scene => new
|
|
{
|
|
scene.Title,
|
|
scene.SortOrder,
|
|
scene.WordCount,
|
|
scene.ContentHash
|
|
})
|
|
});
|
|
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<WordCompanionSyncManuscriptResponse>(
|
|
"dbo.WordCompanion_Manuscript_Sync",
|
|
new
|
|
{
|
|
BookID = bookId,
|
|
UserID = userId,
|
|
request.DocumentId,
|
|
StructureJson = System.Text.Json.JsonSerializer.Serialize(chapters)
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, int userId, WordCompanionUpdateSceneLinksRequest request)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<WordCompanionUpdateSceneLinksResponse>(
|
|
"dbo.WordCompanion_SceneLinks_Update",
|
|
new
|
|
{
|
|
SceneID = sceneId,
|
|
UserID = userId,
|
|
CharacterIds = ToCsv(request.CharacterIds),
|
|
AssetIds = ToCsv(request.AssetIds),
|
|
LocationIds = ToCsv(request.LocationIds),
|
|
request.PrimaryLocationId
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<WordCompanionUpdateWordCountResponse?> UpdateWordCountAsync(int sceneId, int userId, int actualWords, DateTime? lastWorkedOn)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<WordCompanionUpdateWordCountResponse>(
|
|
"dbo.WordCompanion_Scene_UpdateWordCount",
|
|
new { SceneID = sceneId, UserID = userId, ActualWords = actualWords, LastWorkedOn = lastWorkedOn },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
private static string ToCsv(IEnumerable<int> ids) => string.Join(',', ids.Where(id => id > 0).Distinct());
|
|
|
|
private sealed class WordCompanionSceneStructureRow
|
|
{
|
|
public int ChapterId { get; set; }
|
|
public int SceneId { get; set; }
|
|
public string Title { get; set; } = string.Empty;
|
|
public int SortOrder { get; set; }
|
|
public string RevisionStatus { get; set; } = string.Empty;
|
|
public int? EstimatedWords { get; set; }
|
|
public int? ActualWords { get; set; }
|
|
}
|
|
}
|