171 lines
6.7 KiB
C#
171 lines
6.7 KiB
C#
using Journal.Core.Dtos;
|
|
using Journal.Core.Repositories;
|
|
|
|
namespace Journal.Core.Services.Entries;
|
|
|
|
public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileService
|
|
{
|
|
private readonly IEntryFileRepository _repo = repo ?? throw new ArgumentNullException(nameof(repo));
|
|
|
|
public IReadOnlyList<EntryListItem> ListEntries(string dataDirectory)
|
|
{
|
|
return [.. _repo.ListMarkdownFiles(dataDirectory)
|
|
.Where(path => !EntryFileNaming.IsTemplateFileName(_repo.GetFileName(path)))
|
|
.Select(path => new EntryListItem(
|
|
FileName: _repo.GetFileName(path),
|
|
FilePath: _repo.GetFullPath(path)))];
|
|
}
|
|
|
|
public IReadOnlyList<EntryListItem> ListTemplates(string dataDirectory)
|
|
{
|
|
return [.. _repo.ListMarkdownFiles(dataDirectory)
|
|
.Where(path => EntryFileNaming.IsTemplateFileName(_repo.GetFileName(path)))
|
|
.Select(path => new EntryListItem(
|
|
FileName: _repo.GetFileName(path),
|
|
FilePath: _repo.GetFullPath(path)))];
|
|
}
|
|
|
|
public EntryLoadResult LoadEntry(string filePath)
|
|
{
|
|
var normalizedPath = _repo.GetFullPath(filePath);
|
|
if (!_repo.FileExists(normalizedPath))
|
|
throw new FileNotFoundException($"Entry file not found: {normalizedPath}");
|
|
|
|
var rawContent = HtmlSanitizer.StripRichHtml(_repo.ReadFile(normalizedPath));
|
|
var fileStem = _repo.GetFileNameWithoutExtension(normalizedPath);
|
|
var entry = JournalParser.ParseJournalContent(rawContent, fileStem);
|
|
|
|
return new EntryLoadResult(
|
|
FileName: _repo.GetFileName(normalizedPath),
|
|
FilePath: normalizedPath,
|
|
Entry: entry.ToDto());
|
|
}
|
|
|
|
public EntryTemplateLoadResult LoadTemplate(string filePath)
|
|
{
|
|
var normalizedPath = _repo.GetFullPath(filePath);
|
|
if (!_repo.FileExists(normalizedPath))
|
|
throw new FileNotFoundException($"Template file not found: {normalizedPath}");
|
|
|
|
var fileName = _repo.GetFileName(normalizedPath);
|
|
if (!EntryFileNaming.IsTemplateFileName(fileName))
|
|
throw new ArgumentException("Template file name must end with .template.md.");
|
|
|
|
var rawContent = HtmlSanitizer.StripRichHtml(_repo.ReadFile(normalizedPath));
|
|
return new EntryTemplateLoadResult(fileName, normalizedPath, rawContent);
|
|
}
|
|
|
|
public EntrySaveResult SaveEntry(EntrySavePayload payload, string defaultDataDirectory)
|
|
{
|
|
var targetPath = ResolveTargetPath(payload.FilePath, payload.FileName, defaultDataDirectory);
|
|
var mode = string.IsNullOrWhiteSpace(payload.Mode) ? "Daily" : payload.Mode.Trim();
|
|
var sanitizedContent = HtmlSanitizer.StripRichHtml(payload.Content ?? "");
|
|
_repo.EnsureDirectory(targetPath);
|
|
|
|
if (string.Equals(mode, "Overwrite", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_repo.WriteFile(targetPath, sanitizedContent);
|
|
return new EntrySaveResult(targetPath);
|
|
}
|
|
|
|
if (string.Equals(mode, "Fragment", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_repo.AppendFile(targetPath, "\n\n" + sanitizedContent.Trim());
|
|
return new EntrySaveResult(targetPath);
|
|
}
|
|
|
|
string finalContent;
|
|
if (_repo.FileExists(targetPath))
|
|
{
|
|
var existingContent = _repo.ReadFile(targetPath);
|
|
var fileStem = _repo.GetFileNameWithoutExtension(targetPath);
|
|
var existingEntry = JournalParser.ParseJournalContent(existingContent, fileStem);
|
|
var newEntryData = JournalParser.ParseJournalContent(sanitizedContent, fileStem);
|
|
existingEntry.MergeWith(newEntryData);
|
|
finalContent = existingEntry.ToMarkdown();
|
|
}
|
|
else
|
|
{
|
|
finalContent = sanitizedContent;
|
|
}
|
|
|
|
_repo.WriteFile(targetPath, finalContent);
|
|
return new EntrySaveResult(targetPath);
|
|
}
|
|
|
|
public EntrySaveResult SaveTemplate(EntryTemplateSavePayload payload, string defaultDataDirectory)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(payload);
|
|
if (string.IsNullOrWhiteSpace(payload.Name))
|
|
throw new ArgumentException("Template name is required.");
|
|
|
|
var directory = string.IsNullOrWhiteSpace(payload.DataDirectory)
|
|
? defaultDataDirectory
|
|
: payload.DataDirectory;
|
|
var targetPath = ResolveTemplatePath(payload.FilePath, payload.Name, directory);
|
|
var fileName = _repo.GetFileName(targetPath);
|
|
if (!EntryFileNaming.IsTemplateFileName(fileName))
|
|
throw new ArgumentException("Template file name must end with .template.md.");
|
|
|
|
var sanitizedContent = HtmlSanitizer.StripRichHtml(payload.Content ?? "");
|
|
_repo.EnsureDirectory(targetPath);
|
|
_repo.WriteFile(targetPath, sanitizedContent);
|
|
return new EntrySaveResult(targetPath);
|
|
}
|
|
|
|
public bool DeleteEntry(string filePath)
|
|
{
|
|
var normalizedPath = _repo.GetFullPath(filePath);
|
|
if (!_repo.FileExists(normalizedPath))
|
|
return false;
|
|
_repo.DeleteFile(normalizedPath);
|
|
return true;
|
|
}
|
|
|
|
public bool DeleteTemplate(string filePath)
|
|
{
|
|
var normalizedPath = _repo.GetFullPath(filePath);
|
|
if (!_repo.FileExists(normalizedPath))
|
|
return false;
|
|
|
|
var fileName = _repo.GetFileName(normalizedPath);
|
|
if (!EntryFileNaming.IsTemplateFileName(fileName))
|
|
return false;
|
|
|
|
_repo.DeleteFile(normalizedPath);
|
|
return true;
|
|
}
|
|
|
|
private string ResolveTargetPath(string? filePath, string? fileName, string defaultDataDirectory)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(filePath))
|
|
return _repo.GetFullPath(filePath);
|
|
|
|
var name = !string.IsNullOrWhiteSpace(fileName)
|
|
? SanitizeFileName(fileName)
|
|
: $"{DateTime.Now:yyyy-MM-dd}";
|
|
|
|
return _repo.GetFullPath(Path.Combine(defaultDataDirectory, $"{name}.md"));
|
|
}
|
|
|
|
private string ResolveTemplatePath(string? filePath, string templateName, string defaultDataDirectory)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(filePath))
|
|
return _repo.GetFullPath(filePath);
|
|
|
|
var name = SanitizeFileName(templateName);
|
|
return _repo.GetFullPath(Path.Combine(defaultDataDirectory, $"{name}{EntryFileNaming.TemplateSuffix}"));
|
|
}
|
|
|
|
private static string SanitizeFileName(string name)
|
|
{
|
|
var trimmed = name.Trim();
|
|
if (trimmed.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
|
|
trimmed = trimmed[..^3];
|
|
|
|
var invalid = Path.GetInvalidFileNameChars();
|
|
var sanitized = new string(trimmed.Select(c => Array.IndexOf(invalid, c) >= 0 ? '_' : c).ToArray());
|
|
return string.IsNullOrWhiteSpace(sanitized) ? "untitled" : sanitized;
|
|
}
|
|
}
|