120 lines
4.7 KiB
C#
120 lines
4.7 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Domain.Common;
|
|
using Tiku.Domain.Platform;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.Infrastructure.Bootstrap;
|
|
|
|
public sealed class BuiltinStarterOfferingSeeder(TikuDbContext dbContext)
|
|
{
|
|
public const string OfferingCode = "starter";
|
|
public const int OfferingVersion = 1;
|
|
|
|
private static readonly string[] FeatureCodes =
|
|
[
|
|
SaasFeatureCatalog.CoreBackoffice,
|
|
SaasFeatureCatalog.SiteContent
|
|
];
|
|
|
|
public async Task SeedAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var availableFeatureCodes = await dbContext.SaasFeatures.AsNoTracking()
|
|
.Where(feature => FeatureCodes.Contains(feature.Code) && feature.Status == SaasFeatureStatus.Active)
|
|
.Select(feature => feature.Code)
|
|
.ToArrayAsync(cancellationToken);
|
|
if (availableFeatureCodes.Length != FeatureCodes.Length)
|
|
throw new InvalidOperationException(
|
|
"The built-in feature catalog must be seeded before the starter offering.");
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
var offering = await dbContext.SaasOfferings
|
|
.SingleOrDefaultAsync(item => item.Code == OfferingCode, cancellationToken);
|
|
if (offering is null)
|
|
{
|
|
offering = new SaasOffering
|
|
{
|
|
Code = OfferingCode,
|
|
Name = "建站基础版",
|
|
Type = SaasOfferingType.BasePlan,
|
|
Status = SaasOfferingStatus.Active,
|
|
Description = "包含租户后台与站点内容管理的免费基础版本。",
|
|
SortOrder = 0
|
|
};
|
|
dbContext.SaasOfferings.Add(offering);
|
|
}
|
|
else
|
|
{
|
|
offering.Name = "建站基础版";
|
|
offering.Type = SaasOfferingType.BasePlan;
|
|
offering.Status = SaasOfferingStatus.Active;
|
|
offering.Description = "包含租户后台与站点内容管理的免费基础版本。";
|
|
offering.SortOrder = 0;
|
|
offering.UpdatedAt = now;
|
|
}
|
|
|
|
var version = await dbContext.SaasOfferingVersions
|
|
.SingleOrDefaultAsync(
|
|
item => item.OfferingId == offering.Id && item.Version == OfferingVersion,
|
|
cancellationToken);
|
|
if (version is null)
|
|
{
|
|
version = new SaasOfferingVersion
|
|
{
|
|
OfferingId = offering.Id,
|
|
Version = OfferingVersion,
|
|
Status = SaasOfferingVersionStatus.Draft,
|
|
BillingCycle = PlatformBillingCycle.Yearly,
|
|
OriginalAmountCents = 0,
|
|
AmountCents = 0,
|
|
Currency = "CNY",
|
|
EffectiveAt = now,
|
|
Metadata = JsonDefaults.Object()
|
|
};
|
|
dbContext.SaasOfferingVersions.Add(version);
|
|
}
|
|
else if (version.Status == SaasOfferingVersionStatus.Draft)
|
|
{
|
|
version.BillingCycle = PlatformBillingCycle.Yearly;
|
|
version.OriginalAmountCents = 0;
|
|
version.AmountCents = 0;
|
|
version.Currency = "CNY";
|
|
version.EffectiveAt ??= now;
|
|
version.RetiredAt = null;
|
|
version.UpdatedAt = now;
|
|
}
|
|
else if (version.Status == SaasOfferingVersionStatus.Retired)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The built-in starter offering version 1 is retired and cannot be repaired automatically.");
|
|
}
|
|
|
|
var existingFeatureCodes = await dbContext.SaasOfferingVersionFeatures
|
|
.Where(binding => binding.OfferingVersionId == version.Id)
|
|
.Select(binding => binding.FeatureCode)
|
|
.ToHashSetAsync(StringComparer.Ordinal, cancellationToken);
|
|
var missingFeatureCodes = FeatureCodes
|
|
.Where(code => !existingFeatureCodes.Contains(code))
|
|
.ToArray();
|
|
if (version.Status == SaasOfferingVersionStatus.Published && missingFeatureCodes.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"The published built-in starter offering is missing required features and cannot be repaired in place.");
|
|
|
|
dbContext.SaasOfferingVersionFeatures.AddRange(missingFeatureCodes.Select(code =>
|
|
new SaasOfferingVersionFeature
|
|
{
|
|
OfferingVersionId = version.Id,
|
|
FeatureCode = code
|
|
}));
|
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
if (version.Status == SaasOfferingVersionStatus.Draft)
|
|
{
|
|
version.Status = SaasOfferingVersionStatus.Published;
|
|
version.PublishedAt = now;
|
|
version.EffectiveAt ??= now;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|
|
} |