mirror of
https://github.com/ONLYOFFICE/DocSpace-server.git
synced 2025-04-18 14:24:02 +03:00
Merge hotfix/v3.0.4 into master
This commit is contained in:
commit
641f16447f
@ -27,7 +27,7 @@
|
||||
namespace ASC.Common.Caching;
|
||||
|
||||
[Singleton]
|
||||
public class RedisCacheNotify<T>(IRedisClient redisCacheClient) : ICacheNotify<T> where T : new()
|
||||
public class RedisCacheNotify<T>(IRedisClient redisCacheClient, ILogger<RedisCacheNotify<T>> logger) : ICacheNotify<T> where T : new()
|
||||
{
|
||||
private readonly IRedisDatabase _redis = redisCacheClient.GetDefaultDatabase();
|
||||
private readonly ConcurrentDictionary<CacheNotifyAction, ConcurrentBag<Action<T>>> _invocationList = new();
|
||||
@ -39,7 +39,14 @@ public class RedisCacheNotify<T>(IRedisClient redisCacheClient) : ICacheNotify<T
|
||||
|
||||
foreach (var handler in GetInvocationList(action))
|
||||
{
|
||||
handler(obj);
|
||||
try
|
||||
{
|
||||
handler(obj);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorRedisCacheNotifyPublish(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +56,14 @@ public class RedisCacheNotify<T>(IRedisClient redisCacheClient) : ICacheNotify<T
|
||||
{
|
||||
if (i.Id != _instanceId && (i.Action == action || Enum.IsDefined(typeof(CacheNotifyAction), (i.Action & action))))
|
||||
{
|
||||
onChange(i.Object);
|
||||
try
|
||||
{
|
||||
onChange(i.Object);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorRedisCacheNotifySubscribe(e);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
|
36
common/ASC.Common/Log/RedisCacheNotifyLogger.cs
Normal file
36
common/ASC.Common/Log/RedisCacheNotifyLogger.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
namespace ASC.Common.Log;
|
||||
|
||||
internal static partial class RedisCacheNotifyLogger
|
||||
{
|
||||
[LoggerMessage(LogLevel.Error, "RedisCacheNotify Publish")]
|
||||
public static partial void ErrorRedisCacheNotifyPublish(this ILogger logger, Exception exception);
|
||||
|
||||
[LoggerMessage(LogLevel.Error, "RedisCacheNotify Subscribe")]
|
||||
public static partial void ErrorRedisCacheNotifySubscribe(this ILogger logger, Exception exception);
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
@ -52,7 +53,17 @@ public class LowercaseDocumentFilter : IDocumentFilter
|
||||
var paths = new OpenApiPaths();
|
||||
foreach (var (key, value) in swaggerDoc.Paths)
|
||||
{
|
||||
var lowerCaseKey = key.ToLowerInvariant();
|
||||
var segments = key.Split('/');
|
||||
|
||||
for (var i = 0; i < segments.Length; i++)
|
||||
{
|
||||
if (!segments[i].StartsWith("{") && !segments[i].EndsWith("}"))
|
||||
{
|
||||
segments[i] = segments[i].ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
var lowerCaseKey = string.Join("/", segments);
|
||||
paths.Add(lowerCaseKey, value);
|
||||
}
|
||||
|
||||
@ -145,11 +156,42 @@ public class TagDescriptionsDocumentFilter : IDocumentFilter
|
||||
|
||||
swaggerDoc.Tags = customTags
|
||||
.Where(tag => _tagDescriptions.ContainsKey(tag))
|
||||
.Select(tag => new OpenApiTag
|
||||
.Select(tag =>
|
||||
{
|
||||
var tagParts = tag.Split(" / ");
|
||||
var displayName = tagParts.Length > 1 ? tagParts[1] : tagParts[0];
|
||||
|
||||
var openApiTag = new OpenApiTag
|
||||
{
|
||||
Name = tag,
|
||||
Description = _tagDescriptions[tag]
|
||||
};
|
||||
openApiTag.Extensions.Add("x-displayName", new OpenApiString(displayName));
|
||||
|
||||
return openApiTag;
|
||||
}).ToList();
|
||||
|
||||
var groupTag = customTags
|
||||
.Where(tag => _tagDescriptions.ContainsKey(tag))
|
||||
.GroupBy(tag => tag.Split(" / ")[0])
|
||||
.ToDictionary(group => group.Key, group => group.ToList());
|
||||
|
||||
var tagGroups = new OpenApiArray();
|
||||
foreach (var group in groupTag)
|
||||
{
|
||||
Name = tag,
|
||||
Description = _tagDescriptions[tag]
|
||||
}).ToList();
|
||||
|
||||
var groupObject = new OpenApiObject();
|
||||
var tagsArray = new OpenApiArray();
|
||||
|
||||
foreach(var tag in group.Value)
|
||||
{
|
||||
tagsArray.Add(new OpenApiString(tag));
|
||||
}
|
||||
|
||||
groupObject["name"] = new OpenApiString(group.Key);
|
||||
groupObject["tags"] = tagsArray;
|
||||
tagGroups.Add(groupObject);
|
||||
}
|
||||
|
||||
swaggerDoc.Extensions["x-tagGroups"] = tagGroups;
|
||||
}
|
||||
}
|
@ -26,21 +26,36 @@
|
||||
|
||||
namespace ASC.Core.Billing;
|
||||
|
||||
[ProtoContract]
|
||||
public class PaymentInfo
|
||||
{
|
||||
public int ID { get; set; }
|
||||
public int Status { get; set; }
|
||||
public int PaymentSystemId { get; set; }
|
||||
public string CartId { get; set; }
|
||||
public string FName { get; set; }
|
||||
public string LName { get; set; }
|
||||
public string Email { get; set; }
|
||||
public DateTime PaymentDate { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public int Qty { get; set; }
|
||||
public string PaymentCurrency { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public int QuotaId { get; set; }
|
||||
public int ProductRef { get; set; }
|
||||
public string CustomerId { get; set; }
|
||||
[ProtoMember(1)] public int ID { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int Status { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int PaymentSystemId { get; set; }
|
||||
|
||||
[ProtoMember(4)] public string CartId { get; set; }
|
||||
|
||||
[ProtoMember(5)] public string FName { get; set; }
|
||||
|
||||
[ProtoMember(6)] public string LName { get; set; }
|
||||
|
||||
[ProtoMember(7)] public string Email { get; set; }
|
||||
|
||||
[ProtoMember(8)] public DateTime PaymentDate { get; set; }
|
||||
|
||||
[ProtoMember(9)] public decimal Price { get; set; }
|
||||
|
||||
[ProtoMember(10)] public int Qty { get; set; }
|
||||
|
||||
[ProtoMember(11)] public string PaymentCurrency { get; set; }
|
||||
|
||||
[ProtoMember(12)] public string PaymentMethod { get; set; }
|
||||
|
||||
[ProtoMember(13)] public int QuotaId { get; set; }
|
||||
|
||||
[ProtoMember(14)] public int ProductRef { get; set; }
|
||||
|
||||
[ProtoMember(15)] public string CustomerId { get; set; }
|
||||
}
|
||||
|
@ -27,41 +27,49 @@
|
||||
namespace ASC.Core.Billing;
|
||||
|
||||
[DebuggerDisplay("{State} before {DueDate}")]
|
||||
[ProtoContract]
|
||||
public class Tariff
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[ProtoMember(1)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tariff state
|
||||
/// </summary>
|
||||
[ProtoMember(2)]
|
||||
public TariffState State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Due date
|
||||
/// </summary>
|
||||
[ProtoMember(3)]
|
||||
public DateTime DueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delay due date
|
||||
/// </summary>
|
||||
[ProtoMember(4)]
|
||||
public DateTime DelayDueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// License date
|
||||
/// </summary>
|
||||
[ProtoMember(5)]
|
||||
public DateTime LicenseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Customer ID
|
||||
/// </summary>
|
||||
[ProtoMember(6)]
|
||||
public string CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of quotas
|
||||
/// </summary>
|
||||
[ProtoMember(7)]
|
||||
public List<Quota> Quotas { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
@ -77,24 +85,38 @@ public class Tariff
|
||||
public bool EqualsByParams(Tariff t)
|
||||
{
|
||||
return t != null
|
||||
&& t.DueDate == DueDate
|
||||
&& t.Quotas.Count == Quotas.Count
|
||||
&& t.Quotas.Exists(Quotas.Contains)
|
||||
&& t.CustomerId == CustomerId;
|
||||
&& t.DueDate == DueDate
|
||||
&& t.Quotas.Count == Quotas.Count
|
||||
&& t.Quotas.Exists(Quotas.Contains)
|
||||
&& t.CustomerId == CustomerId;
|
||||
}
|
||||
}
|
||||
|
||||
public class Quota(int id, int quantity) : IEquatable<Quota>
|
||||
[ProtoContract]
|
||||
public class Quota : IEquatable<Quota>
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
public int Id { get; set; } = id;
|
||||
[ProtoMember(1)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Quantity
|
||||
/// </summary>
|
||||
public int Quantity { get; set; } = quantity;
|
||||
[ProtoMember(2)]
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public Quota()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Quota(int id, int quantity)
|
||||
{
|
||||
Id = id;
|
||||
Quantity = quantity;
|
||||
}
|
||||
|
||||
public bool Equals(Quota other)
|
||||
{
|
||||
|
@ -24,6 +24,8 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
|
||||
namespace ASC.Core.Billing;
|
||||
|
||||
[Singleton]
|
||||
@ -50,64 +52,6 @@ public class TenantExtraConfig(CoreBaseSettings coreBaseSettings, LicenseReaderC
|
||||
}
|
||||
}
|
||||
|
||||
[Singleton]
|
||||
public class TariffServiceStorage
|
||||
{
|
||||
private static readonly TimeSpan _defaultCacheExpiration = TimeSpan.FromMinutes(5);
|
||||
private static readonly TimeSpan _standaloneCacheExpiration = TimeSpan.FromMinutes(15);
|
||||
private readonly ICache _cache;
|
||||
private readonly CoreBaseSettings _coreBaseSettings;
|
||||
private TimeSpan _cacheExpiration;
|
||||
|
||||
public TariffServiceStorage(ICacheNotify<TariffCacheItem> notify, ICache cache, CoreBaseSettings coreBaseSettings, IServiceProvider serviceProvider)
|
||||
{
|
||||
_cacheExpiration = _defaultCacheExpiration;
|
||||
|
||||
_cache = cache;
|
||||
_coreBaseSettings = coreBaseSettings;
|
||||
notify.Subscribe(i =>
|
||||
{
|
||||
_cache.Insert(TariffService.GetTariffNeedToUpdateCacheKey(i.TenantId), "update", _cacheExpiration);
|
||||
|
||||
_cache.Remove(TariffService.GetTariffCacheKey(i.TenantId));
|
||||
_cache.Remove(TariffService.GetBillingUrlCacheKey(i.TenantId));
|
||||
_cache.Remove(TariffService.GetBillingPaymentCacheKey(i.TenantId)); // clear all payments
|
||||
}, CacheNotifyAction.Remove);
|
||||
|
||||
notify.Subscribe(i =>
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var tariffService = scope.ServiceProvider.GetService<ITariffService>();
|
||||
var tariff = tariffService.GetBillingInfoAsync(i.TenantId, i.TariffId).Result;
|
||||
if (tariff != null)
|
||||
{
|
||||
InsertToCache(i.TenantId, tariff);
|
||||
}
|
||||
}, CacheNotifyAction.Insert);
|
||||
}
|
||||
|
||||
private TimeSpan GetCacheExpiration()
|
||||
{
|
||||
if (_coreBaseSettings.Standalone && _cacheExpiration < _standaloneCacheExpiration)
|
||||
{
|
||||
_cacheExpiration = _cacheExpiration.Add(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
return _cacheExpiration;
|
||||
}
|
||||
|
||||
public void InsertToCache(int tenantId, Tariff tariff)
|
||||
{
|
||||
_cache.Insert(TariffService.GetTariffCacheKey(tenantId), tariff, DateTime.UtcNow.Add(GetCacheExpiration()));
|
||||
}
|
||||
|
||||
public void ResetCacheExpiration()
|
||||
{
|
||||
if (_coreBaseSettings.Standalone)
|
||||
{
|
||||
_cacheExpiration = _defaultCacheExpiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Scope(typeof(ITariffService))]
|
||||
public class TariffService(
|
||||
@ -118,14 +62,18 @@ public class TariffService(
|
||||
IConfiguration configuration,
|
||||
IDbContextFactory<CoreDbContext> coreDbContextManager,
|
||||
ICache cache,
|
||||
ICacheNotify<TariffCacheItem> notify,
|
||||
TariffServiceStorage tariffServiceStorage,
|
||||
IDistributedCache distributedCache,
|
||||
IDistributedLockProvider distributedLockProvider,
|
||||
ILogger<TariffService> logger,
|
||||
BillingClient billingClient,
|
||||
IServiceProvider serviceProvider,
|
||||
TenantExtraConfig tenantExtraConfig)
|
||||
: ITariffService
|
||||
{
|
||||
private static readonly TimeSpan _defaultCacheExpiration = TimeSpan.FromMinutes(5);
|
||||
private static readonly TimeSpan _standaloneCacheExpiration = TimeSpan.FromMinutes(15);
|
||||
private TimeSpan _cacheExpiration = _defaultCacheExpiration;
|
||||
|
||||
private const int DefaultTrialPeriod = 30;
|
||||
|
||||
//private readonly int _activeUsersMin;
|
||||
@ -145,133 +93,135 @@ public class TariffService(
|
||||
tenantId = -1;
|
||||
}
|
||||
|
||||
var tariff = refresh ? null : cache.Get<Tariff>(GetTariffCacheKey(tenantId));
|
||||
var key = GetTariffCacheKey(tenantId);
|
||||
var tariff = refresh ? null : await GetFromCache<Tariff>(key);
|
||||
|
||||
if (tariff == null)
|
||||
{
|
||||
tariff = await GetBillingInfoAsync(tenantId) ?? await CreateDefaultAsync();
|
||||
tariff = await CalculateTariffAsync(tenantId, tariff);
|
||||
tariffServiceStorage.InsertToCache(tenantId, tariff);
|
||||
|
||||
if (billingClient.Configured && withRequestToPaymentSystem)
|
||||
await using (await distributedLockProvider.TryAcquireLockAsync($"{key}_lock"))
|
||||
{
|
||||
try
|
||||
tariff = refresh ? null : await GetFromCache<Tariff>(key);
|
||||
if (tariff != null)
|
||||
{
|
||||
var currentPayments = await billingClient.GetCurrentPaymentsAsync(await coreSettings.GetKeyAsync(tenantId), refresh);
|
||||
if (currentPayments.Length == 0)
|
||||
{
|
||||
throw new BillingNotFoundException("Empty PaymentLast");
|
||||
}
|
||||
tariff = await CalculateTariffAsync(tenantId, tariff);
|
||||
return tariff;
|
||||
}
|
||||
|
||||
tariff = await GetBillingInfoAsync(tenantId) ?? await CreateDefaultAsync();
|
||||
tariff = await CalculateTariffAsync(tenantId, tariff);
|
||||
await InsertToCache(tenantId, tariff);
|
||||
|
||||
var asynctariff = await CreateDefaultAsync(true);
|
||||
string email = null;
|
||||
var tenantQuotas = await quotaService.GetTenantQuotasAsync();
|
||||
|
||||
foreach (var currentPayment in currentPayments.OrderBy(r => r.EndDate))
|
||||
if (billingClient.Configured && withRequestToPaymentSystem)
|
||||
{
|
||||
try
|
||||
{
|
||||
var quota = tenantQuotas.SingleOrDefault(q => q.ProductId == currentPayment.ProductId.ToString());
|
||||
if (quota == null)
|
||||
var currentPayments = await billingClient.GetCurrentPaymentsAsync(await coreSettings.GetKeyAsync(tenantId), refresh);
|
||||
if (currentPayments.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Quota with id {currentPayment.ProductId} not found for portal {await coreSettings.GetKeyAsync(tenantId)}.");
|
||||
throw new BillingNotFoundException("Empty PaymentLast");
|
||||
}
|
||||
|
||||
asynctariff.Id = currentPayment.PaymentId;
|
||||
var asynctariff = await CreateDefaultAsync(true);
|
||||
string email = null;
|
||||
var tenantQuotas = await quotaService.GetTenantQuotasAsync();
|
||||
|
||||
var paymentEndDate = 9999 <= currentPayment.EndDate.Year ? DateTime.MaxValue : currentPayment.EndDate;
|
||||
asynctariff.DueDate = DateTime.Compare(asynctariff.DueDate, paymentEndDate) < 0 ? asynctariff.DueDate : paymentEndDate;
|
||||
foreach (var currentPayment in currentPayments.OrderBy(r => r.EndDate))
|
||||
{
|
||||
var quota = tenantQuotas.SingleOrDefault(q => q.ProductId == currentPayment.ProductId.ToString());
|
||||
if (quota == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Quota with id {currentPayment.ProductId} not found for portal {await coreSettings.GetKeyAsync(tenantId)}.");
|
||||
}
|
||||
|
||||
asynctariff.Quotas = asynctariff.Quotas.Where(r => r.Id != quota.TenantId).ToList();
|
||||
asynctariff.Quotas.Add(new Quota(quota.TenantId, currentPayment.Quantity));
|
||||
email = currentPayment.PaymentEmail;
|
||||
asynctariff.Id = currentPayment.PaymentId;
|
||||
|
||||
var paymentEndDate = 9999 <= currentPayment.EndDate.Year ? DateTime.MaxValue : currentPayment.EndDate;
|
||||
asynctariff.DueDate = DateTime.Compare(asynctariff.DueDate, paymentEndDate) < 0 ? asynctariff.DueDate : paymentEndDate;
|
||||
|
||||
asynctariff.Quotas = asynctariff.Quotas.Where(r => r.Id != quota.TenantId).ToList();
|
||||
asynctariff.Quotas.Add(new Quota(quota.TenantId, currentPayment.Quantity));
|
||||
email = currentPayment.PaymentEmail;
|
||||
}
|
||||
|
||||
TenantQuota updatedQuota = null;
|
||||
|
||||
foreach (var quota in asynctariff.Quotas)
|
||||
{
|
||||
var tenantQuota = tenantQuotas.SingleOrDefault(q => q.TenantId == quota.Id);
|
||||
|
||||
tenantQuota *= quota.Quantity;
|
||||
updatedQuota += tenantQuota;
|
||||
}
|
||||
|
||||
if (updatedQuota != null)
|
||||
{
|
||||
await updatedQuota.CheckAsync(serviceProvider);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
asynctariff.CustomerId = email;
|
||||
}
|
||||
|
||||
if (await SaveBillingInfoAsync(tenantId, asynctariff))
|
||||
{
|
||||
asynctariff = await CalculateTariffAsync(tenantId, asynctariff);
|
||||
tariff = asynctariff;
|
||||
}
|
||||
|
||||
await InsertToCache(tenantId, tariff);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
if (error is not BillingNotFoundException)
|
||||
{
|
||||
LogError(error, tenantId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
TenantQuota updatedQuota = null;
|
||||
|
||||
foreach (var quota in asynctariff.Quotas)
|
||||
if (tariff.Id == 0)
|
||||
{
|
||||
var tenantQuota = tenantQuotas.SingleOrDefault(q => q.TenantId == quota.Id);
|
||||
var freeTariff = await tariff.Quotas.ToAsyncEnumerable().FirstOrDefaultAwaitAsync(async tariffRow =>
|
||||
{
|
||||
var q = await quotaService.GetTenantQuotaAsync(tariffRow.Id);
|
||||
return q == null
|
||||
|| (TrialEnabled && q.Trial)
|
||||
|| q.Free
|
||||
|| q.NonProfit
|
||||
|| q.Custom;
|
||||
});
|
||||
|
||||
tenantQuota *= quota.Quantity;
|
||||
updatedQuota += tenantQuota;
|
||||
}
|
||||
var asynctariff = await CreateDefaultAsync();
|
||||
|
||||
if (updatedQuota != null)
|
||||
{
|
||||
await updatedQuota.CheckAsync(serviceProvider);
|
||||
}
|
||||
if (freeTariff == null)
|
||||
{
|
||||
asynctariff.DueDate = DateTime.Today.AddDays(-1);
|
||||
asynctariff.State = TariffState.NotPaid;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
asynctariff.CustomerId = email;
|
||||
}
|
||||
if (await SaveBillingInfoAsync(tenantId, asynctariff))
|
||||
{
|
||||
asynctariff = await CalculateTariffAsync(tenantId, asynctariff);
|
||||
tariff = asynctariff;
|
||||
}
|
||||
|
||||
if (await SaveBillingInfoAsync(tenantId, asynctariff))
|
||||
{
|
||||
asynctariff = await CalculateTariffAsync(tenantId, asynctariff);
|
||||
tariff = asynctariff;
|
||||
}
|
||||
|
||||
await UpdateCacheAsync(tariff.Id);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
if (error is not BillingNotFoundException)
|
||||
{
|
||||
LogError(error, tenantId.ToString());
|
||||
await InsertToCache(tenantId, tariff);
|
||||
}
|
||||
}
|
||||
|
||||
if (tariff.Id == 0)
|
||||
else if (tenantExtraConfig.Enterprise && tariff.Id == 0 && tariff.LicenseDate == DateTime.MaxValue)
|
||||
{
|
||||
var freeTariff = await tariff.Quotas.ToAsyncEnumerable().FirstOrDefaultAwaitAsync(async tariffRow =>
|
||||
{
|
||||
var q = await quotaService.GetTenantQuotaAsync(tariffRow.Id);
|
||||
return q == null
|
||||
|| (TrialEnabled && q.Trial)
|
||||
|| q.Free
|
||||
|| q.NonProfit
|
||||
|| q.Custom;
|
||||
});
|
||||
var defaultQuota = await quotaService.GetTenantQuotaAsync(Tenant.DefaultTenant);
|
||||
|
||||
var asynctariff = await CreateDefaultAsync();
|
||||
var quota = new TenantQuota(defaultQuota) { Name = "start_trial", Trial = true, TenantId = -1000 };
|
||||
|
||||
if (freeTariff == null)
|
||||
{
|
||||
asynctariff.DueDate = DateTime.Today.AddDays(-1);
|
||||
asynctariff.State = TariffState.NotPaid;
|
||||
}
|
||||
await quotaService.SaveTenantQuotaAsync(quota);
|
||||
|
||||
if (await SaveBillingInfoAsync(tenantId, asynctariff))
|
||||
{
|
||||
asynctariff = await CalculateTariffAsync(tenantId, asynctariff);
|
||||
tariff = asynctariff;
|
||||
}
|
||||
tariff = new Tariff { Quotas = [new(quota.TenantId, 1)], DueDate = DateTime.UtcNow.AddDays(DefaultTrialPeriod) };
|
||||
|
||||
await UpdateCacheAsync(tariff.Id);
|
||||
await SetTariffAsync(Tenant.DefaultTenant, tariff, [quota]);
|
||||
await InsertToCache(tenantId, tariff);
|
||||
}
|
||||
}
|
||||
else if (tenantExtraConfig.Enterprise && tariff.Id == 0 && tariff.LicenseDate == DateTime.MaxValue)
|
||||
{
|
||||
var defaultQuota = await quotaService.GetTenantQuotaAsync(Tenant.DefaultTenant);
|
||||
|
||||
var quota = new TenantQuota(defaultQuota)
|
||||
{
|
||||
Name = "start_trial",
|
||||
Trial = true,
|
||||
TenantId = -1000
|
||||
};
|
||||
|
||||
await quotaService.SaveTenantQuotaAsync(quota);
|
||||
|
||||
tariff = new Tariff
|
||||
{
|
||||
Quotas = [new(quota.TenantId, 1)],
|
||||
DueDate = DateTime.UtcNow.AddDays(DefaultTrialPeriod)
|
||||
};
|
||||
|
||||
await SetTariffAsync(Tenant.DefaultTenant, tariff, [quota]);
|
||||
await UpdateCacheAsync(tariff.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -279,11 +229,6 @@ public class TariffService(
|
||||
}
|
||||
|
||||
return tariff;
|
||||
|
||||
async Task UpdateCacheAsync(int tariffId)
|
||||
{
|
||||
await notify.PublishAsync(new TariffCacheItem { TenantId = tenantId, TariffId = tariffId }, CacheNotifyAction.Insert);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PaymentChangeAsync(int tenantId, Dictionary<string, int> quantity)
|
||||
@ -385,16 +330,7 @@ public class TariffService(
|
||||
{
|
||||
return $"{tenantId}:tariff";
|
||||
}
|
||||
|
||||
internal static string GetTariffNeedToUpdateCacheKey(int tenantId)
|
||||
{
|
||||
return $"{tenantId}:update";
|
||||
}
|
||||
|
||||
internal static string GetBillingUrlCacheKey(int tenantId)
|
||||
{
|
||||
return $"{tenantId}:billing:urls";
|
||||
}
|
||||
|
||||
|
||||
internal static string GetBillingPaymentCacheKey(int tenantId)
|
||||
{
|
||||
@ -404,38 +340,50 @@ public class TariffService(
|
||||
|
||||
private async Task ClearCacheAsync(int tenantId)
|
||||
{
|
||||
await notify.PublishAsync(new TariffCacheItem { TenantId = tenantId, TariffId = -1 }, CacheNotifyAction.Remove);
|
||||
await distributedCache.RemoveAsync(GetTariffCacheKey(tenantId));
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PaymentInfo>> GetPaymentsAsync(int tenantId)
|
||||
{
|
||||
var key = GetBillingPaymentCacheKey(tenantId);
|
||||
var payments = cache.Get<List<PaymentInfo>>(key);
|
||||
var payments = await GetFromCache<List<PaymentInfo>>(key);
|
||||
if (payments == null)
|
||||
{
|
||||
payments = [];
|
||||
if (billingClient.Configured)
|
||||
await using (await distributedLockProvider.TryAcquireLockAsync($"{key}_lock"))
|
||||
{
|
||||
try
|
||||
payments = await GetFromCache<List<PaymentInfo>>(key);
|
||||
if (payments != null)
|
||||
{
|
||||
var quotas = await quotaService.GetTenantQuotasAsync();
|
||||
foreach (var pi in await billingClient.GetPaymentsAsync(await coreSettings.GetKeyAsync(tenantId)))
|
||||
return payments;
|
||||
}
|
||||
|
||||
payments = [];
|
||||
if (billingClient.Configured)
|
||||
{
|
||||
try
|
||||
{
|
||||
var quota = quotas.SingleOrDefault(q => q.ProductId == pi.ProductRef.ToString());
|
||||
if (quota != null)
|
||||
var quotas = await quotaService.GetTenantQuotasAsync();
|
||||
foreach (var pi in await billingClient.GetPaymentsAsync(await coreSettings.GetKeyAsync(tenantId)))
|
||||
{
|
||||
pi.QuotaId = quota.TenantId;
|
||||
var quota = quotas.SingleOrDefault(q => q.ProductId == pi.ProductRef.ToString());
|
||||
if (quota != null)
|
||||
{
|
||||
pi.QuotaId = quota.TenantId;
|
||||
}
|
||||
|
||||
payments.Add(pi);
|
||||
}
|
||||
payments.Add(pi);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
LogError(error, tenantId.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
LogError(error, tenantId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
cache.Insert(key, payments, DateTime.UtcNow.Add(TimeSpan.FromMinutes(10)));
|
||||
using var ms = new MemoryStream();
|
||||
Serializer.Serialize(ms, payments);
|
||||
await distributedCache.SetAsync(key, ms.ToArray(), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
|
||||
}
|
||||
}
|
||||
|
||||
return payments;
|
||||
@ -512,7 +460,7 @@ public class TariffService(
|
||||
cache.Insert(key, url, DateTime.UtcNow.Add(TimeSpan.FromMinutes(10)));
|
||||
}
|
||||
|
||||
tariffServiceStorage.ResetCacheExpiration();
|
||||
ResetCacheExpiration();
|
||||
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
@ -887,4 +835,42 @@ public class TariffService(
|
||||
{
|
||||
return billingClient.Configured;
|
||||
}
|
||||
|
||||
private TimeSpan GetCacheExpiration()
|
||||
{
|
||||
if (coreBaseSettings.Standalone && _cacheExpiration < _standaloneCacheExpiration)
|
||||
{
|
||||
_cacheExpiration = _cacheExpiration.Add(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
return _cacheExpiration;
|
||||
}
|
||||
|
||||
private async Task InsertToCache(int tenantId, Tariff tariff)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
Serializer.Serialize(ms, tariff);
|
||||
await distributedCache.SetAsync(GetTariffCacheKey(tenantId), ms.ToArray(), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = GetCacheExpiration()});
|
||||
}
|
||||
|
||||
private async Task<T> GetFromCache<T>(string key)
|
||||
{
|
||||
var serializedObject = await distributedCache.GetAsync(key);
|
||||
|
||||
if (serializedObject == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream(serializedObject);
|
||||
|
||||
return Serializer.Deserialize<T>(ms);
|
||||
}
|
||||
|
||||
private void ResetCacheExpiration()
|
||||
{
|
||||
if (coreBaseSettings.Standalone)
|
||||
{
|
||||
_cacheExpiration = _defaultCacheExpiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public static class StringExtension
|
||||
|
||||
try
|
||||
{
|
||||
var punyCode = idn.GetAscii(data);
|
||||
var punyCode = idn.GetAscii(data.TrimStart('.'));
|
||||
var domain2 = idn.GetUnicode(punyCode);
|
||||
|
||||
if (!string.Equals(punyCode, domain2))
|
||||
@ -79,8 +79,9 @@ public static class StringExtension
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
catch (ArgumentException ex) when(ex.ParamName == "unicode")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -45,12 +45,22 @@ public abstract class ActivePassiveBackgroundService<T>(ILogger logger, IService
|
||||
var registerInstanceService = serviceScope.ServiceProvider.GetService<IRegisterInstanceManager<T>>();
|
||||
var workerOptions = serviceScope.ServiceProvider.GetService<IOptions<InstanceWorkerOptions<T>>>().Value;
|
||||
|
||||
if (!await registerInstanceService.IsActive())
|
||||
const int millisecondsDelay = 1000;
|
||||
try
|
||||
{
|
||||
logger.TraceActivePassiveBackgroundServiceIsNotActive(serviceName, workerOptions.InstanceId);
|
||||
|
||||
await Task.Delay(1000, stoppingToken);
|
||||
if (!await registerInstanceService.IsActive())
|
||||
{
|
||||
logger.TraceActivePassiveBackgroundServiceIsNotActive(serviceName, workerOptions.InstanceId);
|
||||
|
||||
await Task.Delay(millisecondsDelay, stoppingToken);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WarningWithException(e);
|
||||
|
||||
await Task.Delay(millisecondsDelay, stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -62,6 +72,5 @@ public abstract class ActivePassiveBackgroundService<T>(ILogger logger, IService
|
||||
|
||||
await Task.Delay(ExecuteTaskPeriod, stoppingToken);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ using Profile = AutoMapper.Profile;
|
||||
|
||||
namespace ASC.Core.Tenants;
|
||||
|
||||
[ProtoContract]
|
||||
public class Tenant : IMapFrom<DbTenant>
|
||||
{
|
||||
public const int DefaultTenant = -1;
|
||||
@ -62,36 +63,62 @@ public class Tenant : IMapFrom<DbTenant>
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
public void Mapping(Profile profile)
|
||||
{
|
||||
profile.CreateMap<DbTenant, Tenant>()
|
||||
.ForMember(r => r.TrustedDomainsType, opt => opt.MapFrom(src => src.TrustedDomainsEnabled))
|
||||
.ForMember(r => r.AffiliateId, opt => opt.MapFrom(src => src.Partner.AffiliateId))
|
||||
.ForMember(r => r.PartnerId, opt => opt.MapFrom(src => src.Partner.PartnerId))
|
||||
.ForMember(r => r.Campaign, opt => opt.MapFrom(src => src.Partner.Campaign));
|
||||
|
||||
profile.CreateMap<TenantUserSecurity, Tenant>()
|
||||
.IncludeMembers(src => src.DbTenant);
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public string AffiliateId { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public bool Calls { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public string Campaign { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public DateTime CreationDateTime { get; internal set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
public string HostedRegion { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
public int Id { get; internal set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
public TenantIndustry Industry { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string Language { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
public string MappedDomain { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
public Guid OwnerId { get; set; }
|
||||
|
||||
[ProtoMember(14)]
|
||||
public string PartnerId { get; set; }
|
||||
|
||||
[ProtoMember(15)]
|
||||
public string PaymentId { get; set; }
|
||||
|
||||
[ProtoMember(16)]
|
||||
public TenantStatus Status { get; internal set; }
|
||||
|
||||
[ProtoMember(17)]
|
||||
public DateTime StatusChangeDate { get; internal set; }
|
||||
|
||||
[ProtoMember(18)]
|
||||
public string TimeZone { get; set; }
|
||||
|
||||
[ProtoMember(19)]
|
||||
public List<string> TrustedDomains
|
||||
{
|
||||
get
|
||||
@ -104,19 +131,29 @@ public class Tenant : IMapFrom<DbTenant>
|
||||
|
||||
return _domains;
|
||||
}
|
||||
|
||||
set => _domains = value;
|
||||
}
|
||||
|
||||
[ProtoMember(20)]
|
||||
public string TrustedDomainsRaw { get; set; }
|
||||
|
||||
[ProtoMember(21)]
|
||||
public TenantTrustedDomainsType TrustedDomainsType { get; set; }
|
||||
|
||||
[ProtoMember(22)]
|
||||
public int Version { get; set; }
|
||||
|
||||
[ProtoMember(23)]
|
||||
public DateTime VersionChanged { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Tenant t && t.Id == Id;
|
||||
}
|
||||
|
||||
public CultureInfo GetCulture() => !string.IsNullOrEmpty(Language) ? CultureInfo.GetCultureInfo(Language.Trim()) : CultureInfo.CurrentCulture;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id;
|
||||
@ -143,19 +180,22 @@ public class Tenant : IMapFrom<DbTenant>
|
||||
result = $"{Alias}.{baseHost}".TrimEnd('.').ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(MappedDomain) && allowMappedDomain)
|
||||
if (string.IsNullOrEmpty(MappedDomain) || !allowMappedDomain)
|
||||
{
|
||||
if (MappedDomain.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
MappedDomain = MappedDomain[7..];
|
||||
}
|
||||
if (MappedDomain.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
MappedDomain = MappedDomain[8..];
|
||||
}
|
||||
result = MappedDomain.ToLowerInvariant();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (MappedDomain.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
MappedDomain = MappedDomain[7..];
|
||||
}
|
||||
if (MappedDomain.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
MappedDomain = MappedDomain[8..];
|
||||
}
|
||||
|
||||
result = MappedDomain.ToLowerInvariant();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -169,6 +209,18 @@ public class Tenant : IMapFrom<DbTenant>
|
||||
{
|
||||
return Alias;
|
||||
}
|
||||
|
||||
public void Mapping(Profile profile)
|
||||
{
|
||||
profile.CreateMap<DbTenant, Tenant>()
|
||||
.ForMember(r => r.TrustedDomainsType, opt => opt.MapFrom(src => src.TrustedDomainsEnabled))
|
||||
.ForMember(r => r.AffiliateId, opt => opt.MapFrom(src => src.Partner.AffiliateId))
|
||||
.ForMember(r => r.PartnerId, opt => opt.MapFrom(src => src.Partner.PartnerId))
|
||||
.ForMember(r => r.Campaign, opt => opt.MapFrom(src => src.Partner.Campaign));
|
||||
|
||||
profile.CreateMap<TenantUserSecurity, Tenant>()
|
||||
.IncludeMembers(src => src.DbTenant);
|
||||
}
|
||||
|
||||
internal string GetTrustedDomains()
|
||||
{
|
||||
@ -192,4 +244,4 @@ public class Tenant : IMapFrom<DbTenant>
|
||||
TrustedDomains.AddRange(trustedDomains.Split(['|'], StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1445,7 +1445,7 @@ public class FileStorageService //: IFileStorageService
|
||||
}
|
||||
else
|
||||
{
|
||||
await entryManager.TrackEditingAsync(fileId, tabId, authContext.CurrentAccount.ID, await tenantManager.GetCurrentTenantIdAsync());
|
||||
await entryManager.TrackEditingAsync(fileId, tabId, authContext.CurrentAccount.ID, await tenantManager.GetCurrentTenantAsync());
|
||||
}
|
||||
|
||||
return new KeyValuePair<bool, string>(true, string.Empty);
|
||||
@ -1556,7 +1556,7 @@ public class FileStorageService //: IFileStorageService
|
||||
throw new InvalidOperationException(FilesCommonResource.ErrorMessage_SecurityException_EditFileTwice);
|
||||
}
|
||||
|
||||
await entryManager.TrackEditingAsync(fileId, Guid.Empty, authContext.CurrentAccount.ID, await tenantManager.GetCurrentTenantIdAsync(), true);
|
||||
await entryManager.TrackEditingAsync(fileId, Guid.Empty, authContext.CurrentAccount.ID, await tenantManager.GetCurrentTenantAsync(), true);
|
||||
|
||||
//without StartTrack, track via old scheme
|
||||
return await documentServiceHelper.GetDocKeyAsync(fileId, -1, DateTime.MinValue);
|
||||
@ -1665,7 +1665,6 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
foreach (var r in history)
|
||||
{
|
||||
await entryStatusManager.SetFileStatusAsync(r);
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ public class DocumentServiceTrackerHelper(SecurityContext securityContext,
|
||||
|
||||
try
|
||||
{
|
||||
file = await entryManager.TrackEditingAsync(fileId, userId, userId, await tenantManager.GetCurrentTenantIdAsync());
|
||||
file = await entryManager.TrackEditingAsync(fileId, userId, userId, await tenantManager.GetCurrentTenantAsync());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -1689,25 +1689,27 @@ public class EntryManager(IDaoFactory daoFactory,
|
||||
return file;
|
||||
}
|
||||
|
||||
public async Task<File<T>> TrackEditingAsync<T>(T fileId, Guid tabId, Guid userId, int tenantId, bool editingAlone = false)
|
||||
public async Task<File<T>> TrackEditingAsync<T>(T fileId, Guid tabId, Guid userId, Tenant tenant, bool editingAlone = false)
|
||||
{
|
||||
var token = externalShare.GetKey();
|
||||
|
||||
var file = await daoFactory.GetFileDao<T>().GetFileStableAsync(fileId);
|
||||
if (file == null)
|
||||
{
|
||||
throw new FileNotFoundException(FilesCommonResource.ErrorMessage_FileNotFound);
|
||||
}
|
||||
|
||||
var docKey = await documentServiceHelper.GetDocKeyAsync(file);
|
||||
|
||||
bool checkRight;
|
||||
if ((await fileTracker.GetEditingByAsync(fileId)).Contains(userId))
|
||||
{
|
||||
checkRight = await fileTracker.ProlongEditingAsync(fileId, tabId, userId, tenantId, commonLinkUtility.ServerRootPath, editingAlone, token);
|
||||
checkRight = await fileTracker.ProlongEditingAsync(fileId, tabId, userId, tenant, commonLinkUtility.ServerRootPath, docKey, editingAlone, token);
|
||||
if (!checkRight)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var file = await daoFactory.GetFileDao<T>().GetFileAsync(fileId);
|
||||
if (file == null)
|
||||
{
|
||||
throw new FileNotFoundException(FilesCommonResource.ErrorMessage_FileNotFound);
|
||||
}
|
||||
|
||||
if (!await CanEditAsync(userId, file))
|
||||
{
|
||||
@ -1724,7 +1726,7 @@ public class EntryManager(IDaoFactory daoFactory,
|
||||
throw new Exception(FilesCommonResource.ErrorMessage_ViewTrashItem);
|
||||
}
|
||||
|
||||
checkRight = await fileTracker.ProlongEditingAsync(fileId, tabId, userId, tenantId, commonLinkUtility.ServerRootPath, editingAlone, token);
|
||||
checkRight = await fileTracker.ProlongEditingAsync(fileId, tabId, userId, tenant, commonLinkUtility.ServerRootPath, docKey, editingAlone, token);
|
||||
if (checkRight)
|
||||
{
|
||||
await fileTracker.ChangeRight(fileId, userId, false);
|
||||
|
@ -78,7 +78,7 @@ public class FileTrackerHelper
|
||||
_logger.Debug("FileTracker subscribed");
|
||||
}
|
||||
|
||||
public async Task<bool> ProlongEditingAsync<T>(T fileId, Guid tabId, Guid userId, int tenantId, string baseUri, bool editingAlone = false, string token = null)
|
||||
public async Task<bool> ProlongEditingAsync<T>(T fileId, Guid tabId, Guid userId, Tenant tenant, string baseUri, string docKey, bool editingAlone = false, string token = null)
|
||||
{
|
||||
var checkRight = true;
|
||||
var tracker = GetTracker(fileId);
|
||||
@ -97,15 +97,13 @@ public class FileTrackerHelper
|
||||
UserId = userId,
|
||||
NewScheme = tabId == userId,
|
||||
EditingAlone = editingAlone,
|
||||
TenantId = tenantId,
|
||||
BaseUri = baseUri,
|
||||
Token = token
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tracker = new FileTracker(tabId, userId, tabId == userId, editingAlone, tenantId, baseUri, token);
|
||||
tracker = new FileTracker(tabId, userId, tabId == userId, editingAlone, tenant, baseUri, docKey, token);
|
||||
}
|
||||
|
||||
await SetTrackerAsync(fileId, tracker);
|
||||
@ -233,33 +231,29 @@ public class FileTrackerHelper
|
||||
|
||||
private Action<object, object, EvictionReason, object> EvictionCallback()
|
||||
{
|
||||
return (cacheFileId, fileTracker, reason, _) =>
|
||||
return (cacheFileId, fileTracker, reason, state) =>
|
||||
{
|
||||
if (reason != EvictionReason.Expired || cacheFileId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var fId = cacheFileId.ToString()?.Substring(Tracker.Length);
|
||||
var fId = cacheFileId.ToString()?[Tracker.Length..];
|
||||
|
||||
var t = int.TryParse(fId, out var internalFileId) ?
|
||||
_ = int.TryParse(fId, out var internalFileId) ?
|
||||
Callback(internalFileId, fileTracker as FileTracker).ConfigureAwait(false) :
|
||||
Callback(fId, fileTracker as FileTracker).ConfigureAwait(false);
|
||||
|
||||
t.GetAwaiter().GetResult();
|
||||
};
|
||||
|
||||
async Task Callback<T>(T fileId, FileTracker fileTracker)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (fileTracker.EditingBy == null || fileTracker.EditingBy.Count == 0)
|
||||
if (fileTracker.EditingBy == null || fileTracker.EditingBy.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var editedBy = fileTracker.EditingBy.FirstOrDefault();
|
||||
|
||||
var token = fileTracker.EditingBy
|
||||
.OrderByDescending(x => x.Value.TrackTime)
|
||||
.Where(x => !string.IsNullOrEmpty(x.Value.Token))
|
||||
@ -268,25 +262,16 @@ public class FileTrackerHelper
|
||||
|
||||
await using var scope = _serviceProvider.CreateAsyncScope();
|
||||
var tenantManager = scope.ServiceProvider.GetRequiredService<TenantManager>();
|
||||
await tenantManager.SetCurrentTenantAsync(editedBy.Value.TenantId);
|
||||
tenantManager.SetCurrentTenant(fileTracker.Tenant);
|
||||
|
||||
var commonLinkUtility = scope.ServiceProvider.GetRequiredService<BaseCommonLinkUtility>();
|
||||
commonLinkUtility.ServerUri = editedBy.Value.BaseUri;
|
||||
commonLinkUtility.ServerUri = fileTracker.BaseUri;
|
||||
|
||||
var helper = scope.ServiceProvider.GetRequiredService<DocumentServiceHelper>();
|
||||
var tracker = scope.ServiceProvider.GetRequiredService<DocumentServiceTrackerHelper>();
|
||||
var daoFactory = scope.ServiceProvider.GetRequiredService<IDaoFactory>();
|
||||
|
||||
var file = await daoFactory.GetFileDao<T>().GetFileStableAsync(fileId);
|
||||
if (file == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var docKey = await helper.GetDocKeyAsync(file);
|
||||
|
||||
using (_logger.BeginScope(new[] { new KeyValuePair<string, object>("DocumentServiceConnector", $"{fileId}") }))
|
||||
{
|
||||
if (await tracker.StartTrackAsync(fileId.ToString(), docKey, token))
|
||||
if (await tracker.StartTrackAsync(fileId.ToString(), fileTracker.DocKey, token))
|
||||
{
|
||||
await SetTrackerAsync(fileId, fileTracker);
|
||||
}
|
||||
@ -303,7 +288,7 @@ public class FileTrackerHelper
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCacheKey<T>(T fileId)
|
||||
private static string GetCacheKey<T>(T fileId)
|
||||
{
|
||||
return Tracker + fileId;
|
||||
}
|
||||
@ -324,19 +309,29 @@ public record FileTracker
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public ConcurrentDictionary<Guid, TrackInfo> EditingBy { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public Tenant Tenant { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string BaseUri { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public string DocKey { get; set; }
|
||||
|
||||
public FileTracker() { }
|
||||
|
||||
internal FileTracker(Guid tabId, Guid userId, bool newScheme, bool editingAlone, int tenantId, string baseUri, string token = null)
|
||||
internal FileTracker(Guid tabId, Guid userId, bool newScheme, bool editingAlone, Tenant tenant, string baseUri, string docKey, string token = null)
|
||||
{
|
||||
DocKey = docKey;
|
||||
Tenant = tenant;
|
||||
BaseUri = baseUri;
|
||||
EditingBy = new ConcurrentDictionary<Guid, TrackInfo>();
|
||||
EditingBy.TryAdd(tabId, new TrackInfo
|
||||
{
|
||||
UserId = userId,
|
||||
NewScheme = newScheme,
|
||||
EditingAlone = editingAlone,
|
||||
TenantId = tenantId,
|
||||
BaseUri = baseUri,
|
||||
Token = token
|
||||
});
|
||||
}
|
||||
@ -354,18 +349,12 @@ public record FileTracker
|
||||
public required Guid UserId { get; init; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public required int TenantId { get; init; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public required string BaseUri { get; init; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
public required bool NewScheme { get; init; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[ProtoMember(5)]
|
||||
public required bool EditingAlone { get; init; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[ProtoMember(6)]
|
||||
public string Token { get; init; }
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ public class FilesControllerInternal(
|
||||
[SwaggerResponse(403, "You don't have enough permission to perform the operation")]
|
||||
[SwaggerResponse(404, "The required file was not found")]
|
||||
[HttpGet("file/{fileId:int}/log")]
|
||||
public IAsyncEnumerable<HistoryDto> GetHistoryAsync(HistoryRequestDto inDto)
|
||||
public IAsyncEnumerable<HistoryDto> GetFileHistoryAsync(HistoryRequestDto inDto)
|
||||
{
|
||||
return historyApiHelper.GetFileHistoryAsync(inDto.FileId, inDto.FromDate, inDto.ToDate);
|
||||
}
|
||||
@ -471,7 +471,7 @@ public abstract class FilesController<T>(FilesControllerHelper filesControllerHe
|
||||
[Tags("Files / Files")]
|
||||
[SwaggerResponse(200, "Order is set")]
|
||||
[HttpPut("order")]
|
||||
public async Task SetOrder(OrdersRequestDto<T> inDto)
|
||||
public async Task SetFilesOrder(OrdersRequestDto<T> inDto)
|
||||
{
|
||||
await fileStorageService.SetOrderAsync(inDto.Items);
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class FoldersControllerInternal(
|
||||
[SwaggerResponse(403, "You don't have enough permission to perform the operation")]
|
||||
[SwaggerResponse(404, "The required folder was not found")]
|
||||
[HttpGet("folder/{folderId:int}/log")]
|
||||
public IAsyncEnumerable<HistoryDto> GetHistoryAsync(HistoryFolderRequestDto inDto)
|
||||
public IAsyncEnumerable<HistoryDto> GetFolderHistoryAsync(HistoryFolderRequestDto inDto)
|
||||
{
|
||||
return historyApiHelper.GetFolderHistoryAsync(inDto.FolderId, inDto.FromDate, inDto.ToDate);
|
||||
}
|
||||
|
@ -596,7 +596,7 @@ public abstract class VirtualRoomsController<T>(
|
||||
[Tags("Files / Rooms")]
|
||||
[SwaggerResponse(200, "List of file entry information", typeof(IAsyncEnumerable<NewItemsDto<FileEntryDto>>))]
|
||||
[HttpGet("{id}/news")]
|
||||
public async Task<List<NewItemsDto<FileEntryDto>>> GetNewItemsAsync(RoomIdRequestDto<T> inDto)
|
||||
public async Task<List<NewItemsDto<FileEntryDto>>> GetNewRoomItemsAsync(RoomIdRequestDto<T> inDto)
|
||||
{
|
||||
var newItems = await _fileStorageService.GetNewRoomFilesAsync(inDto.Id);
|
||||
var result = new List<NewItemsDto<FileEntryDto>>();
|
||||
|
@ -24,6 +24,7 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
using ASC.Core.Common;
|
||||
using ASC.Files.Service.Services.Thumbnail;
|
||||
|
||||
namespace ASC.Files.Service.IntegrationEvents.EventHandling;
|
||||
@ -36,6 +37,7 @@ public class ThumbnailRequestedIntegrationEventHandler : IIntegrationEventHandle
|
||||
private readonly ITariffService _tariffService;
|
||||
private readonly TenantManager _tenantManager;
|
||||
private readonly IDbContextFactory<FilesDbContext> _dbContextFactory;
|
||||
private readonly BaseCommonLinkUtility _baseCommonLinkUtility;
|
||||
|
||||
private ThumbnailRequestedIntegrationEventHandler()
|
||||
{
|
||||
@ -47,13 +49,15 @@ public class ThumbnailRequestedIntegrationEventHandler : IIntegrationEventHandle
|
||||
IDbContextFactory<FilesDbContext> dbContextFactory,
|
||||
ITariffService tariffService,
|
||||
TenantManager tenantManager,
|
||||
ChannelWriter<FileData<int>> channelWriter)
|
||||
ChannelWriter<FileData<int>> channelWriter,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility)
|
||||
{
|
||||
_logger = logger;
|
||||
_channelWriter = channelWriter;
|
||||
_tariffService = tariffService;
|
||||
_tenantManager = tenantManager;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_baseCommonLinkUtility = baseCommonLinkUtility;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<FileData<int>>> GetFreezingThumbnailsAsync()
|
||||
@ -79,7 +83,9 @@ public class ThumbnailRequestedIntegrationEventHandler : IIntegrationEventHandle
|
||||
{
|
||||
_ = await _tenantManager.SetCurrentTenantAsync(r.TenantId);
|
||||
var tariff = await _tariffService.GetTariffAsync(r.TenantId);
|
||||
var fileData = new FileData<int>(r.TenantId, r.ModifiedBy, r.Id, "", tariff.State);
|
||||
var baseUrl = _baseCommonLinkUtility.GetFullAbsolutePath(string.Empty);
|
||||
|
||||
var fileData = new FileData<int>(r.TenantId, r.ModifiedBy, r.Id, baseUrl, tariff.State);
|
||||
|
||||
return fileData;
|
||||
}).ToListAsync();
|
||||
|
@ -53,7 +53,7 @@ public class Builder<T>(ThumbnailSettings settings,
|
||||
private IDataStore _dataStore;
|
||||
|
||||
private readonly List<string> _imageFormatsCanBeCrop =
|
||||
[".bmp", ".gif", ".jpeg", ".jpg", ".pbm", ".png", ".tiff", ".tga", ".webp"];
|
||||
[".bmp", ".jpeg", ".jpg", ".pbm", ".png", ".tiff", ".tga", ".webp"];
|
||||
|
||||
internal async Task BuildThumbnail(FileData<T> fileData)
|
||||
{
|
||||
|
@ -91,14 +91,21 @@ public class ThumbnailBuilderService(IServiceScopeFactory serviceScopeFactory,
|
||||
{
|
||||
await foreach (var fileData in reader.ReadAllAsync(stoppingToken))
|
||||
{
|
||||
await using var scope = serviceScopeFactory.CreateAsyncScope();
|
||||
try
|
||||
{
|
||||
await using var scope = serviceScopeFactory.CreateAsyncScope();
|
||||
|
||||
var commonLinkUtility = scope.ServiceProvider.GetService<CommonLinkUtility>();
|
||||
commonLinkUtility.ServerUri = fileData.BaseUri;
|
||||
var commonLinkUtility = scope.ServiceProvider.GetService<CommonLinkUtility>();
|
||||
commonLinkUtility.ServerUri = fileData.BaseUri;
|
||||
|
||||
var builder = scope.ServiceProvider.GetService<Builder<int>>();
|
||||
var builder = scope.ServiceProvider.GetService<Builder<int>>();
|
||||
|
||||
await builder.BuildThumbnail(fileData);
|
||||
await builder.BuildThumbnail(fileData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorWithException(e);
|
||||
}
|
||||
}
|
||||
}, stoppingToken));
|
||||
}
|
||||
|
@ -256,20 +256,26 @@ public class PortalController(
|
||||
|
||||
var result = new TariffDto
|
||||
{
|
||||
Id = source.Id,
|
||||
State = source.State,
|
||||
DueDate = source.DueDate,
|
||||
DelayDueDate = source.DelayDueDate,
|
||||
LicenseDate = source.LicenseDate,
|
||||
CustomerId = source.CustomerId,
|
||||
Quotas = source.Quotas,
|
||||
};
|
||||
|
||||
var currentUserType = await userManager.GetUserTypeAsync(securityContext.CurrentAccount.ID);
|
||||
|
||||
if (currentUserType is EmployeeType.RoomAdmin or EmployeeType.DocSpaceAdmin)
|
||||
{
|
||||
result.DueDate = source.DueDate;
|
||||
result.DelayDueDate = source.DelayDueDate;
|
||||
}
|
||||
|
||||
if (await permissionContext.CheckPermissionsAsync(SecurityConstants.EditPortalSettings))
|
||||
{
|
||||
result.Id = source.Id;
|
||||
result.OpenSource = tenantExtra.Opensource;
|
||||
result.Enterprise = tenantExtra.Enterprise;
|
||||
result.Developer = tenantExtra.Developer;
|
||||
result.CustomerId = source.CustomerId;
|
||||
result.LicenseDate = source.LicenseDate;
|
||||
result.Quotas = source.Quotas;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
Loading…
x
Reference in New Issue
Block a user