AuthorBuddy/Services/ProjectService.cs
2026-05-31 19:31:27 +02:00

275 lines
9.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AuthorBuddy.Web.Data;
using AuthorBuddy.Web.Models;
using LiteDB;
namespace AuthorBuddy.Web.Services;
public class ProjectService : IProjectService
{
private readonly ILiteDatabase _db;
private readonly IFileScannerService _fileScannerService;
public ProjectService(ILiteDatabase db, IFileScannerService fileScannerService)
{
_db = db;
_fileScannerService = fileScannerService;
}
private ILiteCollection<FileCategoryEntity> CategoryCol =>
_db.GetCollection<FileCategoryEntity>("fileCategories");
public Task<int> CreateNewProjectAsync(string projectName, string rootPath, int userId, ProjectTemplate template = ProjectTemplate.Novel)
{
if (template == ProjectTemplate.Technical)
{
string[] subFolders = { "00_Anforderungen", "01_UseCases", "02_Architektur", "03_Glossar" };
foreach (var subFolder in subFolders)
{
Directory.CreateDirectory(Path.Combine(rootPath, subFolder));
}
string readmePath = Path.Combine(rootPath, "00_Projekt_Info.md");
File.WriteAllText(readmePath, $"# Projekt: {projectName}\nErstellt am: {DateTime.Now}\nTyp: Technische Vorlage (Requirements)");
string lastenheftPath = Path.Combine(rootPath, "Lastenheft.md");
File.WriteAllText(lastenheftPath, $"# {projectName} Lastenheft\n\n" +
"## 1. Zielbestimmung\n\n" +
"## 2. Produkteinsatz\n\n" +
"## 3. Produktumgebung\n\n" +
"## 4. Funktionale Anforderungen\n\n" +
"## 5. Nicht-funktionale Anforderungen\n\n" +
"## 6. Lieferumfang\n\n");
string pflichtenheftPath = Path.Combine(rootPath, "Pflichtenheft.md");
File.WriteAllText(pflichtenheftPath, $"# {projectName} Pflichtenheft\n\n" +
"## 1. Systemarchitektur\n\n" +
"## 2. Komponentenbeschreibung\n\n" +
"## 3. Schnittstellendefinition\n\n" +
"## 4. Datenmodell\n\n" +
"## 5. Technische Umsetzung\n\n");
}
else
{
string[] subFolders = { "00_Drafts", "01_Fakten", "02_Characters", "03_Archive" };
foreach (var subFolder in subFolders)
{
Directory.CreateDirectory(Path.Combine(rootPath, subFolder));
}
string readmePath = Path.Combine(rootPath, "00_Projekt_Info.md");
File.WriteAllText(readmePath, $"# Projekt: {projectName}\nErstellt am: {DateTime.Now}");
string exposeePath = Path.Combine(rootPath, "Die Handlung (Das Exposé).md");
File.WriteAllText(exposeePath, $"# {projectName} Die Handlung (Das Exposé)\n\n" +
"## Überblick\n\n" +
"## Erster Akt (Aufbau)\n\n" +
"## Zweiter Akt (Konfrontation)\n\n" +
"## Dritter Akt (Auflösung)\n\n" +
"## Wichtige Wendepunkte\n\n" +
"## Charakterbögen\n\n");
string ideasPath = Path.Combine(rootPath, "Ideen Sammlung.md");
File.WriteAllText(ideasPath, $"# Ideen Sammlung {projectName}\n\n");
}
var col = _db.GetCollection<ProjectEntity>("projects");
var entity = new ProjectEntity
{
Name = projectName,
RootPath = rootPath,
UserId = userId,
LastOpened = DateTime.Now,
Template = template
};
col.Insert(entity);
return Task.FromResult(entity.Id);
}
public Task<List<Project>> GetAllProjectsAsync(int? userId = null)
{
var col = _db.GetCollection<ProjectEntity>("projects");
var query = userId == null
? col.FindAll()
: col.Find(p => p.UserId == userId);
var list = query
.OrderByDescending(p => p.LastOpened)
.Select(p => new Project
{
Id = p.Id,
Name = p.Name,
RootPath = p.RootPath,
Template = p.Template
})
.ToList();
return Task.FromResult(list);
}
public Task<Project?> GetProjectAsync(int projectId)
{
var col = _db.GetCollection<ProjectEntity>("projects");
var entity = col.FindById(projectId);
if (entity == null) return Task.FromResult<Project?>(null);
return Task.FromResult<Project?>(new Project
{
Id = entity.Id,
Name = entity.Name,
RootPath = entity.RootPath,
Template = entity.Template
});
}
public async Task<List<FileTreeItem>> GetProjectFileTreeAsync(int projectId)
{
var col = _db.GetCollection<ProjectEntity>("projects");
var entity = col.FindById(projectId);
if (entity == null || !Directory.Exists(entity.RootPath))
return new List<FileTreeItem>();
var categories = await GetFileCategoriesAsync(projectId);
var result = new List<FileTreeItem>();
BuildTree(entity.RootPath, result, 0, categories);
return result;
}
private static void BuildTree(string directory, List<FileTreeItem> items, int depth, Dictionary<string, FileCategory> categories)
{
if (depth > 4) return;
var dirInfo = new DirectoryInfo(directory);
if (!dirInfo.Exists) return;
foreach (var dir in dirInfo.GetDirectories().OrderBy(d => d.Name))
{
var node = new FileTreeItem
{
Name = dir.Name,
FullPath = dir.FullName,
IsDirectory = true
};
BuildTree(dir.FullName, node.Children, depth + 1, categories);
if (node.Children.Count > 0 || dir.Name.StartsWith("00_") || dir.Name.StartsWith("01_") || dir.Name.StartsWith("02_"))
items.Add(node);
}
foreach (var file in dirInfo.GetFiles("*.md").OrderBy(f => f.Name))
{
categories.TryGetValue(file.FullName, out var cat);
items.Add(new FileTreeItem
{
Name = file.Name,
FullPath = file.FullName,
IsDirectory = false,
Category = cat
});
}
}
public async Task SyncProjectFilesAsync(int projectId, string directoryPath, IProgress<string> progress)
{
await _fileScannerService.ScanProjectFolderAsync(projectId, directoryPath, progress);
}
public Task<int> CopyProjectAsync(int sourceProjectId, string newName, string newRootPath, int userId)
{
var col = _db.GetCollection<ProjectEntity>("projects");
var source = col.FindById(sourceProjectId);
if (source == null)
throw new InvalidOperationException("Source project not found.");
Directory.CreateDirectory(newRootPath);
CopyDirectory(source.RootPath, newRootPath);
var entity = new ProjectEntity
{
Name = newName,
RootPath = newRootPath,
UserId = userId,
LastOpened = DateTime.Now,
Template = source.Template
};
col.Insert(entity);
var settings = _db.GetCollection<SettingEntity>("settings");
var projectSettings = settings.Find(s =>
s.UserId == userId && s.ProjectId == sourceProjectId);
foreach (var s in projectSettings)
{
settings.Insert(new SettingEntity
{
Key = s.Key,
Value = s.Value,
UserId = userId,
ProjectId = entity.Id
});
}
return Task.FromResult(entity.Id);
}
private static void CopyDirectory(string sourceDir, string destDir)
{
foreach (var dir in Directory.GetDirectories(sourceDir, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(dir.Replace(sourceDir, destDir));
}
foreach (var file in Directory.GetFiles(sourceDir, "*.*", SearchOption.AllDirectories))
{
var dest = file.Replace(sourceDir, destDir);
File.Copy(file, dest, true);
}
}
public Task<Dictionary<string, FileCategory>> GetFileCategoriesAsync(int projectId)
{
var entries = CategoryCol.Find(e => e.ProjectId == projectId).ToList();
var result = new Dictionary<string, FileCategory>(StringComparer.OrdinalIgnoreCase);
foreach (var e in entries)
result[e.FilePath] = e.Category;
return Task.FromResult(result);
}
public Task<bool> SetFileCategoryAsync(int projectId, string filePath, FileCategory category)
{
if (!FileCategoryHelper.IsValid(category)) return Task.FromResult(false);
if (category.HasFlag(FileCategory.Exposee))
{
var existingExposee = CategoryCol.Find(e => e.ProjectId == projectId)
.FirstOrDefault(e => e.Category.HasFlag(FileCategory.Exposee) && e.FilePath != filePath);
if (existingExposee != null) return Task.FromResult(false);
}
var col = CategoryCol;
var existing = col.FindOne(e => e.ProjectId == projectId && e.FilePath == filePath);
if (existing != null)
{
if (category == FileCategory.None)
col.Delete(existing.Id);
else
{
existing.Category = category;
col.Update(existing);
}
}
else if (category != FileCategory.None)
{
col.Insert(new FileCategoryEntity
{
ProjectId = projectId,
FilePath = filePath,
Category = category
});
}
return Task.FromResult(true);
}
public Task ClearFileCategoryAsync(int projectId, string filePath)
{
var col = CategoryCol;
var existing = col.FindOne(e => e.ProjectId == projectId && e.FilePath == filePath);
if (existing != null)
col.Delete(existing.Id);
return Task.CompletedTask;
}
}