Open Closed

Can I disable multi-tenancy for the Organizational Unit entity? #4501


User avatar
0
jlavallet created

Can I disable multi-tenancy for the Organizational Unit entity?

I know how to perform queries by using (_dataFilter.Disable<IMultiTenant>()). I created a utility class to do that:

public static class OuHelper
{
  public static async Task<IEnumerable<OrganizationUnit>> GetHostOusAsync(
    IOrganizationUnitRepository ouRepository,
    IDataFilter dataFilter)
  {
    using (dataFilter.Disable<IMultiTenant>())
    {
      return await ouRepository.GetListAsync();
    }
  }
}

but later when I want to do this:

  public async Task HandleEventAsync(EntityCreatedEventData<IdentityUser> eventData)
  {
    var ous = await OuHelper.GetHostOusAsync(_ouRepository, _dataFilter);
    var adminsOu = ous.FirstOrDefault(ou => ou.DisplayName == OrganizationUnitConsts.Admins);
    var agentsOu = ous.FirstOrDefault(ou => ou.DisplayName == OrganizationUnitConsts.Agents);
    var reportersOu = ous.FirstOrDefault(ou => ou.DisplayName == OrganizationUnitConsts.Reporters);

    var user = eventData.Entity;

    if (adminsOu != null)
    {
      var isInAdminOu = await _userManager.IsInOrganizationUnitAsync(user.Id, adminsOu.Id);
      var isInAdminRole = await _userManager.IsInRoleAsync(user, RoleConsts.Admin);

      if (isInAdminOu && !isInAdminRole)
      {
        await _userManager.AddToRoleAsync(user, RoleConsts.Admin);
      }
      else if (!isInAdminOu && isInAdminRole)
      {
        // THIS FAILS
        await _userManager.AddToOrganizationUnitAsync(user.Id, adminsOu.Id);
      }
    }

    if (agentsOu != null)
    {
      var isInAgentOu = await _userManager.IsInOrganizationUnitAsync(user.Id, agentsOu.Id);
      var isInAgentRole = await _userManager.IsInRoleAsync(user, RoleConsts.Agent);

      if (isInAgentOu && !isInAgentRole)
      {
        await _userManager.AddToRoleAsync(user, RoleConsts.Agent);
      }
      else if (!isInAgentOu && isInAgentRole)
      {
        // THIS FAILS
        await _userManager.AddToOrganizationUnitAsync(user.Id, agentsOu.Id);
      }
    }

    if (reportersOu != null)
    {
      var isInReporterOu = await _userManager.IsInOrganizationUnitAsync(user.Id, reportersOu.Id);
      var isInReporterRole = await _userManager.IsInRoleAsync(user, RoleConsts.Reporter);

      if (isInReporterOu && !isInReporterRole)
      {
        await _userManager.AddToRoleAsync(user, RoleConsts.Reporter);
      }
      else if (!isInReporterOu && isInReporterRole)
      {
        // THIS FAILS
        await _userManager.AddToOrganizationUnitAsync(user.Id, reportersOu.Id);
      }
    }
  }

Since it's not my entity and it's built into the Saas module, i can't remove the IMultiTenant interface. Or can I?

The desired behavior is for the Organizational Units to be application scoped and not tenant scoped.

FYI- I am using the commercial module.


6 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    You can override the CreateFilterExpression method of DbContext:

    protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
    {
        if (typeof(TEntity) == typeof(OrganizationUnit))
        {
            using (DataFilter.Disable<IMultiTenant>())
            {
                return base.CreateFilterExpression<TEntity>();
            }
        }
        return base.CreateFilterExpression<TEntity>();
    }
    

    This way needs your DbContext to replace IIdentityProDbContext

  • User Avatar
    0
    jlavallet created
    [ReplaceDbContext(typeof(IIdentityProDbContext))]
    [ReplaceDbContext(typeof(ISaasDbContext))]
    [ConnectionStringName("Default")]
    public class BlueSpotDbContext :
      BlueSpotDbContextBase<BlueSpotDbContext>,
      IIdentityProDbContext,
      ISaasDbContext
    {
      public BlueSpotDbContext(DbContextOptions<BlueSpotDbContext> options)
        : base(options) { }
    
      public DbSet<Locality> Localities { get; set; }
      public DbSet<RejectionReason> RejectionReasons { get; set; }
      public DbSet<State> States { get; set; }
      public DbSet<Violation> Violations { get; set; }
      public DbSet<ViolationStatus> ViolationStatuses { get; set; }
      public DbSet<Photo> Photos { get; set; }
    
      /// <inheritdoc />
      public DbSet<IdentityUser> Users { get; set; }
    
      /// <inheritdoc />
      public DbSet<IdentityRole> Roles { get; set; }
    
      /// <inheritdoc />
      public DbSet<IdentityClaimType> ClaimTypes { get; set; }
    
      /// <inheritdoc />
      public DbSet<OrganizationUnit> OrganizationUnits { get; set; }
    
      /// <inheritdoc />
      public DbSet<IdentitySecurityLog> SecurityLogs { get; set; }
    
      /// <inheritdoc />
      public DbSet<IdentityLinkUser> LinkUsers { get; set; }
    
      /// <inheritdoc />
      public DbSet<Tenant> Tenants { get; set; }
    
      /// <inheritdoc />
      public DbSet<Edition> Editions { get; set; }
    
      /// <inheritdoc />
      public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; }
    
      protected override void OnModelCreating(ModelBuilder builder)
      {
        builder.SetMultiTenancySide(MultiTenancySides.Both);
    
        base.OnModelCreating(builder);
    
        //...
      }
    
      protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
      {
        if (typeof(TEntity) == typeof(OrganizationUnit))
        {
          using (DataFilter.Disable<IMultiTenant>())
          {
            return base.CreateFilterExpression<TEntity>();
          }
        }
    
        return base.CreateFilterExpression<TEntity>();
      }
    }
    

    Everything compiles but when I do a Add-Migration I get the following:

    PM> Add-Migration IdentityAndSassDbContext -Context BlueSpotDbContext
    Multiple startup projects set.
    Using project 'src\BlueSpot.EntityFrameworkCore' as the startup project.
    Build started...
    Build succeeded.
    System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
     ---> System.NullReferenceException: Object reference not set to an instance of an object.
       at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.get_DataFilter() in D:\ci\Jenkins\workspace\abp-framework-release\abp\framework\src\Volo.Abp.EntityFrameworkCore\Volo\Abp\EntityFrameworkCore\AbpDbContext.cs:line 51
       at BlueSpot.EntityFrameworkCore.BlueSpotDbContext.CreateFilterExpression[TEntity]() in D:\Century\Internal\BlueSpot\BlueSpot\src\BlueSpot.EntityFrameworkCore\EntityFrameworkCore\BlueSpotDbContext.cs:line 176
       at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.ConfigureGlobalFilters[TEntity](ModelBuilder modelBuilder, IMutableEntityType mutableEntityType) in D:\ci\Jenkins\workspace\abp-framework-release\abp\framework\src\Volo.Abp.EntityFrameworkCore\Volo\Abp\EntityFrameworkCore\AbpDbContext.cs:line 604
       at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.ConfigureBaseProperties[TEntity](ModelBuilder modelBuilder, IMutableEntityType mutableEntityType) in D:\ci\Jenkins\workspace\abp-framework-release\abp\framework\src\Volo.Abp.EntityFrameworkCore\Volo\Abp\EntityFrameworkCore\AbpDbContext.cs:line 596
       --- End of inner exception stack trace ---
       at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
       at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.OnModelCreating(ModelBuilder modelBuilder) in D:\ci\Jenkins\workspace\abp-framework-release\abp\framework\src\Volo.Abp.EntityFrameworkCore\Volo\Abp\EntityFrameworkCore\AbpDbContext.cs:line 105
       at BlueSpot.EntityFrameworkCore.BlueSpotDbContextBase`1.OnModelCreating(ModelBuilder builder) in D:\Century\Internal\BlueSpot\BlueSpot\src\BlueSpot.EntityFrameworkCore\EntityFrameworkCore\BlueSpotDbContextBase.cs:line 35
       at BlueSpot.EntityFrameworkCore.BlueSpotDbContext.OnModelCreating(ModelBuilder builder) in D:\Century\Internal\BlueSpot\BlueSpot\src\BlueSpot.EntityFrameworkCore\EntityFrameworkCore\BlueSpotDbContext.cs:line 78
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
       at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
       at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
       at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
       at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
       at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
       at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
       at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
       at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
       at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
       at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
       at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.&lt;&gt;c__DisplayClass0_0.&lt;.ctor&gt;b__0()
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.&lt;&gt;c__DisplayClass3_0`1.<Execute>b__0()
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
    Exception has been thrown by the target of an invocation.
    

    Please advise. I hope you guys are safe out there. The earthquake seems terrible.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Sorry, please try this:

    protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
    {
        Expression<Func<TEntity, bool>> expression = null;
    
        if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
        {
            expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, "IsDeleted");
        }
    
        if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)) && typeof(TEntity) != typeof(OrganizationUnit))
        {
            Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, "TenantId") == CurrentTenantId;
            expression = expression == null ? multiTenantFilter : CombineExpressions(expression, multiTenantFilter);
        }
    
        return expression;
    }
    

    We are fine, thank you. : )

  • User Avatar
    0
    jlavallet created

    https://centurycorporation-my.sharepoint.com/:u:/p/jlavallet/EcMPpnAcCJZJjYWscoCsSlYBUukgDLOGlP4Slu0F46YU7g?e=lepKQV

    I've Included a link to the logs after incorporating that change. I'm getting a foreign key constraint violation with the database. Please see the BlueSpot.HttpApi.Host\logs.txt file. It appears to have some kind of conflict with the CMS module.

    Again, everything compiles fine. The migration was created just fine - although I didn't need it because there weren't any schema changes. The DbMigrator seemed to run fine - although I did see some red output lines when it ran, there was nothing in its log. The problem manifested itself when I tried to create a new tenant.

    Related With this problem, I'm also having a problem when I try to programmatically create a Tenet. I am trying to do this using the _tenantManager.CreateAsync(string name, Guid? editionId) method:

          if (!_currentTenant.IsAvailable &&
              await _tenantRepository.FindByNameAsync(TenantConsts.ShelbyCoTenantName) == null)
          {
            var editions = await _editionRepository.GetListAsync();
            var edition = editions.FirstOrDefault(edition => edition.DisplayName == "Standard");
            var tenant = await _tenantManager.CreateAsync(TenantConsts.ShelbyCoTenantName, edition.Id);
    
            //await _tenantRepository.UpdateAsync(tenant);
          }
    
          else if (_currentTenant.IsAvailable && _currentTenant.Name == TenantConsts.ShelbyCoTenantName)
          {
            var tenant = await _tenantRepository.FindByIdAsync(_currentTenant.Id.Value);
            var alState = await _stateRepository.GetAsync(state => state.Code == "AL");
    
            tenant.ExtraProperties[TenantConsts.AddressLine1PropertyName] = "P.O. Box 1095";
            tenant.ExtraProperties[TenantConsts.CityPropertyName] = "Columbiana";
            tenant.ExtraProperties[TenantConsts.StateIdPropertyName] = alState.Id;
            tenant.ExtraProperties[TenantConsts.ZipCodePropertyName] = "35051";
    
            await _tenantRepository.UpdateAsync(tenant);
          }
    

    The method completes but no Tenant is created. What's up with that? I am using Extra Properties for my Tenant and there is no way to pass in those properties using this method. The else if block above was one attempt I had made to update those properties after the tenant was created but that did not help. I also tried the following code to try and initialize the extra properties:

    public class TenantCreatedEventHandler : ILocalEventHandler<EntityCreatedEventData<Tenant>>, ITransientDependency
    {
      private readonly ICurrentTenant _currentTenant;
      private readonly IStateRepository _stateRepository;
      private readonly ITenantRepository _tenantRepository;
      private readonly IUserDataSeeder _userDataSeeder;
    
      public TenantCreatedEventHandler(ITenantRepository tenantRepository, IStateRepository stateRepository,
        ICurrentTenant currentTenant, IUserDataSeeder userDataSeeder)
      {
        _tenantRepository = tenantRepository;
        _stateRepository = stateRepository;
        _currentTenant = currentTenant;
        _userDataSeeder = userDataSeeder;
      }
    
      /// <inheritdoc />
      public async Task HandleEventAsync(EntityCreatedEventData<Tenant> eventData)
      {
        try
        {
          var tenant = eventData.Entity;
          if (tenant.Name == TenantConsts.ShelbyCoTenantName)
          {
            using (_currentTenant.Change(tenant.Id))
            {
              var alState = await _stateRepository.GetAsync(state => state.Code == "AL");
    
              tenant.ExtraProperties[TenantConsts.AddressLine1PropertyName] = "P.O. Box 1095";
              tenant.ExtraProperties[TenantConsts.CityPropertyName] = "Columbiana";
              tenant.ExtraProperties[TenantConsts.StateIdPropertyName] = alState.Id;
              tenant.ExtraProperties[TenantConsts.ZipCodePropertyName] = "35051";
    
              await _tenantRepository.UpdateAsync(tenant);
    
              await _userDataSeeder.CreateUsersAsync();
            }
          }
        }
        catch (Exception e)
        {
          Console.WriteLine(e);
          throw;
        }
      }
    }
    

    That code was not called after the programmatic creation attempt.

    One more thing As I am using the commercial version of the Identity and Sass modules, I would like access to the following .cshtml files:

    Pages\Tenets\CreateModal.cshtml
    Pages\Tenets\EditModal.cshtml
    

    I'm not sure about the namespace/Intermediate folders. I need to change some controls and styling. How do I get access to that? I tried getting the framework's TenantManagement files but I quickly determined that that was the framework and not the commercial module pages that I need.

    Thankful you guys are okay!

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I could not reproduce the problem, can you use a new project to reproduce the problem and share it with me? shiwei.liang@volosoft.com I will check it.

    One more thing As I am using the commercial version of the Identity and Sass modules, I would like access to the following .cshtml files:

    The team license does not include source code, I'm not sure I can share the code with you, I will ask ABP team.

    I'm not sure about the namespace/Intermediate folders

    You can see it in the request

  • User Avatar
    0
    jlavallet created

    Is it possible for me to get the cshtml files without the models, etc.?

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11