1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

refactored cipherfolder and fav to JSON columns

This commit is contained in:
Kyle Spearrin
2017-04-15 22:26:45 -04:00
parent 8e193dfc62
commit f21652b46b
21 changed files with 216 additions and 272 deletions

View File

@ -10,6 +10,8 @@ namespace Bit.Core.Models.Table
public Guid? OrganizationId { get; set; }
public Enums.CipherType Type { get; set; }
public string Data { get; set; }
public string Favorites { get; set; }
public string Folders { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;

View File

@ -1,10 +0,0 @@
using System;
namespace Bit.Core.Models.Table
{
public class Favorite
{
public Guid CipherId { get; set; }
public Guid UserId { get; set; }
}
}

View File

@ -1,11 +0,0 @@
using System;
namespace Bit.Core.Models.Table
{
public class FolderCipher
{
public Guid CipherId { get; set; }
public Guid FolderId { get; set; }
public Guid UserId { get; set; }
}
}

View File

@ -19,7 +19,6 @@ namespace Bit.Core.Repositories
Task ReplaceAsync(Cipher obj, IEnumerable<Guid> subvaultIds);
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers);
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Favorite> favorites, IEnumerable<Folder> folders,
IEnumerable<FolderCipher> folderCiphers);
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
}
}

View File

@ -237,8 +237,7 @@ namespace Bit.Core.Repositories.SqlServer
return Task.FromResult(0);
}
public Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Favorite> favorites, IEnumerable<Folder> folders,
IEnumerable<FolderCipher> folderCiphers)
public Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders)
{
if(!ciphers.Any())
{
@ -272,28 +271,6 @@ namespace Bit.Core.Repositories.SqlServer
bulkCopy.WriteToServer(dataTable);
}
if(folderCiphers.Any())
{
using(var bulkCopy = new SqlBulkCopy(connection,
SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction))
{
bulkCopy.DestinationTableName = "[dbo].[FolderCipher]";
var dataTable = BuildFolderCiphersTable(folderCiphers);
bulkCopy.WriteToServer(dataTable);
}
}
if(favorites.Any())
{
using(var bulkCopy = new SqlBulkCopy(connection,
SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction))
{
bulkCopy.DestinationTableName = "[dbo].[Favorite]";
var dataTable = BuildFavoritesTable(favorites);
bulkCopy.WriteToServer(dataTable);
}
}
transaction.Commit();
}
catch
@ -327,6 +304,10 @@ namespace Bit.Core.Repositories.SqlServer
ciphersTable.Columns.Add(typeColumn);
var dataColumn = new DataColumn(nameof(c.Data), typeof(string));
ciphersTable.Columns.Add(dataColumn);
var favoritesColumn = new DataColumn(nameof(c.Favorites), typeof(string));
ciphersTable.Columns.Add(favoritesColumn);
var foldersColumn = new DataColumn(nameof(c.Folders), typeof(string));
ciphersTable.Columns.Add(foldersColumn);
var creationDateColumn = new DataColumn(nameof(c.CreationDate), c.CreationDate.GetType());
ciphersTable.Columns.Add(creationDateColumn);
var revisionDateColumn = new DataColumn(nameof(c.RevisionDate), c.RevisionDate.GetType());
@ -345,6 +326,8 @@ namespace Bit.Core.Repositories.SqlServer
row[organizationId] = cipher.OrganizationId.HasValue ? (object)cipher.OrganizationId.Value : DBNull.Value;
row[typeColumn] = (short)cipher.Type;
row[dataColumn] = cipher.Data;
row[favoritesColumn] = cipher.Favorites;
row[foldersColumn] = cipher.Folders;
row[creationDateColumn] = cipher.CreationDate;
row[revisionDateColumn] = cipher.RevisionDate;
@ -354,76 +337,6 @@ namespace Bit.Core.Repositories.SqlServer
return ciphersTable;
}
private DataTable BuildFavoritesTable(IEnumerable<Favorite> favorites)
{
var f = favorites.FirstOrDefault();
if(f == null)
{
throw new ApplicationException("Must have some favorites to bulk import.");
}
var favoritesTable = new DataTable("FavoriteDataTable");
var userIdColumn = new DataColumn(nameof(f.UserId), f.UserId.GetType());
favoritesTable.Columns.Add(userIdColumn);
var cipherIdColumn = new DataColumn(nameof(f.CipherId), f.CipherId.GetType());
favoritesTable.Columns.Add(cipherIdColumn);
var keys = new DataColumn[2];
keys[0] = userIdColumn;
keys[1] = cipherIdColumn;
favoritesTable.PrimaryKey = keys;
foreach(var favorite in favorites)
{
var row = favoritesTable.NewRow();
row[cipherIdColumn] = favorite.CipherId;
row[userIdColumn] = favorite.UserId;
favoritesTable.Rows.Add(row);
}
return favoritesTable;
}
private DataTable BuildFolderCiphersTable(IEnumerable<FolderCipher> folderCiphers)
{
var f = folderCiphers.FirstOrDefault();
if(f == null)
{
throw new ApplicationException("Must have some folderCiphers to bulk import.");
}
var folderCiphersTable = new DataTable("FolderCipherDataTable");
var folderIdColumn = new DataColumn(nameof(f.FolderId), f.FolderId.GetType());
folderCiphersTable.Columns.Add(folderIdColumn);
var cipherIdColumn = new DataColumn(nameof(f.CipherId), f.CipherId.GetType());
folderCiphersTable.Columns.Add(cipherIdColumn);
var userIdColumn = new DataColumn(nameof(f.UserId), f.UserId.GetType());
folderCiphersTable.Columns.Add(userIdColumn);
var keys = new DataColumn[3];
keys[0] = folderIdColumn;
keys[1] = cipherIdColumn;
keys[2] = userIdColumn;
folderCiphersTable.PrimaryKey = keys;
foreach(var folderCipher in folderCiphers)
{
var row = folderCiphersTable.NewRow();
row[folderIdColumn] = folderCipher.FolderId;
row[cipherIdColumn] = folderCipher.CipherId;
row[userIdColumn] = folderCipher.UserId;
folderCiphersTable.Rows.Add(row);
}
return folderCiphersTable;
}
private DataTable BuildFoldersTable(IEnumerable<Folder> folders)
{
var f = folders.FirstOrDefault();

View File

@ -186,19 +186,13 @@ namespace Bit.Core.Services
List<CipherDetails> ciphers,
IEnumerable<KeyValuePair<int, int>> folderRelationships)
{
// Init. ids and build out favorites.
var favorites = new List<Favorite>();
foreach(var cipher in ciphers)
{
cipher.SetNewId();
if(cipher.UserId.HasValue && cipher.Favorite)
{
favorites.Add(new Favorite
{
UserId = cipher.UserId.Value,
CipherId = cipher.Id
});
cipher.Favorites = $"[{{\"u\":\"{cipher.UserId.ToString().ToUpperInvariant()}\"}}]";
}
}
@ -209,7 +203,6 @@ namespace Bit.Core.Services
}
// Create the folder associations based on the newly created folder ids
var folderCiphers = new List<FolderCipher>();
foreach(var relationship in folderRelationships)
{
var cipher = ciphers.ElementAtOrDefault(relationship.Key);
@ -220,16 +213,12 @@ namespace Bit.Core.Services
continue;
}
folderCiphers.Add(new FolderCipher
{
FolderId = folder.Id,
CipherId = cipher.Id,
UserId = folder.UserId
});
cipher.Folders = $"[{{\"u\":\"{cipher.UserId.ToString().ToUpperInvariant()}\"," +
$"\"f\":\"{folder.Id.ToString().ToUpperInvariant()}\"}}]";
}
// Create it all
await _cipherRepository.CreateAsync(ciphers, favorites, folders, folderCiphers);
await _cipherRepository.CreateAsync(ciphers, folders);
// push
var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId;

View File

@ -70,9 +70,7 @@
<Build Include="dbo\Tables\Device.sql" />
<Build Include="dbo\Tables\Cipher.sql" />
<Build Include="dbo\Tables\Organization.sql" />
<Build Include="dbo\Tables\FolderCipher.sql" />
<Build Include="dbo\Tables\Grant.sql" />
<Build Include="dbo\Tables\Favorite.sql" />
<Build Include="dbo\Tables\Group.sql" />
<Build Include="dbo\Tables\User.sql" />
<Build Include="dbo\Tables\GroupUser.sql" />
@ -104,10 +102,8 @@
<Build Include="dbo\Stored Procedures\OrganizationUser_ReadCountByFreeOrganizationAdminUser.sql" />
<Build Include="dbo\Stored Procedures\Cipher_Create.sql" />
<Build Include="dbo\Stored Procedures\Cipher_DeleteById.sql" />
<Build Include="dbo\Stored Procedures\Favorite_Create.sql" />
<Build Include="dbo\Stored Procedures\Cipher_ReadById.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_ReadCountByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\Favorite_Delete.sql" />
<Build Include="dbo\Stored Procedures\Subvault_ReadCountByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\Folder_Create.sql" />
<Build Include="dbo\Stored Procedures\Folder_DeleteById.sql" />
@ -115,9 +111,7 @@
<Build Include="dbo\Stored Procedures\Cipher_Update.sql" />
<Build Include="dbo\Stored Procedures\Folder_Update.sql" />
<Build Include="dbo\Stored Procedures\Device_ClearPushTokenByIdentifier.sql" />
<Build Include="dbo\Stored Procedures\FolderCipher_Create.sql" />
<Build Include="dbo\Stored Procedures\Device_Create.sql" />
<Build Include="dbo\Stored Procedures\FolderCipher_Delete.sql" />
<Build Include="dbo\Stored Procedures\Device_DeleteById.sql" />
<Build Include="dbo\Stored Procedures\Organization_Create.sql" />
<Build Include="dbo\Stored Procedures\Device_ReadById.sql" />

View File

@ -2,14 +2,44 @@
RETURNS TABLE
AS RETURN
SELECT
C.*,
CASE WHEN F.[CipherId] IS NULL THEN 0 ELSE 1 END [Favorite],
FO.[Id] [FolderId]
C.[Id],
C.[UserId],
C.[OrganizationId],
C.[Type],
C.[Data],
C.[CreationDate],
C.[RevisionDate],
CASE WHEN
C.[Favorites] IS NULL
OR (
SELECT TOP 1
1
FROM
OPENJSON(C.[Favorites])
WITH (
[Favorites_UserId] UNIQUEIDENTIFIER '$.u'
)
WHERE
[Favorites_UserId] = @UserId
) IS NULL
THEN 0
ELSE 1
END [Favorite],
CASE WHEN
C.[Folders] IS NULL
THEN NULL
ELSE (
SELECT TOP 1
[Folders_FolderId]
FROM
OPENJSON(C.[Folders])
WITH (
[Folders_UserId] UNIQUEIDENTIFIER '$.u',
[Folders_FolderId] UNIQUEIDENTIFIER '$.f'
)
WHERE
[Folders_UserId] = @UserId
)
END [FolderId]
FROM
[dbo].[Cipher] C
LEFT JOIN
[dbo].[Favorite] F ON F.[CipherId] = C.[Id] AND F.[UserId] = @UserId
LEFT JOIN
[dbo].[FolderCipher] FC ON FC.[UserId] = @UserId AND FC.[CipherId] = C.[Id]
LEFT JOIN
[dbo].[Folder] FO ON FO.[Id] = FC.[FolderId]

View File

@ -4,6 +4,8 @@
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,
@ -19,6 +21,8 @@ BEGIN
[OrganizationId],
[Type],
[Data],
[Favorites],
[Folders],
[CreationDate],
[RevisionDate]
)
@ -29,17 +33,9 @@ BEGIN
@OrganizationId,
@Type,
@Data,
CASE WHEN @Favorite = 0 THEN NULL ELSE JSON_QUERY((SELECT @UserId u FOR JSON PATH)) END,
CASE WHEN @FolderId IS NULL THEN NULL ELSE JSON_QUERY((SELECT @UserId u, @FolderId f FOR JSON PATH)) END,
@CreationDate,
@RevisionDate
)
IF @FolderId IS NOT NULL
BEGIN
EXEC [dbo].[FolderCipher_Create] @FolderId, @Id, @UserId
END
IF @Favorite = 1
BEGIN
EXEC [dbo].[Favorite_Create] @UserId, @Id
END
END

View File

@ -4,6 +4,8 @@
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,

View File

@ -4,6 +4,8 @@
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7)
AS
@ -17,6 +19,8 @@ BEGIN
[OrganizationId],
[Type],
[Data],
[Favorites],
[Folders],
[CreationDate],
[RevisionDate]
)
@ -27,6 +31,8 @@ BEGIN
@OrganizationId,
@Type,
@Data,
@Favorites,
@Folders,
@CreationDate,
@RevisionDate
)

View File

@ -4,6 +4,8 @@
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7)
AS
@ -17,6 +19,8 @@ BEGIN
[OrganizationId] = @OrganizationId,
[Type] = @Type,
[Data] = @Data,
[Favorites] = @Favorites,
[Folders] = @Folders,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate
WHERE

View File

@ -2,39 +2,158 @@
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@FolderId UNIQUEIDENTIFIER,
@Favorite TINYINT
@Favorite BIT
AS
BEGIN
SET NOCOUNT ON
DECLARE @FavoritesJson VARCHAR(MAX) = NULL
DECLARE @FoldersJson VARCHAR(MAX) = NULL
SELECT
@FavoritesJson = [Favorites],
@FoldersJson = [Folders]
FROM
[dbo].[Cipher]
WHERE
[Id] = @Id
DECLARE @ExistingFolderId UNIQUEIDENTIFIER = NULL
SELECT TOP 1
@ExistingFolderId = F.Id
FROM
[dbo].[FolderCipher] FC
INNER JOIN
[dbo].[Folder] F ON F.[Id] = FC.[FolderId]
WHERE
F.[UserId] = @UserId
AND FC.[CipherId] = @Id
-- NOTE
-- JSON_MODIFY operations involving @Existing__Index may be subject to race conditions involving that index/key.
-- Look for a better approach, like removing objects by conditions.
DECLARE @ExistingFolderIndex INT = NULL
DECLARE @ExistingFavoriteIndex INT = NULL
IF @ExistingFolderId IS NOT NULL AND (@FolderId IS NULL OR @FolderId != @ExistingFolderId)
IF @FoldersJson IS NOT NULL
BEGIN
EXEC [dbo].[FolderCipher_Delete] @ExistingFolderId, @Id
END
IF @FolderId IS NOT NULL AND (@ExistingFolderId IS NULL OR @FolderId != @ExistingFolderId)
BEGIN
EXEC [dbo].[FolderCipher_Create] @FolderId, @Id, @UserId
SELECT TOP 1
@ExistingFolderId = JSON_VALUE([Value], '$.f'),
@ExistingFolderIndex = [Key]
FROM (
SELECT
[Key],
[Value]
FROM
OPENJSON(@FoldersJson)
) [Results]
WHERE JSON_VALUE([Value], '$.u') = @UserId
END
IF @Favorite = 0
IF @FavoritesJson IS NOT NULL
BEGIN
EXEC [dbo].[Favorite_Delete] @UserId, @Id
SELECT TOP 1
@ExistingFavoriteIndex = [Key]
FROM (
SELECT
[Key],
[Value]
FROM
OPENJSON(@FavoritesJson)
) [Results]
WHERE JSON_VALUE([Value], '$.u') = @UserId
END
ELSE IF (SELECT COUNT(1) FROM [dbo].[Favorite] WHERE [UserId] = @UserId AND [CipherId] = @Id) = 0
-- ----------------------------
-- Update [Folders]
-- ----------------------------
IF @ExistingFolderId IS NOT NULL AND @FolderId IS NULL
BEGIN
EXEC [dbo].[Favorite_Create] @UserId, @Id
-- User had an existing folder, but now they have removed the folder assignment.
-- Remove the index of the existing folder object from the [Folders] JSON array.
UPDATE
[dbo].[Cipher]
SET
-- TODO: How to remove index?
[Folders] = JSON_MODIFY([Folders], CONCAT('$[', @ExistingFolderIndex, ']'), NULL)
WHERE
[Id] = @Id
END
ELSE IF @FolderId IS NOT NULL
BEGIN
IF @FoldersJson IS NULL
BEGIN
-- [Folders] has no existing JSON data.
-- Set @FolderId (with @UserId) as a new JSON object for index 0 of an array.
UPDATE
[dbo].[Cipher]
SET
[Folders] = JSON_QUERY((SELECT @UserId u, @FolderId f FOR JSON PATH))
WHERE
[Id] = @Id
END
ELSE IF @ExistingFolderId IS NULL
BEGIN
-- [Folders] has some existing JSON data, but the user had no existing folder.
-- Append @FolderId (with @UserId) as a new JSON object.
UPDATE
[dbo].[Cipher]
SET
[Folders] = JSON_MODIFY([Folders], 'append $',
JSON_QUERY((SELECT @UserId u, @FolderId f FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)))
WHERE
[Id] = @Id
END
ELSE IF @FolderId != @ExistingFolderId
BEGIN
-- User had an existing folder assignemnt, but have changed the assignment to another folder.
-- Update the index of the existing folder object from the [Folders] JSON array to to include the new @FolderId
UPDATE
[dbo].[Cipher]
SET
[Folders] = JSON_MODIFY([Folders], CONCAT('$[', @ExistingFolderIndex, ']'),
JSON_QUERY((SELECT @UserId u, @FolderId f FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)))
WHERE
[Id] = @Id
END
END
-- ----------------------------
-- Update [Favorites]
-- ----------------------------
IF @Favorite = 0 AND @ExistingFavoriteIndex IS NOT NULL
BEGIN
-- User had the cipher marked as a favorite, but now it is not.
-- Remove the index of the existing user object from the [Favorites] JSON array.
UPDATE
[dbo].[Cipher]
SET
-- TODO: How to remove index?
[Favorites] = JSON_MODIFY([Favorites], CONCAT('$[', @ExistingFavoriteIndex, ']'), NULL)
WHERE
[Id] = @Id
END
ELSE IF @Favorite = 1 AND @FavoritesJson IS NULL
BEGIN
-- User is marking the cipher as a favorite and there is no existing JSON data
-- Set @UserId as a new JSON object for index 0 of an array.
UPDATE
[dbo].[Cipher]
SET
[Favorites] = JSON_QUERY((SELECT @UserId u FOR JSON PATH))
WHERE
[Id] = @Id
END
ELSE IF @Favorite = 1 AND @ExistingFavoriteIndex IS NULL
BEGIN
-- User is marking the cipher as a favorite whenever it previously was not.
-- Append @UserId as a new JSON object.
UPDATE
[dbo].[Cipher]
SET
[Favorites] = JSON_MODIFY([Favorites], 'append $',
JSON_QUERY((SELECT @UserId u FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)))
WHERE
[Id] = @Id
END
END

View File

@ -4,6 +4,8 @@
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@SubvaultIds AS [dbo].[GuidIdArray] READONLY
@ -16,10 +18,9 @@ BEGIN
SET
[UserId] = NULL,
[OrganizationId] = @OrganizationId,
[Type] = @Type,
[Data] = @Data,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate
-- No need to update CreationDate, Favorites, Folders, or Type since that data will not change
WHERE
[Id] = @Id

View File

@ -1,18 +0,0 @@
CREATE PROCEDURE [dbo].[Favorite_Create]
@UserId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Favorite]
(
[UserId],
[CipherId]
)
VALUES
(
@UserId,
@CipherId
)
END

View File

@ -1,14 +0,0 @@
CREATE PROCEDURE [dbo].[Favorite_Delete]
@UserId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
DELETE
FROM
[dbo].[Favorite]
WHERE
[UserId] = @UserId
AND [CipherId] = @CipherId
END

View File

@ -1,21 +0,0 @@
CREATE PROCEDURE [dbo].[FolderCipher_Create]
@FolderId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[FolderCipher]
(
[FolderId],
[CipherId],
[UserId]
)
VALUES
(
@FolderId,
@CipherId,
@UserId
)
END

View File

@ -1,16 +0,0 @@
CREATE PROCEDURE [dbo].[FolderCipher_Delete]
@FolderId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
DELETE
FROM
[dbo].[FolderCipher]
WHERE
[FolderId] = @FolderId
AND [CipherId] = @CipherId
AND [UserId] = @UserId
END

View File

@ -4,6 +4,8 @@
[OrganizationId] UNIQUEIDENTIFIER NULL,
[Type] TINYINT NOT NULL,
[Data] NVARCHAR (MAX) NOT NULL,
[Favorites] NVARCHAR (MAX) NULL,
[Folders] NVARCHAR (MAX) NULL,
[CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NOT NULL,
CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),

View File

@ -1,8 +0,0 @@
CREATE TABLE [dbo].[Favorite] (
[UserId] UNIQUEIDENTIFIER NOT NULL,
[CipherId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_Favorite] PRIMARY KEY CLUSTERED ([UserId] ASC, [CipherId] ASC),
CONSTRAINT [FK_Favorite_Cipher] FOREIGN KEY ([CipherId]) REFERENCES [dbo].[Cipher] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_Favorite_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
);

View File

@ -1,15 +0,0 @@
CREATE TABLE [dbo].[FolderCipher] (
[FolderId] UNIQUEIDENTIFIER NOT NULL,
[CipherId] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_FolderCipher] PRIMARY KEY CLUSTERED ([UserId] ASC, [FolderId] ASC, [CipherId] ASC),
CONSTRAINT [FK_FolderCipher_Cipher] FOREIGN KEY ([CipherId]) REFERENCES [dbo].[Cipher] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_FolderCipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_FolderCipher_Folder] FOREIGN KEY ([FolderId]) REFERENCES [dbo].[Folder] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_FolderCipher_CipherId]
ON [dbo].[FolderCipher]([CipherId] ASC);