feat(saas): implement marketplace and tenant onboarding

This commit is contained in:
2026-07-29 13:58:59 +08:00
parent 76606029e2
commit 6db200a2fc
145 changed files with 16862 additions and 94529 deletions

View File

@@ -2,7 +2,9 @@ using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Tiku.Application.Tenancy;
using Tiku.Application.Security;
using Tiku.Domain.Common;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
@@ -10,7 +12,8 @@ namespace Tiku.Infrastructure.Tenancy;
public sealed class TenantFrontendConfigService(
TikuDbContext dbContext,
IMemoryCache cache) : ITenantFrontendConfigService
IMemoryCache cache,
IFeatureAccessService featureAccessService) : ITenantFrontendConfigService
{
private static readonly TimeSpan RuntimeCacheDuration = TimeSpan.FromMinutes(2);
@@ -90,6 +93,16 @@ public sealed class TenantFrontendConfigService(
item => item.Id == tenantId && item.Status == TenantStatus.Active,
cancellationToken)
?? throw new TenantFrontendConfigException("tenant_not_found", "Active tenant was not found.");
var now = DateTimeOffset.UtcNow;
var hasActiveSubscription = await dbContext.TenantSaasSubscriptions.AsNoTracking().AnyAsync(item =>
item.TenantId == tenantId &&
(item.Status == TenantSaasSubscriptionStatus.Trial || item.Status == TenantSaasSubscriptionStatus.Active) &&
item.StartsAt <= now && item.CurrentPeriodEnd > now,
cancellationToken);
if (!hasActiveSubscription)
{
throw new TenantFrontendConfigException("subscription_inactive", "An active tenant subscription is required.");
}
var config = await dbContext.TenantFrontendConfigs.AsNoTracking()
.SingleOrDefaultAsync(item => item.TenantId == tenantId, cancellationToken)
?? CreateDefault(tenantId);
@@ -102,7 +115,20 @@ public sealed class TenantFrontendConfigService(
config.PublishedTheme.Clone(),
config.PublishedFeatures.Clone(),
config.PublishedNavigation.Clone(),
config.PublishedHomeModules.Clone());
config.PublishedHomeModules.Clone(),
(await featureAccessService.GetEnabledFeaturesAsync(tenantId, FeatureAccessOperation.Read, cancellationToken))
.Where(code => code != SaasFeatureCatalog.CoreBackoffice)
.Order(StringComparer.Ordinal)
.ToArray(),
(await dbContext.TenantAuthPolicies.AsNoTracking()
.Where(item => item.TenantId == tenantId)
.Select(item => item.AllowedStudentLoginMethods)
.SingleOrDefaultAsync(cancellationToken) ?? ["password"])
.Select(value => value.Trim().ToLowerInvariant())
.Where(value => value.Length > 0)
.Distinct(StringComparer.Ordinal)
.Order(StringComparer.Ordinal)
.ToArray());
cache.Set(CacheKey(tenantId), result, RuntimeCacheDuration);
return result;
}