forked from xiongyuxing/tiku-backend.net
feat(saas): implement marketplace and tenant onboarding
This commit is contained in:
@@ -20,8 +20,19 @@ namespace Tiku.Infrastructure.Content;
|
||||
public sealed class DirectContentService(
|
||||
TikuDbContext dbContext,
|
||||
IQuestionReferenceService questionReferenceService,
|
||||
ICurrentAccessContext currentAccessContext) : IDirectContentService
|
||||
ICurrentAccessContext currentAccessContext,
|
||||
IFeatureAccessService featureAccessService) : IDirectContentService
|
||||
{
|
||||
private static readonly string[] ContentPermissions =
|
||||
[
|
||||
BackendPermissions.TenantContentManage,
|
||||
BackendPermissions.TenantVocabularyManage,
|
||||
BackendPermissions.TenantHandbookManage,
|
||||
BackendPermissions.TenantVideoManage,
|
||||
BackendPermissions.TenantScorelineManage,
|
||||
BackendPermissions.TenantSiteContentManage,
|
||||
BackendPermissions.TenantJobManage
|
||||
];
|
||||
private const int DefaultLimit = 100;
|
||||
private const int MaxLimit = 1000;
|
||||
private static readonly Regex ScorelineFieldKeyRegex = new("^[A-Za-z][A-Za-z0-9_]{0,63}$", RegexOptions.Compiled);
|
||||
@@ -50,6 +61,13 @@ public sealed class DirectContentService(
|
||||
TenantId = actor.TenantId
|
||||
};
|
||||
ApplyQuestion(question, command);
|
||||
if (question.Status != QuestionStatus.Archived)
|
||||
{
|
||||
await featureAccessService.ConsumeQuotaIfConfiguredAsync(
|
||||
actor.TenantId,
|
||||
SaasQuotaMetricCatalog.PrivateQuestionCount,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
dbContext.Questions.Add(question);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -85,7 +103,19 @@ public sealed class DirectContentService(
|
||||
}
|
||||
|
||||
await AssertQuestionReferencesAsync(actor.TenantId, command, cancellationToken);
|
||||
await using var transaction = dbContext.Database.CurrentTransaction is null
|
||||
? await dbContext.Database.BeginTransactionAsync(cancellationToken)
|
||||
: null;
|
||||
var wasCounted = question.Status != QuestionStatus.Archived;
|
||||
ApplyQuestion(question, command);
|
||||
var isCounted = question.Status != QuestionStatus.Archived;
|
||||
if (!wasCounted && isCounted)
|
||||
{
|
||||
await featureAccessService.ConsumeQuotaIfConfiguredAsync(
|
||||
actor.TenantId,
|
||||
SaasQuotaMetricCatalog.PrivateQuestionCount,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
QuestionVersion? version;
|
||||
if (command.CreateVersion || !question.CurrentVersionId.HasValue)
|
||||
{
|
||||
@@ -120,6 +150,18 @@ public sealed class DirectContentService(
|
||||
await SyncPrimaryCollectionItemAsync(actor, question, cancellationToken);
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
if (wasCounted && !isCounted)
|
||||
{
|
||||
await featureAccessService.ReleaseQuotaAsync(
|
||||
actor.TenantId,
|
||||
SaasQuotaMetricCatalog.PrivateQuestionCount,
|
||||
1,
|
||||
cancellationToken);
|
||||
}
|
||||
if (transaction is not null)
|
||||
{
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
}
|
||||
return new ContentManagementResult<QuestionManagementItem>(ToQuestionItem(question, version));
|
||||
}
|
||||
|
||||
@@ -1815,7 +1857,7 @@ public sealed class DirectContentService(
|
||||
if (!access.IsCurrentTenantMember ||
|
||||
access.UserId != actor.UserId ||
|
||||
access.TenantId != actor.TenantId ||
|
||||
!access.HasTenantPermission(BackendPermissions.TenantContentManage))
|
||||
!ContentPermissions.Any(access.HasTenantPermission))
|
||||
{
|
||||
throw new ContentManagementException("Tenant content access was denied.", "content_access_denied");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user