Skip to content

Commit 33f5a19

Browse files
authoredApr 3, 2025··
[PM-17562] Add Dapper and EF Repositories For Ogranization Integrations and Configurations (#5589)
* [PM-17562] Add Dapper and EF Repositories For Ogranization Integrations and Configurations * Updated with changes from PR comments
1 parent 60e9827 commit 33f5a19

11 files changed

+315
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Text.Json.Nodes;
2+
using Bit.Core.Enums;
3+
4+
#nullable enable
5+
6+
namespace Bit.Core.Models.Data.Organizations;
7+
8+
public class OrganizationIntegrationConfigurationDetails
9+
{
10+
public Guid Id { get; set; }
11+
public Guid OrganizationIntegrationId { get; set; }
12+
public IntegrationType IntegrationType { get; set; }
13+
public EventType EventType { get; set; }
14+
public string? Configuration { get; set; }
15+
public string? IntegrationConfiguration { get; set; }
16+
public string? Template { get; set; }
17+
18+
public JsonObject MergedConfiguration
19+
{
20+
get
21+
{
22+
var integrationJson = IntegrationConfigurationJson;
23+
24+
foreach (var kvp in ConfigurationJson)
25+
{
26+
integrationJson[kvp.Key] = kvp.Value?.DeepClone();
27+
}
28+
29+
return integrationJson;
30+
}
31+
}
32+
33+
private JsonObject ConfigurationJson
34+
{
35+
get
36+
{
37+
try
38+
{
39+
var configuration = Configuration ?? string.Empty;
40+
return JsonNode.Parse(configuration) as JsonObject ?? new JsonObject();
41+
}
42+
catch
43+
{
44+
return new JsonObject();
45+
}
46+
}
47+
}
48+
49+
private JsonObject IntegrationConfigurationJson
50+
{
51+
get
52+
{
53+
try
54+
{
55+
var integration = IntegrationConfiguration ?? string.Empty;
56+
return JsonNode.Parse(integration) as JsonObject ?? new JsonObject();
57+
}
58+
catch
59+
{
60+
return new JsonObject();
61+
}
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
using Bit.Core.Enums;
3+
using Bit.Core.Models.Data.Organizations;
4+
5+
namespace Bit.Core.Repositories;
6+
7+
public interface IOrganizationIntegrationConfigurationRepository : IRepository<OrganizationIntegrationConfiguration, Guid>
8+
{
9+
Task<List<OrganizationIntegrationConfigurationDetails>> GetConfigurationDetailsAsync(
10+
Guid organizationId,
11+
IntegrationType integrationType,
12+
EventType eventType);
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
3+
namespace Bit.Core.Repositories;
4+
5+
public interface IOrganizationIntegrationRepository : IRepository<OrganizationIntegration, Guid>
6+
{
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Data;
2+
using Bit.Core.AdminConsole.Entities;
3+
using Bit.Core.Enums;
4+
using Bit.Core.Models.Data.Organizations;
5+
using Bit.Core.Repositories;
6+
using Bit.Core.Settings;
7+
using Bit.Infrastructure.Dapper.Repositories;
8+
using Dapper;
9+
using Microsoft.Data.SqlClient;
10+
11+
namespace Bit.Infrastructure.Dapper.AdminConsole.Repositories;
12+
13+
public class OrganizationIntegrationConfigurationRepository : Repository<OrganizationIntegrationConfiguration, Guid>, IOrganizationIntegrationConfigurationRepository
14+
{
15+
public OrganizationIntegrationConfigurationRepository(GlobalSettings globalSettings)
16+
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
17+
{ }
18+
19+
public OrganizationIntegrationConfigurationRepository(string connectionString, string readOnlyConnectionString)
20+
: base(connectionString, readOnlyConnectionString)
21+
{ }
22+
23+
public async Task<List<OrganizationIntegrationConfigurationDetails>> GetConfigurationDetailsAsync(
24+
Guid organizationId,
25+
IntegrationType integrationType,
26+
EventType eventType)
27+
{
28+
using (var connection = new SqlConnection(ConnectionString))
29+
{
30+
var results = await connection.QueryAsync<OrganizationIntegrationConfigurationDetails>(
31+
"[dbo].[OrganizationIntegrationConfigurationDetails_ReadManyByEventTypeOrganizationIdIntegrationType]",
32+
new
33+
{
34+
EventType = eventType,
35+
OrganizationId = organizationId,
36+
IntegrationType = integrationType
37+
},
38+
commandType: CommandType.StoredProcedure);
39+
40+
return results.ToList();
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
using Bit.Core.Repositories;
3+
using Bit.Core.Settings;
4+
5+
namespace Bit.Infrastructure.Dapper.Repositories;
6+
7+
public class OrganizationIntegrationRepository : Repository<OrganizationIntegration, Guid>, IOrganizationIntegrationRepository
8+
{
9+
public OrganizationIntegrationRepository(GlobalSettings globalSettings)
10+
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
11+
{ }
12+
13+
public OrganizationIntegrationRepository(string connectionString, string readOnlyConnectionString)
14+
: base(connectionString, readOnlyConnectionString)
15+
{ }
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using AutoMapper;
2+
using Bit.Core.Enums;
3+
using Bit.Core.Models.Data.Organizations;
4+
using Bit.Core.Repositories;
5+
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
6+
using Bit.Infrastructure.EntityFramework.Repositories;
7+
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
8+
using Microsoft.EntityFrameworkCore;
9+
using Microsoft.Extensions.DependencyInjection;
10+
11+
namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories;
12+
13+
public class OrganizationIntegrationConfigurationRepository : Repository<Core.AdminConsole.Entities.OrganizationIntegrationConfiguration, OrganizationIntegrationConfiguration, Guid>, IOrganizationIntegrationConfigurationRepository
14+
{
15+
public OrganizationIntegrationConfigurationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
16+
: base(serviceScopeFactory, mapper, context => context.OrganizationIntegrationConfigurations)
17+
{ }
18+
19+
public async Task<List<OrganizationIntegrationConfigurationDetails>> GetConfigurationDetailsAsync(
20+
Guid organizationId,
21+
IntegrationType integrationType,
22+
EventType eventType)
23+
{
24+
using (var scope = ServiceScopeFactory.CreateScope())
25+
{
26+
var dbContext = GetDatabaseContext(scope);
27+
var query = new OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery(
28+
organizationId, eventType, integrationType
29+
);
30+
return await query.Run(dbContext).ToListAsync();
31+
}
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using AutoMapper;
2+
using Bit.Core.Repositories;
3+
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
4+
using Bit.Infrastructure.EntityFramework.Repositories;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories;
8+
9+
public class OrganizationIntegrationRepository : Repository<Core.AdminConsole.Entities.OrganizationIntegration, OrganizationIntegration, Guid>, IOrganizationIntegrationRepository
10+
{
11+
public OrganizationIntegrationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
12+
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationIntegrations)
13+
{ }
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Bit.Core.Enums;
2+
using Bit.Core.Models.Data.Organizations;
3+
4+
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
5+
6+
public class OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery : IQuery<OrganizationIntegrationConfigurationDetails>
7+
{
8+
private readonly Guid _organizationId;
9+
private readonly EventType _eventType;
10+
private readonly IntegrationType _integrationType;
11+
12+
public OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery(Guid organizationId, EventType eventType, IntegrationType integrationType)
13+
{
14+
_organizationId = organizationId;
15+
_eventType = eventType;
16+
_integrationType = integrationType;
17+
}
18+
19+
public IQueryable<OrganizationIntegrationConfigurationDetails> Run(DatabaseContext dbContext)
20+
{
21+
var query = from oic in dbContext.OrganizationIntegrationConfigurations
22+
join oi in dbContext.OrganizationIntegrations on oic.OrganizationIntegrationId equals oi.Id into oioic
23+
from oi in dbContext.OrganizationIntegrations
24+
where oi.OrganizationId == _organizationId &&
25+
oi.Type == _integrationType &&
26+
oic.EventType == _eventType
27+
select new OrganizationIntegrationConfigurationDetails()
28+
{
29+
Id = oic.Id,
30+
OrganizationIntegrationId = oic.OrganizationIntegrationId,
31+
IntegrationType = oi.Type,
32+
EventType = oic.EventType,
33+
Configuration = oic.Configuration,
34+
IntegrationConfiguration = oi.Configuration,
35+
Template = oic.Template
36+
};
37+
return query;
38+
}
39+
}

‎src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ public static void AddPasswordManagerEFRepositories(this IServiceCollection serv
7878
services.AddSingleton<IMaintenanceRepository, MaintenanceRepository>();
7979
services.AddSingleton<IOrganizationApiKeyRepository, OrganizationApiKeyRepository>();
8080
services.AddSingleton<IOrganizationConnectionRepository, OrganizationConnectionRepository>();
81+
services.AddSingleton<IOrganizationIntegrationRepository, OrganizationIntegrationRepository>();
82+
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, OrganizationIntegrationConfigurationRepository>();
8183
services.AddSingleton<IOrganizationRepository, OrganizationRepository>();
8284
services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>();
8385
services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>();

‎src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public DatabaseContext(DbContextOptions<DatabaseContext> options)
5555
public DbSet<OrganizationApiKey> OrganizationApiKeys { get; set; }
5656
public DbSet<OrganizationSponsorship> OrganizationSponsorships { get; set; }
5757
public DbSet<OrganizationConnection> OrganizationConnections { get; set; }
58+
public DbSet<OrganizationIntegration> OrganizationIntegrations { get; set; }
59+
public DbSet<OrganizationIntegrationConfiguration> OrganizationIntegrationConfigurations { get; set; }
5860
public DbSet<OrganizationUser> OrganizationUsers { get; set; }
5961
public DbSet<Policy> Policies { get; set; }
6062
public DbSet<Provider> Providers { get; set; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Text.Json;
2+
using Bit.Core.Models.Data.Organizations;
3+
using Xunit;
4+
5+
namespace Bit.Core.Test.Models.Data.Organizations;
6+
7+
public class OrganizationIntegrationConfigurationDetailsTests
8+
{
9+
[Fact]
10+
public void MergedConfiguration_WithValidConfigAndIntegration_ReturnsMergedJson()
11+
{
12+
var config = new { config = "A new config value" };
13+
var integration = new { integration = "An integration value" };
14+
var expectedObj = new { integration = "An integration value", config = "A new config value" };
15+
var expected = JsonSerializer.Serialize(expectedObj);
16+
17+
var sut = new OrganizationIntegrationConfigurationDetails();
18+
sut.Configuration = JsonSerializer.Serialize(config);
19+
sut.IntegrationConfiguration = JsonSerializer.Serialize(integration);
20+
21+
var result = sut.MergedConfiguration;
22+
Assert.Equal(expected, result.ToJsonString());
23+
}
24+
25+
[Fact]
26+
public void MergedConfiguration_WithInvalidJsonConfigAndIntegration_ReturnsEmptyJson()
27+
{
28+
var expectedObj = new { };
29+
var expected = JsonSerializer.Serialize(expectedObj);
30+
31+
var sut = new OrganizationIntegrationConfigurationDetails();
32+
sut.Configuration = "Not JSON";
33+
sut.IntegrationConfiguration = "Not JSON";
34+
35+
var result = sut.MergedConfiguration;
36+
Assert.Equal(expected, result.ToJsonString());
37+
}
38+
39+
[Fact]
40+
public void MergedConfiguration_WithNullConfigAndIntegration_ReturnsEmptyJson()
41+
{
42+
var expectedObj = new { };
43+
var expected = JsonSerializer.Serialize(expectedObj);
44+
45+
var sut = new OrganizationIntegrationConfigurationDetails();
46+
sut.Configuration = null;
47+
sut.IntegrationConfiguration = null;
48+
49+
var result = sut.MergedConfiguration;
50+
Assert.Equal(expected, result.ToJsonString());
51+
}
52+
53+
[Fact]
54+
public void MergedConfiguration_WithValidIntegrationAndNullConfig_ReturnsIntegrationJson()
55+
{
56+
var integration = new { integration = "An integration value" };
57+
var expectedObj = new { integration = "An integration value" };
58+
var expected = JsonSerializer.Serialize(expectedObj);
59+
60+
var sut = new OrganizationIntegrationConfigurationDetails();
61+
sut.Configuration = null;
62+
sut.IntegrationConfiguration = JsonSerializer.Serialize(integration);
63+
64+
var result = sut.MergedConfiguration;
65+
Assert.Equal(expected, result.ToJsonString());
66+
}
67+
68+
[Fact]
69+
public void MergedConfiguration_WithValidConfigAndNullIntegration_ReturnsConfigJson()
70+
{
71+
var config = new { config = "A new config value" };
72+
var expectedObj = new { config = "A new config value" };
73+
var expected = JsonSerializer.Serialize(expectedObj);
74+
75+
var sut = new OrganizationIntegrationConfigurationDetails();
76+
sut.Configuration = JsonSerializer.Serialize(config);
77+
sut.IntegrationConfiguration = null;
78+
79+
var result = sut.MergedConfiguration;
80+
Assert.Equal(expected, result.ToJsonString());
81+
}
82+
}

0 commit comments

Comments
 (0)
Please sign in to comment.