using SharepointToolbox.Web.Core.Helpers;
using SharepointToolbox.Web.Core.Models;
using SharepointToolbox.Web.Infrastructure.Auth;
using SharepointToolbox.Web.Services.Session;
namespace SharepointToolbox.Web.Services;
///
/// Enumerates every site collection in a tenant via the SharePoint tenant-admin endpoint
/// (Tenant.GetSitePropertiesFromSharePointByFilters), paging through all results.
/// The auth model only changes how the admin-host context is built:
///
/// • Certificate (app-only) profiles build the admin context through the cert factory — the
/// same path the background report scheduler uses (), which
/// relies only on the SharePoint Sites.FullControl.All application permission the cert
/// app already holds. (The earlier Graph /sites/getAllSites path was dropped: it needs
/// a separate Graph Sites.Read.All grant the cert app is not provisioned with, so it
/// returned empty/403 and tenant-wide audits silently fell back to the root site alone.)
/// • Delegated profiles build the admin context through the session manager; this requires the
/// signed-in user to be a SharePoint administrator.
///
/// The Graph /sites?search=* endpoint was deliberately abandoned for both: it ranks by
/// relevance and is capped server-side, silently dropping sites and returning varying counts.
///
public class SiteDiscoveryService : ISiteDiscoveryService
{
private readonly ISessionManager _sessionManager;
private readonly IAppOnlyContextFactory _appOnly;
public SiteDiscoveryService(
ISessionManager sessionManager,
IAppOnlyContextFactory appOnly)
{
_sessionManager = sessionManager;
_appOnly = appOnly;
}
public async Task> SearchSitesAsync(
TenantProfile profile,
string? query = null,
CancellationToken ct = default)
{
ArgumentException.ThrowIfNullOrEmpty(profile.TenantUrl);
var adminUrl = TenantSiteEnumerator.BuildAdminUrl(profile.TenantUrl);
// App-only profiles: build the admin-host context through the cert factory (matches the
// scheduler), enumerating under the SharePoint app permission the cert already grants.
if (_appOnly.IsConfigured(profile))
{
using var adminCtx = await _appOnly.CreateContextAsync(profile, adminUrl, ct);
return await TenantSiteEnumerator.EnumerateAsync(adminCtx, ct);
}
// Delegated profiles: enumeration only exists on the tenant admin endpoint.
var adminProfile = profile.CloneForSite(adminUrl);
var ctx = await _sessionManager.GetOrCreateContextAsync(adminProfile, ct);
return await TenantSiteEnumerator.EnumerateAsync(ctx, ct);
}
}