diff --git a/Doppler.HtmlEditorApi.Test/Repositories.DopplerDb/DopplerTemplateRepositoryTest.cs b/Doppler.HtmlEditorApi.Test/Repositories.DopplerDb/DopplerTemplateRepositoryTest.cs
index d7de2855..b1e4cbf5 100644
--- a/Doppler.HtmlEditorApi.Test/Repositories.DopplerDb/DopplerTemplateRepositoryTest.cs
+++ b/Doppler.HtmlEditorApi.Test/Repositories.DopplerDb/DopplerTemplateRepositoryTest.cs
@@ -135,4 +135,166 @@ public async Task UpdateTemplate_should_execute_the_right_query_and_parameters()
dbQuery.VerifySqlQueryContains("PreviewImage = @PreviewImage");
dbQuery.VerifySqlQueryContains("Name = @Name");
}
+
+ [Fact]
+ public async Task CreatePrivateTemplate_should_execute_the_right_query_and_parameters()
+ {
+ // Arrange
+ var dbContextMock = new Mock();
+ dbContextMock.Setup(x =>
+ x.ExecuteAsync(It.IsAny>()))
+ .ReturnsAsync(new CreatePrivateTemplateDbQuery.Result() { NewTemplateId = 123 });
+
+ var sut = new DopplerTemplateRepository(dbContextMock.Object);
+
+ var previewImage = "NEW PREVIEW IMAGE";
+ var name = "NEW NAME";
+ var htmlComplete = "NEW HTML CONTENT";
+ var meta = "{\"test\":\"NEW META\"}";
+ var templateModel = new TemplateModel(
+ TemplateId: 0,
+ IsPublic: false,
+ PreviewImage: previewImage,
+ Name: name,
+ Content: new UnlayerTemplateContentData(
+ HtmlComplete: htmlComplete,
+ Meta: meta));
+ var accountName = "test@test";
+
+ // Act
+ var result = await sut.CreatePrivateTemplate(accountName, templateModel);
+
+ // Assert
+ var dbQuery = dbContextMock.VerifyAndGetSingleItemDbQuery();
+ dbQuery.VerifySqlParametersContain("EditorType", _unlayerEditorType);
+ dbQuery.VerifySqlParametersContain("HtmlCode", htmlComplete);
+ dbQuery.VerifySqlParametersContain("Meta", meta);
+ dbQuery.VerifySqlParametersContain("PreviewImage", previewImage);
+ dbQuery.VerifySqlParametersContain("Name", name);
+ dbQuery.VerifySqlQueryContains("FROM [User] u");
+ dbQuery.VerifySqlQueryContains("WHERE u.Email = @AccountName");
+ dbQuery.VerifySqlQueryContains("INSERT INTO Template (IdUser, EditorType, HtmlCode, Meta, PreviewImage, Name, Active)");
+ dbQuery.VerifySqlQueryContains("u.IdUser AS IdUser");
+ dbQuery.VerifySqlQueryContains("@EditorType AS EditorType");
+ dbQuery.VerifySqlQueryContains("@HtmlCode AS HtmlCode");
+ dbQuery.VerifySqlQueryContains("@Meta AS Meta");
+ dbQuery.VerifySqlQueryContains("@PreviewImage AS PreviewImage");
+ dbQuery.VerifySqlQueryContains("@Name AS Name");
+ dbQuery.VerifySqlQueryContains("1 AS Active");
+ dbQuery.VerifySqlQueryContains("OUTPUT INSERTED.idTemplate AS NewTemplateId");
+ }
+
+ [Fact]
+ public async Task CreatePrivateTemplate_throw_when_there_are_no_rows_inserted()
+ {
+ // Arrange
+ var dbContextMock = new Mock();
+ dbContextMock.Setup(x =>
+ x.ExecuteAsync(It.IsAny>()))
+ .ReturnsAsync((CreatePrivateTemplateDbQuery.Result)null);
+
+ var sut = new DopplerTemplateRepository(dbContextMock.Object);
+
+ var templateModel = new TemplateModel(
+ TemplateId: 0,
+ IsPublic: false,
+ PreviewImage: "NEW PREVIEW IMAGE",
+ Name: "NEW NAME",
+ Content: new UnlayerTemplateContentData(
+ HtmlComplete: "NEW HTML CONTENT",
+ Meta: "{\"test\":\"NEW META\"}"));
+ var accountName = "test@test";
+
+ // Act
+ var action = async () => await sut.CreatePrivateTemplate(accountName, templateModel);
+
+ // Assert
+ var exception = await Assert.ThrowsAsync(action);
+ Assert.Equal("accountName", exception.ParamName);
+ Assert.Equal($"Account with name '{accountName}' does not exist. (Parameter 'accountName')", exception.Message);
+ }
+
+ [Fact]
+ public async Task CreatePrivateTemplate_should_throw_when_TemplateId_is_not_0()
+ {
+ // Arrange
+ var templateId = 123;
+ var dbContextMock = new Mock();
+
+ var sut = new DopplerTemplateRepository(dbContextMock.Object);
+
+ var templateModel = new TemplateModel(
+ TemplateId: templateId,
+ IsPublic: false,
+ PreviewImage: "NEW PREVIEW IMAGE",
+ Name: "NEW NAME",
+ Content: new UnlayerTemplateContentData(
+ HtmlComplete: "NEW HTML CONTENT",
+ Meta: "{\"test\":\"NEW META\"}"));
+ var accountName = "test@test";
+
+ // Act
+ var action = async () => await sut.CreatePrivateTemplate(accountName, templateModel);
+
+ // Assert
+ var exception = await Assert.ThrowsAsync(action);
+ Assert.Equal("templateModel", exception.ParamName);
+ Assert.Equal("TemplateId should not be set to create a new private template (Parameter 'templateModel')", exception.Message);
+ dbContextMock.VerifyNoOtherCalls();
+ }
+
+ [Fact]
+ public async Task CreatePrivateTemplate_should_throw_when_IsPublic_is_true()
+ {
+ // Arrange
+ var isPublic = true;
+ var dbContextMock = new Mock();
+
+ var sut = new DopplerTemplateRepository(dbContextMock.Object);
+
+ var templateModel = new TemplateModel(
+ TemplateId: 0,
+ IsPublic: isPublic,
+ PreviewImage: "NEW PREVIEW IMAGE",
+ Name: "NEW NAME",
+ Content: new UnlayerTemplateContentData(
+ HtmlComplete: "NEW HTML CONTENT",
+ Meta: "{\"test\":\"NEW META\"}"));
+ var accountName = "test@test";
+
+ // Act
+ var action = async () => await sut.CreatePrivateTemplate(accountName, templateModel);
+
+ // Assert
+ var exception = await Assert.ThrowsAsync(action);
+ Assert.Equal("templateModel", exception.ParamName);
+ Assert.Equal("IsPublic should be false to create a new private template (Parameter 'templateModel')", exception.Message);
+ dbContextMock.VerifyNoOtherCalls();
+ }
+
+ [Fact]
+ public async Task CreatePrivateTemplate_should_throw_when_the_template_is_not_unlayer_one()
+ {
+ // Arrange
+ var content = new UnknownTemplateContentData(_msEditorType);
+ var dbContextMock = new Mock();
+
+ var sut = new DopplerTemplateRepository(dbContextMock.Object);
+
+ var templateModel = new TemplateModel(
+ TemplateId: 0,
+ IsPublic: false,
+ PreviewImage: "NEW PREVIEW IMAGE",
+ Name: "NEW NAME",
+ Content: content);
+ var accountName = "test@test";
+
+ // Act
+ var action = async () => await sut.CreatePrivateTemplate(accountName, templateModel);
+
+ // Assert
+ var exception = await Assert.ThrowsAsync(action);
+ Assert.Equal("Unsupported template content type Doppler.HtmlEditorApi.Domain.UnknownTemplateContentData", exception.Message);
+ dbContextMock.VerifyNoOtherCalls();
+ }
}
diff --git a/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/GetTemplateTest.cs b/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/GetTemplateTest.cs
index 411ad559..b695d0de 100644
--- a/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/GetTemplateTest.cs
+++ b/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/GetTemplateTest.cs
@@ -195,11 +195,10 @@ public async Task GET_template_should_error_when_template_content_is_mseditor(st
var responseContentJson = responseContentDoc.RootElement;
// Assert
- Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
- Assert.Equal("https://httpstatuses.io/500", responseContentJson.GetProperty("type").GetString());
- Assert.Equal("Internal Server Error", responseContentJson.GetProperty("title").GetString());
+ Assert.Equal(HttpStatusCode.NotImplemented, response.StatusCode);
+ Assert.Equal("Not Implemented", responseContentJson.GetProperty("title").GetString());
Assert.Equal("Unsupported template content type Doppler.HtmlEditorApi.Domain.UnknownTemplateContentData", responseContentJson.GetProperty("detail").GetString());
- Assert.Equal(500, responseContentJson.GetProperty("status").GetInt32());
+ Assert.Equal(501, responseContentJson.GetProperty("status").GetInt32());
}
[Theory]
@@ -284,6 +283,8 @@ public async Task GET_template_should_return_not_found_when_template_is_public(s
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- Assert.Equal($"It is a public template, use /shared/templates/{idTemplate}", responseContent);
+ Assert.Contains($"\"detail\":\"It is a public template, use /shared/templates/{idTemplate}\"", responseContent);
+ Assert.Contains("\"title\":\"Not Found\"", responseContent);
+ Assert.Contains("\"status\":404", responseContent);
}
}
diff --git a/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/PutTemplateTest.cs b/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/PutTemplateTest.cs
index 1928b126..c8244447 100644
--- a/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/PutTemplateTest.cs
+++ b/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/PutTemplateTest.cs
@@ -232,7 +232,9 @@ public async Task PUT_template_should_return_404_when_template_does_not_exist()
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- Assert.Equal("Template not found, belongs to a different account, or it is a public template.", responseContent);
+ Assert.Contains("Template not found, belongs to a different account, or it is a public template", responseContent);
+ Assert.Contains("\"title\":\"Not Found\"", responseContent);
+ Assert.Contains("\"status\":404", responseContent);
}
[Fact]
@@ -273,7 +275,9 @@ public async Task PUT_template_should_return_404_when_template_is_public()
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- Assert.Equal("Template not found, belongs to a different account, or it is a public template.", responseContent);
+ Assert.Contains("\"detail\":\"Template not found, belongs to a different account, or it is a public template.\"", responseContent);
+ Assert.Contains("\"title\":\"Not Found\"", responseContent);
+ Assert.Contains("\"status\":404", responseContent);
}
[Fact]
diff --git a/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/from-template/_/PostTemplateFromTemplateTest.cs b/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/from-template/_/PostTemplateFromTemplateTest.cs
new file mode 100644
index 00000000..e5431568
--- /dev/null
+++ b/Doppler.HtmlEditorApi.Test/http/accounts/_/templates/_/from-template/_/PostTemplateFromTemplateTest.cs
@@ -0,0 +1,216 @@
+using System.Net;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Doppler.HtmlEditorApi.DataAccess;
+using Doppler.HtmlEditorApi.Domain;
+using Doppler.HtmlEditorApi.Repositories;
+using Doppler.HtmlEditorApi.Repositories.DopplerDb.Queries;
+using Doppler.HtmlEditorApi.Test.Utils;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Moq;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Doppler.HtmlEditorApi;
+
+public class PostTemplateFromTemplateTest : IClassFixture>
+{
+ private readonly WebApplicationFactory _factory;
+ private readonly ITestOutputHelper _output;
+
+ public PostTemplateFromTemplateTest(WebApplicationFactory factory, ITestOutputHelper output)
+ {
+ _factory = factory;
+ _output = output;
+ }
+
+ [Theory]
+ [InlineData("/accounts/x@x.com/templates/from-template/456", HttpStatusCode.Unauthorized)]
+ public async Task GET_template_should_require_token(string url, HttpStatusCode expectedStatusCode)
+ {
+ // Arrange
+ var client = _factory.CreateClient(new WebApplicationFactoryClientOptions());
+
+ // Act
+ var response = await client.PostAsync(url, null);
+ _output.WriteLine(response.GetHeadersAsString());
+
+ // Assert
+ Assert.Equal(expectedStatusCode, response.StatusCode);
+ Assert.Equal("Bearer", response.Headers.WwwAuthenticate.ToString());
+ }
+
+ [Theory]
+ [InlineData("/accounts/x@x.com/templates/from-template/456", TestUsersData.TOKEN_TEST1_EXPIRE_20330518, HttpStatusCode.Forbidden)]
+ [InlineData("/accounts/x@x.com/templates/from-template/456", TestUsersData.TOKEN_EXPIRE_20330518, HttpStatusCode.Forbidden)]
+ public async Task GET_template_should_not_accept_the_token_of_another_account(string url, string token, HttpStatusCode expectedStatusCode)
+ {
+ // Arrange
+ var client = _factory.CreateClient(new WebApplicationFactoryClientOptions());
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
+
+ // Act
+ var response = await client.PostAsync(url, null);
+ _output.WriteLine(response.GetHeadersAsString());
+
+ // Assert
+ Assert.Equal(expectedStatusCode, response.StatusCode);
+ }
+
+ [Theory]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/456", TestUsersData.TOKEN_TEST1_EXPIRE_20010908, HttpStatusCode.Unauthorized)]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/456", TestUsersData.TOKEN_SUPERUSER_EXPIRE_20010908, HttpStatusCode.Unauthorized)]
+ public async Task GET_template_should_not_accept_a_expired_token(string url, string token, HttpStatusCode expectedStatusCode)
+ {
+ // Arrange
+ var client = _factory.CreateClient(new WebApplicationFactoryClientOptions());
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
+
+ // Act
+ var response = await client.PostAsync(url, null);
+ _output.WriteLine(response.GetHeadersAsString());
+
+ // Assert
+ Assert.Equal(expectedStatusCode, response.StatusCode);
+ Assert.Contains("Bearer", response.Headers.WwwAuthenticate.ToString());
+ Assert.Contains("invalid_token", response.Headers.WwwAuthenticate.ToString());
+ Assert.Contains("token expired", response.Headers.WwwAuthenticate.ToString());
+ }
+
+ [Theory]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/459", TestUsersData.TOKEN_TEST1_EXPIRE_20330518, TestUsersData.EMAIL_TEST1, 459)]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/459", TestUsersData.TOKEN_SUPERUSER_EXPIRE_20330518, TestUsersData.EMAIL_TEST1, 459)]
+ [InlineData("/accounts/otro@test.com/templates/from-template/459", TestUsersData.TOKEN_SUPERUSER_EXPIRE_20330518, "otro@test.com", 459)]
+ public async Task GET_template_should_accept_right_tokens_and_return_404_when_not_exist(string url, string token, string accountName, int baseTemplateId)
+ {
+ // Arrange
+ TemplateModel templateModel = null;
+ var repositoryMock = new Mock();
+
+ repositoryMock
+ .Setup(x => x.GetOwnOrPublicTemplate(accountName, baseTemplateId))
+ .ReturnsAsync(templateModel);
+
+ var client = _factory.CreateSutClient(
+ serviceToOverride1: repositoryMock.Object,
+ token: token);
+
+ // Act
+ var response = await client.PostAsync(url, null);
+ _output.WriteLine(response.GetHeadersAsString());
+
+ // Assert
+ repositoryMock.VerifyAll();
+ repositoryMock.VerifyNoOtherCalls();
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Theory]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/459", TestUsersData.TOKEN_TEST1_EXPIRE_20330518, TestUsersData.EMAIL_TEST1, 459)]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/459", TestUsersData.TOKEN_SUPERUSER_EXPIRE_20330518, TestUsersData.EMAIL_TEST1, 459)]
+ [InlineData("/accounts/otro@test.com/templates/from-template/459", TestUsersData.TOKEN_SUPERUSER_EXPIRE_20330518, "otro@test.com", 459)]
+ public async Task GET_template_should_accept_right_tokens_and_call_repository_and_return_createdResourceId(string url, string token, string accountName, int baseTemplateId)
+ {
+ // Arrange
+ const int unlayerEditorType = 5;
+ var newTemplateId = 8;
+ var expectedSchemaVersion = 999;
+ var isPublic = true;
+ var previewImage = "PreviewImage";
+ var name = "Name";
+ var htmlComplete = "";
+ var meta = JsonSerializer.Serialize(new
+ {
+ schemaVersion = expectedSchemaVersion
+ });
+
+ var dbContextMock = new Mock();
+
+ dbContextMock.Setup(x =>
+ x.ExecuteAsync(new GetTemplateByIdWithStatusDbQuery(baseTemplateId, accountName)))
+ .ReturnsAsync(new GetTemplateByIdWithStatusDbQuery.Result()
+ {
+ IsPublic = isPublic,
+ EditorType = unlayerEditorType,
+ HtmlCode = htmlComplete,
+ Meta = meta,
+ PreviewImage = previewImage,
+ Name = name,
+ });
+
+ dbContextMock.Setup(x =>
+ x.ExecuteAsync(new CreatePrivateTemplateDbQuery(
+ accountName,
+ unlayerEditorType,
+ htmlComplete,
+ meta,
+ previewImage,
+ name)))
+ .ReturnsAsync(new CreatePrivateTemplateDbQuery.Result()
+ {
+ NewTemplateId = newTemplateId
+ });
+
+ var client = _factory.CreateSutClient(
+ serviceToOverride1: dbContextMock.Object,
+ token: token);
+
+ // Act
+ var response = await client.PostAsync(url, null);
+ var headers = response.GetHeadersAsString();
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ dbContextMock.VerifyAll();
+ dbContextMock.VerifyNoOtherCalls();
+ Assert.Matches($$"""{"createdResourceId":{{newTemplateId}}}""", responseContent);
+ Assert.Contains($"Location: http://localhost/accounts/{accountName}/templates/{newTemplateId}", headers);
+ }
+
+ [Theory]
+ [InlineData($"/accounts/{TestUsersData.EMAIL_TEST1}/templates/from-template/459", TestUsersData.TOKEN_TEST1_EXPIRE_20330518, TestUsersData.EMAIL_TEST1, 459)]
+ public async Task GET_template_should_error_when_base_template_content_is_mseditor(string url, string token, string accountName, int baseTemplateId)
+ {
+ // Arrange
+ var editorType = 4;
+ var isPublic = true;
+ var previewImage = "PreviewImage";
+ var name = "Name";
+ var contentData = new UnknownTemplateContentData(editorType);
+
+ var templateModel = new TemplateModel(
+ TemplateId: baseTemplateId,
+ IsPublic: isPublic,
+ PreviewImage: previewImage,
+ Name: name,
+ Content: contentData);
+
+ var repositoryMock = new Mock();
+
+ repositoryMock
+ .Setup(x => x.GetOwnOrPublicTemplate(accountName, baseTemplateId))
+ .ReturnsAsync(templateModel);
+
+ var client = _factory.CreateSutClient(
+ serviceToOverride1: repositoryMock.Object,
+ token: token);
+
+ // Act
+ var response = await client.PostAsync(url, null);
+ _output.WriteLine(response.GetHeadersAsString());
+ var responseContent = await response.Content.ReadAsStringAsync();
+ using var responseContentDoc = JsonDocument.Parse(responseContent);
+ var responseContentJson = responseContentDoc.RootElement;
+
+ // Assert
+ repositoryMock.VerifyAll();
+ repositoryMock.VerifyNoOtherCalls();
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Equal("https://httpstatuses.io/500", responseContentJson.GetProperty("type").GetString());
+ Assert.Equal("Internal Server Error", responseContentJson.GetProperty("title").GetString());
+ Assert.Equal("Unsupported template content type Doppler.HtmlEditorApi.Domain.UnknownTemplateContentData", responseContentJson.GetProperty("detail").GetString());
+ Assert.Equal(500, responseContentJson.GetProperty("status").GetInt32());
+ }
+}
diff --git a/Doppler.HtmlEditorApi/ApiModels/ResourceCreated.cs b/Doppler.HtmlEditorApi/ApiModels/ResourceCreated.cs
new file mode 100644
index 00000000..99f6b62b
--- /dev/null
+++ b/Doppler.HtmlEditorApi/ApiModels/ResourceCreated.cs
@@ -0,0 +1,3 @@
+namespace Doppler.HtmlEditorApi.ApiModels;
+
+public record ResourceCreated(int createdResourceId);
diff --git a/Doppler.HtmlEditorApi/Controllers/TemplatesController.cs b/Doppler.HtmlEditorApi/Controllers/TemplatesController.cs
index 995bd5b1..95b3a954 100644
--- a/Doppler.HtmlEditorApi/Controllers/TemplatesController.cs
+++ b/Doppler.HtmlEditorApi/Controllers/TemplatesController.cs
@@ -5,6 +5,8 @@
using Doppler.HtmlEditorApi.DopplerSecurity;
using Doppler.HtmlEditorApi.Repositories;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
namespace Doppler.HtmlEditorApi.Controllers
@@ -21,47 +23,58 @@ public TemplatesController(ITemplateRepository templateRepository)
}
[Authorize(Policies.OwnResourceOrSuperUser)]
- [HttpGet("/accounts/{accountName}/templates/{templateId}")]
- public async Task> GetTemplate(string accountName, int templateId)
+ [HttpGet("/accounts/{accountName}/templates/{templateId}", Name = "GetTemplate")]
+ public async Task, ProblemHttpResult, Ok>> GetTemplate(string accountName, int templateId)
{
// TODO: Considere refactoring accountName validation
var templateModel = await _templateRepository.GetOwnOrPublicTemplate(accountName, templateId);
if (templateModel == null)
{
- return new NotFoundObjectResult("Template not found or belongs to a different account");
+ return TypedResults.NotFound(new ProblemDetails()
+ {
+ Detail = "Template not found or belongs to a different account"
+ });
}
if (templateModel.IsPublic)
{
- return new NotFoundObjectResult($"It is a public template, use /shared/templates/{templateId}");
+ return TypedResults.NotFound(new ProblemDetails()
+ {
+ Detail = $"It is a public template, use /shared/templates/{templateId}"
+ });
}
- ActionResult result = templateModel.Content switch
+ if (templateModel.Content is UnlayerTemplateContentData unlayerContent)
{
- UnlayerTemplateContentData unlayerContent => new Template(
+ return TypedResults.Ok(new Template(
type: ContentType.unlayer,
templateName: templateModel.Name,
isPublic: templateModel.IsPublic,
previewImage: templateModel.PreviewImage,
htmlContent: unlayerContent.HtmlComplete,
- meta: Utils.ParseAsJsonElement(unlayerContent.Meta)),
- _ => throw new NotImplementedException($"Unsupported template content type {templateModel.Content.GetType()}")
- };
+ meta: Utils.ParseAsJsonElement(unlayerContent.Meta)));
+ }
- return result;
+ return TypedResults.Problem(
+ detail: $"Unsupported template content type {templateModel.Content.GetType()}",
+ statusCode: StatusCodes.Status501NotImplemented,
+ title: "Not Implemented");
}
[Authorize(Policies.OwnResourceOrSuperUser)]
[HttpPut("/accounts/{accountName}/templates/{templateId}")]
- public async Task SaveTemplate(string accountName, int templateId, Template template)
+ public async Task, Ok>> SaveTemplate(string accountName, int templateId, Template template)
{
// TODO: Considere refactoring accountName validation
var currentTemplate = await _templateRepository.GetOwnOrPublicTemplate(accountName, templateId);
if (currentTemplate == null || currentTemplate.IsPublic)
{
- return new NotFoundObjectResult("Template not found, belongs to a different account, or it is a public template.");
+ return TypedResults.NotFound(new ProblemDetails()
+ {
+ Detail = "Template not found, belongs to a different account, or it is a public template."
+ });
}
var htmlDocument = ExtractHtmlDomFromTemplateContent(template.htmlContent);
@@ -77,19 +90,50 @@ public async Task SaveTemplate(string accountName, int templateId
await _templateRepository.UpdateTemplate(templateModel);
- return new OkObjectResult($"El template'{templateId}' del usuario '{accountName}' se guardó exitosamente.");
+ return TypedResults.Ok($"El template'{templateId}' del usuario '{accountName}' se guardó exitosamente.");
+ }
+
+ [Authorize(Policies.OwnResourceOrSuperUser)]
+ [HttpPost("/accounts/{accountName}/templates/from-template/{baseTemplateId}")]
+ public async Task, CreatedAtRoute>> CreateTemplateFromTemplate(string accountName, int baseTemplateId)
+ {
+ var templateModel = await _templateRepository.GetOwnOrPublicTemplate(accountName, baseTemplateId);
+ if (templateModel == null)
+ {
+ return TypedResults.NotFound(new ProblemDetails()
+ {
+ Detail = "The template not exists or Inactive"
+ });
+ }
+
+ if (templateModel.Content is not UnlayerTemplateContentData)
+ {
+ throw new NotImplementedException($"Unsupported template content type {templateModel.Content.GetType()}");
+ }
+
+ // To avoid ambiguities
+ var newTemplate = templateModel with
+ {
+ TemplateId = 0,
+ IsPublic = false
+ };
+
+ var templateId = await _templateRepository.CreatePrivateTemplate(accountName, newTemplate);
+
+ return TypedResults.CreatedAtRoute(new ResourceCreated(templateId), "GetTemplate", new { accountName, templateId });
}
+
[Authorize(Policies.OwnResourceOrSuperUser)]
[HttpPost("/accounts/{accountName}/templates")]
- public Task CreateTemplate(string accountName, Template templateModel)
+ public Task CreateTemplate(string accountName, Template templateModel)
{
throw new NotImplementedException();
}
[Authorize(Policies.OnlySuperUser)]
[HttpPost("/shared/templates/{templateId}")]
- public Task> GetSharedTemplate(int templateId)
+ public Task, Ok>> GetSharedTemplate(int templateId)
{
throw new NotImplementedException();
}
diff --git a/Doppler.HtmlEditorApi/Repositories.DopplerDb/DopplerTemplateRepository.cs b/Doppler.HtmlEditorApi/Repositories.DopplerDb/DopplerTemplateRepository.cs
index 0637032c..58fd6441 100644
--- a/Doppler.HtmlEditorApi/Repositories.DopplerDb/DopplerTemplateRepository.cs
+++ b/Doppler.HtmlEditorApi/Repositories.DopplerDb/DopplerTemplateRepository.cs
@@ -59,4 +59,40 @@ public async Task UpdateTemplate(TemplateModel templateModel)
await _dbContext.ExecuteAsync(updateTemplateQuery);
}
+
+ public async Task CreatePrivateTemplate(string accountName, TemplateModel templateModel)
+ {
+ // To avoid ambiguities
+ if (templateModel.TemplateId > 0)
+ {
+ throw new ArgumentException("TemplateId should not be set to create a new private template", nameof(templateModel));
+ }
+ if (templateModel.IsPublic)
+ {
+ throw new ArgumentException("IsPublic should be false to create a new private template", nameof(templateModel));
+ }
+ if (templateModel.Content is not UnlayerTemplateContentData unlayerTemplateContentData)
+ {
+ // I am breaking the Liskov Substitution Principle, and I like it!
+ throw new NotImplementedException($"Unsupported template content type {templateModel.Content.GetType()}");
+ }
+
+ var createTemplateQuery = new CreatePrivateTemplateDbQuery(
+ AccountName: accountName,
+ EditorType: 5,
+ HtmlCode: unlayerTemplateContentData.HtmlComplete,
+ Meta: unlayerTemplateContentData.Meta,
+ PreviewImage: templateModel.PreviewImage,
+ Name: templateModel.Name
+ );
+
+ var result = await _dbContext.ExecuteAsync(createTemplateQuery);
+
+ if (result is null)
+ {
+ throw new ArgumentException($"Account with name '{accountName}' does not exist.", nameof(accountName));
+ }
+
+ return result.NewTemplateId;
+ }
}
diff --git a/Doppler.HtmlEditorApi/Repositories.DopplerDb/Queries/CreatePrivateTemplateDbQuery.cs b/Doppler.HtmlEditorApi/Repositories.DopplerDb/Queries/CreatePrivateTemplateDbQuery.cs
new file mode 100644
index 00000000..c8e00704
--- /dev/null
+++ b/Doppler.HtmlEditorApi/Repositories.DopplerDb/Queries/CreatePrivateTemplateDbQuery.cs
@@ -0,0 +1,33 @@
+using Doppler.HtmlEditorApi.DataAccess;
+
+namespace Doppler.HtmlEditorApi.Repositories.DopplerDb.Queries;
+
+public record CreatePrivateTemplateDbQuery(
+ string AccountName,
+ int? EditorType,
+ string HtmlCode,
+ string Meta,
+ string PreviewImage,
+ string Name
+) : ISingleItemDbQuery
+{
+ public string GenerateSqlQuery() => $"""
+ INSERT INTO Template (IdUser, EditorType, HtmlCode, Meta, PreviewImage, Name, Active)
+ OUTPUT INSERTED.idTemplate AS NewTemplateId
+ SELECT
+ u.IdUser AS IdUser,
+ @EditorType AS EditorType,
+ @HtmlCode AS HtmlCode,
+ @Meta AS Meta,
+ @PreviewImage AS PreviewImage,
+ @Name AS Name,
+ 1 AS Active
+ FROM [User] u
+ WHERE u.Email = @AccountName
+ """;
+
+ public class Result
+ {
+ public int NewTemplateId { get; init; }
+ }
+}
diff --git a/Doppler.HtmlEditorApi/Repositories/ITemplateRepository.cs b/Doppler.HtmlEditorApi/Repositories/ITemplateRepository.cs
index 3e19dda0..ab7c3546 100644
--- a/Doppler.HtmlEditorApi/Repositories/ITemplateRepository.cs
+++ b/Doppler.HtmlEditorApi/Repositories/ITemplateRepository.cs
@@ -7,4 +7,5 @@ public interface ITemplateRepository
{
Task GetOwnOrPublicTemplate(string accountName, int templateId);
Task UpdateTemplate(TemplateModel templateModel);
+ Task CreatePrivateTemplate(string accountName, TemplateModel templateModel);
}