using Microsoft.EntityFrameworkCore;
using w4c_workflows.Data;
using w4c_workflows.Models;
namespace w4c_workflows.Services;
///
/// Reads and updates the tenant's selected workflow repository name. The repo name
/// is a basename (e.g. workflows by default, or a user-selected repo like
/// wiz4apps). The full repo is {login}/{repoName}; its local clone dir
/// is {CopiesRoot}/{tenantId}/{login}_{repoName} (see
/// ).
///
/// The selection is durable per tenant (table WorkflowRepos) and falls back
/// to WorkflowSource:WorkflowRepoName (default workflows) when no row
/// exists — so a fresh tenant automatically uses the repository named "workflow".
///
public sealed class WorkflowRepoStore
{
private readonly WorkflowsDbContext _db;
private readonly string _defaultName;
public WorkflowRepoStore(WorkflowsDbContext db, Microsoft.Extensions.Configuration.IConfiguration config)
{
_db = db;
var configured = config["WorkflowSource:WorkflowRepoName"]?.Trim();
_defaultName = string.IsNullOrWhiteSpace(configured) ? "workflows" : configured;
}
/// The configured default repo name (used when no per-tenant row exists).
public string DefaultName => _defaultName;
/// Resolves the tenant's current workflow repo name (stored or default).
public async Task GetNameAsync(string tenantId, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(tenantId)) return _defaultName;
var row = await _db.WorkflowRepos.AsNoTracking().FirstOrDefaultAsync(r => r.TenantId == tenantId, ct);
return string.IsNullOrWhiteSpace(row?.RepoName) ? _defaultName : row!.RepoName;
}
///
/// Persists the tenant's workflow repo name. Returns the normalized name (trimmed,
/// no leading/trailing slashes).
///
public async Task SetNameAsync(string tenantId, string repoName, CancellationToken ct = default)
{
var clean = (repoName ?? string.Empty).Trim().Trim('/');
if (string.IsNullOrWhiteSpace(clean))
throw new ArgumentException("Workflow repo name is required", nameof(repoName));
var row = await _db.WorkflowRepos.FirstOrDefaultAsync(r => r.TenantId == tenantId, ct);
if (row == null)
{
_db.WorkflowRepos.Add(new TenantWorkflowRepo
{
TenantId = tenantId,
RepoName = clean,
UpdatedAt = DateTime.UtcNow,
});
}
else
{
row.RepoName = clean;
row.UpdatedAt = DateTime.UtcNow;
}
await _db.SaveChangesAsync(ct);
return clean;
}
///
/// Validates a workflow repo basename. The repo is always owned by the
/// caller's own Forgejo account, so only a single safe name segment is
/// accepted — no owner prefix, path separators or traversal. Returns false
/// for anything else.
///
public static bool TryNormalizeName(string? repoName, out string normalized)
{
normalized = string.Empty;
var clean = (repoName ?? string.Empty).Trim();
if (clean.Length == 0 || clean.Length > 120)
return false;
foreach (var ch in clean)
{
if (!(char.IsLetterOrDigit(ch) || ch is '.' or '_' or '-'))
return false;
}
// A name made only of dots ("..", ".") is not a repo.
if (clean.Trim('.').Length == 0)
return false;
normalized = clean;
return true;
}
}