using Microsoft.Graph;
using Microsoft.Graph.Models;
using SharepointToolbox.Core.Models;
using AppGraphClientFactory = SharepointToolbox.Infrastructure.Auth.GraphClientFactory;
namespace SharepointToolbox.Services;
///
/// Enumerates all enabled member users from a tenant via Microsoft Graph,
/// using PageIterator for transparent multi-page iteration.
/// Used by Phase 13's User Directory ViewModel.
///
public class GraphUserDirectoryService : IGraphUserDirectoryService
{
private readonly AppGraphClientFactory _graphClientFactory;
public GraphUserDirectoryService(AppGraphClientFactory graphClientFactory)
{
_graphClientFactory = graphClientFactory;
}
///
public async Task> GetUsersAsync(
string clientId,
bool includeGuests = false,
IProgress? progress = null,
CancellationToken ct = default)
{
var graphClient = await _graphClientFactory.CreateClientAsync(clientId, ct);
var response = await graphClient.Users.GetAsync(config =>
{
config.QueryParameters.Filter = includeGuests
? "accountEnabled eq true"
: "accountEnabled eq true and userType eq 'Member'";
config.QueryParameters.Select = new[]
{
"displayName", "userPrincipalName", "mail", "department", "jobTitle", "userType"
};
config.QueryParameters.Top = 999;
// No ConsistencyLevel header: standard equality filter does not require eventual consistency
}, ct);
if (response is null)
return Array.Empty();
var results = new List();
var pageIterator = PageIterator.CreatePageIterator(
graphClient,
response,
user =>
{
// Honour cancellation inside the callback — returning false stops iteration
if (ct.IsCancellationRequested)
return false;
results.Add(MapUser(user));
progress?.Report(results.Count);
return true;
});
await pageIterator.IterateAsync(ct);
return results;
}
///
/// Maps a Graph SDK object to a record.
/// Extracted as an internal static method to allow direct unit-test coverage of mapping logic
/// without requiring a live Graph endpoint.
///
internal static GraphDirectoryUser MapUser(User user) =>
new(
DisplayName: user.DisplayName ?? user.UserPrincipalName ?? string.Empty,
UserPrincipalName: user.UserPrincipalName ?? string.Empty,
Mail: user.Mail,
Department: user.Department,
JobTitle: user.JobTitle,
UserType: user.UserType);
}