using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using w4c_workflows.Filters;
using w4c_workflows.Services;
namespace w4c_workflows.Controllers;
///
/// Workflow file management endpoints. Provides CRUD operations for the
/// workflow YAML definitions and their sibling code files (shell scripts,
/// JS, Python, etc.) that live in the tenant's per-tenant workflow repository.
///
/// The repository is the single source of truth for a tenant's workflow content:
/// the workflows module edits files here and POST /api/workflows/sync
/// recompiles them (see ). Edits land in the
/// SAME directory reads from, so the "edit in the
/// workflows module" and "the engine runs it" paths are always coherent.
///
/// Works in both storage modes:
/// - Forgejo-backed (Forgejo:AdminToken set): files live in a private
/// per-user Forgejo repo {login}/workflows-{login}, cloned locally.
/// /commit and /pull sync to/from the remote.
/// - Filesystem-only (no AdminToken): files live under
/// {CopiesRoot}/{tenantId}/ with no remote. /commit and
/// /pull are no-ops.
///
[ApiController]
[Route("api/workflow-files")]
public class WorkflowFilesController : ControllerBase
{
private readonly WorkflowSourceFactory _sourceFactory;
private readonly ILogger _logger;
public WorkflowFilesController(
WorkflowSourceFactory sourceFactory,
ILogger logger)
{
_sourceFactory = sourceFactory;
_logger = logger;
}
private string TenantId => (string?)HttpContext.Items["TenantId"]
?? throw new InvalidOperationException("TenantId not resolved by auth middleware");
///
/// The user's Forgejo login when the middleware resolved it (Forgejo-backed
/// mode succeeded). Null when in filesystem-only mode OR when the Forgejo
/// login could not be resolved (the request then falls back to the filesystem
/// source, so file CRUD must target the filesystem dir to stay consistent).
///
private string? MaybeForgejoLogin =>
HttpContext.Items.TryGetValue("ForgejoLogin", out var v) ? v as string : null;
///
/// The on-disk directory holding the tenant's workflow files. In Forgejo-backed
/// mode (login resolved) it is the per-user clone dir; otherwise it is the
/// per-tenant directory. Both are exactly the root
/// reads from, so file edits made through these endpoints are what get compiled
/// on sync.
///
private string SourceDir => _sourceFactory.ResolveTenantSourceDir(TenantId, MaybeForgejoLogin);
/// Returns the Forgejo repo info for this tenant's workflow files.
[HttpGet("repo")]
[RequireScope("read")]
public IActionResult GetRepoInfo()
{
if (_sourceFactory.Forgejo == null)
return StatusCode(503, new { error = "Forgejo-backed workflow mode is not configured." });
var login = MaybeForgejoLogin;
if (string.IsNullOrWhiteSpace(login))
return StatusCode(503, new { error = "Forgejo login not resolved for this tenant." });
var cloneDir = _sourceFactory.Forgejo.TenantCloneDir(TenantId, login);
var fullName = ForgejoWorkflowRepoService.RepoFullNameForLogin(login);
var exists = Directory.Exists(Path.Combine(cloneDir, ".git"));
return Ok(new
{
repoFullName = fullName,
cloneDir,
cloned = exists,
forgejoUrl = $"{_sourceFactory.Forgejo.ForgejoBaseUrl}/{fullName}",
});
}
///
/// Lists all files under a directory (recursive, excluding .git), plus the
/// top-level subdirectories. Returns repo-relative paths.
///
[HttpGet]
[RequireScope("read")]
public IActionResult ListFiles([FromQuery] string? path = null)
{
var sourceDir = SourceDir;
if (!Directory.Exists(sourceDir))
return Ok(new { files = Array.Empty