AuthorBuddy/Services/ProjectService.cs

276 lines
9.9 KiB
C#
Raw Normal View History

2026-05-14 12:31:35 +02:00
using AuthorBuddy.Web.Data;
using AuthorBuddy.Web.Models;
2026-05-20 06:12:54 +02:00
using LiteDB;
2026-05-14 12:31:35 +02:00
namespace AuthorBuddy.Web.Services;
public class ProjectService : IProjectService
{
2026-05-20 06:12:54 +02:00
private readonly ILiteDatabase _db;
2026-05-14 12:31:35 +02:00
private readonly IFileScannerService _fileScannerService;
2026-05-20 06:12:54 +02:00
public ProjectService(ILiteDatabase db, IFileScannerService fileScannerService)
2026-05-14 12:31:35 +02:00
{
2026-05-20 06:12:54 +02:00
_db = db;
2026-05-14 12:31:35 +02:00
_fileScannerService = fileScannerService;
}
2026-05-31 19:31:27 +02:00
private ILiteCollection<FileCategoryEntity> CategoryCol =>
_db.GetCollection<FileCategoryEntity>("fileCategories");
public Task<int> CreateNewProjectAsync(string projectName, string rootPath, int userId, ProjectTemplate template = ProjectTemplate.Novel)
2026-05-14 12:31:35 +02:00
{
2026-05-31 19:31:27 +02:00
if (template == ProjectTemplate.Technical)
2026-05-14 12:31:35 +02:00
{
2026-05-31 19:31:27 +02:00
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");
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
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}");
2026-05-14 12:31:35 +02:00
2026-05-31 19:31:27 +02:00
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");
}
2026-05-14 12:31:35 +02:00
2026-05-20 06:12:54 +02:00
var col = _db.GetCollection<ProjectEntity>("projects");
2026-05-14 12:31:35 +02:00
var entity = new ProjectEntity
{
Name = projectName,
RootPath = rootPath,
2026-05-31 19:31:27 +02:00
UserId = userId,
LastOpened = DateTime.Now,
Template = template
2026-05-14 12:31:35 +02:00
};
2026-05-20 06:12:54 +02:00
col.Insert(entity);
return Task.FromResult(entity.Id);
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
public Task<List<Project>> GetAllProjectsAsync(int? userId = null)
2026-05-14 12:31:35 +02:00
{
2026-05-20 06:12:54 +02:00
var col = _db.GetCollection<ProjectEntity>("projects");
2026-05-31 19:31:27 +02:00
var query = userId == null
? col.FindAll()
: col.Find(p => p.UserId == userId);
var list = query
2026-05-14 12:31:35 +02:00
.OrderByDescending(p => p.LastOpened)
.Select(p => new Project
{
Id = p.Id,
Name = p.Name,
2026-05-31 19:31:27 +02:00
RootPath = p.RootPath,
Template = p.Template
2026-05-14 12:31:35 +02:00
})
2026-05-20 06:12:54 +02:00
.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,
2026-05-31 19:31:27 +02:00
RootPath = entity.RootPath,
Template = entity.Template
2026-05-20 06:12:54 +02:00
});
}
2026-05-31 19:31:27 +02:00
public async Task<List<FileTreeItem>> GetProjectFileTreeAsync(int projectId)
2026-05-20 06:12:54 +02:00
{
var col = _db.GetCollection<ProjectEntity>("projects");
var entity = col.FindById(projectId);
if (entity == null || !Directory.Exists(entity.RootPath))
2026-05-31 19:31:27 +02:00
return new List<FileTreeItem>();
2026-05-20 06:12:54 +02:00
2026-05-31 19:31:27 +02:00
var categories = await GetFileCategoriesAsync(projectId);
2026-05-20 06:12:54 +02:00
var result = new List<FileTreeItem>();
2026-05-31 19:31:27 +02:00
BuildTree(entity.RootPath, result, 0, categories);
return result;
2026-05-20 06:12:54 +02:00
}
2026-05-31 19:31:27 +02:00
private static void BuildTree(string directory, List<FileTreeItem> items, int depth, Dictionary<string, FileCategory> categories)
2026-05-20 06:12:54 +02:00
{
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
};
2026-05-31 19:31:27 +02:00
BuildTree(dir.FullName, node.Children, depth + 1, categories);
2026-05-20 06:12:54 +02:00
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))
{
2026-05-31 19:31:27 +02:00
categories.TryGetValue(file.FullName, out var cat);
2026-05-20 06:12:54 +02:00
items.Add(new FileTreeItem
{
Name = file.Name,
FullPath = file.FullName,
2026-05-31 19:31:27 +02:00
IsDirectory = false,
Category = cat
2026-05-20 06:12:54 +02:00
});
}
2026-05-14 12:31:35 +02:00
}
public async Task SyncProjectFilesAsync(int projectId, string directoryPath, IProgress<string> progress)
{
await _fileScannerService.ScanProjectFolderAsync(projectId, directoryPath, progress);
}
2026-05-25 06:38:16 +02:00
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,
2026-05-31 19:31:27 +02:00
UserId = userId,
LastOpened = DateTime.Now,
Template = source.Template
2026-05-25 06:38:16 +02:00
};
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);
}
}
2026-05-31 19:31:27 +02:00
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;
}
2026-05-14 12:31:35 +02:00
}