mirror of
https://github.com/bitwarden/server.git
synced 2025-04-23 14:05:10 -05:00
post, upload, and save cipher attachment
This commit is contained in:
parent
71f755dd44
commit
6cea556ae1
@ -9,6 +9,8 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Api.Utilities;
|
using Bit.Api.Utilities;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
{
|
{
|
||||||
@ -219,13 +221,10 @@ namespace Bit.Api.Controllers
|
|||||||
string.IsNullOrWhiteSpace(model.FolderId) ? (Guid?)null : new Guid(model.FolderId), userId);
|
string.IsNullOrWhiteSpace(model.FolderId) ? (Guid?)null : new Guid(model.FolderId), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("attachment")]
|
[HttpPost("{id}/attachment")]
|
||||||
[DisableFormValueModelBinding]
|
[DisableFormValueModelBinding]
|
||||||
public async Task Post(string id)
|
public async Task PostAttachment(string id)
|
||||||
{
|
{
|
||||||
// throw for now
|
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
if(!Request?.ContentType.Contains("multipart/") ?? true)
|
if(!Request?.ContentType.Contains("multipart/") ?? true)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Invalid content.");
|
throw new BadRequestException("Invalid content.");
|
||||||
@ -239,22 +238,16 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Request.GetFilesAsync(async (stream, fileName) =>
|
await Request.GetFileAsync(async (stream, fileName) =>
|
||||||
{
|
{
|
||||||
var attachmentId = Guid.NewGuid();
|
await _cipherService.AttachAsync(cipher, stream, fileName, Request.ContentLength.GetValueOrDefault(0), userId);
|
||||||
// TODO: store attachmentId + fileName reference in database
|
|
||||||
var storedFilename = $"{idGuid}_{attachmentId}";
|
|
||||||
await _attachmentStorageService.UploadAttachmentAsync(stream, storedFilename);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}/attachment/{attachmentId}")]
|
[HttpDelete("{id}/attachment/{attachmentId}")]
|
||||||
[HttpPost("{id}/attachment/{attachmentId}/delete")]
|
[HttpPost("{id}/attachment/{attachmentId}/delete")]
|
||||||
public async Task Delete(string id, string attachmentId)
|
public async Task DeleteAttachment(string id, string attachmentId)
|
||||||
{
|
{
|
||||||
// throw for now
|
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
var idGuid = new Guid(id);
|
var idGuid = new Guid(id);
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid, userId);
|
var cipher = await _cipherRepository.GetByIdAsync(idGuid, userId);
|
||||||
@ -263,11 +256,9 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var attachmentIdGuid = new Guid(attachmentId);
|
|
||||||
|
|
||||||
// TODO: check and remove attachmentId from cipher in database
|
// TODO: check and remove attachmentId from cipher in database
|
||||||
|
|
||||||
var storedFilename = $"{idGuid}_{attachmentId}";
|
var storedFilename = $"{idGuid}/{attachmentId}";
|
||||||
await _attachmentStorageService.DeleteAttachmentAsync(storedFilename);
|
await _attachmentStorageService.DeleteAttachmentAsync(storedFilename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,23 +12,38 @@ namespace Bit.Api.Utilities
|
|||||||
{
|
{
|
||||||
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
||||||
|
|
||||||
public static async Task GetFilesAsync(this HttpRequest request, Func<Stream, string, Task> callback)
|
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, Task> callback)
|
||||||
|
{
|
||||||
|
await request.GetFilesAsync(1, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task GetFilesAsync(this HttpRequest request, int? fileCount, Func<Stream, string, Task> callback)
|
||||||
{
|
{
|
||||||
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
||||||
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
||||||
var reader = new MultipartReader(boundary, request.Body);
|
var reader = new MultipartReader(boundary, request.Body);
|
||||||
|
|
||||||
var section = await reader.ReadNextSectionAsync();
|
var section = await reader.ReadNextSectionAsync();
|
||||||
while(section != null)
|
var fileNumber = 1;
|
||||||
|
while(section != null && fileNumber <= fileCount)
|
||||||
{
|
{
|
||||||
ContentDispositionHeaderValue content;
|
ContentDispositionHeaderValue content;
|
||||||
if(ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out content) &&
|
if(ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out content) &&
|
||||||
HasFileContentDisposition(content))
|
HasFileContentDisposition(content))
|
||||||
{
|
{
|
||||||
await callback(section.Body, HeaderUtilities.RemoveQuotes(content.FileName));
|
var fileName = HeaderUtilities.RemoveQuotes(content.FileName) ?? string.Empty;
|
||||||
|
await callback(section.Body, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
if(fileNumber >= fileCount)
|
||||||
|
{
|
||||||
|
section = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
section = await reader.ReadNextSectionAsync();
|
||||||
|
fileNumber++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
src/Core/Models/Data/Attachment.cs
Normal file
19
src/Core/Models/Data/Attachment.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Data
|
||||||
|
{
|
||||||
|
public class CipherAttachment
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid? UserId { get; set; }
|
||||||
|
public Guid? OrganizationId { get; set; }
|
||||||
|
public string AttachmentId { get; set; }
|
||||||
|
public string AttachmentData { get; set; }
|
||||||
|
|
||||||
|
public class MetaData
|
||||||
|
{
|
||||||
|
public long Size { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ namespace Bit.Core.Models.Table
|
|||||||
public string Data { get; set; }
|
public string Data { get; set; }
|
||||||
public string Favorites { get; set; }
|
public string Favorites { get; set; }
|
||||||
public string Folders { get; set; }
|
public string Folders { get; set; }
|
||||||
|
public string Attachments { get; set; }
|
||||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Core.Models.Data;
|
using Core.Models.Data;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Repositories
|
namespace Bit.Core.Repositories
|
||||||
{
|
{
|
||||||
@ -19,6 +20,7 @@ namespace Bit.Core.Repositories
|
|||||||
Task UpsertAsync(CipherDetails cipher);
|
Task UpsertAsync(CipherDetails cipher);
|
||||||
Task ReplaceAsync(Cipher obj, IEnumerable<Guid> collectionIds);
|
Task ReplaceAsync(Cipher obj, IEnumerable<Guid> collectionIds);
|
||||||
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
|
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
|
||||||
|
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
||||||
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||||
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
||||||
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||||
|
@ -9,6 +9,7 @@ using Dapper;
|
|||||||
using Core.Models.Data;
|
using Core.Models.Data;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Repositories.SqlServer
|
namespace Bit.Core.Repositories.SqlServer
|
||||||
{
|
{
|
||||||
@ -176,6 +177,17 @@ namespace Bit.Core.Repositories.SqlServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAttachmentAsync(CipherAttachment attachment)
|
||||||
|
{
|
||||||
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.ExecuteAsync(
|
||||||
|
$"[{Schema}].[Cipher_UpdateAttachment]",
|
||||||
|
attachment,
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||||
{
|
{
|
||||||
using(var connection = new SqlConnection(ConnectionString))
|
using(var connection = new SqlConnection(ConnectionString))
|
||||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Core.Models.Data;
|
using Core.Models.Data;
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -10,6 +11,8 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false);
|
Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false);
|
||||||
Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId);
|
Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId);
|
||||||
|
Task AttachAsync(Cipher cipher, Stream stream, string fileName, long requestLength, Guid savingUserId,
|
||||||
|
bool orgAdmin = false);
|
||||||
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||||
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
||||||
Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId);
|
Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId);
|
||||||
|
@ -6,6 +6,9 @@ using Bit.Core.Models.Table;
|
|||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Core.Models.Data;
|
using Core.Models.Data;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -18,6 +21,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
||||||
private readonly IPushNotificationService _pushService;
|
private readonly IPushNotificationService _pushService;
|
||||||
|
private readonly IAttachmentStorageService _attachmentStorageService;
|
||||||
|
|
||||||
public CipherService(
|
public CipherService(
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
@ -26,7 +30,8 @@ namespace Bit.Core.Services
|
|||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
ICollectionCipherRepository collectionCipherRepository,
|
ICollectionCipherRepository collectionCipherRepository,
|
||||||
IPushNotificationService pushService)
|
IPushNotificationService pushService,
|
||||||
|
IAttachmentStorageService attachmentStorageService)
|
||||||
{
|
{
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_folderRepository = folderRepository;
|
_folderRepository = folderRepository;
|
||||||
@ -35,6 +40,7 @@ namespace Bit.Core.Services
|
|||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_collectionCipherRepository = collectionCipherRepository;
|
_collectionCipherRepository = collectionCipherRepository;
|
||||||
_pushService = pushService;
|
_pushService = pushService;
|
||||||
|
_attachmentStorageService = attachmentStorageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false)
|
public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false)
|
||||||
@ -86,6 +92,45 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AttachAsync(Cipher cipher, Stream stream, string fileName, long requestLength,
|
||||||
|
Guid savingUserId, bool orgAdmin = false)
|
||||||
|
{
|
||||||
|
if(!orgAdmin && !(await UserCanEditAsync(cipher, savingUserId)))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You do not have permissions to edit this.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(requestLength < 1)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("No data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check available space against requestLength
|
||||||
|
|
||||||
|
var attachmentId = Utilities.CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
||||||
|
await _attachmentStorageService.UploadAttachmentAsync(stream, $"{cipher.Id}/{attachmentId}");
|
||||||
|
|
||||||
|
var data = new CipherAttachment.MetaData
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
Size = stream.Length
|
||||||
|
};
|
||||||
|
|
||||||
|
var attachment = new CipherAttachment
|
||||||
|
{
|
||||||
|
Id = cipher.Id,
|
||||||
|
UserId = cipher.UserId,
|
||||||
|
OrganizationId = cipher.OrganizationId,
|
||||||
|
AttachmentId = attachmentId,
|
||||||
|
AttachmentData = JsonConvert.SerializeObject(data)
|
||||||
|
};
|
||||||
|
|
||||||
|
await _cipherRepository.UpdateAttachmentAsync(attachment);
|
||||||
|
|
||||||
|
// push
|
||||||
|
await _pushService.PushSyncCipherUpdateAsync(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false)
|
public async Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false)
|
||||||
{
|
{
|
||||||
if(!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
|
if(!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
|
||||||
|
@ -53,8 +53,7 @@ namespace Bit.Core.Utilities
|
|||||||
services.AddSingleton<IPushNotificationService, NotificationHubPushNotificationService>();
|
services.AddSingleton<IPushNotificationService, NotificationHubPushNotificationService>();
|
||||||
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
|
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
|
||||||
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
||||||
// noop for now
|
services.AddSingleton<IAttachmentStorageService, AzureAttachmentStorageService>();
|
||||||
services.AddSingleton<IAttachmentStorageService, NoopAttachmentStorageService>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddNoopServices(this IServiceCollection services)
|
public static void AddNoopServices(this IServiceCollection services)
|
||||||
|
@ -200,5 +200,6 @@
|
|||||||
<Build Include="dbo\Stored Procedures\Organization_Create.sql" />
|
<Build Include="dbo\Stored Procedures\Organization_Create.sql" />
|
||||||
<Build Include="dbo\User Defined Types\GuidIdArray.sql" />
|
<Build Include="dbo\User Defined Types\GuidIdArray.sql" />
|
||||||
<Build Include="dbo\User Defined Types\SelectionReadOnlyArray.sql" />
|
<Build Include="dbo\User Defined Types\SelectionReadOnlyArray.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\Cipher_UpdateAttachment.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -7,6 +7,7 @@ SELECT
|
|||||||
C.[OrganizationId],
|
C.[OrganizationId],
|
||||||
C.[Type],
|
C.[Type],
|
||||||
C.[Data],
|
C.[Data],
|
||||||
|
C.[Attachments],
|
||||||
C.[CreationDate],
|
C.[CreationDate],
|
||||||
C.[RevisionDate],
|
C.[RevisionDate],
|
||||||
CASE WHEN
|
CASE WHEN
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
@Data NVARCHAR(MAX),
|
@Data NVARCHAR(MAX),
|
||||||
@Favorites NVARCHAR(MAX), -- not used
|
@Favorites NVARCHAR(MAX), -- not used
|
||||||
@Folders NVARCHAR(MAX), -- not used
|
@Folders NVARCHAR(MAX), -- not used
|
||||||
|
@Attachments NVARCHAR(MAX), -- not used
|
||||||
@CreationDate DATETIME2(7),
|
@CreationDate DATETIME2(7),
|
||||||
@RevisionDate DATETIME2(7),
|
@RevisionDate DATETIME2(7),
|
||||||
@FolderId UNIQUEIDENTIFIER,
|
@FolderId UNIQUEIDENTIFIER,
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
@Data NVARCHAR(MAX),
|
@Data NVARCHAR(MAX),
|
||||||
@Favorites NVARCHAR(MAX), -- not used
|
@Favorites NVARCHAR(MAX), -- not used
|
||||||
@Folders NVARCHAR(MAX), -- not used
|
@Folders NVARCHAR(MAX), -- not used
|
||||||
|
@Attachments NVARCHAR(MAX), -- not used
|
||||||
@CreationDate DATETIME2(7),
|
@CreationDate DATETIME2(7),
|
||||||
@RevisionDate DATETIME2(7),
|
@RevisionDate DATETIME2(7),
|
||||||
@FolderId UNIQUEIDENTIFIER,
|
@FolderId UNIQUEIDENTIFIER,
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
@Data NVARCHAR(MAX),
|
@Data NVARCHAR(MAX),
|
||||||
@Favorites NVARCHAR(MAX),
|
@Favorites NVARCHAR(MAX),
|
||||||
@Folders NVARCHAR(MAX),
|
@Folders NVARCHAR(MAX),
|
||||||
|
@Attachments NVARCHAR(MAX),
|
||||||
@CreationDate DATETIME2(7),
|
@CreationDate DATETIME2(7),
|
||||||
@RevisionDate DATETIME2(7)
|
@RevisionDate DATETIME2(7)
|
||||||
AS
|
AS
|
||||||
@ -21,6 +22,7 @@ BEGIN
|
|||||||
[Data],
|
[Data],
|
||||||
[Favorites],
|
[Favorites],
|
||||||
[Folders],
|
[Folders],
|
||||||
|
[Attachments],
|
||||||
[CreationDate],
|
[CreationDate],
|
||||||
[RevisionDate]
|
[RevisionDate]
|
||||||
)
|
)
|
||||||
@ -33,6 +35,7 @@ BEGIN
|
|||||||
@Data,
|
@Data,
|
||||||
@Favorites,
|
@Favorites,
|
||||||
@Folders,
|
@Folders,
|
||||||
|
@Attachments,
|
||||||
@CreationDate,
|
@CreationDate,
|
||||||
@RevisionDate
|
@RevisionDate
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
@Data NVARCHAR(MAX),
|
@Data NVARCHAR(MAX),
|
||||||
@Favorites NVARCHAR(MAX),
|
@Favorites NVARCHAR(MAX),
|
||||||
@Folders NVARCHAR(MAX),
|
@Folders NVARCHAR(MAX),
|
||||||
|
@Attachments NVARCHAR(MAX),
|
||||||
@CreationDate DATETIME2(7),
|
@CreationDate DATETIME2(7),
|
||||||
@RevisionDate DATETIME2(7)
|
@RevisionDate DATETIME2(7)
|
||||||
AS
|
AS
|
||||||
@ -21,6 +22,7 @@ BEGIN
|
|||||||
[Data] = @Data,
|
[Data] = @Data,
|
||||||
[Favorites] = @Favorites,
|
[Favorites] = @Favorites,
|
||||||
[Folders] = @Folders,
|
[Folders] = @Folders,
|
||||||
|
[Attachments] = @Attachments,
|
||||||
[CreationDate] = @CreationDate,
|
[CreationDate] = @CreationDate,
|
||||||
[RevisionDate] = @RevisionDate
|
[RevisionDate] = @RevisionDate
|
||||||
WHERE
|
WHERE
|
||||||
|
35
src/Sql/dbo/Stored Procedures/Cipher_UpdateAttachment.sql
Normal file
35
src/Sql/dbo/Stored Procedures/Cipher_UpdateAttachment.sql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Cipher_UpdateAttachment]
|
||||||
|
@Id UNIQUEIDENTIFIER,
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@OrganizationId UNIQUEIDENTIFIER,
|
||||||
|
@AttachmentId VARCHAR(50),
|
||||||
|
@AttachmentData NVARCHAR(MAX)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
DECLARE @AttachmentIdKey VARCHAR(50) = CONCAT('"', @AttachmentId, '"')
|
||||||
|
DECLARE @AttachmentIdPath VARCHAR(50) = CONCAT('$.', @AttachmentIdKey)
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
[dbo].[Cipher]
|
||||||
|
SET
|
||||||
|
[Attachments] =
|
||||||
|
CASE
|
||||||
|
WHEN [Attachments] IS NULL THEN
|
||||||
|
CONCAT('{', @AttachmentIdKey, ':', @AttachmentData, '}')
|
||||||
|
ELSE
|
||||||
|
JSON_MODIFY([Attachments], @AttachmentIdPath, JSON_QUERY(@AttachmentData, '$'))
|
||||||
|
END
|
||||||
|
WHERE
|
||||||
|
[Id] = @Id
|
||||||
|
|
||||||
|
IF @OrganizationId IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||||
|
END
|
||||||
|
ELSE IF @UserId IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||||
|
END
|
||||||
|
END
|
@ -6,6 +6,7 @@
|
|||||||
@Data NVARCHAR(MAX),
|
@Data NVARCHAR(MAX),
|
||||||
@Favorites NVARCHAR(MAX),
|
@Favorites NVARCHAR(MAX),
|
||||||
@Folders NVARCHAR(MAX),
|
@Folders NVARCHAR(MAX),
|
||||||
|
@Attachments NVARCHAR(MAX),
|
||||||
@CreationDate DATETIME2(7),
|
@CreationDate DATETIME2(7),
|
||||||
@RevisionDate DATETIME2(7),
|
@RevisionDate DATETIME2(7),
|
||||||
@CollectionIds AS [dbo].[GuidIdArray] READONLY
|
@CollectionIds AS [dbo].[GuidIdArray] READONLY
|
||||||
@ -20,7 +21,7 @@ BEGIN
|
|||||||
[OrganizationId] = @OrganizationId,
|
[OrganizationId] = @OrganizationId,
|
||||||
[Data] = @Data,
|
[Data] = @Data,
|
||||||
[RevisionDate] = @RevisionDate
|
[RevisionDate] = @RevisionDate
|
||||||
-- No need to update CreationDate, Favorites, Folders, or Type since that data will not change
|
-- No need to update Attachments, CreationDate, Favorites, Folders, or Type since that data will not change
|
||||||
WHERE
|
WHERE
|
||||||
[Id] = @Id
|
[Id] = @Id
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
[Data] NVARCHAR (MAX) NOT NULL,
|
[Data] NVARCHAR (MAX) NOT NULL,
|
||||||
[Favorites] NVARCHAR (MAX) NULL,
|
[Favorites] NVARCHAR (MAX) NULL,
|
||||||
[Folders] NVARCHAR (MAX) NULL,
|
[Folders] NVARCHAR (MAX) NULL,
|
||||||
|
[Attachments] NVARCHAR (MAX) NULL,
|
||||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||||
[RevisionDate] DATETIME2 (7) NOT NULL,
|
[RevisionDate] DATETIME2 (7) NOT NULL,
|
||||||
CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),
|
CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user