public async Task <string> CreateTemplate(long id, AuthorInfo authorInfo, ITemplateDescriptor templateDescriptor) { if (id == 0) { throw new ArgumentException("Template Id must be set", nameof(id)); } var redLock = await _distributedLockManager.CreateLockAsync(id); try { if (await _templatesStorageReader.IsTemplateExists(id)) { throw new ObjectAlreadyExistsException(id); } await PutTemplate(id, authorInfo, templateDescriptor); // ceph does not return version-id response header, so we need to do another request to get version return(await _templatesStorageReader.GetTemplateLatestVersion(id)); } finally { redLock?.Dispose(); } }
private async Task <string> PutTemplate(long id, AuthorInfo authorInfo, ITemplateDescriptor templateDescriptor, int?prevVersionIndex = null) { await VerifyElementDescriptorsConsistency(templateDescriptor.Elements); var versionIndex = prevVersionIndex + 1 ?? 0; var json = JsonConvert.SerializeObject(templateDescriptor, SerializerSettings.Default); var template = new Template { Id = id, VersionIndex = versionIndex, Data = json, Author = authorInfo.Author, AuthorLogin = authorInfo.AuthorLogin, AuthorName = authorInfo.AuthorName, LastModified = DateTime.UtcNow }; using (var scope = await _context.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead)) { await _context.Templates.AddAsync(template); await _context.SaveChangesAsync(); scope.Commit(); } return(template.VersionId); }
public async Task <IActionResult> Create( long id, [FromHeader(Name = Http.HeaderNames.AmsAuthor)] string author, [FromHeader(Name = Http.HeaderNames.AmsAuthorLogin)] string authorLogin, [FromHeader(Name = Http.HeaderNames.AmsAuthorName)] string authorName, [FromBody] ITemplateDescriptor templateDescriptor) { return(await CreateInternal(id, author, authorLogin, authorName, templateDescriptor, GenerateTemplateErrorJson)); }
public async Task <IActionResult> Modify( long id, ApiVersion apiVersion, [FromHeader(Name = HeaderNames.IfMatch)] string ifMatch, [FromHeader(Name = Http.HeaderNames.AmsAuthor)] string author, [FromHeader(Name = Http.HeaderNames.AmsAuthorLogin)] string authorLogin, [FromHeader(Name = Http.HeaderNames.AmsAuthorName)] string authorName, [FromBody] ITemplateDescriptor templateDescriptor) => await ModifyInternal(id, ifMatch, apiVersion, author, authorLogin, authorName, templateDescriptor, GenerateTemplateErrorJson);
public static IReadOnlyCollection <int> GetBinaryElementTemplateCodes(this ITemplateDescriptor templateDescriptor) { return(templateDescriptor.Elements .Where(x => x.Type == ElementDescriptorType.Article || x.Type == ElementDescriptorType.BitmapImage || x.Type == ElementDescriptorType.VectorImage) .Select(x => x.TemplateCode) .ToList()); }
private async Task <IActionResult> ModifyInternal( long id, string ifMatch, string author, string authorLogin, string authorName, ITemplateDescriptor templateDescriptor, Func <TemplateValidationException, JToken> errorGenerator) { if (string.IsNullOrEmpty(ifMatch)) { return(BadRequest($"'{HeaderNames.IfMatch}' request header must be specified.")); } if (string.IsNullOrEmpty(author) || string.IsNullOrEmpty(authorLogin) || string.IsNullOrEmpty(authorName)) { return(BadRequest( $"'{Http.HeaderNames.AmsAuthor}', '{Http.HeaderNames.AmsAuthorLogin}' and '{Http.HeaderNames.AmsAuthorName}' " + "request headers must be specified.")); } if (templateDescriptor == null) { return(BadRequest("Template descriptor must be set.")); } try { var latestVersionId = await _templatesManagementService.ModifyTemplate( id, ifMatch.Trim('"'), new AuthorInfo(author, authorLogin, authorName), templateDescriptor); var url = Url.AbsoluteAction("GetVersion", "Templates", new { id, versionId = latestVersionId }); Response.Headers[HeaderNames.ETag] = $"\"{latestVersionId}\""; return(NoContent(url)); } catch (ObjectNotFoundException) { return(NotFound()); } catch (TemplateValidationException ex) { return(Unprocessable(errorGenerator(ex))); } catch (LockAlreadyExistsException) { return(Locked("Simultaneous modification of template")); } catch (ConcurrencyException) { return(PreconditionFailed()); } }
private async Task <IActionResult> CreateInternal( long id, string author, string authorLogin, string authorName, ApiVersion apiVersion, ITemplateDescriptor templateDescriptor, Func <TemplateValidationException, JToken> errorGenerator) { if (string.IsNullOrEmpty(author) || string.IsNullOrEmpty(authorLogin) || string.IsNullOrEmpty(authorName)) { return(BadRequest( $"'{Http.HeaderNames.AmsAuthor}', '{Http.HeaderNames.AmsAuthorLogin}' and '{Http.HeaderNames.AmsAuthorName}' " + "request headers must be specified.")); } if (TryGetModelErrors(out var errors)) { return(BadRequest(errors)); } try { var versionId = await _templatesManagementService.CreateTemplate(id, new AuthorInfo(author, authorLogin, authorName), templateDescriptor); Response.Headers[HeaderNames.ETag] = $"\"{versionId}\""; var routeValues = new Dictionary <string, string> { ["api-version"] = apiVersion.ToString(), [nameof(id)] = id.ToString(), [nameof(versionId)] = versionId }; return(CreatedAtAction(nameof(GetVersion), routeValues, null)); } catch (ObjectAlreadyExistsException) { return(Conflict("Template with the same id already exists")); } catch (LockAlreadyExistsException) { return(Locked("Simultaneous creation of template with the same id")); } catch (TemplateValidationException ex) { return(Unprocessable(errorGenerator(ex))); } catch (InputDataValidationException ex) { return(BadRequest(ex.Message)); } }
public async Task <string> CreateTemplate(long id, AuthorInfo authorInfo, ITemplateDescriptor templateDescriptor) { if (id == default) { throw new InputDataValidationException("Template Id must be set"); } using (await _distributedLockManager.AcquireLockAsync(id)) { if (await _templatesStorageReader.IsTemplateExists(id)) { throw new ObjectAlreadyExistsException(id); } return(await PutTemplate(id, authorInfo, templateDescriptor)); } }
private async Task <IActionResult> CreateInternal( long id, string author, string authorLogin, string authorName, ITemplateDescriptor templateDescriptor, Func <TemplateValidationException, JToken> errorGenerator) { if (string.IsNullOrEmpty(author) || string.IsNullOrEmpty(authorLogin) || string.IsNullOrEmpty(authorName)) { return(BadRequest( $"'{Http.HeaderNames.AmsAuthor}', '{Http.HeaderNames.AmsAuthorLogin}' and '{Http.HeaderNames.AmsAuthorName}' " + "request headers must be specified.")); } if (templateDescriptor == null) { return(BadRequest("Template descriptor must be set.")); } try { var versionId = await _templatesManagementService.CreateTemplate(id, new AuthorInfo(author, authorLogin, authorName), templateDescriptor); var url = Url.AbsoluteAction("GetVersion", "Templates", new { id, versionId }); Response.Headers[HeaderNames.ETag] = $"\"{versionId}\""; return(Created(url, null)); } catch (ObjectAlreadyExistsException) { return(Conflict("Template with the same id already exists")); } catch (LockAlreadyExistsException) { return(Locked("Simultaneous creation of template with the same id")); } catch (TemplateValidationException ex) { return(Unprocessable(errorGenerator(ex))); } }
private async Task PutTemplate(long id, AuthorInfo authorInfo, ITemplateDescriptor templateDescriptor) { await VerifyElementDescriptorsConsistency(templateDescriptor.Elements); var putRequest = new PutObjectRequest { Key = id.ToString(), BucketName = _bucketName, ContentType = ContentType.Json, ContentBody = JsonConvert.SerializeObject(templateDescriptor, SerializerSettings.Default), CannedACL = S3CannedACL.PublicRead, }; var metadataWrapper = MetadataCollectionWrapper.For(putRequest.Metadata); metadataWrapper.Write(MetadataElement.Author, authorInfo.Author); metadataWrapper.Write(MetadataElement.AuthorLogin, authorInfo.AuthorLogin); metadataWrapper.Write(MetadataElement.AuthorName, authorInfo.AuthorName); await _s3Client.PutObjectAsync(putRequest); }
public static IReadOnlyCollection <UploadUrl> Generate(ITemplateDescriptor templateDescriptor, Func <int, string> urlComposer) { return(templateDescriptor.GetBinaryElementTemplateCodes() .Select(x => new UploadUrl(x, urlComposer(x))) .ToList()); }
public async Task <string> ModifyTemplate(long id, string versionId, AuthorInfo authorInfo, ITemplateDescriptor templateDescriptor) { if (id == 0) { throw new ArgumentException("Template Id must be set", nameof(id)); } if (string.IsNullOrEmpty(versionId)) { throw new ArgumentException("VersionId must be set", nameof(versionId)); } using (await _distributedLockManager.AcquireLockAsync(id)) { if (!await _templatesStorageReader.IsTemplateExists(id)) { throw new ObjectNotFoundException($"Template '{id}' does not exist"); } var latestVersionId = await _templatesStorageReader.GetTemplateLatestVersion(id); if (!versionId.Equals(latestVersionId, StringComparison.Ordinal)) { throw new ConcurrencyException(id, versionId, latestVersionId); } await PutTemplate(id, authorInfo, templateDescriptor); // ceph does not return version-id response header, so we need to do another request to get version return(await _templatesStorageReader.GetTemplateLatestVersion(id)); } }
private async Task <IActionResult> ModifyInternal( long id, string ifMatch, ApiVersion apiVersion, string author, string authorLogin, string authorName, ITemplateDescriptor templateDescriptor, Func <TemplateValidationException, JToken> errorGenerator) { if (string.IsNullOrEmpty(ifMatch)) { return(BadRequest($"'{HeaderNames.IfMatch}' request header must be specified.")); } if (string.IsNullOrEmpty(author) || string.IsNullOrEmpty(authorLogin) || string.IsNullOrEmpty(authorName)) { return(BadRequest( $"'{Http.HeaderNames.AmsAuthor}', '{Http.HeaderNames.AmsAuthorLogin}' and '{Http.HeaderNames.AmsAuthorName}' " + "request headers must be specified.")); } if (TryGetModelErrors(out var errors)) { return(BadRequest(errors)); } try { var versionId = await _templatesManagementService.ModifyTemplate( id, ifMatch.Trim('"'), new AuthorInfo(author, authorLogin, authorName), templateDescriptor); var routeValues = new Dictionary <string, string> { ["api-version"] = apiVersion.ToString(), [nameof(id)] = id.ToString(), [nameof(versionId)] = versionId }; var url = Url.AbsoluteAction("GetVersion", "Templates", routeValues); Response.Headers[HeaderNames.ETag] = $"\"{versionId}\""; return(NoContent(url)); } catch (ObjectNotFoundException) { return(NotFound()); } catch (TemplateValidationException ex) { return(Unprocessable(errorGenerator(ex))); } catch (LockAlreadyExistsException) { return(Locked("Simultaneous modification of template")); } catch (ConcurrencyException) { return(PreconditionFailed()); } catch (InputDataValidationException ex) { return(BadRequest(ex.Message)); } }
public async Task TestTemplateDeserialization(string apiVersion) { const long TemplateId = 100500L; const string CreatedTemplateVersion = "some_version_id"; var authorInfo = new AuthorInfo("id", "login", "name"); ITemplateDescriptor receivedDescriptor = null; _mockTemplatesManagementService.Reset(); _mockTemplatesManagementService.Setup(x => x.CreateTemplate(It.IsAny <long>(), It.IsAny <AuthorInfo>(), It.IsAny <ITemplateDescriptor>())) .Callback <long, AuthorInfo, ITemplateDescriptor>((id, author, descriptor) => receivedDescriptor = descriptor) .ReturnsAsync(CreatedTemplateVersion); const string ObjectJson = @"{ ""properties"": { ""baz"": 123, ""foo"": ""bar"" }, ""elements"": [{ ""type"": ""plainText"", ""templateCode"": 911, ""id"": 100500, ""properties"": { ""foo"": ""bar"", ""baz"": [ 321, 456 ] }, ""constraints"": { ""unspecified"": { ""maxSymbols"": 10, ""maxSymbolsPerWord"": null, ""maxLines"": 2 } } }] }"; using (var httpContent = new StringContent(ObjectJson, Encoding.UTF8, NuClear.VStore.Http.ContentType.Json)) { using (var request = new HttpRequestMessage(HttpMethod.Post, $"/api/{apiVersion}/templates/{TemplateId}")) { request.Content = httpContent; request.Headers.Add(NuClear.VStore.Http.HeaderNames.AmsAuthor, authorInfo.Author); request.Headers.Add(NuClear.VStore.Http.HeaderNames.AmsAuthorLogin, authorInfo.AuthorLogin); request.Headers.Add(NuClear.VStore.Http.HeaderNames.AmsAuthorName, authorInfo.AuthorName); using (var response = await _client.SendAsync(request)) { Assert.Equal(HttpStatusCode.Created, response.StatusCode); Assert.NotNull(response.Headers.Location); Assert.Equal($"/api/{apiVersion}/templates/{TemplateId}/{CreatedTemplateVersion}", response.Headers.Location.PathAndQuery); Assert.Equal($"\"{CreatedTemplateVersion}\"", response.Headers.ETag.Tag); Assert.NotNull(receivedDescriptor); Assert.Equal(JObject.Parse(@"{""foo"": ""bar"", ""baz"": 123}"), receivedDescriptor.Properties, JTokenEqualityComparer); Assert.Single(receivedDescriptor.Elements); var element = receivedDescriptor.Elements.First(); Assert.Equal(ElementDescriptorType.PlainText, element.Type); Assert.Equal(911, element.TemplateCode); Assert.Equal(JObject.Parse(@"{""foo"": ""bar"", ""baz"": [ 321, 456 ]}"), element.Properties, JTokenEqualityComparer); Assert.Single(element.Constraints); var constraintSetItem = element.Constraints.First <ConstraintSetItem>(); Assert.IsType <PlainTextElementConstraints>(constraintSetItem.ElementConstraints); var constraints = (PlainTextElementConstraints)constraintSetItem.ElementConstraints; Assert.Equal(new PlainTextElementConstraints { MaxLines = 2, MaxSymbols = 10, MaxSymbolsPerWord = null }, constraints); _mockTemplatesManagementService.Verify(x => x.CreateTemplate(TemplateId, It.Is <AuthorInfo>(a => a.Author == authorInfo.Author && a.AuthorLogin == authorInfo.AuthorLogin && a.AuthorName == authorInfo.AuthorName), It.IsAny <ITemplateDescriptor>()), Times.Exactly(1)); } } } _mockTemplatesManagementService.Reset(); }
public async Task <string> ModifyTemplate(long id, string versionId, AuthorInfo authorInfo, ITemplateDescriptor templateDescriptor) { if (id == default) { throw new InputDataValidationException("Template Id must be set"); } if (string.IsNullOrEmpty(versionId)) { throw new InputDataValidationException("VersionId must be set"); } using (await _distributedLockManager.AcquireLockAsync(id)) { var(latestVersionId, latestVersionIndex) = await _templatesStorageReader.GetTemplateLatestVersion(id); if (!versionId.Equals(latestVersionId, StringComparison.Ordinal)) { throw new ConcurrencyException(id, versionId, latestVersionId); } return(await PutTemplate(id, authorInfo, templateDescriptor, latestVersionIndex)); } }
public static IReadOnlyCollection <int> GetBinaryElementTemplateCodes(this ITemplateDescriptor templateDescriptor) => templateDescriptor.Elements .GetBinaryElements() .Select(x => x.TemplateCode) .ToList();