using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System.Security.Cryptography; using System.Text; namespace JobTrackerApi.Services { public sealed class AppPaths { public string DataRoot { get; } public string AttachmentsRoot { get; } public string CvArtifactsRoot { get; } public string CvExportsRoot { get; } public string CvBenchmarksRoot { get; } public string AccountDeletionTombstonesRoot { get; } public AppPaths(IConfiguration cfg, IHostEnvironment env) { var dataRoot = (cfg["Data:Root"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(dataRoot)) dataRoot = env.ContentRootPath; if (!Path.IsPathRooted(dataRoot)) dataRoot = Path.Combine(env.ContentRootPath, dataRoot); Directory.CreateDirectory(dataRoot); DataRoot = dataRoot; var attachmentsRoot = (cfg["Data:AttachmentsRoot"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(attachmentsRoot)) attachmentsRoot = Path.Combine(DataRoot, "Attachments"); if (!Path.IsPathRooted(attachmentsRoot)) attachmentsRoot = Path.Combine(env.ContentRootPath, attachmentsRoot); Directory.CreateDirectory(attachmentsRoot); AttachmentsRoot = attachmentsRoot; var cvArtifactsRoot = (cfg["Data:CvArtifactsRoot"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(cvArtifactsRoot)) cvArtifactsRoot = Path.Combine(DataRoot, "CvArtifacts"); if (!Path.IsPathRooted(cvArtifactsRoot)) cvArtifactsRoot = Path.Combine(env.ContentRootPath, cvArtifactsRoot); Directory.CreateDirectory(cvArtifactsRoot); CvArtifactsRoot = cvArtifactsRoot; var cvExportsRoot = (cfg["Data:CvExportsRoot"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(cvExportsRoot)) cvExportsRoot = Path.Combine(DataRoot, "CvExports"); if (!Path.IsPathRooted(cvExportsRoot)) cvExportsRoot = Path.Combine(env.ContentRootPath, cvExportsRoot); Directory.CreateDirectory(cvExportsRoot); CvExportsRoot = cvExportsRoot; var cvBenchmarksRoot = (cfg["Data:CvBenchmarksRoot"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(cvBenchmarksRoot)) cvBenchmarksRoot = Path.Combine(DataRoot, "CvBenchmarks"); if (!Path.IsPathRooted(cvBenchmarksRoot)) cvBenchmarksRoot = Path.Combine(env.ContentRootPath, cvBenchmarksRoot); Directory.CreateDirectory(cvBenchmarksRoot); CvBenchmarksRoot = cvBenchmarksRoot; var tombstonesRoot = (cfg["AccountLifecycle:TombstonesRoot"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(tombstonesRoot)) tombstonesRoot = Path.Combine(DataRoot, "DeletionTombstones"); if (!Path.IsPathRooted(tombstonesRoot)) tombstonesRoot = Path.Combine(env.ContentRootPath, tombstonesRoot); Directory.CreateDirectory(tombstonesRoot); AccountDeletionTombstonesRoot = tombstonesRoot; } public string GetDbPath(string fileName = "jobtracker.db") => Path.Combine(DataRoot, fileName); public string GetExportsRoot(string? configuredFolder) { var folder = (configuredFolder ?? "").Trim(); if (string.IsNullOrWhiteSpace(folder)) return Path.Combine(DataRoot, "exports"); return Path.IsPathRooted(folder) ? folder : Path.Combine(DataRoot, folder); } public static string GetOwnerStorageKey(string ownerUserId) { ArgumentException.ThrowIfNullOrWhiteSpace(ownerUserId); return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(ownerUserId))).ToLowerInvariant(); } public string GetOwnerCvExportsRoot(string ownerUserId) => Path.Combine(CvExportsRoot, GetOwnerStorageKey(ownerUserId)); public string GetOwnerDailyExportsRoot(string? configuredFolder, string ownerUserId) => Path.Combine(GetExportsRoot(configuredFolder), GetOwnerStorageKey(ownerUserId)); public string GetOwnerAccountExportsRoot(string ownerUserId) => Path.Combine(DataRoot, "AccountExports", GetOwnerStorageKey(ownerUserId)); } }