Files
tiku-backend.net/Tiku.Infrastructure/Tenancy/TenantOnboardingService.cs

82 lines
4.9 KiB
C#

using Microsoft.EntityFrameworkCore;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Tenancy;
internal sealed class TenantOnboardingService(
TikuDbContext dbContext,
IFeatureAccessService featureAccessService) : ITenantOnboardingService
{
public async Task<TenantOnboardingStatus> GetStatusAsync(
Guid tenantId,
CancellationToken cancellationToken = default)
{
var tenant = await dbContext.Tenants.AsNoTracking().SingleOrDefaultAsync(value => value.Id == tenantId, cancellationToken)
?? throw new TenantExternalProviderException("Tenant was not found.", "tenant_not_found");
var ownerActivated = tenant.OwnerUserId.HasValue && await (
from membership in dbContext.TenantMemberships.AsNoTracking()
join user in dbContext.Users.AsNoTracking() on membership.UserId equals user.Id
where membership.TenantId == tenantId && membership.UserId == tenant.OwnerUserId &&
membership.Role == TenantRole.TenantOwner && membership.Status == MembershipStatus.Active &&
user.Status == Tiku.Domain.Identity.UserStatus.Active && !user.ForcePasswordChange
select membership.Id).AnyAsync(cancellationToken);
var now = DateTimeOffset.UtcNow;
var subscriptionActive = await dbContext.TenantSaasSubscriptions.AsNoTracking().AnyAsync(value =>
value.TenantId == tenantId &&
(value.Status == TenantSaasSubscriptionStatus.Trial || value.Status == TenantSaasSubscriptionStatus.Active) &&
value.StartsAt <= now && value.CurrentPeriodEnd > now, cancellationToken);
var primaryDomainActive = await dbContext.TenantDomains.AsNoTracking().AnyAsync(value =>
value.TenantId == tenantId && value.IsPrimary && value.Status == TenantDomainStatus.Active, cancellationToken);
var frontendPublished = await dbContext.TenantFrontendConfigs.AsNoTracking().AnyAsync(value =>
value.TenantId == tenantId && value.PublishedAt != null, cancellationToken);
var loginMethods = await dbContext.TenantAuthPolicies.AsNoTracking()
.Where(value => value.TenantId == tenantId)
.Select(value => value.AllowedStudentLoginMethods)
.SingleOrDefaultAsync(cancellationToken) ?? ["password"];
var hasLoginMethod = loginMethods.Any(value => !string.IsNullOrWhiteSpace(value));
var enabledFeatures = await featureAccessService.GetEnabledFeaturesAsync(tenantId, FeatureAccessOperation.Read, cancellationToken);
var paymentRequired = enabledFeatures.Contains(SaasFeatureCatalog.StudentStore);
var storageRequired = enabledFeatures.Overlaps(new[]
{
SaasFeatureCatalog.PrivateQuestionBank,
SaasFeatureCatalog.Video,
SaasFeatureCatalog.Handbook
});
var smsRequired = loginMethods.Any(value => string.Equals(value, "sms", StringComparison.OrdinalIgnoreCase));
var providers = await dbContext.TenantExternalProviders.AsNoTracking()
.Where(value => value.TenantId == tenantId && value.Status == TenantExternalProviderStatus.Active)
.Select(value => value.Capability)
.Distinct()
.ToArrayAsync(cancellationToken);
var steps = new List<TenantOnboardingStep>
{
new("tenant_active", true, tenant.Status == TenantStatus.Active, tenant.Status.ToString()),
new("owner_activated", true, ownerActivated, tenant.OwnerUserId?.ToString()),
new("subscription_active", true, subscriptionActive, null),
new("primary_domain_active", true, primaryDomainActive, null),
new("frontend_config_published", true, frontendPublished, null),
new("student_login_configured", true, hasLoginMethod, string.Join(',', loginMethods)),
ProviderStep("object_storage_provider", TenantExternalProviderCapability.ObjectStorage, storageRequired, providers),
ProviderStep("sms_provider", TenantExternalProviderCapability.Sms, smsRequired, providers),
ProviderStep("payment_provider", TenantExternalProviderCapability.Payment, paymentRequired, providers),
ProviderStep("notification_provider", TenantExternalProviderCapability.Notification, false, providers)
};
var required = steps.Count(value => value.Required);
var completed = steps.Count(value => value.Required && value.Completed);
return new TenantOnboardingStatus(tenantId, completed == required, completed, required, steps);
}
private static TenantOnboardingStep ProviderStep(
string code,
TenantExternalProviderCapability capability,
bool required,
IReadOnlyCollection<TenantExternalProviderCapability> providers) =>
new(code, required, providers.Contains(capability), providers.Contains(capability) ? "active" : "missing");
}