Files
tiku-backend.net/Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantAdminDirectService.SiteSettings.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

245 lines
11 KiB
C#

using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Application.TenantAdmin;
using Tiku.Domain.Common;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;
namespace Tiku.Infrastructure.TenantAdmin;
public sealed partial class TenantAdminDirectService
{
public async Task<CatalogList<TenantAdminAuditLogItem>> GetAuditLogsAsync(
TenantAdminActor actor,
TenantAdminAuditLogFilter filter,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
var query = dbContext.AuditLogs.AsNoTracking().Where(item => item.TenantId == actor.TenantId);
if (!string.IsNullOrWhiteSpace(filter.Action))
{
var action = filter.Action.Trim();
query = query.Where(item => item.Action.StartsWith(action));
}
if (!string.IsNullOrWhiteSpace(filter.TargetType))
query = query.Where(item => item.TargetType == filter.TargetType.Trim());
if (filter.ActorUserId.HasValue) query = query.Where(item => item.ActorUserId == filter.ActorUserId.Value);
var logs = await query
.OrderByDescending(item => item.CreatedAt)
.Take(ResolveLimit(filter.Limit))
.ToArrayAsync(cancellationToken);
var actorIds = logs.Where(item => item.ActorUserId.HasValue).Select(item => item.ActorUserId!.Value).ToArray();
var users = await dbContext.Users.AsNoTracking()
.Where(user => actorIds.Contains(user.Id))
.ToDictionaryAsync(user => user.Id, cancellationToken);
return new CatalogList<TenantAdminAuditLogItem>(logs.Select(item =>
{
var user = item.ActorUserId.HasValue && users.TryGetValue(item.ActorUserId.Value, out var actorUser)
? actorUser
: null;
return new TenantAdminAuditLogItem(
item.Id,
item.ActorUserId,
item.Action,
item.TargetType,
item.TargetId,
item.Details,
item.IpAddress,
item.UserAgent,
user?.Name ?? user?.UserName,
user?.Phone,
item.CreatedAt);
}).ToArray());
}
public async Task<ContentManagementResult<TenantBrandingItem>> UpsertBrandingAsync(
TenantAdminActor actor,
UpsertTenantBrandingCommand command,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
ArgumentException.ThrowIfNullOrWhiteSpace(command.BrandName);
var item = await dbContext.TenantBrandings.FirstOrDefaultAsync(branding => branding.TenantId == actor.TenantId,
cancellationToken);
if (item is null)
{
item = new TenantBranding { TenantId = actor.TenantId };
dbContext.TenantBrandings.Add(item);
}
item.BrandName = command.BrandName.Trim();
item.ShortName = Normalize(command.ShortName);
item.Slogan = Normalize(command.Slogan);
item.OrganizationName = Normalize(command.OrganizationName);
item.LogoUrl = Normalize(command.LogoUrl);
item.FaviconUrl = Normalize(command.FaviconUrl);
item.ServiceWechat = Normalize(command.ServiceWechat);
item.ServiceAccountName = Normalize(command.ServiceAccountName);
await AddAuditAsync(actor, "tenant.branding.updated", "tenant_branding", actor.TenantId, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return new ContentManagementResult<TenantBrandingItem>(ToBrandingItem(item));
}
public async Task<ContentManagementResult<TenantSettingsItem>> UpsertSettingsAsync(
TenantAdminActor actor,
UpsertTenantSettingsCommand command,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
AssertNoSecrets(command.PublicConfig, "public_config");
var item = await dbContext.TenantSettings.FirstOrDefaultAsync(settings => settings.TenantId == actor.TenantId,
cancellationToken);
if (item is null)
{
item = new TenantSettings { TenantId = actor.TenantId };
dbContext.TenantSettings.Add(item);
}
item.FeatureFlags = JsonObjectOrDefault(command.FeatureFlags);
item.AdminFeatureFlags = JsonObjectOrDefault(command.AdminFeatureFlags);
item.PublicConfig = JsonObjectOrDefault(command.PublicConfig);
await AddAuditAsync(actor, "tenant.settings.updated", "tenant_settings", actor.TenantId, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return new ContentManagementResult<TenantSettingsItem>(ToSettingsItem(item));
}
public async Task<CatalogList<TenantThemeTemplateItem>> GetThemeTemplatesAsync(
TenantAdminActor actor,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
await Task.CompletedTask.WaitAsync(cancellationToken);
var items = await dbContext.TenantThemeTemplates.AsNoTracking()
.Where(item => item.Status == TenantThemeTemplateStatus.Active)
.OrderBy(item => item.SortOrder)
.ThenBy(item => item.Code)
.Select(item => new TenantThemeTemplateItem(
item.Code,
item.Name,
item.Description,
item.PreviewImageUrl,
item.Theme,
item.PublicAssets,
item.SortOrder))
.ToArrayAsync(cancellationToken);
return new CatalogList<TenantThemeTemplateItem>(items);
}
public async Task<ContentManagementResult<TenantThemeItem>> GetThemeAsync(
TenantAdminActor actor,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
var item = await dbContext.TenantThemeConfigs.AsNoTracking()
.FirstOrDefaultAsync(theme => theme.TenantId == actor.TenantId, cancellationToken);
if (item is not null) return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));
var branding = await dbContext.TenantBrandings.AsNoTracking()
.FirstOrDefaultAsync(tenantBranding => tenantBranding.TenantId == actor.TenantId, cancellationToken);
return new ContentManagementResult<TenantThemeItem>(new TenantThemeItem(
actor.TenantId,
null,
branding?.Theme ?? JsonDefaults.Object(),
branding?.PublicAssets ?? JsonDefaults.Object(),
null,
JsonDefaults.Object(),
JsonDefaults.Object(),
TenantThemeConfigStatus.Published,
null,
null,
null,
branding?.UpdatedAt ?? DateTimeOffset.UtcNow));
}
public async Task<ContentManagementResult<TenantThemeItem>> PreviewThemeAsync(
TenantAdminActor actor,
PreviewTenantThemeCommand command,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
ArgumentException.ThrowIfNullOrWhiteSpace(command.TemplateCode);
var template = await dbContext.TenantThemeTemplates.FirstOrDefaultAsync(
item => item.Code == command.TemplateCode && item.Status == TenantThemeTemplateStatus.Active,
cancellationToken);
if (template is null)
throw new TenantAdminDirectException("Theme template was not found.", "theme_template_not_found");
var item = await dbContext.TenantThemeConfigs.FirstOrDefaultAsync(theme => theme.TenantId == actor.TenantId,
cancellationToken);
if (item is null)
{
item = new TenantThemeConfig { TenantId = actor.TenantId };
dbContext.TenantThemeConfigs.Add(item);
}
item.DraftTemplateCode = template.Code;
item.DraftTheme = MergeJsonObjects(template.Theme, command.Theme);
item.DraftPublicAssets = MergeJsonObjects(template.PublicAssets, command.PublicAssets);
item.Status = TenantThemeConfigStatus.Draft;
item.DraftUpdatedBy = actor.UserId;
await AddAuditAsync(actor, "tenant.theme.previewed", "tenant_theme_configs", actor.TenantId, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));
}
public async Task<ContentManagementResult<TenantThemeItem>> PublishThemeAsync(
TenantAdminActor actor,
PublishTenantThemeCommand command,
CancellationToken cancellationToken = default)
{
await RequireAllDataScopeAsync(actor, cancellationToken);
var item = await dbContext.TenantThemeConfigs.FirstOrDefaultAsync(theme => theme.TenantId == actor.TenantId,
cancellationToken);
JsonElement activeTheme;
JsonElement activeAssets;
string? templateCode;
if (command.UseDraft)
{
if (item is null || string.IsNullOrWhiteSpace(item.DraftTemplateCode))
throw new TenantAdminDirectException("No draft theme to publish.", "theme_draft_not_found");
activeTheme = item.DraftTheme;
activeAssets = item.DraftPublicAssets;
templateCode = item.DraftTemplateCode;
}
else
{
ArgumentException.ThrowIfNullOrWhiteSpace(command.TemplateCode);
var template = await dbContext.TenantThemeTemplates.FirstOrDefaultAsync(
theme => theme.Code == command.TemplateCode && theme.Status == TenantThemeTemplateStatus.Active,
cancellationToken);
if (template is null)
throw new TenantAdminDirectException("Theme template was not found.", "theme_template_not_found");
item ??= new TenantThemeConfig { TenantId = actor.TenantId };
if (dbContext.Entry(item).State == EntityState.Detached) dbContext.TenantThemeConfigs.Add(item);
activeTheme = MergeJsonObjects(template.Theme, command.Theme);
activeAssets = MergeJsonObjects(template.PublicAssets, command.PublicAssets);
templateCode = template.Code;
}
item ??= new TenantThemeConfig { TenantId = actor.TenantId };
if (dbContext.Entry(item).State == EntityState.Detached) dbContext.TenantThemeConfigs.Add(item);
item.ActiveTemplateCode = templateCode;
item.ActiveTheme = activeTheme.Clone();
item.ActivePublicAssets = activeAssets.Clone();
item.DraftTemplateCode = null;
item.DraftTheme = JsonDefaults.Object();
item.DraftPublicAssets = JsonDefaults.Object();
item.Status = TenantThemeConfigStatus.Published;
item.PublishedAt = DateTimeOffset.UtcNow;
item.PublishedBy = actor.UserId;
await EnsureBrandingThemeAsync(actor.TenantId, activeTheme, activeAssets, cancellationToken);
await AddAuditAsync(actor, "tenant.theme.published", "tenant_theme_configs", actor.TenantId, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));
}
}