Files
tiku-backend.net/Tiku.Infrastructure/Bootstrap/BuiltinStarterOfferingSeeder.cs
xiong 33375a38d7
Some checks failed
ci / release-gate (push) Has been cancelled
refactor(architecture): harden module boundaries
2026-08-04 12:10:36 +08:00

120 lines
4.8 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(IPlatformControlPlanePersistence 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);
}
}
}