mirror of
https://github.com/bitwarden/server
synced 2026-01-10 20:44:05 +00:00
Rename PlayData -> PlayItem
This is still a join table, but the Data suffix was colliding with the concept of a JSON transfer model. The PlayItem name is designed to indicate that these records are not Play entities, but indications that a given Item (user or organization for now) is associated with a given Play
This commit is contained in:
@@ -5,11 +5,11 @@ using Bit.Core.Utilities;
|
||||
namespace Bit.Core.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// PlayData is a join table tracking entities created during automated testing.
|
||||
/// PlayItem is a join table tracking entities created during automated testing.
|
||||
/// A `PlayId` is supplied by the clients in the `x-play-id` header to inform the server
|
||||
/// that any data created should be associated with the play, and therefore cleaned up with it.
|
||||
/// </summary>
|
||||
public class PlayData : ITableObject<Guid>
|
||||
public class PlayItem : ITableObject<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(256)]
|
||||
@@ -27,14 +27,14 @@ public class PlayData : ITableObject<Guid>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PlayData record associated with a User.
|
||||
/// Creates a new PlayItem record associated with a User.
|
||||
/// </summary>
|
||||
/// <param name="user">The user entity created during the play.</param>
|
||||
/// <param name="playId">The play identifier from the x-play-id header.</param>
|
||||
/// <returns>A new PlayData instance tracking the user.</returns>
|
||||
public static PlayData Create(User user, string playId)
|
||||
/// <returns>A new PlayItem instance tracking the user.</returns>
|
||||
public static PlayItem Create(User user, string playId)
|
||||
{
|
||||
return new PlayData
|
||||
return new PlayItem
|
||||
{
|
||||
PlayId = playId,
|
||||
UserId = user.Id,
|
||||
@@ -43,14 +43,14 @@ public class PlayData : ITableObject<Guid>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PlayData record associated with an Organization.
|
||||
/// Creates a new PlayItem record associated with an Organization.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization entity created during the play.</param>
|
||||
/// <param name="playId">The play identifier from the x-play-id header.</param>
|
||||
/// <returns>A new PlayData instance tracking the organization.</returns>
|
||||
public static PlayData Create(Organization organization, string playId)
|
||||
/// <returns>A new PlayItem instance tracking the organization.</returns>
|
||||
public static PlayItem Create(Organization organization, string playId)
|
||||
{
|
||||
return new PlayData
|
||||
return new PlayItem
|
||||
{
|
||||
PlayId = playId,
|
||||
OrganizationId = organization.Id,
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
namespace Bit.Core.Repositories;
|
||||
|
||||
public interface IPlayDataRepository : IRepository<PlayData, Guid>
|
||||
public interface IPlayItemRepository : IRepository<PlayItem, Guid>
|
||||
{
|
||||
Task<ICollection<PlayData>> GetByPlayIdAsync(string playId);
|
||||
Task<ICollection<PlayItem>> GetByPlayIdAsync(string playId);
|
||||
Task DeleteByPlayIdAsync(string playId);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
/// Service for managing Play identifiers in automated testing infrastructure.
|
||||
/// A "Play" is a test session that groups entities created during testing to enable cleanup.
|
||||
/// The PlayId flows from client request (x-play-id header) through PlayIdMiddleware to this service,
|
||||
/// which repositories query to create PlayData tracking records via IPlayDataService. The SeederAPI uses these records
|
||||
/// which repositories query to create PlayItem tracking records via IPlayItemService. The SeederAPI uses these records
|
||||
/// to bulk delete all entities associated with a PlayId. Only active in Development environments.
|
||||
/// </summary>
|
||||
public interface IPlayIdService
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace Bit.Core.Services;
|
||||
/// <summary>
|
||||
/// Service used to track added users and organizations during a Play session.
|
||||
/// </summary>
|
||||
public interface IPlayDataService
|
||||
public interface IPlayItemService
|
||||
{
|
||||
/// <summary>
|
||||
/// Records a PlayData entry for the given User created during a Play session.
|
||||
/// Records a PlayItem entry for the given User created during a Play session.
|
||||
///
|
||||
/// Does nothing if no Play Id is set for this http scope.
|
||||
/// </summary>
|
||||
@@ -17,7 +17,7 @@ public interface IPlayDataService
|
||||
/// <returns></returns>
|
||||
Task Record(User user);
|
||||
/// <summary>
|
||||
/// Records a PlayData entry for the given Organization created during a Play session.
|
||||
/// Records a PlayItem entry for the given Organization created during a Play session.
|
||||
///
|
||||
/// Does nothing if no Play Id is set for this http scope.
|
||||
/// </summary>
|
||||
@@ -5,14 +5,14 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class PlayDataService(IPlayIdService playIdService, IPlayDataRepository playDataRepository, ILogger<PlayDataService> logger) : IPlayDataService
|
||||
public class PlayItemService(IPlayIdService playIdService, IPlayItemRepository playItemRepository, ILogger<PlayItemService> logger) : IPlayItemService
|
||||
{
|
||||
public async Task Record(User user)
|
||||
{
|
||||
if (playIdService.InPlay(out var playId))
|
||||
{
|
||||
logger.LogInformation("Associating user {UserId} with Play ID {PlayId}", user.Id, playId);
|
||||
await playDataRepository.CreateAsync(PlayData.Create(user, playId));
|
||||
await playItemRepository.CreateAsync(PlayItem.Create(user, playId));
|
||||
}
|
||||
}
|
||||
public async Task Record(Organization organization)
|
||||
@@ -20,7 +20,7 @@ public class PlayDataService(IPlayIdService playIdService, IPlayDataRepository p
|
||||
if (playIdService.InPlay(out var playId))
|
||||
{
|
||||
logger.LogInformation("Associating organization {OrganizationId} with Play ID {PlayId}", organization.Id, playId);
|
||||
await playDataRepository.CreateAsync(PlayData.Create(organization, playId));
|
||||
await playItemRepository.CreateAsync(PlayItem.Create(organization, playId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,17 @@ enable bulk cleanup via the SeederAPI.
|
||||
1. Test client sends `x-play-id` header with a unique Play identifier
|
||||
2. `PlayIdMiddleware` extracts the header and sets it on `IPlayIdService`
|
||||
3. Repositories check `IPlayIdService.InPlay()` when creating entities
|
||||
4. `IPlayDataService` records PlayData entries for tracked entities
|
||||
5. SeederAPI uses PlayData records to bulk delete all entities associated with a PlayId
|
||||
4. `IPlayItemService` records PlayItem entries for tracked entities
|
||||
5. SeederAPI uses PlayItem records to bulk delete all entities associated with a PlayId
|
||||
|
||||
Play services are **only active in Development environments**.
|
||||
|
||||
## Classes
|
||||
|
||||
- **`IPlayIdService`** - Interface for managing Play identifiers in the current request scope
|
||||
- **`IPlayDataService`** - Interface for tracking entities created during a Play session
|
||||
- **`IPlayItemService`** - Interface for tracking entities created during a Play session
|
||||
- **`PlayIdService`** - Default scoped implementation for tracking Play sessions per HTTP request
|
||||
- **`NeverPlayIdServices`** - No-op implementation used as fallback when no HttpContext is available
|
||||
- **`PlayIdSingletonService`** - Singleton wrapper that allows singleton services to access scoped PlayIdService via
|
||||
HttpContext
|
||||
- **`PlayDataService`** - Implementation that records PlayData entries for entities created during Play sessions
|
||||
HttpContext
|
||||
- **`PlayItemService`** - Implementation that records PlayItem entries for entities created during Play sessions
|
||||
|
||||
@@ -51,7 +51,7 @@ public static class DapperServiceCollectionExtensions
|
||||
services.AddSingleton<IOrganizationRepository, OrganizationRepository>();
|
||||
services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>();
|
||||
services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>();
|
||||
services.AddSingleton<IPlayDataRepository, PlayDataRepository>();
|
||||
services.AddSingleton<IPlayItemRepository, PlayItemRepository>();
|
||||
services.AddSingleton<IPolicyRepository, PolicyRepository>();
|
||||
services.AddSingleton<IProviderOrganizationRepository, ProviderOrganizationRepository>();
|
||||
services.AddSingleton<IProviderRepository, ProviderRepository>();
|
||||
|
||||
@@ -9,22 +9,22 @@ using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Bit.Infrastructure.Dapper.Repositories;
|
||||
|
||||
public class PlayDataRepository : Repository<PlayData, Guid>, IPlayDataRepository
|
||||
public class PlayItemRepository : Repository<PlayItem, Guid>, IPlayItemRepository
|
||||
{
|
||||
public PlayDataRepository(GlobalSettings globalSettings)
|
||||
public PlayItemRepository(GlobalSettings globalSettings)
|
||||
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
|
||||
{ }
|
||||
|
||||
public PlayDataRepository(string connectionString, string readOnlyConnectionString)
|
||||
public PlayItemRepository(string connectionString, string readOnlyConnectionString)
|
||||
: base(connectionString, readOnlyConnectionString)
|
||||
{ }
|
||||
|
||||
public async Task<ICollection<PlayData>> GetByPlayIdAsync(string playId)
|
||||
public async Task<ICollection<PlayItem>> GetByPlayIdAsync(string playId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<PlayData>(
|
||||
"[dbo].[PlayData_ReadByPlayId]",
|
||||
var results = await connection.QueryAsync<PlayItem>(
|
||||
"[dbo].[PlayItem_ReadByPlayId]",
|
||||
new { PlayId = playId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
@@ -37,7 +37,7 @@ public class PlayDataRepository : Repository<PlayData, Guid>, IPlayDataRepositor
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"[dbo].[PlayData_DeleteByPlayId]",
|
||||
"[dbo].[PlayItem_DeleteByPlayId]",
|
||||
new { PlayId = playId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
@@ -4,9 +4,9 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Configurations;
|
||||
|
||||
public class PlayDataEntityTypeConfiguration : IEntityTypeConfiguration<PlayData>
|
||||
public class PlayItemEntityTypeConfiguration : IEntityTypeConfiguration<PlayItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlayData> builder)
|
||||
public void Configure(EntityTypeBuilder<PlayItem> builder)
|
||||
{
|
||||
builder
|
||||
.Property(pd => pd.Id)
|
||||
@@ -37,9 +37,9 @@ public class PlayDataEntityTypeConfiguration : IEntityTypeConfiguration<PlayData
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.ToTable(nameof(PlayData))
|
||||
.ToTable(nameof(PlayItem))
|
||||
.HasCheckConstraint(
|
||||
"CK_PlayData_UserOrOrganization",
|
||||
"CK_PlayItem_UserOrOrganization",
|
||||
"(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"
|
||||
);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public static class EntityFrameworkServiceCollectionExtensions
|
||||
services.AddSingleton<IOrganizationRepository, OrganizationRepository>();
|
||||
services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>();
|
||||
services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>();
|
||||
services.AddSingleton<IPlayDataRepository, PlayDataRepository>();
|
||||
services.AddSingleton<IPlayItemRepository, PlayItemRepository>();
|
||||
services.AddSingleton<IPolicyRepository, PolicyRepository>();
|
||||
services.AddSingleton<IProviderOrganizationRepository, ProviderOrganizationRepository>();
|
||||
services.AddSingleton<IProviderRepository, ProviderRepository>();
|
||||
|
||||
@@ -4,16 +4,16 @@ using AutoMapper;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Models;
|
||||
|
||||
public class PlayData : Core.Entities.PlayData
|
||||
public class PlayItem : Core.Entities.PlayItem
|
||||
{
|
||||
public virtual User? User { get; set; }
|
||||
public virtual AdminConsole.Models.Organization? Organization { get; set; }
|
||||
}
|
||||
|
||||
public class PlayDataMapperProfile : Profile
|
||||
public class PlayItemMapperProfile : Profile
|
||||
{
|
||||
public PlayDataMapperProfile()
|
||||
public PlayItemMapperProfile()
|
||||
{
|
||||
CreateMap<Core.Entities.PlayData, PlayData>().ReverseMap();
|
||||
CreateMap<Core.Entities.PlayItem, PlayItem>().ReverseMap();
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class DatabaseContext : DbContext
|
||||
public DbSet<OrganizationApiKey> OrganizationApiKeys { get; set; }
|
||||
public DbSet<OrganizationSponsorship> OrganizationSponsorships { get; set; }
|
||||
public DbSet<OrganizationConnection> OrganizationConnections { get; set; }
|
||||
public DbSet<PlayData> PlayData { get; set; }
|
||||
public DbSet<PlayItem> PlayItem { get; set; }
|
||||
public DbSet<OrganizationIntegration> OrganizationIntegrations { get; set; }
|
||||
public DbSet<OrganizationIntegrationConfiguration> OrganizationIntegrationConfigurations { get; set; }
|
||||
public DbSet<OrganizationUser> OrganizationUsers { get; set; }
|
||||
|
||||
@@ -8,21 +8,21 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Repositories;
|
||||
|
||||
public class PlayDataRepository : Repository<Core.Entities.PlayData, PlayData, Guid>, IPlayDataRepository
|
||||
public class PlayItemRepository : Repository<Core.Entities.PlayItem, PlayItem, Guid>, IPlayItemRepository
|
||||
{
|
||||
public PlayDataRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
|
||||
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.PlayData)
|
||||
public PlayItemRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
|
||||
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.PlayItem)
|
||||
{ }
|
||||
|
||||
public async Task<ICollection<Core.Entities.PlayData>> GetByPlayIdAsync(string playId)
|
||||
public async Task<ICollection<Core.Entities.PlayItem>> GetByPlayIdAsync(string playId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var playDataEntities = await GetDbSet(dbContext)
|
||||
var playItemEntities = await GetDbSet(dbContext)
|
||||
.Where(pd => pd.PlayId == playId)
|
||||
.ToListAsync();
|
||||
return Mapper.Map<List<Core.Entities.PlayData>>(playDataEntities);
|
||||
return Mapper.Map<List<Core.Entities.PlayItem>>(playItemEntities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class PlayDataRepository : Repository<Core.Entities.PlayData, PlayData, G
|
||||
.Where(pd => pd.PlayId == playId)
|
||||
.ToListAsync();
|
||||
|
||||
dbContext.PlayData.RemoveRange(entities);
|
||||
dbContext.PlayItem.RemoveRange(entities);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,21 @@ namespace Bit.SharedWeb.Play.Repositories;
|
||||
/// </summary>
|
||||
public class DapperTestOrganizationTrackingOrganizationRepository : OrganizationRepository
|
||||
{
|
||||
private readonly IPlayDataService _playDataService;
|
||||
private readonly IPlayItemService _playItemService;
|
||||
|
||||
public DapperTestOrganizationTrackingOrganizationRepository(
|
||||
IPlayDataService playDataService,
|
||||
IPlayItemService playItemService,
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<OrganizationRepository> logger)
|
||||
: base(globalSettings, logger)
|
||||
{
|
||||
_playDataService = playDataService;
|
||||
_playItemService = playItemService;
|
||||
}
|
||||
|
||||
public override async Task<Organization> CreateAsync(Organization obj)
|
||||
{
|
||||
var createdOrganization = await base.CreateAsync(obj);
|
||||
await _playDataService.Record(createdOrganization);
|
||||
await _playItemService.Record(createdOrganization);
|
||||
return createdOrganization;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,22 @@ namespace Bit.SharedWeb.Play.Repositories;
|
||||
/// </summary>
|
||||
public class DapperTestUserTrackingUserRepository : UserRepository
|
||||
{
|
||||
private readonly IPlayDataService _playDataService;
|
||||
private readonly IPlayItemService _playItemService;
|
||||
|
||||
public DapperTestUserTrackingUserRepository(
|
||||
IPlayDataService playDataService,
|
||||
IPlayItemService playItemService,
|
||||
GlobalSettings globalSettings,
|
||||
IDataProtectionProvider dataProtectionProvider)
|
||||
: base(globalSettings, dataProtectionProvider)
|
||||
{
|
||||
_playDataService = playDataService;
|
||||
_playItemService = playItemService;
|
||||
}
|
||||
|
||||
public override async Task<User> CreateAsync(User user)
|
||||
{
|
||||
var createdUser = await base.CreateAsync(user);
|
||||
|
||||
await _playDataService.Record(createdUser);
|
||||
await _playItemService.Record(createdUser);
|
||||
return createdUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,22 @@ namespace Bit.SharedWeb.Play.Repositories;
|
||||
/// </summary>
|
||||
public class EFTestOrganizationTrackingOrganizationRepository : OrganizationRepository
|
||||
{
|
||||
private readonly IPlayDataService _playDataService;
|
||||
private readonly IPlayItemService _playItemService;
|
||||
|
||||
public EFTestOrganizationTrackingOrganizationRepository(
|
||||
IPlayDataService playDataService,
|
||||
IPlayItemService playItemService,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IMapper mapper,
|
||||
ILogger<OrganizationRepository> logger)
|
||||
: base(serviceScopeFactory, mapper, logger)
|
||||
{
|
||||
_playDataService = playDataService;
|
||||
_playItemService = playItemService;
|
||||
}
|
||||
|
||||
public override async Task<Core.AdminConsole.Entities.Organization> CreateAsync(Core.AdminConsole.Entities.Organization organization)
|
||||
{
|
||||
var createdOrganization = await base.CreateAsync(organization);
|
||||
await _playDataService.Record(createdOrganization);
|
||||
await _playItemService.Record(createdOrganization);
|
||||
return createdOrganization;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@ namespace Bit.SharedWeb.Play.Repositories;
|
||||
/// </summary>
|
||||
public class EFTestUserTrackingUserRepository : UserRepository
|
||||
{
|
||||
private readonly IPlayDataService _playDataService;
|
||||
private readonly IPlayItemService _playItemService;
|
||||
|
||||
public EFTestUserTrackingUserRepository(
|
||||
IPlayDataService playDataService,
|
||||
IPlayItemService playItemService,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IMapper mapper)
|
||||
: base(serviceScopeFactory, mapper)
|
||||
{
|
||||
_playDataService = playDataService;
|
||||
_playItemService = playItemService;
|
||||
}
|
||||
|
||||
public override async Task<Core.Entities.User> CreateAsync(Core.Entities.User user)
|
||||
{
|
||||
var createdUser = await base.CreateAsync(user);
|
||||
await _playDataService.Record(createdUser);
|
||||
await _playItemService.Record(createdUser);
|
||||
return createdUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public static class ServiceCollectionExtensions
|
||||
// We need the http context accessor to use the Singleton version, which pulls from the scoped version
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddSingleton<IPlayDataService, PlayDataService>();
|
||||
services.AddSingleton<IPlayItemService, PlayItemService>();
|
||||
services.AddSingleton<IPlayIdService, PlayIdSingletonService>();
|
||||
services.AddScoped<PlayIdService>();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
CREATE PROCEDURE [dbo].[PlayData_Create]
|
||||
CREATE PROCEDURE [dbo].[PlayItem_Create]
|
||||
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||
@PlayId NVARCHAR(256),
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@@ -8,7 +8,7 @@ AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
INSERT INTO [dbo].[PlayData]
|
||||
INSERT INTO [dbo].[PlayItem]
|
||||
(
|
||||
[Id],
|
||||
[PlayId],
|
||||
@@ -1,4 +1,4 @@
|
||||
CREATE PROCEDURE [dbo].[PlayData_DeleteByPlayId]
|
||||
CREATE PROCEDURE [dbo].[PlayItem_DeleteByPlayId]
|
||||
@PlayId NVARCHAR(256)
|
||||
AS
|
||||
BEGIN
|
||||
@@ -6,7 +6,7 @@ BEGIN
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[PlayData]
|
||||
[dbo].[PlayItem]
|
||||
WHERE
|
||||
[PlayId] = @PlayId
|
||||
END
|
||||
@@ -1,4 +1,4 @@
|
||||
CREATE PROCEDURE [dbo].[PlayData_ReadByPlayId]
|
||||
CREATE PROCEDURE [dbo].[PlayItem_ReadByPlayId]
|
||||
@PlayId NVARCHAR(256)
|
||||
AS
|
||||
BEGIN
|
||||
@@ -11,7 +11,7 @@ BEGIN
|
||||
[OrganizationId],
|
||||
[CreationDate]
|
||||
FROM
|
||||
[dbo].[PlayData]
|
||||
[dbo].[PlayItem]
|
||||
WHERE
|
||||
[PlayId] = @PlayId
|
||||
END
|
||||
@@ -1,23 +0,0 @@
|
||||
CREATE TABLE [dbo].[PlayData] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[PlayId] NVARCHAR (256) NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
CONSTRAINT [PK_PlayData] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_PlayData_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PlayData_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [CK_PlayData_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL))
|
||||
);
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayData_PlayId]
|
||||
ON [dbo].[PlayData]([PlayId] ASC);
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayData_UserId]
|
||||
ON [dbo].[PlayData]([UserId] ASC);
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayData_OrganizationId]
|
||||
ON [dbo].[PlayData]([OrganizationId] ASC);
|
||||
23
src/Sql/dbo/Tables/PlayItem.sql
Normal file
23
src/Sql/dbo/Tables/PlayItem.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE [dbo].[PlayItem] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[PlayId] NVARCHAR (256) NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
CONSTRAINT [PK_PlayItem] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_PlayItem_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PlayItem_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [CK_PlayItem_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL))
|
||||
);
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayItem_PlayId]
|
||||
ON [dbo].[PlayItem]([PlayId] ASC);
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayItem_UserId]
|
||||
ON [dbo].[PlayItem]([UserId] ASC);
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayItem_OrganizationId]
|
||||
ON [dbo].[PlayItem]([OrganizationId] ASC);
|
||||
@@ -11,14 +11,14 @@ using Xunit;
|
||||
namespace Bit.Core.Test.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class PlayDataServiceTests
|
||||
public class PlayItemServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Record_User_WhenInPlay_RecordsPlayData(
|
||||
public async Task Record_User_WhenInPlay_RecordsPlayItem(
|
||||
string playId,
|
||||
User user,
|
||||
SutProvider<PlayDataService> sutProvider)
|
||||
SutProvider<PlayItemService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IPlayIdService>()
|
||||
.InPlay(out Arg.Any<string>())
|
||||
@@ -30,14 +30,14 @@ public class PlayDataServiceTests
|
||||
|
||||
await sutProvider.Sut.Record(user);
|
||||
|
||||
await sutProvider.GetDependency<IPlayDataRepository>()
|
||||
await sutProvider.GetDependency<IPlayItemRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(Arg.Is<PlayData>(pd =>
|
||||
.CreateAsync(Arg.Is<PlayItem>(pd =>
|
||||
pd.PlayId == playId &&
|
||||
pd.UserId == user.Id &&
|
||||
pd.OrganizationId == null));
|
||||
|
||||
sutProvider.GetDependency<ILogger<PlayDataService>>()
|
||||
sutProvider.GetDependency<ILogger<PlayItemService>>()
|
||||
.Received(1)
|
||||
.Log(
|
||||
LogLevel.Information,
|
||||
@@ -49,9 +49,9 @@ public class PlayDataServiceTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Record_User_WhenNotInPlay_DoesNotRecordPlayData(
|
||||
public async Task Record_User_WhenNotInPlay_DoesNotRecordPlayItem(
|
||||
User user,
|
||||
SutProvider<PlayDataService> sutProvider)
|
||||
SutProvider<PlayItemService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IPlayIdService>()
|
||||
.InPlay(out Arg.Any<string>())
|
||||
@@ -63,11 +63,11 @@ public class PlayDataServiceTests
|
||||
|
||||
await sutProvider.Sut.Record(user);
|
||||
|
||||
await sutProvider.GetDependency<IPlayDataRepository>()
|
||||
await sutProvider.GetDependency<IPlayItemRepository>()
|
||||
.DidNotReceive()
|
||||
.CreateAsync(Arg.Any<PlayData>());
|
||||
.CreateAsync(Arg.Any<PlayItem>());
|
||||
|
||||
sutProvider.GetDependency<ILogger<PlayDataService>>()
|
||||
sutProvider.GetDependency<ILogger<PlayItemService>>()
|
||||
.DidNotReceive()
|
||||
.Log(
|
||||
LogLevel.Information,
|
||||
@@ -79,10 +79,10 @@ public class PlayDataServiceTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Record_Organization_WhenInPlay_RecordsPlayData(
|
||||
public async Task Record_Organization_WhenInPlay_RecordsPlayItem(
|
||||
string playId,
|
||||
Organization organization,
|
||||
SutProvider<PlayDataService> sutProvider)
|
||||
SutProvider<PlayItemService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IPlayIdService>()
|
||||
.InPlay(out Arg.Any<string>())
|
||||
@@ -94,14 +94,14 @@ public class PlayDataServiceTests
|
||||
|
||||
await sutProvider.Sut.Record(organization);
|
||||
|
||||
await sutProvider.GetDependency<IPlayDataRepository>()
|
||||
await sutProvider.GetDependency<IPlayItemRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(Arg.Is<PlayData>(pd =>
|
||||
.CreateAsync(Arg.Is<PlayItem>(pd =>
|
||||
pd.PlayId == playId &&
|
||||
pd.OrganizationId == organization.Id &&
|
||||
pd.UserId == null));
|
||||
|
||||
sutProvider.GetDependency<ILogger<PlayDataService>>()
|
||||
sutProvider.GetDependency<ILogger<PlayItemService>>()
|
||||
.Received(1)
|
||||
.Log(
|
||||
LogLevel.Information,
|
||||
@@ -113,9 +113,9 @@ public class PlayDataServiceTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Record_Organization_WhenNotInPlay_DoesNotRecordPlayData(
|
||||
public async Task Record_Organization_WhenNotInPlay_DoesNotRecordPlayItem(
|
||||
Organization organization,
|
||||
SutProvider<PlayDataService> sutProvider)
|
||||
SutProvider<PlayItemService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IPlayIdService>()
|
||||
.InPlay(out Arg.Any<string>())
|
||||
@@ -127,11 +127,11 @@ public class PlayDataServiceTests
|
||||
|
||||
await sutProvider.Sut.Record(organization);
|
||||
|
||||
await sutProvider.GetDependency<IPlayDataRepository>()
|
||||
await sutProvider.GetDependency<IPlayItemRepository>()
|
||||
.DidNotReceive()
|
||||
.CreateAsync(Arg.Any<PlayData>());
|
||||
.CreateAsync(Arg.Any<PlayItem>());
|
||||
|
||||
sutProvider.GetDependency<ILogger<PlayDataService>>()
|
||||
sutProvider.GetDependency<ILogger<PlayItemService>>()
|
||||
.DidNotReceive()
|
||||
.Log(
|
||||
LogLevel.Information,
|
||||
@@ -1,31 +1,31 @@
|
||||
-- Create PlayData table
|
||||
IF OBJECT_ID('dbo.PlayData') IS NULL
|
||||
-- Create PlayItem table
|
||||
IF OBJECT_ID('dbo.PlayItem') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE [dbo].[PlayData] (
|
||||
CREATE TABLE [dbo].[PlayItem] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[PlayId] NVARCHAR (256) NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
CONSTRAINT [PK_PlayData] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_PlayData_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PlayData_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [CK_PlayData_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL))
|
||||
CONSTRAINT [PK_PlayItem] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_PlayItem_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PlayItem_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [CK_PlayItem_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayData_PlayId]
|
||||
ON [dbo].[PlayData]([PlayId] ASC);
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayItem_PlayId]
|
||||
ON [dbo].[PlayItem]([PlayId] ASC);
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayData_UserId]
|
||||
ON [dbo].[PlayData]([UserId] ASC);
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayItem_UserId]
|
||||
ON [dbo].[PlayItem]([UserId] ASC);
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayData_OrganizationId]
|
||||
ON [dbo].[PlayData]([OrganizationId] ASC);
|
||||
CREATE NONCLUSTERED INDEX [IX_PlayItem_OrganizationId]
|
||||
ON [dbo].[PlayItem]([OrganizationId] ASC);
|
||||
END
|
||||
GO
|
||||
|
||||
-- Create PlayData_Create stored procedure
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PlayData_Create]
|
||||
-- Create PlayItem_Create stored procedure
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PlayItem_Create]
|
||||
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||
@PlayId NVARCHAR(256),
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@@ -35,7 +35,7 @@ AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
INSERT INTO [dbo].[PlayData]
|
||||
INSERT INTO [dbo].[PlayItem]
|
||||
(
|
||||
[Id],
|
||||
[PlayId],
|
||||
@@ -54,8 +54,8 @@ BEGIN
|
||||
END
|
||||
GO
|
||||
|
||||
-- Create PlayData_ReadByPlayId stored procedure
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PlayData_ReadByPlayId]
|
||||
-- Create PlayItem_ReadByPlayId stored procedure
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PlayItem_ReadByPlayId]
|
||||
@PlayId NVARCHAR(256)
|
||||
AS
|
||||
BEGIN
|
||||
@@ -68,14 +68,14 @@ BEGIN
|
||||
[OrganizationId],
|
||||
[CreationDate]
|
||||
FROM
|
||||
[dbo].[PlayData]
|
||||
[dbo].[PlayItem]
|
||||
WHERE
|
||||
[PlayId] = @PlayId
|
||||
END
|
||||
GO
|
||||
|
||||
-- Create PlayData_DeleteByPlayId stored procedure
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PlayData_DeleteByPlayId]
|
||||
-- Create PlayItem_DeleteByPlayId stored procedure
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PlayItem_DeleteByPlayId]
|
||||
@PlayId NVARCHAR(256)
|
||||
AS
|
||||
BEGIN
|
||||
@@ -83,7 +83,7 @@ BEGIN
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[PlayData]
|
||||
[dbo].[PlayItem]
|
||||
WHERE
|
||||
[PlayId] = @PlayId
|
||||
END
|
||||
@@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Bit.MySqlMigrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20251118024031_PlayData")]
|
||||
partial class PlayData
|
||||
[Migration("20251118024031_PlayItem")]
|
||||
partial class PlayItem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -1624,7 +1624,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.ToTable("OrganizationUser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)");
|
||||
@@ -1654,9 +1654,9 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.HasIndex("UserId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("PlayData", null, t =>
|
||||
b.ToTable("PlayItem", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3033,7 +3033,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace Bit.MySqlMigrations.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class PlayData : Migration
|
||||
public partial class PlayItem : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
@@ -19,7 +19,7 @@ public partial class PlayData : Migration
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlayData",
|
||||
name: "PlayItem",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
@@ -31,16 +31,16 @@ public partial class PlayData : Migration
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlayData", x => x.Id);
|
||||
table.CheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
table.PrimaryKey("PK_PlayItem", x => x.Id);
|
||||
table.CheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
table.ForeignKey(
|
||||
name: "FK_PlayData_Organization_OrganizationId",
|
||||
name: "FK_PlayItem_Organization_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PlayData_User_UserId",
|
||||
name: "FK_PlayItem_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
@@ -49,18 +49,18 @@ public partial class PlayData : Migration
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_OrganizationId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_OrganizationId",
|
||||
table: "PlayItem",
|
||||
column: "OrganizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_PlayId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_PlayId",
|
||||
table: "PlayItem",
|
||||
column: "PlayId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_UserId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_UserId",
|
||||
table: "PlayItem",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public partial class PlayData : Migration
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlayData");
|
||||
name: "PlayItem");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "WaitTimeDays",
|
||||
@@ -1627,7 +1627,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.ToTable("OrganizationUser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)");
|
||||
@@ -1657,9 +1657,9 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.HasIndex("UserId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("PlayData", null, t =>
|
||||
b.ToTable("PlayItem", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3039,7 +3039,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
|
||||
@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace Bit.PostgresMigrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20251118024041_PlayData")]
|
||||
partial class PlayData
|
||||
[Migration("20251118024041_PlayItem")]
|
||||
partial class PlayItem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -1629,7 +1629,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.ToTable("OrganizationUser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
@@ -1659,9 +1659,9 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.HasIndex("UserId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("PlayData", null, t =>
|
||||
b.ToTable("PlayItem", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3039,7 +3039,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace Bit.PostgresMigrations.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class PlayData : Migration
|
||||
public partial class PlayItem : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
@@ -19,7 +19,7 @@ public partial class PlayData : Migration
|
||||
oldType: "integer");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlayData",
|
||||
name: "PlayItem",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
@@ -30,16 +30,16 @@ public partial class PlayData : Migration
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlayData", x => x.Id);
|
||||
table.CheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
table.PrimaryKey("PK_PlayItem", x => x.Id);
|
||||
table.CheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
table.ForeignKey(
|
||||
name: "FK_PlayData_Organization_OrganizationId",
|
||||
name: "FK_PlayItem_Organization_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PlayData_User_UserId",
|
||||
name: "FK_PlayItem_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
@@ -47,18 +47,18 @@ public partial class PlayData : Migration
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_OrganizationId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_OrganizationId",
|
||||
table: "PlayItem",
|
||||
column: "OrganizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_PlayId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_PlayId",
|
||||
table: "PlayItem",
|
||||
column: "PlayId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_UserId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_UserId",
|
||||
table: "PlayItem",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public partial class PlayData : Migration
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlayData");
|
||||
name: "PlayItem");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "WaitTimeDays",
|
||||
@@ -1632,7 +1632,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.ToTable("OrganizationUser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
@@ -1662,9 +1662,9 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.HasIndex("UserId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("PlayData", null, t =>
|
||||
b.ToTable("PlayItem", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3045,7 +3045,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
|
||||
@@ -9,16 +9,16 @@ public class DestroySceneCommand(
|
||||
DatabaseContext databaseContext,
|
||||
ILogger<DestroySceneCommand> logger,
|
||||
IUserRepository userRepository,
|
||||
IPlayDataRepository playDataRepository,
|
||||
IPlayItemRepository playItemRepository,
|
||||
IOrganizationRepository organizationRepository) : IDestroySceneCommand
|
||||
{
|
||||
public async Task<object?> DestroyAsync(string playId)
|
||||
{
|
||||
// Note, delete cascade will remove PlayData entries
|
||||
// Note, delete cascade will remove PlayItem entries
|
||||
|
||||
var playData = await playDataRepository.GetByPlayIdAsync(playId);
|
||||
var userIds = playData.Select(pd => pd.UserId).Distinct().ToList();
|
||||
var organizationIds = playData.Select(pd => pd.OrganizationId).Distinct().ToList();
|
||||
var playItem = await playItemRepository.GetByPlayIdAsync(playId);
|
||||
var userIds = playItem.Select(pd => pd.UserId).Distinct().ToList();
|
||||
var organizationIds = playItem.Select(pd => pd.OrganizationId).Distinct().ToList();
|
||||
|
||||
// Delete Users before Organizations to respect foreign key constraints
|
||||
if (userIds.Count > 0)
|
||||
|
||||
@@ -7,7 +7,7 @@ public class GetAllPlayIdsQuery(DatabaseContext databaseContext) : IGetAllPlayId
|
||||
{
|
||||
public List<string> GetAllPlayIds()
|
||||
{
|
||||
return databaseContext.PlayData
|
||||
return databaseContext.PlayItem
|
||||
.Select(pd => pd.PlayId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
@@ -85,9 +85,7 @@ curl -X POST http://localhost:5000/query \
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
"/accept-emergency?..."
|
||||
]
|
||||
["/accept-emergency?..."]
|
||||
```
|
||||
|
||||
### Destroying Seeded Data
|
||||
@@ -148,7 +146,7 @@ The SeederApi requires the following configuration:
|
||||
## Play ID Tracking
|
||||
|
||||
Certain entities such as Users and Organizations are tracked when created by a request including a PlayId. This enables
|
||||
entities to be deleted after using the PlayId.
|
||||
entities to be deleted after using the PlayId.
|
||||
|
||||
### The X-Play-Id Header
|
||||
|
||||
@@ -168,7 +166,7 @@ When `TestPlayIdTrackingEnabled` is enabled in GlobalSettings, the `PlayIdMiddle
|
||||
1. **Extracts** the `X-Play-Id` header from incoming requests
|
||||
2. **Sets** the play ID in the `PlayIdService` for the request scope
|
||||
3. **Tracks** all entities (users, organizations, etc.) created during the request
|
||||
4. **Associates** them with the play ID in the `PlayData` table
|
||||
4. **Associates** them with the play ID in the `PlayItem` table
|
||||
5. **Enables** complete cleanup via the delete endpoints
|
||||
|
||||
This tracking works for **any API request** that includes the `X-Play-Id` header, not just SeederApi endpoints. This means
|
||||
|
||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Bit.SqliteMigrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20251118024036_PlayData")]
|
||||
partial class PlayData
|
||||
[Migration("20251118024036_PlayItem")]
|
||||
partial class PlayItem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -1613,7 +1613,7 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.ToTable("OrganizationUser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
@@ -1643,9 +1643,9 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.HasIndex("UserId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("PlayData", null, t =>
|
||||
b.ToTable("PlayItem", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3022,7 +3022,7 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
@@ -5,13 +5,13 @@
|
||||
namespace Bit.SqliteMigrations.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class PlayData : Migration
|
||||
public partial class PlayItem : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlayData",
|
||||
name: "PlayItem",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
@@ -22,16 +22,16 @@ public partial class PlayData : Migration
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlayData", x => x.Id);
|
||||
table.CheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
table.PrimaryKey("PK_PlayItem", x => x.Id);
|
||||
table.CheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
table.ForeignKey(
|
||||
name: "FK_PlayData_Organization_OrganizationId",
|
||||
name: "FK_PlayItem_Organization_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PlayData_User_UserId",
|
||||
name: "FK_PlayItem_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
@@ -39,18 +39,18 @@ public partial class PlayData : Migration
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_OrganizationId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_OrganizationId",
|
||||
table: "PlayItem",
|
||||
column: "OrganizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_PlayId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_PlayId",
|
||||
table: "PlayItem",
|
||||
column: "PlayId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlayData_UserId",
|
||||
table: "PlayData",
|
||||
name: "IX_PlayItem_UserId",
|
||||
table: "PlayItem",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
@@ -58,6 +58,6 @@ public partial class PlayData : Migration
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlayData");
|
||||
name: "PlayItem");
|
||||
}
|
||||
}
|
||||
@@ -1616,7 +1616,7 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.ToTable("OrganizationUser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
@@ -1646,9 +1646,9 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.HasIndex("UserId")
|
||||
.HasAnnotation("SqlServer:Clustered", false);
|
||||
|
||||
b.ToTable("PlayData", null, t =>
|
||||
b.ToTable("PlayItem", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_PlayData_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3028,7 +3028,7 @@ namespace Bit.SqliteMigrations.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayData", b =>
|
||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b =>
|
||||
{
|
||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||
.WithMany()
|
||||
|
||||
Reference in New Issue
Block a user