1
0
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:
Matt Gibson
2026-01-08 08:45:38 -08:00
parent 266925399c
commit 814612cb51
37 changed files with 216 additions and 218 deletions

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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));
}
}
}

View File

@@ -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

View File

@@ -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>();

View File

@@ -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);
}

View File

@@ -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)"
);
}

View File

@@ -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>();

View File

@@ -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();
}
}

View File

@@ -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; }

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>();

View File

@@ -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],

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View 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);

View File

@@ -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,

View File

@@ -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

View File

@@ -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()

View File

@@ -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",

View File

@@ -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()

View File

@@ -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()

View File

@@ -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",

View File

@@ -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()

View File

@@ -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)

View File

@@ -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();

View File

@@ -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

View File

@@ -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()

View File

@@ -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");
}
}

View File

@@ -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()