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 CategoryCol => _db.GetCollection("fileCategories"); public Task 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("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> GetAllProjectsAsync(int? userId = null) { var col = _db.GetCollection("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 GetProjectAsync(int projectId) { var col = _db.GetCollection("projects"); var entity = col.FindById(projectId); if (entity == null) return Task.FromResult(null); return Task.FromResult(new Project { Id = entity.Id, Name = entity.Name, RootPath = entity.RootPath, Template = entity.Template }); } public async Task> GetProjectFileTreeAsync(int projectId) { var col = _db.GetCollection("projects"); var entity = col.FindById(projectId); if (entity == null || !Directory.Exists(entity.RootPath)) return new List(); var categories = await GetFileCategoriesAsync(projectId); var result = new List(); BuildTree(entity.RootPath, result, 0, categories); return result; } private static void BuildTree(string directory, List items, int depth, Dictionary 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 progress) { await _fileScannerService.ScanProjectFolderAsync(projectId, directoryPath, progress); } public Task CopyProjectAsync(int sourceProjectId, string newName, string newRootPath, int userId) { var col = _db.GetCollection("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("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> GetFileCategoriesAsync(int projectId) { var entries = CategoryCol.Find(e => e.ProjectId == projectId).ToList(); var result = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var e in entries) result[e.FilePath] = e.Category; return Task.FromResult(result); } public Task 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; } }