Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-11127] Write OrganizationInstallation record when license is retrieved #5090

27 changes: 26 additions & 1 deletion src/Api/Billing/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using Bit.Api.Models.Response;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Repositories;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Enums;
Expand Down Expand Up @@ -42,7 +44,8 @@
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand,
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand,
IReferenceEventService referenceEventService,
ISubscriberService subscriberService)
ISubscriberService subscriberService,
IOrganizationInstallationRepository organizationInstallationRepository)
: Controller
{
[HttpGet("{id:guid}/subscription")]
Expand Down Expand Up @@ -97,6 +100,8 @@
throw new NotFoundException();
}

await SaveOrganizationInstallationAsync(id, installationId);

Check warning on line 103 in src/Api/Billing/Controllers/OrganizationsController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Billing/Controllers/OrganizationsController.cs#L103

Added line #L103 was not covered by tests

return license;
}

Expand Down Expand Up @@ -366,4 +371,24 @@

return await organizationRepository.GetByIdAsync(id);
}

private async Task SaveOrganizationInstallationAsync(Guid organizationId, Guid installationId)
{
var organizationInstallation =
await organizationInstallationRepository.GetByInstallationIdAsync(installationId);

Check warning on line 378 in src/Api/Billing/Controllers/OrganizationsController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Billing/Controllers/OrganizationsController.cs#L376-L378

Added lines #L376 - L378 were not covered by tests

if (organizationInstallation == null)
{
await organizationInstallationRepository.CreateAsync(new OrganizationInstallation
{
OrganizationId = organizationId,
InstallationId = installationId
});
}

Check warning on line 387 in src/Api/Billing/Controllers/OrganizationsController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Billing/Controllers/OrganizationsController.cs#L381-L387

Added lines #L381 - L387 were not covered by tests
else if (organizationInstallation.OrganizationId == organizationId)
{
organizationInstallation.RevisionDate = DateTime.UtcNow;
await organizationInstallationRepository.ReplaceAsync(organizationInstallation);
}
}

Check warning on line 393 in src/Api/Billing/Controllers/OrganizationsController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Billing/Controllers/OrganizationsController.cs#L389-L393

Added lines #L389 - L393 were not covered by tests
}
24 changes: 24 additions & 0 deletions src/Core/Billing/Entities/OrganizationInstallation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
๏ปฟusing Bit.Core.Entities;
using Bit.Core.Utilities;

namespace Bit.Core.Billing.Entities;

#nullable enable

public class OrganizationInstallation : ITableObject<Guid>
{
public Guid Id { get; set; }

Check warning on line 10 in src/Core/Billing/Entities/OrganizationInstallation.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Entities/OrganizationInstallation.cs#L10

Added line #L10 was not covered by tests

public Guid OrganizationId { get; set; }
public Guid InstallationId { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime? RevisionDate { get; set; }

Check warning on line 15 in src/Core/Billing/Entities/OrganizationInstallation.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Entities/OrganizationInstallation.cs#L12-L15

Added lines #L12 - L15 were not covered by tests

public void SetNewId()
{

Check warning on line 18 in src/Core/Billing/Entities/OrganizationInstallation.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Entities/OrganizationInstallation.cs#L18

Added line #L18 was not covered by tests
if (Id == default)
{
Id = CoreHelpers.GenerateComb();
}
}

Check warning on line 23 in src/Core/Billing/Entities/OrganizationInstallation.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Entities/OrganizationInstallation.cs#L20-L23

Added lines #L20 - L23 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
๏ปฟusing Bit.Core.Billing.Entities;
using Bit.Core.Repositories;

namespace Bit.Core.Billing.Repositories;

public interface IOrganizationInstallationRepository : IRepository<OrganizationInstallation, Guid>
{
Task<OrganizationInstallation> GetByInstallationIdAsync(Guid installationId);
Task<ICollection<OrganizationInstallation>> GetByOrganizationIdAsync(Guid organizationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
๏ปฟusing System.Data;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Repositories;
using Bit.Core.Settings;
using Bit.Infrastructure.Dapper.Repositories;
using Dapper;
using Microsoft.Data.SqlClient;

namespace Bit.Infrastructure.Dapper.Billing.Repositories;

public class OrganizationInstallationRepository(
GlobalSettings globalSettings) : Repository<OrganizationInstallation, Guid>(
globalSettings.SqlServer.ConnectionString,
globalSettings.SqlServer.ReadOnlyConnectionString), IOrganizationInstallationRepository

Check warning on line 14 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L12-L14

Added lines #L12 - L14 were not covered by tests
{
public async Task<OrganizationInstallation> GetByInstallationIdAsync(Guid installationId)
{
var sqlConnection = new SqlConnection(ConnectionString);

Check warning on line 18 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L17-L18

Added lines #L17 - L18 were not covered by tests

var results = await sqlConnection.QueryAsync<OrganizationInstallation>(
"[dbo].[OrganizationInstallation_ReadByInstallationId]",
new { InstallationId = installationId },
commandType: CommandType.StoredProcedure);

Check warning on line 23 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L20-L23

Added lines #L20 - L23 were not covered by tests

return results.FirstOrDefault();
}

Check warning on line 26 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L25-L26

Added lines #L25 - L26 were not covered by tests

public async Task<ICollection<OrganizationInstallation>> GetByOrganizationIdAsync(Guid organizationId)
{
var sqlConnection = new SqlConnection(ConnectionString);

Check warning on line 30 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L29-L30

Added lines #L29 - L30 were not covered by tests

var results = await sqlConnection.QueryAsync<OrganizationInstallation>(
"[dbo].[OrganizationInstallation_ReadByOrganizationId]",
new { OrganizationId = organizationId },
commandType: CommandType.StoredProcedure);

Check warning on line 35 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L32-L35

Added lines #L32 - L35 were not covered by tests

return results.ToArray();
}

Check warning on line 38 in src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Billing/Repositories/OrganizationInstallationRepository.cs#L37-L38

Added lines #L37 - L38 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
services.AddSingleton<IPasswordHealthReportApplicationRepository, PasswordHealthReportApplicationRepository>();
services.AddSingleton<ISecurityTaskRepository, SecurityTaskRepository>();
services.AddSingleton<IUserAsymmetricKeysRepository, UserAsymmetricKeysRepository>();
services.AddSingleton<IOrganizationInstallationRepository, OrganizationInstallationRepository>();

Check warning on line 66 in src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs#L66

Added line #L66 was not covered by tests

if (selfHosted)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
๏ปฟusing Bit.Infrastructure.EntityFramework.Billing.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Bit.Infrastructure.EntityFramework.Billing.Configurations;

public class OrganizationInstallationEntityTypeConfiguration : IEntityTypeConfiguration<OrganizationInstallation>
{
public void Configure(EntityTypeBuilder<OrganizationInstallation> builder)
{
builder
.Property(oi => oi.Id)
.ValueGeneratedNever();

builder
.HasKey(oi => oi.Id)
.IsClustered();

builder
.HasIndex(oi => oi.OrganizationId)
.IsClustered(false);

builder
.HasIndex(oi => oi.InstallationId)
.IsClustered(false);

builder.ToTable(nameof(OrganizationInstallation));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
๏ปฟusing AutoMapper;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
using Bit.Infrastructure.EntityFramework.Models;

namespace Bit.Infrastructure.EntityFramework.Billing.Models;

public class OrganizationInstallation : Core.Billing.Entities.OrganizationInstallation
{
public virtual Installation Installation { get; set; }
public virtual Organization Organization { get; set; }

Check warning on line 10 in src/Infrastructure.EntityFramework/Billing/Models/OrganizationInstallation.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Models/OrganizationInstallation.cs#L9-L10

Added lines #L9 - L10 were not covered by tests
}

public class OrganizationInstallationMapperProfile : Profile
{
public OrganizationInstallationMapperProfile()
{
CreateMap<Core.Billing.Entities.OrganizationInstallation, OrganizationInstallation>().ReverseMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
๏ปฟusing AutoMapper;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using EFOrganizationInstallation = Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation;

namespace Bit.Infrastructure.EntityFramework.Billing.Repositories;

public class OrganizationInstallationRepository(
IMapper mapper,
IServiceScopeFactory serviceScopeFactory) : Repository<OrganizationInstallation, EFOrganizationInstallation, Guid>(
serviceScopeFactory,
mapper,
context => context.OrganizationInstallations), IOrganizationInstallationRepository

Check warning on line 16 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L13-L16

Added lines #L13 - L16 were not covered by tests
{
public async Task<OrganizationInstallation> GetByInstallationIdAsync(Guid installationId)
{
using var serviceScope = ServiceScopeFactory.CreateScope();

Check warning on line 20 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L19-L20

Added lines #L19 - L20 were not covered by tests

var databaseContext = GetDatabaseContext(serviceScope);

Check warning on line 22 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L22

Added line #L22 was not covered by tests

var query =
from organizationInstallation in databaseContext.OrganizationInstallations
where organizationInstallation.Id == installationId
select organizationInstallation;

Check warning on line 27 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L24-L27

Added lines #L24 - L27 were not covered by tests

return await query.FirstOrDefaultAsync();
}

Check warning on line 30 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L29-L30

Added lines #L29 - L30 were not covered by tests

public async Task<ICollection<OrganizationInstallation>> GetByOrganizationIdAsync(Guid organizationId)
{
using var serviceScope = ServiceScopeFactory.CreateScope();

Check warning on line 34 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L33-L34

Added lines #L33 - L34 were not covered by tests

var databaseContext = GetDatabaseContext(serviceScope);

Check warning on line 36 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L36

Added line #L36 was not covered by tests

var query =
from organizationInstallation in databaseContext.OrganizationInstallations
where organizationInstallation.OrganizationId == organizationId
select organizationInstallation;

Check warning on line 41 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L38-L41

Added lines #L38 - L41 were not covered by tests

return await query.ToArrayAsync();
}

Check warning on line 44 in src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Billing/Repositories/OrganizationInstallationRepository.cs#L43-L44

Added lines #L43 - L44 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public DatabaseContext(DbContextOptions<DatabaseContext> options)
public DbSet<ClientOrganizationMigrationRecord> ClientOrganizationMigrationRecords { get; set; }
public DbSet<PasswordHealthReportApplication> PasswordHealthReportApplications { get; set; }
public DbSet<SecurityTask> SecurityTasks { get; set; }
public DbSet<OrganizationInstallation> OrganizationInstallations { get; set; }

protected override void OnModelCreating(ModelBuilder builder)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
CREATE PROCEDURE [dbo].[OrganizationInstallation_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@OrganizationId UNIQUEIDENTIFIER,
@InstallationId UNIQUEIDENTIFIER,
@CreationDate DATETIME2 (7),
@RevisionDate DATETIME2 (7) = NULL
AS
BEGIN
SET NOCOUNT ON

INSERT INTO [dbo].[OrganizationInstallation]
(
[Id],
[OrganizationId],
[InstallationId],
[CreationDate],
[RevisionDate]
)
VALUES
(
@Id,
@OrganizationId,
@InstallationId,
@CreationDate,
@RevisionDate
)
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE PROCEDURE [dbo].[OrganizationInstallation_DeleteById]
@Id UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON

DELETE
FROM
[dbo].[OrganizationInstallation]
WHERE
[Id] = @Id
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadById]
@Id UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON

SELECT
*
FROM
[dbo].[OrganizationInstallationView]
WHERE
[Id] = @Id
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadByInstallationId]
@InstallationId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON

SELECT
*
FROM
[dbo].[OrganizationInstallationView]
WHERE
[InstallationId] = @InstallationId
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE PROCEDURE [dbo].[OrganizationInstallation_ReadByOrganizationId]
@OrganizationId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON

SELECT
*
FROM
[dbo].[OrganizationInstallationView]
WHERE
[OrganizationId] = @OrganizationId
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE PROCEDURE [dbo].[OrganizationInstallation_Update]
@Id UNIQUEIDENTIFIER OUTPUT,
@OrganizationId UNIQUEIDENTIFIER,
@InstallationId UNIQUEIDENTIFIER,
@CreationDate DATETIME2 (7),
@RevisionDate DATETIME2 (7)
AS
BEGIN
SET NOCOUNT ON

UPDATE
[dbo].[OrganizationInstallation]
SET
[RevisionDate] = @RevisionDate
WHERE
[Id] = @Id
END
18 changes: 18 additions & 0 deletions src/Sql/Billing/dbo/Tables/OrganizationInstallation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CREATE TABLE [dbo].[OrganizationInstallation] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
[InstallationId] UNIQUEIDENTIFIER NOT NULL,
[CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NULL,
CONSTRAINT [PK_OrganizationInstallation] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_OrganizationInstallation_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_OrganizationInstallation_Installation] FOREIGN KEY ([InstallationId]) REFERENCES [dbo].[Installation] ([Id]) ON DELETE CASCADE
);
GO

CREATE NONCLUSTERED INDEX [IX_OrganizationInstallation_OrganizationId]
ON [dbo].[OrganizationInstallation]([OrganizationId] ASC);
GO

CREATE NONCLUSTERED INDEX [IX_OrganizationInstallation_InstallationId]
ON [dbo].[OrganizationInstallation]([InstallationId] ASC);
6 changes: 6 additions & 0 deletions src/Sql/Billing/dbo/Views/OrganizationInstallationView.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE VIEW [dbo].[OrganizationInstallationView]
AS
SELECT
*
FROM
[dbo].[OrganizationInstallation];
Loading
Loading