275 lines
9.9 KiB
C#
275 lines
9.9 KiB
C#
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;
|
||
}
|
||
}
|